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