1 // Copyright Brian Schott (Hackerpilot) 2014-2015. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 module analysis.unused; 6 7 import dparse.ast; 8 import dparse.lexer; 9 import analysis.base; 10 import std.container; 11 import std.regex : Regex, regex, matchAll; 12 import dsymbol.scope_ : Scope; 13 14 /** 15 * Checks for unused variables. 16 */ 17 class UnusedVariableCheck : BaseAnalyzer 18 { 19 alias visit = BaseAnalyzer.visit; 20 21 /** 22 * Params: 23 * fileName = the name of the file being analyzed 24 */ 25 this(string fileName, const(Scope)* sc) 26 { 27 super(fileName, sc); 28 re = regex("[\\p{Alphabetic}_][\\w_]*"); 29 } 30 31 override void visit(const Module mod) 32 { 33 pushScope(); 34 mod.accept(this); 35 popScope(); 36 } 37 38 override void visit(const Declaration declaration) 39 { 40 if (!isOverride) 41 foreach (attribute; declaration.attributes) 42 isOverride = isOverride || (attribute.attribute == tok!"override"); 43 declaration.accept(this); 44 isOverride = false; 45 } 46 47 override void visit(const FunctionDeclaration functionDec) 48 { 49 pushScope(); 50 if (functionDec.functionBody !is null) 51 { 52 immutable bool ias = inAggregateScope; 53 inAggregateScope = false; 54 if (!isOverride) 55 functionDec.parameters.accept(this); 56 functionDec.functionBody.accept(this); 57 inAggregateScope = ias; 58 } 59 popScope(); 60 } 61 62 mixin PartsUseVariables!AliasInitializer; 63 mixin PartsUseVariables!ArgumentList; 64 mixin PartsUseVariables!AssertExpression; 65 mixin PartsUseVariables!ClassDeclaration; 66 mixin PartsUseVariables!FunctionBody; 67 mixin PartsUseVariables!FunctionCallExpression; 68 mixin PartsUseVariables!FunctionDeclaration; 69 mixin PartsUseVariables!IndexExpression; 70 mixin PartsUseVariables!Initializer; 71 mixin PartsUseVariables!InterfaceDeclaration; 72 mixin PartsUseVariables!NewExpression; 73 mixin PartsUseVariables!StaticIfCondition; 74 mixin PartsUseVariables!StructDeclaration; 75 mixin PartsUseVariables!TemplateArgumentList; 76 mixin PartsUseVariables!ThrowStatement; 77 mixin PartsUseVariables!CastExpression; 78 79 override void visit(const SwitchStatement switchStatement) 80 { 81 if (switchStatement.expression !is null) 82 { 83 interestDepth++; 84 switchStatement.expression.accept(this); 85 interestDepth--; 86 } 87 switchStatement.accept(this); 88 } 89 90 override void visit(const WhileStatement whileStatement) 91 { 92 interestDepth++; 93 whileStatement.expression.accept(this); 94 interestDepth--; 95 whileStatement.declarationOrStatement.accept(this); 96 } 97 98 override void visit(const DoStatement doStatement) 99 { 100 interestDepth++; 101 doStatement.expression.accept(this); 102 interestDepth--; 103 doStatement.statementNoCaseNoDefault.accept(this); 104 } 105 106 override void visit(const ForStatement forStatement) 107 { 108 if (forStatement.initialization !is null) 109 forStatement.initialization.accept(this); 110 if (forStatement.test !is null) 111 { 112 interestDepth++; 113 forStatement.test.accept(this); 114 interestDepth--; 115 } 116 if (forStatement.increment !is null) 117 { 118 interestDepth++; 119 forStatement.increment.accept(this); 120 interestDepth--; 121 } 122 forStatement.declarationOrStatement.accept(this); 123 } 124 125 override void visit(const IfStatement ifStatement) 126 { 127 if (ifStatement.expression !is null) 128 { 129 interestDepth++; 130 ifStatement.expression.accept(this); 131 interestDepth--; 132 } 133 ifStatement.thenStatement.accept(this); 134 if (ifStatement.elseStatement !is null) 135 ifStatement.elseStatement.accept(this); 136 } 137 138 override void visit(const ForeachStatement foreachStatement) 139 { 140 if (foreachStatement.low !is null) 141 { 142 interestDepth++; 143 foreachStatement.low.accept(this); 144 interestDepth--; 145 } 146 if (foreachStatement.high !is null) 147 { 148 interestDepth++; 149 foreachStatement.high.accept(this); 150 interestDepth--; 151 } 152 foreachStatement.accept(this); 153 } 154 155 override void visit(const AssignExpression assignExp) 156 { 157 assignExp.ternaryExpression.accept(this); 158 if (assignExp.expression !is null) 159 { 160 interestDepth++; 161 assignExp.expression.accept(this); 162 interestDepth--; 163 } 164 } 165 166 override void visit(const TemplateDeclaration templateDeclaration) 167 { 168 immutable inAgg = inAggregateScope; 169 inAggregateScope = true; 170 templateDeclaration.accept(this); 171 inAggregateScope = inAgg; 172 } 173 174 override void visit(const IdentifierOrTemplateChain chain) 175 { 176 if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"") 177 variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text); 178 chain.accept(this); 179 } 180 181 override void visit(const TemplateSingleArgument single) 182 { 183 if (single.token != tok!"") 184 variableUsed(single.token.text); 185 } 186 187 override void visit(const UnaryExpression unary) 188 { 189 if (unary.prefix == tok!"*") 190 interestDepth++; 191 unary.accept(this); 192 if (unary.prefix == tok!"*") 193 interestDepth--; 194 } 195 196 override void visit(const MixinExpression mix) 197 { 198 interestDepth++; 199 mixinDepth++; 200 mix.accept(this); 201 mixinDepth--; 202 interestDepth--; 203 } 204 205 override void visit(const PrimaryExpression primary) 206 { 207 if (interestDepth > 0) 208 { 209 if (primary.identifierOrTemplateInstance !is null 210 && primary.identifierOrTemplateInstance.identifier != tok!"") 211 { 212 variableUsed(primary.identifierOrTemplateInstance.identifier.text); 213 } 214 if (mixinDepth > 0 && primary.primary == tok!"stringLiteral" 215 || primary.primary == tok!"wstringLiteral" 216 || primary.primary == tok!"dstringLiteral") 217 { 218 foreach (part; matchAll(primary.primary.text, re)) 219 { 220 immutable size_t treeIndex = tree.length - 1; 221 auto uu = UnUsed(part.hit); 222 auto r = tree[treeIndex].equalRange(&uu); 223 if (!r.empty) 224 r.front.uncertain = true; 225 } 226 } 227 } 228 primary.accept(this); 229 } 230 231 override void visit(const ReturnStatement retStatement) 232 { 233 if (retStatement.expression !is null) 234 { 235 interestDepth++; 236 visit(retStatement.expression); 237 interestDepth--; 238 } 239 } 240 241 override void visit(const BlockStatement blockStatement) 242 { 243 immutable bool sb = inAggregateScope; 244 inAggregateScope = false; 245 if (blockStatementIntroducesScope) 246 pushScope(); 247 blockStatement.accept(this); 248 if (blockStatementIntroducesScope) 249 popScope(); 250 inAggregateScope = sb; 251 } 252 253 override void visit(const VariableDeclaration variableDeclaration) 254 { 255 foreach (d; variableDeclaration.declarators) 256 this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false); 257 variableDeclaration.accept(this); 258 } 259 260 override void visit(const AutoDeclaration autoDeclaration) 261 { 262 foreach (t; autoDeclaration.identifiers) 263 this.variableDeclared(t.text, t.line, t.column, false, false); 264 autoDeclaration.accept(this); 265 } 266 267 override void visit(const WithStatement withStatetement) 268 { 269 interestDepth++; 270 withStatetement.expression.accept(this); 271 interestDepth--; 272 withStatetement.statementNoCaseNoDefault.accept(this); 273 } 274 275 override void visit(const Parameter parameter) 276 { 277 import std.algorithm : canFind; 278 import std.array : array; 279 280 if (parameter.name != tok!"") 281 { 282 immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref") 283 || canFind(parameter.parameterAttributes, 284 cast(IdType) tok!"in") || canFind(parameter.parameterAttributes, 285 cast(IdType) tok!"out"); 286 variableDeclared(parameter.name.text, parameter.name.line, 287 parameter.name.column, true, isRef); 288 if (parameter.default_ !is null) 289 { 290 interestDepth++; 291 parameter.default_.accept(this); 292 interestDepth--; 293 } 294 } 295 } 296 297 override void visit(const StructBody structBody) 298 { 299 immutable bool sb = inAggregateScope; 300 inAggregateScope = true; 301 foreach (dec; structBody.declarations) 302 visit(dec); 303 inAggregateScope = sb; 304 } 305 306 override void visit(const ConditionalStatement conditionalStatement) 307 { 308 immutable bool cs = blockStatementIntroducesScope; 309 blockStatementIntroducesScope = false; 310 conditionalStatement.accept(this); 311 blockStatementIntroducesScope = cs; 312 } 313 314 override void visit(const AsmPrimaryExp primary) 315 { 316 if (primary.token != tok!"") 317 variableUsed(primary.token.text); 318 if (primary.identifierChain !is null) 319 variableUsed(primary.identifierChain.identifiers[0].text); 320 } 321 322 override void visit(const TraitsExpression) 323 { 324 // issue #266: Ignore unused variables inside of `__traits` expressions 325 } 326 327 override void visit(const TypeofExpression) 328 { 329 // issue #270: Ignore unused variables inside of `typeof` expressions 330 } 331 332 private: 333 334 mixin template PartsUseVariables(NodeType) 335 { 336 override void visit(const NodeType node) 337 { 338 interestDepth++; 339 node.accept(this); 340 interestDepth--; 341 } 342 } 343 344 void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) 345 { 346 if (inAggregateScope) 347 return; 348 tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); 349 } 350 351 void variableUsed(string name) 352 { 353 size_t treeIndex = tree.length - 1; 354 auto uu = UnUsed(name); 355 while (true) 356 { 357 if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) 358 break; 359 treeIndex--; 360 } 361 } 362 363 void popScope() 364 { 365 foreach (uu; tree[$ - 1]) 366 { 367 if (!uu.isRef && tree.length > 1) 368 { 369 immutable string certainty = uu.uncertain ? " might not be used." 370 : " is never used."; 371 immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ") 372 ~ uu.name ~ certainty; 373 addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter" 374 : "dscanner.suspicious.unused_variable", errorMessage); 375 } 376 } 377 tree = tree[0 .. $ - 1]; 378 } 379 380 void pushScope() 381 { 382 tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name"); 383 } 384 385 struct UnUsed 386 { 387 string name; 388 size_t line; 389 size_t column; 390 bool isParameter; 391 bool isRef; 392 bool uncertain; 393 } 394 395 RedBlackTree!(UnUsed*, "a.name < b.name")[] tree; 396 397 uint interestDepth; 398 399 uint mixinDepth; 400 401 bool isOverride; 402 403 bool inAggregateScope; 404 405 bool blockStatementIntroducesScope = true; 406 407 Regex!char re; 408 } 409 410 unittest 411 { 412 import std.stdio : stderr; 413 import analysis.config : StaticAnalysisConfig; 414 import analysis.helpers : assertAnalyzerWarnings; 415 416 StaticAnalysisConfig sac; 417 sac.unused_variable_check = true; 418 assertAnalyzerWarnings(q{ 419 420 // Issue 274 421 unittest 422 { 423 size_t byteIndex = 0; 424 *(cast(FieldType*)(retVal.ptr + byteIndex)) = item; 425 } 426 427 unittest 428 { 429 int a; // [warn]: Variable a is never used. 430 } 431 432 void doStuff(int a, int b) // [warn]: Parameter b is never used. 433 { 434 return a; 435 } 436 437 }}, sac); 438 stderr.writeln("Unittest for UnusedVariableCheck passed."); 439 }