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 AutoDeclaration autoDeclaration) 266 { 267 foreach (t; autoDeclaration.parts.map!(a => a.identifier)) 268 this.variableDeclared(t.text, t.line, t.column, false, false); 269 autoDeclaration.accept(this); 270 } 271 272 override void visit(const WithStatement withStatetement) 273 { 274 interestDepth++; 275 withStatetement.expression.accept(this); 276 interestDepth--; 277 withStatetement.statementNoCaseNoDefault.accept(this); 278 } 279 280 override void visit(const Parameter parameter) 281 { 282 import std.algorithm : canFind; 283 import std.array : array; 284 285 if (parameter.name != tok!"") 286 { 287 immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref") 288 || canFind(parameter.parameterAttributes, 289 cast(IdType) tok!"in") || canFind(parameter.parameterAttributes, 290 cast(IdType) tok!"out"); 291 variableDeclared(parameter.name.text, parameter.name.line, 292 parameter.name.column, true, isRef); 293 if (parameter.default_ !is null) 294 { 295 interestDepth++; 296 parameter.default_.accept(this); 297 interestDepth--; 298 } 299 } 300 } 301 302 override void visit(const StructBody structBody) 303 { 304 immutable bool sb = inAggregateScope; 305 inAggregateScope = true; 306 foreach (dec; structBody.declarations) 307 visit(dec); 308 inAggregateScope = sb; 309 } 310 311 override void visit(const ConditionalStatement conditionalStatement) 312 { 313 immutable bool cs = blockStatementIntroducesScope; 314 blockStatementIntroducesScope = false; 315 conditionalStatement.accept(this); 316 blockStatementIntroducesScope = cs; 317 } 318 319 override void visit(const AsmPrimaryExp primary) 320 { 321 if (primary.token != tok!"") 322 variableUsed(primary.token.text); 323 if (primary.identifierChain !is null) 324 variableUsed(primary.identifierChain.identifiers[0].text); 325 } 326 327 override void visit(const TraitsExpression) 328 { 329 // issue #266: Ignore unused variables inside of `__traits` expressions 330 } 331 332 override void visit(const TypeofExpression) 333 { 334 // issue #270: Ignore unused variables inside of `typeof` expressions 335 } 336 337 private: 338 339 mixin template PartsUseVariables(NodeType) 340 { 341 override void visit(const NodeType node) 342 { 343 interestDepth++; 344 node.accept(this); 345 interestDepth--; 346 } 347 } 348 349 void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) 350 { 351 if (inAggregateScope) 352 return; 353 tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); 354 } 355 356 void variableUsed(string name) 357 { 358 size_t treeIndex = tree.length - 1; 359 auto uu = UnUsed(name); 360 while (true) 361 { 362 if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) 363 break; 364 treeIndex--; 365 } 366 } 367 368 void popScope() 369 { 370 foreach (uu; tree[$ - 1]) 371 { 372 if (!uu.isRef && tree.length > 1) 373 { 374 immutable string certainty = uu.uncertain ? " might not be used." 375 : " is never used."; 376 immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ") 377 ~ uu.name ~ certainty; 378 addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter" 379 : "dscanner.suspicious.unused_variable", errorMessage); 380 } 381 } 382 tree = tree[0 .. $ - 1]; 383 } 384 385 void pushScope() 386 { 387 tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name"); 388 } 389 390 struct UnUsed 391 { 392 string name; 393 size_t line; 394 size_t column; 395 bool isParameter; 396 bool isRef; 397 bool uncertain; 398 } 399 400 RedBlackTree!(UnUsed*, "a.name < b.name")[] tree; 401 402 uint interestDepth; 403 404 uint mixinDepth; 405 406 bool isOverride; 407 408 bool inAggregateScope; 409 410 bool blockStatementIntroducesScope = true; 411 412 Regex!char re; 413 } 414 415 unittest 416 { 417 import std.stdio : stderr; 418 import analysis.config : StaticAnalysisConfig, Check; 419 import analysis.helpers : assertAnalyzerWarnings; 420 421 StaticAnalysisConfig sac; 422 sac.unused_variable_check = Check.enabled; 423 assertAnalyzerWarnings(q{ 424 425 // Issue 274 426 unittest 427 { 428 size_t byteIndex = 0; 429 *(cast(FieldType*)(retVal.ptr + byteIndex)) = item; 430 } 431 432 unittest 433 { 434 int a; // [warn]: Variable a is never used. 435 } 436 437 // Issue 380 438 int templatedEnum() 439 { 440 enum a(T) = T.init; 441 return a!int; 442 } 443 444 // Issue 380 445 int otherTemplatedEnum() 446 { 447 auto a(T) = T.init; // [warn]: Variable a is never used. 448 return 0; 449 } 450 451 void doStuff(int a, int b) // [warn]: Parameter b is never used. 452 { 453 return a; 454 } 455 456 }}, sac); 457 stderr.writeln("Unittest for UnusedVariableCheck passed."); 458 }