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 foreachStatement.declarationOrStatement.accept(this); 164 } 165 166 override void visit(const TraitsExpression) 167 { 168 // issue #266: Ignore unmodified variables inside of `__traits` expressions 169 } 170 171 override void visit(const TypeofExpression) 172 { 173 // issue #270: Ignore unmodified variables inside of `typeof` expressions 174 } 175 176 override void visit(const AsmStatement a) 177 { 178 inAsm = true; 179 a.accept(this); 180 inAsm = false; 181 } 182 183 private: 184 185 template PartsMightModify(T) 186 { 187 override void visit(const T t) 188 { 189 interest++; 190 t.accept(this); 191 interest--; 192 } 193 } 194 195 void variableMightBeModified(string name) 196 { 197 size_t index = tree.length - 1; 198 auto vi = VariableInfo(name); 199 if (guaranteeUse == 0) 200 { 201 auto r = tree[index].equalRange(&vi); 202 if (!r.empty && r.front.isValueType && !inAsm) 203 return; 204 } 205 while (true) 206 { 207 if (tree[index].removeKey(&vi) != 0 || index == 0) 208 break; 209 index--; 210 } 211 } 212 213 bool initializedFromCast(const Initializer initializer) 214 { 215 import std.typecons : scoped; 216 217 static class CastFinder : ASTVisitor 218 { 219 alias visit = ASTVisitor.visit; 220 override void visit(const CastExpression castExpression) 221 { 222 foundCast = true; 223 castExpression.accept(this); 224 } 225 226 bool foundCast = false; 227 } 228 229 if (initializer is null) 230 return false; 231 auto finder = scoped!CastFinder(); 232 finder.visit(initializer); 233 return finder.foundCast; 234 } 235 236 bool canFindImmutableOrConst(const Declaration dec) 237 { 238 import std.algorithm : canFind, map, filter; 239 240 return !dec.attributes.map!(a => a.attribute) 241 .filter!(a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const").empty; 242 } 243 244 bool canFindImmutable(const VariableDeclaration dec) 245 { 246 import std.algorithm : canFind; 247 248 foreach (storageClass; dec.storageClasses) 249 { 250 if (storageClass.token == tok!"enum") 251 return true; 252 } 253 foreach (sc; dec.storageClasses) 254 { 255 if (sc.token == tok!"immutable" || sc.token == tok!"const") 256 return true; 257 } 258 if (dec.type !is null) 259 { 260 if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable")) 261 return true; 262 } 263 return false; 264 } 265 266 static struct VariableInfo 267 { 268 string name; 269 size_t line; 270 size_t column; 271 bool isValueType; 272 } 273 274 void popScope() 275 { 276 foreach (vi; tree[$ - 1]) 277 { 278 immutable string errorMessage = "Variable " ~ vi.name 279 ~ " is never modified and could have been declared const" ~ " or immutable."; 280 addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", errorMessage); 281 } 282 tree = tree[0 .. $ - 1]; 283 } 284 285 void pushScope() 286 { 287 tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name"); 288 } 289 290 int blockStatementDepth; 291 292 int interest; 293 294 int guaranteeUse; 295 296 int isImmutable; 297 298 bool inAsm; 299 300 RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree; 301 } 302 303 bool isValueTypeSimple(const Type type) pure nothrow @nogc 304 { 305 if (type.type2 is null) 306 return false; 307 return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0; 308 }