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 : 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 if (primary.identifierOrTemplateInstance !is null 211 && primary.identifierOrTemplateInstance.identifier != tok!"") 212 { 213 variableUsed(primary.identifierOrTemplateInstance.identifier.text); 214 } 215 if (mixinDepth > 0 && primary.primary == tok!"stringLiteral" 216 || primary.primary == tok!"wstringLiteral" 217 || primary.primary == tok!"dstringLiteral") 218 { 219 foreach (part; matchAll(primary.primary.text, re)) 220 { 221 immutable size_t treeIndex = tree.length - 1; 222 auto uu = UnUsed(part.hit); 223 auto r = tree[treeIndex].equalRange(&uu); 224 if (!r.empty) 225 r.front.uncertain = true; 226 } 227 } 228 } 229 primary.accept(this); 230 } 231 232 override void visit(const ReturnStatement retStatement) 233 { 234 if (retStatement.expression !is null) 235 { 236 interestDepth++; 237 visit(retStatement.expression); 238 interestDepth--; 239 } 240 } 241 242 override void visit(const BlockStatement blockStatement) 243 { 244 immutable bool sb = inAggregateScope; 245 inAggregateScope = false; 246 if (blockStatementIntroducesScope) 247 pushScope(); 248 blockStatement.accept(this); 249 if (blockStatementIntroducesScope) 250 popScope(); 251 inAggregateScope = sb; 252 } 253 254 override void visit(const VariableDeclaration variableDeclaration) 255 { 256 foreach (d; variableDeclaration.declarators) 257 this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false); 258 variableDeclaration.accept(this); 259 } 260 261 override void visit(const AutoDeclaration autoDeclaration) 262 { 263 foreach (t; autoDeclaration.parts.map!(a => a.identifier)) 264 this.variableDeclared(t.text, t.line, t.column, false, false); 265 autoDeclaration.accept(this); 266 } 267 268 override void visit(const WithStatement withStatetement) 269 { 270 interestDepth++; 271 withStatetement.expression.accept(this); 272 interestDepth--; 273 withStatetement.statementNoCaseNoDefault.accept(this); 274 } 275 276 override void visit(const Parameter parameter) 277 { 278 import std.algorithm : canFind; 279 import std.array : array; 280 281 if (parameter.name != tok!"") 282 { 283 immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref") 284 || canFind(parameter.parameterAttributes, 285 cast(IdType) tok!"in") || canFind(parameter.parameterAttributes, 286 cast(IdType) tok!"out"); 287 variableDeclared(parameter.name.text, parameter.name.line, 288 parameter.name.column, true, isRef); 289 if (parameter.default_ !is null) 290 { 291 interestDepth++; 292 parameter.default_.accept(this); 293 interestDepth--; 294 } 295 } 296 } 297 298 override void visit(const StructBody structBody) 299 { 300 immutable bool sb = inAggregateScope; 301 inAggregateScope = true; 302 foreach (dec; structBody.declarations) 303 visit(dec); 304 inAggregateScope = sb; 305 } 306 307 override void visit(const ConditionalStatement conditionalStatement) 308 { 309 immutable bool cs = blockStatementIntroducesScope; 310 blockStatementIntroducesScope = false; 311 conditionalStatement.accept(this); 312 blockStatementIntroducesScope = cs; 313 } 314 315 override void visit(const AsmPrimaryExp primary) 316 { 317 if (primary.token != tok!"") 318 variableUsed(primary.token.text); 319 if (primary.identifierChain !is null) 320 variableUsed(primary.identifierChain.identifiers[0].text); 321 } 322 323 override void visit(const TraitsExpression) 324 { 325 // issue #266: Ignore unused variables inside of `__traits` expressions 326 } 327 328 override void visit(const TypeofExpression) 329 { 330 // issue #270: Ignore unused variables inside of `typeof` expressions 331 } 332 333 private: 334 335 mixin template PartsUseVariables(NodeType) 336 { 337 override void visit(const NodeType node) 338 { 339 interestDepth++; 340 node.accept(this); 341 interestDepth--; 342 } 343 } 344 345 void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) 346 { 347 if (inAggregateScope) 348 return; 349 tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); 350 } 351 352 void variableUsed(string name) 353 { 354 size_t treeIndex = tree.length - 1; 355 auto uu = UnUsed(name); 356 while (true) 357 { 358 if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) 359 break; 360 treeIndex--; 361 } 362 } 363 364 void popScope() 365 { 366 foreach (uu; tree[$ - 1]) 367 { 368 if (!uu.isRef && tree.length > 1) 369 { 370 immutable string certainty = uu.uncertain ? " might not be used." 371 : " is never used."; 372 immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ") 373 ~ uu.name ~ certainty; 374 addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter" 375 : "dscanner.suspicious.unused_variable", errorMessage); 376 } 377 } 378 tree = tree[0 .. $ - 1]; 379 } 380 381 void pushScope() 382 { 383 tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name"); 384 } 385 386 struct UnUsed 387 { 388 string name; 389 size_t line; 390 size_t column; 391 bool isParameter; 392 bool isRef; 393 bool uncertain; 394 } 395 396 RedBlackTree!(UnUsed*, "a.name < b.name")[] tree; 397 398 uint interestDepth; 399 400 uint mixinDepth; 401 402 bool isOverride; 403 404 bool inAggregateScope; 405 406 bool blockStatementIntroducesScope = true; 407 408 Regex!char re; 409 } 410 411 unittest 412 { 413 import std.stdio : stderr; 414 import analysis.config : StaticAnalysisConfig, Check; 415 import analysis.helpers : assertAnalyzerWarnings; 416 417 StaticAnalysisConfig sac; 418 sac.unused_variable_check = Check.enabled; 419 assertAnalyzerWarnings(q{ 420 421 // Issue 274 422 unittest 423 { 424 size_t byteIndex = 0; 425 *(cast(FieldType*)(retVal.ptr + byteIndex)) = item; 426 } 427 428 unittest 429 { 430 int a; // [warn]: Variable a is never used. 431 } 432 433 void doStuff(int a, int b) // [warn]: Parameter b is never used. 434 { 435 return a; 436 } 437 438 }}, sac); 439 stderr.writeln("Unittest for UnusedVariableCheck passed."); 440 }