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