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