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 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 immutable size_t treeIndex = tree.length - 1; 238 auto uu = UnUsed(part.hit); 239 auto r = tree[treeIndex].equalRange(&uu); 240 if (!r.empty) 241 r.front.uncertain = true; 242 } 243 } 244 } 245 primary.accept(this); 246 } 247 248 override void visit(const ReturnStatement retStatement) 249 { 250 if (retStatement.expression !is null) 251 { 252 interestDepth++; 253 visit(retStatement.expression); 254 interestDepth--; 255 } 256 } 257 258 override void visit(const BlockStatement blockStatement) 259 { 260 immutable bool sb = inAggregateScope; 261 inAggregateScope = false; 262 if (blockStatementIntroducesScope) 263 pushScope(); 264 blockStatement.accept(this); 265 if (blockStatementIntroducesScope) 266 popScope(); 267 inAggregateScope = sb; 268 } 269 270 override void visit(const VariableDeclaration variableDeclaration) 271 { 272 foreach (d; variableDeclaration.declarators) 273 this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false); 274 variableDeclaration.accept(this); 275 } 276 277 override void visit(const Type2 tp) 278 { 279 if (tp.symbol && tp.symbol.identifierOrTemplateChain && 280 tp.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances) 281 { 282 const IdentifierOrTemplateInstance idt = tp.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0]; 283 if (idt.identifier != tok!"") 284 variableUsed(idt.identifier.text); 285 else if (idt.templateInstance) 286 { 287 const TemplateInstance ti = idt.templateInstance; 288 if (ti.identifier != tok!"") 289 variableUsed(idt.templateInstance.identifier.text); 290 if (ti.templateArguments && ti.templateArguments.templateSingleArgument) 291 variableUsed(ti.templateArguments.templateSingleArgument.token.text); 292 } 293 } 294 tp.accept(this); 295 } 296 297 override void visit(const AutoDeclaration autoDeclaration) 298 { 299 foreach (t; autoDeclaration.parts.map!(a => a.identifier)) 300 this.variableDeclared(t.text, t.line, t.column, false, false); 301 autoDeclaration.accept(this); 302 } 303 304 override void visit(const WithStatement withStatetement) 305 { 306 interestDepth++; 307 withStatetement.expression.accept(this); 308 interestDepth--; 309 withStatetement.statementNoCaseNoDefault.accept(this); 310 } 311 312 override void visit(const Parameter parameter) 313 { 314 import std.algorithm : among; 315 import std.algorithm.iteration : filter; 316 import std.range : empty; 317 import std.array : array; 318 319 if (parameter.name != tok!"") 320 { 321 immutable bool isRef = !parameter.parameterAttributes 322 .filter!(a => a.among(tok!"ref", tok!"out")).empty; 323 immutable bool isPtr = parameter.type && !parameter.type 324 .typeSuffixes.filter!(a => a.star != tok!"").empty; 325 326 variableDeclared(parameter.name.text, parameter.name.line, 327 parameter.name.column, true, isRef | isPtr); 328 329 if (parameter.default_ !is null) 330 { 331 interestDepth++; 332 parameter.default_.accept(this); 333 interestDepth--; 334 } 335 } 336 } 337 338 override void visit(const StructBody structBody) 339 { 340 immutable bool sb = inAggregateScope; 341 inAggregateScope = true; 342 foreach (dec; structBody.declarations) 343 visit(dec); 344 inAggregateScope = sb; 345 } 346 347 override void visit(const ConditionalStatement conditionalStatement) 348 { 349 immutable bool cs = blockStatementIntroducesScope; 350 blockStatementIntroducesScope = false; 351 conditionalStatement.accept(this); 352 blockStatementIntroducesScope = cs; 353 } 354 355 override void visit(const AsmPrimaryExp primary) 356 { 357 if (primary.token != tok!"") 358 variableUsed(primary.token.text); 359 if (primary.identifierChain !is null) 360 variableUsed(primary.identifierChain.identifiers[0].text); 361 } 362 363 override void visit(const TraitsExpression) 364 { 365 // issue #266: Ignore unused variables inside of `__traits` expressions 366 } 367 368 override void visit(const TypeofExpression) 369 { 370 // issue #270: Ignore unused variables inside of `typeof` expressions 371 } 372 373 private: 374 375 mixin template PartsUseVariables(NodeType) 376 { 377 override void visit(const NodeType node) 378 { 379 interestDepth++; 380 node.accept(this); 381 interestDepth--; 382 } 383 } 384 385 void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) 386 { 387 if (inAggregateScope || name.all!(a => a == '_')) 388 return; 389 tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); 390 } 391 392 void variableUsed(string name) 393 { 394 size_t treeIndex = tree.length - 1; 395 auto uu = UnUsed(name); 396 while (true) 397 { 398 if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) 399 break; 400 treeIndex--; 401 } 402 } 403 404 void popScope() 405 { 406 foreach (uu; tree[$ - 1]) 407 { 408 if (!uu.isRef && tree.length > 1) 409 { 410 immutable string certainty = uu.uncertain ? " might not be used." 411 : " is never used."; 412 immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ") 413 ~ uu.name ~ certainty; 414 addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter" 415 : "dscanner.suspicious.unused_variable", errorMessage); 416 } 417 } 418 tree = tree[0 .. $ - 1]; 419 } 420 421 void pushScope() 422 { 423 tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name"); 424 } 425 426 struct UnUsed 427 { 428 string name; 429 size_t line; 430 size_t column; 431 bool isParameter; 432 bool isRef; 433 bool uncertain; 434 } 435 436 RedBlackTree!(UnUsed*, "a.name < b.name")[] tree; 437 438 uint interestDepth; 439 440 uint mixinDepth; 441 442 bool isOverride; 443 444 bool inAggregateScope; 445 446 bool blockStatementIntroducesScope = true; 447 448 Regex!char re; 449 } 450 451 @system unittest 452 { 453 import std.stdio : stderr; 454 import analysis.config : StaticAnalysisConfig, Check, disabledConfig; 455 import analysis.helpers : assertAnalyzerWarnings; 456 457 StaticAnalysisConfig sac = disabledConfig(); 458 sac.unused_variable_check = Check.enabled; 459 assertAnalyzerWarnings(q{ 460 461 // Issue 274 462 unittest 463 { 464 size_t byteIndex = 0; 465 *(cast(FieldType*)(retVal.ptr + byteIndex)) = item; 466 } 467 468 unittest 469 { 470 int a; // [warn]: Variable a is never used. 471 } 472 473 void inPSC(in int a){} // [warn]: Parameter a is never used. 474 475 // Issue 380 476 int templatedEnum() 477 { 478 enum a(T) = T.init; 479 return a!int; 480 } 481 482 // Issue 380 483 int otherTemplatedEnum() 484 { 485 auto a(T) = T.init; // [warn]: Variable a is never used. 486 return 0; 487 } 488 489 void doStuff(int a, int b) // [warn]: Parameter b is never used. 490 { 491 return a; 492 } 493 494 // Issue 364 495 void test364_1() 496 { 497 enum s = 8; 498 immutable t = 2; 499 int[s][t] a; 500 a[0][0] = 1; 501 } 502 503 void test364_2() 504 { 505 enum s = 8; 506 alias a = e!s; 507 a = 1; 508 } 509 510 // Issue 352 511 void test352_1() 512 { 513 void f(int *x) {*x = 1;} 514 } 515 516 void test352_2() 517 { 518 void f(Bat** bat) {*bat = bats.ptr + 8;} 519 } 520 521 // Issue 490 522 void test490() 523 { 524 auto cb1 = delegate(size_t _) {}; 525 cb1(3); 526 auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used. 527 cb2(3); 528 } 529 530 }}, sac); 531 stderr.writeln("Unittest for UnusedVariableCheck passed."); 532 } 533