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 final 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 && functionDec.functionBody.specifiedFunctionBody) 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 interestDepth++; 170 assignExp.accept(this); 171 interestDepth--; 172 } 173 174 override void visit(const TemplateDeclaration templateDeclaration) 175 { 176 immutable inAgg = inAggregateScope; 177 inAggregateScope = true; 178 templateDeclaration.accept(this); 179 inAggregateScope = inAgg; 180 } 181 182 override void visit(const IdentifierOrTemplateChain chain) 183 { 184 if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"") 185 variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text); 186 chain.accept(this); 187 } 188 189 override void visit(const TemplateSingleArgument single) 190 { 191 if (single.token != tok!"") 192 variableUsed(single.token.text); 193 } 194 195 override void visit(const UnaryExpression unary) 196 { 197 const bool interesting = unary.prefix == tok!"*" || unary.unaryExpression !is null; 198 interestDepth += interesting; 199 unary.accept(this); 200 interestDepth -= interesting; 201 } 202 203 override void visit(const MixinExpression mix) 204 { 205 interestDepth++; 206 mixinDepth++; 207 mix.accept(this); 208 mixinDepth--; 209 interestDepth--; 210 } 211 212 override void visit(const PrimaryExpression primary) 213 { 214 if (interestDepth > 0) 215 { 216 const IdentifierOrTemplateInstance idt = primary.identifierOrTemplateInstance; 217 218 if (idt !is null) 219 { 220 if (idt.identifier != tok!"") 221 variableUsed(idt.identifier.text); 222 else if (idt.templateInstance && idt.templateInstance.identifier != tok!"") 223 variableUsed(idt.templateInstance.identifier.text); 224 } 225 if (mixinDepth > 0 && primary.primary == tok!"stringLiteral" 226 || primary.primary == tok!"wstringLiteral" 227 || primary.primary == tok!"dstringLiteral") 228 { 229 foreach (part; matchAll(primary.primary.text, re)) 230 { 231 void checkTree(in size_t treeIndex) 232 { 233 auto uu = UnUsed(part.hit); 234 auto r = tree[treeIndex].equalRange(&uu); 235 if (!r.empty) 236 r.front.uncertain = true; 237 } 238 checkTree(tree.length - 1); 239 if (tree.length >= 2) 240 checkTree(tree.length - 2); 241 } 242 } 243 } 244 primary.accept(this); 245 } 246 247 override void visit(const ReturnStatement retStatement) 248 { 249 if (retStatement.expression !is null) 250 { 251 interestDepth++; 252 visit(retStatement.expression); 253 interestDepth--; 254 } 255 } 256 257 override void visit(const BlockStatement blockStatement) 258 { 259 immutable bool sb = inAggregateScope; 260 inAggregateScope = false; 261 if (blockStatementIntroducesScope) 262 pushScope(); 263 blockStatement.accept(this); 264 if (blockStatementIntroducesScope) 265 popScope(); 266 inAggregateScope = sb; 267 } 268 269 override void visit(const VariableDeclaration variableDeclaration) 270 { 271 foreach (d; variableDeclaration.declarators) 272 this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false); 273 variableDeclaration.accept(this); 274 } 275 276 override void visit(const Type2 tp) 277 { 278 if (tp.typeIdentifierPart && 279 tp.typeIdentifierPart.identifierOrTemplateInstance) 280 { 281 const IdentifierOrTemplateInstance idt = tp.typeIdentifierPart.identifierOrTemplateInstance; 282 if (idt.identifier != tok!"") 283 variableUsed(idt.identifier.text); 284 else if (idt.templateInstance) 285 { 286 const TemplateInstance ti = idt.templateInstance; 287 if (ti.identifier != tok!"") 288 variableUsed(idt.templateInstance.identifier.text); 289 if (ti.templateArguments && ti.templateArguments.templateSingleArgument) 290 variableUsed(ti.templateArguments.templateSingleArgument.token.text); 291 } 292 } 293 tp.accept(this); 294 } 295 296 override void visit(const AutoDeclaration autoDeclaration) 297 { 298 foreach (t; autoDeclaration.parts.map!(a => a.identifier)) 299 this.variableDeclared(t.text, t.line, t.column, false, false); 300 autoDeclaration.accept(this); 301 } 302 303 override void visit(const WithStatement withStatetement) 304 { 305 interestDepth++; 306 if (withStatetement.expression) 307 withStatetement.expression.accept(this); 308 interestDepth--; 309 if (withStatetement.declarationOrStatement) 310 withStatetement.declarationOrStatement.accept(this); 311 } 312 313 override void visit(const Parameter parameter) 314 { 315 import std.algorithm : among; 316 import std.algorithm.iteration : filter; 317 import std.range : empty; 318 import std.array : array; 319 320 if (parameter.name != tok!"") 321 { 322 immutable bool isRef = !parameter.parameterAttributes 323 .filter!(a => a.idType.among(tok!"ref", tok!"out")).empty; 324 immutable bool isPtr = parameter.type && !parameter.type 325 .typeSuffixes.filter!(a => a.star != tok!"").empty; 326 327 variableDeclared(parameter.name.text, parameter.name.line, 328 parameter.name.column, true, isRef | isPtr); 329 330 if (parameter.default_ !is null) 331 { 332 interestDepth++; 333 parameter.default_.accept(this); 334 interestDepth--; 335 } 336 } 337 } 338 339 override void visit(const StructBody structBody) 340 { 341 immutable bool sb = inAggregateScope; 342 inAggregateScope = true; 343 foreach (dec; structBody.declarations) 344 visit(dec); 345 inAggregateScope = sb; 346 } 347 348 override void visit(const ConditionalStatement conditionalStatement) 349 { 350 immutable bool cs = blockStatementIntroducesScope; 351 blockStatementIntroducesScope = false; 352 conditionalStatement.accept(this); 353 blockStatementIntroducesScope = cs; 354 } 355 356 override void visit(const AsmPrimaryExp primary) 357 { 358 if (primary.token != tok!"") 359 variableUsed(primary.token.text); 360 if (primary.identifierChain !is null) 361 variableUsed(primary.identifierChain.identifiers[0].text); 362 } 363 364 override void visit(const TraitsExpression) 365 { 366 // issue #266: Ignore unused variables inside of `__traits` expressions 367 } 368 369 override void visit(const TypeofExpression) 370 { 371 // issue #270: Ignore unused variables inside of `typeof` expressions 372 } 373 374 private: 375 376 mixin template PartsUseVariables(NodeType) 377 { 378 override void visit(const NodeType node) 379 { 380 interestDepth++; 381 node.accept(this); 382 interestDepth--; 383 } 384 } 385 386 void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) 387 { 388 if (inAggregateScope || name.all!(a => a == '_')) 389 return; 390 tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); 391 } 392 393 void variableUsed(string name) 394 { 395 size_t treeIndex = tree.length - 1; 396 auto uu = UnUsed(name); 397 while (true) 398 { 399 if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) 400 break; 401 treeIndex--; 402 } 403 } 404 405 void popScope() 406 { 407 foreach (uu; tree[$ - 1]) 408 { 409 if (!uu.isRef && tree.length > 1) 410 { 411 if (uu.uncertain) 412 continue; 413 immutable string certainty = uu.uncertain ? " might not be used." 414 : " is never used."; 415 immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ") 416 ~ uu.name ~ certainty; 417 addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter" 418 : "dscanner.suspicious.unused_variable", errorMessage); 419 } 420 } 421 tree = tree[0 .. $ - 1]; 422 } 423 424 void pushScope() 425 { 426 tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name"); 427 } 428 429 struct UnUsed 430 { 431 string name; 432 size_t line; 433 size_t column; 434 bool isParameter; 435 bool isRef; 436 bool uncertain; 437 } 438 439 RedBlackTree!(UnUsed*, "a.name < b.name")[] tree; 440 441 uint interestDepth; 442 443 uint mixinDepth; 444 445 bool isOverride; 446 447 bool inAggregateScope; 448 449 bool blockStatementIntroducesScope = true; 450 451 Regex!char re; 452 } 453 454 @system unittest 455 { 456 import std.stdio : stderr; 457 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 458 import dscanner.analysis.helpers : assertAnalyzerWarnings; 459 460 StaticAnalysisConfig sac = disabledConfig(); 461 sac.unused_variable_check = Check.enabled; 462 assertAnalyzerWarnings(q{ 463 464 // Issue 274 465 unittest 466 { 467 size_t byteIndex = 0; 468 *(cast(FieldType*)(retVal.ptr + byteIndex)) = item; 469 } 470 471 // bug encountered after correct DIP 1009 impl in dparse 472 version (StdDdoc) 473 { 474 bool isAbsolute(R)(R path) pure nothrow @safe 475 if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || 476 is(StringTypeOf!R)); 477 } 478 479 unittest 480 { 481 int a; // [warn]: Variable a is never used. 482 } 483 484 void inPSC(in int a){} // [warn]: Parameter a is never used. 485 486 // Issue 380 487 int templatedEnum() 488 { 489 enum a(T) = T.init; 490 return a!int; 491 } 492 493 // Issue 380 494 int otherTemplatedEnum() 495 { 496 auto a(T) = T.init; // [warn]: Variable a is never used. 497 return 0; 498 } 499 500 void doStuff(int a, int b) // [warn]: Parameter b is never used. 501 { 502 return a; 503 } 504 505 // Issue 364 506 void test364_1() 507 { 508 enum s = 8; 509 immutable t = 2; 510 int[s][t] a; 511 a[0][0] = 1; 512 } 513 514 void test364_2() 515 { 516 enum s = 8; 517 alias a = e!s; 518 a = 1; 519 } 520 521 // Issue 352 522 void test352_1() 523 { 524 void f(int *x) {*x = 1;} 525 } 526 527 void test352_2() 528 { 529 void f(Bat** bat) {*bat = bats.ptr + 8;} 530 } 531 532 // Issue 490 533 void test490() 534 { 535 auto cb1 = delegate(size_t _) {}; 536 cb1(3); 537 auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used. 538 cb2(3); 539 } 540 541 void oops () 542 { 543 class Identity { int val; } 544 Identity v; 545 v.val = 0; 546 } 547 548 bool hasDittos(int decl) 549 { 550 mixin("decl++;"); 551 } 552 553 void main() 554 { 555 const int testValue; 556 testValue.writeln; 557 } 558 559 }}, sac); 560 stderr.writeln("Unittest for UnusedVariableCheck passed."); 561 } 562