1 // Copyright Basile Burg 2017. 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.useless_initializer; 6 7 import analysis.base; 8 import containers.dynamicarray; 9 import containers.hashmap; 10 import dparse.ast; 11 import dparse.lexer; 12 import std.algorithm; 13 import std.range : empty; 14 import std.stdio; 15 16 /* 17 Limitations: 18 - Stuff s = Stuff.init does not work with type with postfixes`*` `[]`. 19 - Stuff s = Stuff.init is only detected for struct within the module. 20 - BasicType b = BasicType(v), default init used in BasicType ctor, e.g int(8). 21 */ 22 23 /** 24 * Check that detects the initializers that are 25 * not different from the implcit initializer. 26 */ 27 final class UselessInitializerChecker : BaseAnalyzer 28 { 29 alias visit = BaseAnalyzer.visit; 30 31 private: 32 33 enum key = "dscanner.useless-initializer"; 34 35 version(unittest) 36 { 37 enum msg = "X"; 38 } 39 else 40 { 41 enum msg = `Variable %s initializer is useless because it does not differ from the default value`; 42 } 43 44 static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"]; 45 46 HashMap!(string, bool) _structCanBeInit; 47 DynamicArray!(string) _structStack; 48 DynamicArray!(bool) _inStruct; 49 DynamicArray!(bool) _atDisabled; 50 bool _inTest; 51 52 public: 53 54 /// 55 this(string fileName, bool skipTests = false) 56 { 57 super(fileName, null, skipTests); 58 _inStruct.insert(false); 59 } 60 61 override void visit(const(Unittest) test) 62 { 63 if (skipTests) 64 return; 65 _inTest = true; 66 test.accept(this); 67 _inTest = false; 68 } 69 70 override void visit(const(StructDeclaration) decl) 71 { 72 if (_inTest) 73 return; 74 75 assert(_inStruct.length > 1); 76 77 const string structName = _inStruct[$-2] ? 78 _structStack.back() ~ "." ~ decl.name.text : 79 decl.name.text; 80 81 _structStack.insert(structName); 82 _structCanBeInit[structName] = false; 83 _atDisabled.insert(false); 84 decl.accept(this); 85 _structStack.removeBack(); 86 _atDisabled.removeBack(); 87 } 88 89 override void visit(const(Declaration) decl) 90 { 91 _inStruct.insert(decl.structDeclaration !is null); 92 decl.accept(this); 93 if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor && 94 ((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) || 95 !decl.constructor.parameters)) 96 { 97 _atDisabled[$-1] = decl.attributes 98 .canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable"); 99 } 100 _inStruct.removeBack(); 101 } 102 103 override void visit(const(Constructor) decl) 104 { 105 if (_inStruct.length > 1 && _inStruct[$-2] && 106 ((decl.parameters && decl.parameters.parameters.length == 0) || !decl.parameters)) 107 { 108 const bool canBeInit = !_atDisabled[$-1]; 109 _structCanBeInit[_structStack.back()] = canBeInit; 110 if (!canBeInit) 111 _structCanBeInit[_structStack.back()] = !decl.memberFunctionAttributes 112 .canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable"); 113 } 114 decl.accept(this); 115 } 116 117 // issue 473, prevent to visit delegates that contain duck type checkers. 118 override void visit(const(TypeofExpression)) {} 119 120 // issue 473, prevent to check expressions in __traits(compiles, ...) 121 override void visit(const(TraitsExpression) e) 122 { 123 if (e.identifier.text == "compiles") 124 { 125 return; 126 } 127 else 128 { 129 e.accept(this); 130 } 131 } 132 133 override void visit(const(VariableDeclaration) decl) 134 { 135 if (!decl.type || !decl.type.type2 || 136 // initializer has to appear clearly in generated ddoc 137 decl.comment !is null || 138 // issue 474, manifest constants HAVE to be initialized. 139 decl.storageClasses.canFind!(a => a.token == tok!"enum")) 140 { 141 return; 142 } 143 144 foreach (declarator; decl.declarators) 145 { 146 if (!declarator.initializer || 147 !declarator.initializer.nonVoidInitializer || 148 declarator.comment !is null) 149 { 150 continue; 151 } 152 153 version(unittest) 154 { 155 enum warn = q{addErrorMessage(declarator.name.line, 156 declarator.name.column, key, msg);}; 157 } 158 else 159 { 160 import std.format : format; 161 enum warn = q{addErrorMessage(declarator.name.line, 162 declarator.name.column, key, msg.format(declarator.name.text));}; 163 } 164 165 // --- Info about the declaration type --- // 166 const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes 167 .canFind!(a => a.star != tok!""); 168 const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes 169 .canFind!(a => a.array); 170 171 bool isStr, isSzInt; 172 Token customType; 173 174 if (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain && 175 decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1) 176 { 177 const IdentifierOrTemplateInstance idt = 178 decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0]; 179 180 customType = idt.identifier; 181 isStr = customType.text.among("string", "wstring", "dstring") != 0; 182 isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0; 183 } 184 185 // --- 'BasicType/Symbol AssignExpression' ---// 186 const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer; 187 const UnaryExpression ue = cast(UnaryExpression) nvi.assignExpression; 188 if (ue && ue.primaryExpression) 189 { 190 const Token value = ue.primaryExpression.primary; 191 192 if (!isPtr && !isArr && !isStr && decl.type.type2.builtinType != tok!"") 193 { 194 switch(decl.type.type2.builtinType) 195 { 196 // check for common cases of default values 197 case tok!"byte", tok!"ubyte": 198 case tok!"short", tok!"ushort": 199 case tok!"int", tok!"uint": 200 case tok!"long", tok!"ulong": 201 case tok!"cent", tok!"ucent": 202 case tok!"bool": 203 if (intDefs.canFind(value.text) || value == tok!"false") 204 mixin(warn); 205 goto default; 206 default: 207 // check for BasicType.init 208 if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType && 209 ue.primaryExpression.primary.text == "init" && 210 !ue.primaryExpression.expression) 211 mixin(warn); 212 } 213 } 214 else if (isSzInt) 215 { 216 if (intDefs.canFind(value.text)) 217 mixin(warn); 218 } 219 else if (isPtr || isStr) 220 { 221 if (str(value.type) == "null") 222 mixin(warn); 223 } 224 else if (isArr) 225 { 226 if (str(value.type) == "null") 227 mixin(warn); 228 else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0) 229 mixin(warn); 230 } 231 } 232 233 // Symbol s = Symbol.init 234 else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression && 235 ue.unaryExpression.primaryExpression.identifierOrTemplateInstance && 236 ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType && 237 ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init") 238 { 239 if (customType.text in _structCanBeInit) 240 { 241 if (!_structCanBeInit[customType.text]) 242 mixin(warn); 243 } 244 } 245 246 // 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init 247 else if (nvi.arrayInitializer && (isArr || isStr)) 248 { 249 if (nvi.arrayInitializer.arrayMemberInitializations.length == 0) 250 mixin(warn); 251 } 252 } 253 254 decl.accept(this); 255 } 256 } 257 258 @system unittest 259 { 260 import analysis.config : Check, disabledConfig, StaticAnalysisConfig; 261 import analysis.helpers: assertAnalyzerWarnings; 262 import std.stdio : stderr; 263 264 StaticAnalysisConfig sac = disabledConfig; 265 sac.useless_initializer = Check.enabled; 266 267 // fails 268 assertAnalyzerWarnings(q{ 269 struct S {} 270 ubyte a = 0x0; // [warn]: X 271 int a = 0; // [warn]: X 272 ulong a = 0; // [warn]: X 273 int* a = null; // [warn]: X 274 Foo* a = null; // [warn]: X 275 int[] a = null; // [warn]: X 276 int[] a = []; // [warn]: X 277 string a = null; // [warn]: X 278 string a = null; // [warn]: X 279 wstring a = null; // [warn]: X 280 dstring a = null; // [warn]: X 281 size_t a = 0; // [warn]: X 282 ptrdiff_t a = 0; // [warn]: X 283 string a = []; // [warn]: X 284 char[] a = null; // [warn]: X 285 int a = int.init; // [warn]: X 286 char a = char.init; // [warn]: X 287 S s = S.init; // [warn]: X 288 bool a = false; // [warn]: X 289 }, sac); 290 291 // passes 292 assertAnalyzerWarnings(q{ 293 struct D {@disable this();} 294 struct E {this() @disable;} 295 ubyte a = 0xFE; 296 int a = 1; 297 ulong a = 1; 298 int* a = &a; 299 Foo* a = &a; 300 int[] a = &a; 301 int[] a = [0]; 302 string a = "sdf"; 303 string a = "sdg"c; 304 wstring a = "sdg"w; 305 dstring a = "fgh"d; 306 string a = q{int a;}; 307 size_t a = 1; 308 ptrdiff_t a; 309 ubyte a; 310 int a; 311 ulong a; 312 int* a; 313 Foo* a; 314 int[] a; 315 string a; 316 wstring a; 317 dstring a; 318 string a = ['a']; 319 string a = ""; 320 string a = ""c; 321 wstring a = ""w; 322 dstring a = ""d; 323 string a = q{}; 324 char[] a = "ze"; 325 S s = S(0,1); 326 S s = s.call(); 327 enum {a} 328 enum ubyte a = 0; 329 static assert(is(typeof((){T t = T.init;}))); 330 void foo(){__traits(compiles, (){int a = 0;}).writeln;} 331 bool a; 332 D d = D.init; 333 E e = E.init; 334 NotKnown nk = NotKnown.init; 335 }, sac); 336 337 stderr.writeln("Unittest for UselessInitializerChecker passed."); 338 } 339