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 std.container; 8 import std.d.ast; 9 import std.d.lexer; 10 import analysis.base; 11 12 /** 13 * Checks for variables that could have been declared const or immutable 14 */ 15 class UnmodifiedFinder:BaseAnalyzer 16 { 17 alias visit = BaseAnalyzer.visit; 18 19 /// 20 this(string fileName) 21 { 22 super(fileName); 23 } 24 25 override void visit(const Module mod) 26 { 27 pushScope(); 28 mod.accept(this); 29 popScope(); 30 } 31 32 override void visit(const BlockStatement blockStatement) 33 { 34 pushScope(); 35 blockStatementDepth++; 36 blockStatement.accept(this); 37 blockStatementDepth--; 38 popScope(); 39 } 40 41 override void visit(const StructBody structBody) 42 { 43 pushScope(); 44 auto oldBlockStatementDepth = blockStatementDepth; 45 blockStatementDepth = 0; 46 structBody.accept(this); 47 blockStatementDepth = oldBlockStatementDepth; 48 popScope(); 49 } 50 51 override void visit(const VariableDeclaration dec) 52 { 53 if (dec.autoDeclaration is null && blockStatementDepth > 0 54 && isImmutable <= 0 && !canFindImmutable(dec)) 55 { 56 foreach (d; dec.declarators) 57 { 58 if (initializedFromCast(d.initializer)) 59 continue; 60 tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line, 61 d.name.column)); 62 } 63 } 64 dec.accept(this); 65 } 66 67 override void visit(const AutoDeclaration autoDeclaration) 68 { 69 import std.algorithm : canFind; 70 71 if (blockStatementDepth > 0 && isImmutable <= 0 72 && (!autoDeclaration.storageClasses.canFind!(a => a.token == tok!"const" 73 || a.token == tok!"enum" || a.token == tok!"immutable"))) 74 { 75 foreach (size_t i, id; autoDeclaration.identifiers) 76 { 77 if (initializedFromCast(autoDeclaration.initializers[i])) 78 continue; 79 tree[$ - 1].insert(new VariableInfo(id.text, id.line, 80 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 assignExpression.ternaryExpression.accept(this); 92 interest--; 93 assignExpression.assignExpression.accept(this); 94 } 95 else 96 assignExpression.accept(this); 97 } 98 99 override void visit(const Declaration dec) 100 { 101 if (canFindImmutableOrConst(dec)) 102 { 103 isImmutable++; 104 dec.accept(this); 105 isImmutable--; 106 } 107 else 108 dec.accept(this); 109 } 110 111 override void visit(const IdentifierChain ic) 112 { 113 if (ic.identifiers.length && interest > 0) 114 variableMightBeModified(ic.identifiers[0].text); 115 ic.accept(this); 116 } 117 118 override void visit(const IdentifierOrTemplateInstance ioti) 119 { 120 if (ioti.identifier != tok!"" && interest > 0) 121 variableMightBeModified(ioti.identifier.text); 122 ioti.accept(this); 123 } 124 125 mixin PartsMightModify!AsmPrimaryExp; 126 mixin PartsMightModify!IndexExpression; 127 mixin PartsMightModify!SliceExpression; 128 mixin PartsMightModify!FunctionCallExpression; 129 mixin PartsMightModify!IdentifierOrTemplateChain; 130 mixin PartsMightModify!ReturnStatement; 131 132 override void visit(const UnaryExpression unary) 133 { 134 if (unary.prefix == tok!"++" || unary.prefix == tok!"--" 135 || unary.suffix == tok!"++" || unary.suffix == tok!"--") 136 { 137 interest++; 138 unary.accept(this); 139 interest--; 140 } 141 else 142 unary.accept(this); 143 } 144 145 override void visit(const ForeachStatement foreachStatement) 146 { 147 if (foreachStatement.low !is null) 148 { 149 interest++; 150 foreachStatement.low.accept(this); 151 interest--; 152 } 153 foreachStatement.declarationOrStatement.accept(this); 154 } 155 156 override void visit(const TraitsExpression) 157 { 158 // issue #266: Ignore unmodified variables inside of `__traits` expressions 159 } 160 161 override void visit(const TypeofExpression) 162 { 163 // issue #270: Ignore unmodified variables inside of `typeof` expressions 164 } 165 166 private: 167 168 template PartsMightModify(T) 169 { 170 override void visit(const T t) 171 { 172 interest++; 173 t.accept(this); 174 interest--; 175 } 176 } 177 178 void variableMightBeModified(string name) 179 { 180 // import std.stdio : stderr; 181 // stderr.writeln("Marking ", name, " as possibly modified"); 182 size_t index = tree.length - 1; 183 auto vi = VariableInfo(name); 184 while (true) 185 { 186 if (tree[index].removeKey(&vi) != 0 || index == 0) 187 break; 188 index--; 189 } 190 } 191 192 bool initializedFromCast(const Initializer initializer) 193 { 194 import std.typecons : scoped; 195 196 static class CastFinder : ASTVisitor 197 { 198 alias visit = ASTVisitor.visit; 199 override void visit(const CastExpression castExpression) 200 { 201 foundCast = true; 202 castExpression.accept(this); 203 } 204 bool foundCast = false; 205 } 206 207 if (initializer is null) 208 return false; 209 auto finder = scoped!CastFinder(); 210 finder.visit(initializer); 211 return finder.foundCast; 212 } 213 214 bool canFindImmutableOrConst(const Declaration dec) 215 { 216 import std.algorithm : canFind, map, filter; 217 return !dec.attributes.map!(a => a.attribute).filter!( 218 a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const") 219 .empty; 220 } 221 222 bool canFindImmutable(const VariableDeclaration dec) 223 { 224 import std.algorithm : canFind; 225 foreach (storageClass; dec.storageClasses) 226 { 227 if (storageClass.token == tok!"enum") 228 return true; 229 } 230 foreach (attr; dec.attributes) 231 { 232 if (attr.attribute.type == tok!"immutable" || attr.attribute.type == tok!"const") 233 return true; 234 } 235 if (dec.type !is null) 236 { 237 if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable")) 238 return true; 239 } 240 return false; 241 } 242 243 static struct VariableInfo 244 { 245 string name; 246 size_t line; 247 size_t column; 248 } 249 250 void popScope() 251 { 252 foreach (vi; tree[$ - 1]) 253 { 254 immutable string errorMessage = "Variable " ~ vi.name 255 ~ " is never modified and could have been declared const" 256 ~ " or immutable."; 257 addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", 258 errorMessage); 259 } 260 tree = tree[0 .. $ - 1]; 261 } 262 263 void pushScope() 264 { 265 tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name"); 266 } 267 268 int blockStatementDepth; 269 270 int interest; 271 272 int isImmutable; 273 274 RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree; 275 } 276