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