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