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