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