1 // Copyright Brian Schott (Hackerpilot) 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.unmodified; 6 7 import dscanner.analysis.base; 8 import dsymbol.scope_ : Scope; 9 import std.container; 10 import dparse.ast; 11 import dparse.lexer; 12 13 /** 14 * Checks for variables that could have been declared const or immutable 15 */ 16 class UnmodifiedFinder : BaseAnalyzer 17 { 18 alias visit = BaseAnalyzer.visit; 19 20 /// 21 this(string fileName, const(Scope)* sc, bool skipTests = false) 22 { 23 super(fileName, sc, skipTests); 24 } 25 26 override void visit(const Module mod) 27 { 28 pushScope(); 29 mod.accept(this); 30 popScope(); 31 } 32 33 override void visit(const BlockStatement blockStatement) 34 { 35 pushScope(); 36 blockStatementDepth++; 37 blockStatement.accept(this); 38 blockStatementDepth--; 39 popScope(); 40 } 41 42 override void visit(const StructBody structBody) 43 { 44 pushScope(); 45 immutable oldBlockStatementDepth = blockStatementDepth; 46 blockStatementDepth = 0; 47 structBody.accept(this); 48 blockStatementDepth = oldBlockStatementDepth; 49 popScope(); 50 } 51 52 override void visit(const VariableDeclaration dec) 53 { 54 if (dec.autoDeclaration is null && blockStatementDepth > 0 55 && isImmutable <= 0 && !canFindImmutable(dec)) 56 { 57 foreach (d; dec.declarators) 58 { 59 if (initializedFromCast(d.initializer)) 60 continue; 61 if (initializedFromNew(d.initializer)) 62 continue; 63 tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line, 64 d.name.column, isValueTypeSimple(dec.type))); 65 } 66 } 67 dec.accept(this); 68 } 69 70 override void visit(const AutoDeclaration autoDeclaration) 71 { 72 import std.algorithm : canFind; 73 74 if (blockStatementDepth > 0 && isImmutable <= 0 75 && (!autoDeclaration.storageClasses.canFind!(a => a.token == tok!"const" 76 || a.token == tok!"enum" || a.token == tok!"immutable"))) 77 { 78 foreach (part; autoDeclaration.parts) 79 { 80 if (initializedFromCast(part.initializer)) 81 continue; 82 if (initializedFromNew(part.initializer)) 83 continue; 84 tree[$ - 1].insert(new VariableInfo(part.identifier.text, 85 part.identifier.line, part.identifier.column)); 86 } 87 } 88 autoDeclaration.accept(this); 89 } 90 91 override void visit(const AssignExpression assignExpression) 92 { 93 if (assignExpression.operator != tok!"") 94 { 95 interest++; 96 guaranteeUse++; 97 assignExpression.ternaryExpression.accept(this); 98 guaranteeUse--; 99 interest--; 100 101 if (assignExpression.operator == tok!"~=") 102 interest++; 103 assignExpression.expression.accept(this); 104 if (assignExpression.operator == tok!"~=") 105 interest--; 106 } 107 else 108 assignExpression.accept(this); 109 } 110 111 override void visit(const Declaration dec) 112 { 113 if (canFindImmutableOrConst(dec)) 114 { 115 isImmutable++; 116 dec.accept(this); 117 isImmutable--; 118 } 119 else 120 dec.accept(this); 121 } 122 123 override void visit(const IdentifierChain ic) 124 { 125 if (ic.identifiers.length && interest > 0) 126 variableMightBeModified(ic.identifiers[0].text); 127 ic.accept(this); 128 } 129 130 override void visit(const IdentifierOrTemplateInstance ioti) 131 { 132 if (ioti.identifier != tok!"" && interest > 0) 133 variableMightBeModified(ioti.identifier.text); 134 ioti.accept(this); 135 } 136 137 mixin PartsMightModify!AsmPrimaryExp; 138 mixin PartsMightModify!IndexExpression; 139 mixin PartsMightModify!FunctionCallExpression; 140 mixin PartsMightModify!IdentifierOrTemplateChain; 141 mixin PartsMightModify!ReturnStatement; 142 143 override void visit(const UnaryExpression unary) 144 { 145 if (unary.prefix == tok!"++" || unary.prefix == tok!"--" 146 || unary.suffix == tok!"++" || unary.suffix == tok!"--" 147 || unary.prefix == tok!"*" || unary.prefix == tok!"&") 148 { 149 interest++; 150 guaranteeUse++; 151 unary.accept(this); 152 guaranteeUse--; 153 interest--; 154 } 155 else 156 unary.accept(this); 157 } 158 159 override void visit(const ForeachStatement foreachStatement) 160 { 161 if (foreachStatement.low !is null) 162 { 163 interest++; 164 foreachStatement.low.accept(this); 165 interest--; 166 } 167 if (foreachStatement.declarationOrStatement !is null) 168 foreachStatement.declarationOrStatement.accept(this); 169 } 170 171 override void visit(const TraitsExpression) 172 { 173 // issue #266: Ignore unmodified variables inside of `__traits` expressions 174 } 175 176 override void visit(const TypeofExpression) 177 { 178 // issue #270: Ignore unmodified variables inside of `typeof` expressions 179 } 180 181 override void visit(const AsmStatement a) 182 { 183 inAsm = true; 184 a.accept(this); 185 inAsm = false; 186 } 187 188 private: 189 190 template PartsMightModify(T) 191 { 192 override void visit(const T t) 193 { 194 interest++; 195 t.accept(this); 196 interest--; 197 } 198 } 199 200 void variableMightBeModified(string name) 201 { 202 size_t index = tree.length - 1; 203 auto vi = VariableInfo(name); 204 if (guaranteeUse == 0) 205 { 206 auto r = tree[index].equalRange(&vi); 207 if (!r.empty && r.front.isValueType && !inAsm) 208 return; 209 } 210 while (true) 211 { 212 if (tree[index].removeKey(&vi) != 0 || index == 0) 213 break; 214 index--; 215 } 216 } 217 218 bool initializedFromNew(const Initializer initializer) 219 { 220 if (initializer && initializer.nonVoidInitializer && 221 initializer.nonVoidInitializer.assignExpression && 222 cast(UnaryExpression) initializer.nonVoidInitializer.assignExpression) 223 { 224 const UnaryExpression ue = 225 cast(UnaryExpression) initializer.nonVoidInitializer.assignExpression; 226 return ue.newExpression !is null; 227 } 228 return false; 229 } 230 231 bool initializedFromCast(const Initializer initializer) 232 { 233 import std.typecons : scoped; 234 235 static class CastFinder : ASTVisitor 236 { 237 alias visit = ASTVisitor.visit; 238 override void visit(const CastExpression castExpression) 239 { 240 foundCast = true; 241 castExpression.accept(this); 242 } 243 244 bool foundCast; 245 } 246 247 if (initializer is null) 248 return false; 249 auto finder = scoped!CastFinder(); 250 finder.visit(initializer); 251 return finder.foundCast; 252 } 253 254 bool canFindImmutableOrConst(const Declaration dec) 255 { 256 import std.algorithm : canFind, map, filter; 257 258 return !dec.attributes.map!(a => a.attribute) 259 .filter!(a => a == tok!"immutable" || a == tok!"const").empty; 260 } 261 262 bool canFindImmutable(const VariableDeclaration dec) 263 { 264 import std.algorithm : canFind; 265 266 foreach (storageClass; dec.storageClasses) 267 { 268 if (storageClass.token == tok!"enum") 269 return true; 270 } 271 foreach (sc; dec.storageClasses) 272 { 273 if (sc.token == tok!"immutable" || sc.token == tok!"const") 274 return true; 275 } 276 if (dec.type !is null) 277 { 278 foreach (tk; dec.type.typeConstructors) 279 if (tk == tok!"immutable" || tk == tok!"const") 280 return true; 281 if (dec.type.type2) 282 { 283 const tk = dec.type.type2.typeConstructor; 284 if (tk == tok!"immutable" || tk == tok!"const") 285 return true; 286 } 287 } 288 return false; 289 } 290 291 static struct VariableInfo 292 { 293 string name; 294 size_t line; 295 size_t column; 296 bool isValueType; 297 } 298 299 void popScope() 300 { 301 foreach (vi; tree[$ - 1]) 302 { 303 immutable string errorMessage = "Variable " ~ vi.name 304 ~ " is never modified and could have been declared const or immutable."; 305 addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", errorMessage); 306 } 307 tree = tree[0 .. $ - 1]; 308 } 309 310 void pushScope() 311 { 312 tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name"); 313 } 314 315 int blockStatementDepth; 316 317 int interest; 318 319 int guaranteeUse; 320 321 int isImmutable; 322 323 bool inAsm; 324 325 RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree; 326 } 327 328 bool isValueTypeSimple(const Type type) pure nothrow @nogc 329 { 330 if (type.type2 is null) 331 return false; 332 return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0; 333 } 334 335 @system unittest 336 { 337 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 338 import dscanner.analysis.helpers : assertAnalyzerWarnings; 339 import std.stdio : stderr; 340 import std.format : format; 341 342 StaticAnalysisConfig sac = disabledConfig(); 343 sac.could_be_immutable_check = Check.enabled; 344 345 // fails 346 347 assertAnalyzerWarnings(q{ 348 void foo(){int i = 1;} // [warn]: Variable i is never modified and could have been declared const or immutable. 349 }, sac); 350 351 // pass 352 353 assertAnalyzerWarnings(q{ 354 void foo(){const(int) i;} 355 }, sac); 356 357 assertAnalyzerWarnings(q{ 358 void foo(){immutable(int)* i;} 359 }, sac); 360 361 assertAnalyzerWarnings(q{ 362 void foo(){enum i = 1;} 363 }, sac); 364 365 assertAnalyzerWarnings(q{ 366 void foo(){E e = new E;} 367 }, sac); 368 369 assertAnalyzerWarnings(q{ 370 void foo(){auto e = new E;} 371 }, sac); 372 373 }