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