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 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!IdentifierOrTemplateChain; 142 mixin PartsMightModify!ReturnStatement; 143 144 override void visit(const UnaryExpression unary) 145 { 146 if (unary.prefix == tok!"++" || unary.prefix == tok!"--" 147 || unary.suffix == tok!"++" || unary.suffix == tok!"--" 148 || unary.prefix == tok!"*" || unary.prefix == tok!"&") 149 { 150 interest++; 151 guaranteeUse++; 152 unary.accept(this); 153 guaranteeUse--; 154 interest--; 155 } 156 else 157 unary.accept(this); 158 } 159 160 override void visit(const ForeachStatement foreachStatement) 161 { 162 if (foreachStatement.low !is null) 163 { 164 interest++; 165 foreachStatement.low.accept(this); 166 interest--; 167 } 168 if (foreachStatement.declarationOrStatement !is null) 169 foreachStatement.declarationOrStatement.accept(this); 170 } 171 172 override void visit(const TraitsExpression) 173 { 174 // issue #266: Ignore unmodified variables inside of `__traits` expressions 175 } 176 177 override void visit(const TypeofExpression) 178 { 179 // issue #270: Ignore unmodified variables inside of `typeof` expressions 180 } 181 182 override void visit(const AsmStatement a) 183 { 184 inAsm = true; 185 a.accept(this); 186 inAsm = false; 187 } 188 189 private: 190 191 template PartsMightModify(T) 192 { 193 override void visit(const T t) 194 { 195 interest++; 196 t.accept(this); 197 interest--; 198 } 199 } 200 201 void variableMightBeModified(string name) 202 { 203 size_t index = tree.length - 1; 204 auto vi = VariableInfo(name); 205 if (guaranteeUse == 0) 206 { 207 auto r = tree[index].equalRange(&vi); 208 if (!r.empty && r.front.isValueType && !inAsm) 209 return; 210 } 211 while (true) 212 { 213 if (tree[index].removeKey(&vi) != 0 || index == 0) 214 break; 215 index--; 216 } 217 } 218 219 bool initializedFromNew(const Initializer initializer) 220 { 221 if (const UnaryExpression ue = cast(UnaryExpression) safeAccess(initializer) 222 .nonVoidInitializer.assignExpression) 223 { 224 return ue.newExpression !is null; 225 } 226 return false; 227 } 228 229 bool initializedFromCast(const Initializer initializer) 230 { 231 import std.typecons : scoped; 232 233 static class CastFinder : ASTVisitor 234 { 235 alias visit = ASTVisitor.visit; 236 override void visit(const CastExpression castExpression) 237 { 238 foundCast = true; 239 castExpression.accept(this); 240 } 241 242 bool foundCast; 243 } 244 245 if (initializer is null) 246 return false; 247 auto finder = scoped!CastFinder(); 248 finder.visit(initializer); 249 return finder.foundCast; 250 } 251 252 bool canFindImmutableOrConst(const Declaration dec) 253 { 254 import std.algorithm : canFind, map, filter; 255 256 return !dec.attributes.map!(a => a.attribute) 257 .filter!(a => a == tok!"immutable" || a == tok!"const").empty; 258 } 259 260 bool canFindImmutable(const VariableDeclaration dec) 261 { 262 import std.algorithm : canFind; 263 264 foreach (storageClass; dec.storageClasses) 265 { 266 if (storageClass.token == tok!"enum") 267 return true; 268 } 269 foreach (sc; dec.storageClasses) 270 { 271 if (sc.token == tok!"immutable" || sc.token == tok!"const") 272 return true; 273 } 274 if (dec.type !is null) 275 { 276 foreach (tk; dec.type.typeConstructors) 277 if (tk == tok!"immutable" || tk == tok!"const") 278 return true; 279 if (dec.type.type2) 280 { 281 const tk = dec.type.type2.typeConstructor; 282 if (tk == tok!"immutable" || tk == tok!"const") 283 return true; 284 } 285 } 286 return false; 287 } 288 289 static struct VariableInfo 290 { 291 string name; 292 size_t line; 293 size_t column; 294 bool isValueType; 295 } 296 297 void popScope() 298 { 299 foreach (vi; tree[$ - 1]) 300 { 301 immutable string errorMessage = "Variable " ~ vi.name 302 ~ " is never modified and could have been declared const or immutable."; 303 addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", errorMessage); 304 } 305 tree = tree[0 .. $ - 1]; 306 } 307 308 void pushScope() 309 { 310 tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name"); 311 } 312 313 int blockStatementDepth; 314 315 int interest; 316 317 int guaranteeUse; 318 319 int isImmutable; 320 321 bool inAsm; 322 323 RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree; 324 } 325 326 bool isValueTypeSimple(const Type type) pure nothrow @nogc 327 { 328 if (type.type2 is null) 329 return false; 330 return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0; 331 } 332 333 @system unittest 334 { 335 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 336 import dscanner.analysis.helpers : assertAnalyzerWarnings; 337 import std.stdio : stderr; 338 import std.format : format; 339 340 StaticAnalysisConfig sac = disabledConfig(); 341 sac.could_be_immutable_check = Check.enabled; 342 343 // fails 344 345 assertAnalyzerWarnings(q{ 346 void foo(){int i = 1;} // [warn]: Variable i is never modified and could have been declared const or immutable. 347 }, sac); 348 349 // pass 350 351 assertAnalyzerWarnings(q{ 352 void foo(){const(int) i;} 353 }, sac); 354 355 assertAnalyzerWarnings(q{ 356 void foo(){immutable(int)* i;} 357 }, sac); 358 359 assertAnalyzerWarnings(q{ 360 void foo(){enum i = 1;} 361 }, sac); 362 363 assertAnalyzerWarnings(q{ 364 void foo(){E e = new E;} 365 }, sac); 366 367 assertAnalyzerWarnings(q{ 368 void foo(){auto e = new E;} 369 }, sac); 370 371 }