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 private: 33 34 enum key = "dscanner.useless-initializer"; 35 36 version(unittest) 37 { 38 enum msg = "X"; 39 } 40 else 41 { 42 enum msg = `Variable %s initializer is useless because it does not differ from the default value`; 43 } 44 45 static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"]; 46 47 HashMap!(string, bool) _structCanBeInit; 48 DynamicArray!(string) _structStack; 49 DynamicArray!(bool) _inStruct; 50 DynamicArray!(bool) _atDisabled; 51 bool _inTest; 52 53 public: 54 55 /// 56 this(string fileName, bool skipTests = false) 57 { 58 super(fileName, null, skipTests); 59 _inStruct.insert(false); 60 } 61 62 override void visit(const(Unittest) test) 63 { 64 if (skipTests) 65 return; 66 _inTest = true; 67 test.accept(this); 68 _inTest = false; 69 } 70 71 override void visit(const(StructDeclaration) decl) 72 { 73 if (_inTest) 74 return; 75 76 assert(_inStruct.length > 1); 77 78 const string structName = _inStruct[$-2] ? 79 _structStack.back() ~ "." ~ decl.name.text : 80 decl.name.text; 81 82 _structStack.insert(structName); 83 _structCanBeInit[structName] = false; 84 _atDisabled.insert(false); 85 decl.accept(this); 86 _structStack.removeBack(); 87 _atDisabled.removeBack(); 88 } 89 90 override void visit(const(Declaration) decl) 91 { 92 _inStruct.insert(decl.structDeclaration !is null); 93 decl.accept(this); 94 if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor && 95 ((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) || 96 !decl.constructor.parameters)) 97 { 98 _atDisabled[$-1] = decl.attributes 99 .canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable"); 100 } 101 _inStruct.removeBack(); 102 } 103 104 override void visit(const(Constructor) decl) 105 { 106 if (_inStruct.length > 1 && _inStruct[$-2] && 107 ((decl.parameters && decl.parameters.parameters.length == 0) || !decl.parameters)) 108 { 109 const bool canBeInit = !_atDisabled[$-1]; 110 _structCanBeInit[_structStack.back()] = canBeInit; 111 if (!canBeInit) 112 _structCanBeInit[_structStack.back()] = !decl.memberFunctionAttributes 113 .canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable"); 114 } 115 decl.accept(this); 116 } 117 118 // issue 473, prevent to visit delegates that contain duck type checkers. 119 override void visit(const(TypeofExpression)) {} 120 121 // issue 473, prevent to check expressions in __traits(compiles, ...) 122 override void visit(const(TraitsExpression) e) 123 { 124 if (e.identifier.text == "compiles") 125 { 126 return; 127 } 128 else 129 { 130 e.accept(this); 131 } 132 } 133 134 override void visit(const(VariableDeclaration) decl) 135 { 136 if (!decl.type || !decl.type.type2 || 137 // initializer has to appear clearly in generated ddoc 138 decl.comment !is null || 139 // issue 474, manifest constants HAVE to be initialized. 140 decl.storageClasses.canFind!(a => a.token == tok!"enum")) 141 { 142 return; 143 } 144 145 foreach (declarator; decl.declarators) 146 { 147 if (!declarator.initializer || 148 !declarator.initializer.nonVoidInitializer || 149 declarator.comment !is null) 150 { 151 continue; 152 } 153 154 version(unittest) 155 { 156 enum warn = q{addErrorMessage(declarator.name.line, 157 declarator.name.column, key, msg);}; 158 } 159 else 160 { 161 import std.format : format; 162 enum warn = q{addErrorMessage(declarator.name.line, 163 declarator.name.column, key, msg.format(declarator.name.text));}; 164 } 165 166 // --- Info about the declaration type --- // 167 const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes 168 .canFind!(a => a.star != tok!""); 169 const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes 170 .canFind!(a => a.array); 171 172 bool isStr, isSzInt; 173 Token customType; 174 175 if (const TypeIdentifierPart tip = safeAccess(decl).type.type2.typeIdentifierPart) 176 { 177 if (!tip.typeIdentifierPart) 178 { 179 customType = tip.identifierOrTemplateInstance.identifier; 180 isStr = customType.text.among("string", "wstring", "dstring") != 0; 181 isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0; 182 } 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 else if (const IdentifierOrTemplateInstance iot = safeAccess(ue) 234 .unaryExpression.primaryExpression.identifierOrTemplateInstance) 235 { 236 // Symbol s = Symbol.init 237 if (ue && customType != tok!"" && iot.identifier == customType && 238 ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init") 239 { 240 if (customType.text in _structCanBeInit) 241 { 242 if (!_structCanBeInit[customType.text]) 243 mixin(warn); 244 } 245 } 246 } 247 248 // 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init 249 else if (nvi.arrayInitializer && (isArr || isStr)) 250 { 251 if (nvi.arrayInitializer.arrayMemberInitializations.length == 0) 252 mixin(warn); 253 } 254 } 255 256 decl.accept(this); 257 } 258 } 259 260 @system unittest 261 { 262 import dscanner.analysis.config : Check, disabledConfig, StaticAnalysisConfig; 263 import dscanner.analysis.helpers: assertAnalyzerWarnings; 264 import std.stdio : stderr; 265 266 StaticAnalysisConfig sac = disabledConfig; 267 sac.useless_initializer = Check.enabled; 268 269 // fails 270 assertAnalyzerWarnings(q{ 271 struct S {} 272 ubyte a = 0x0; // [warn]: X 273 int a = 0; // [warn]: X 274 ulong a = 0; // [warn]: X 275 int* a = null; // [warn]: X 276 Foo* a = null; // [warn]: X 277 int[] a = null; // [warn]: X 278 int[] a = []; // [warn]: X 279 string a = null; // [warn]: X 280 string a = null; // [warn]: X 281 wstring a = null; // [warn]: X 282 dstring a = null; // [warn]: X 283 size_t a = 0; // [warn]: X 284 ptrdiff_t a = 0; // [warn]: X 285 string a = []; // [warn]: X 286 char[] a = null; // [warn]: X 287 int a = int.init; // [warn]: X 288 char a = char.init; // [warn]: X 289 S s = S.init; // [warn]: X 290 bool a = false; // [warn]: X 291 }, sac); 292 293 // passes 294 assertAnalyzerWarnings(q{ 295 struct D {@disable this();} 296 struct E {this() @disable;} 297 ubyte a = 0xFE; 298 int a = 1; 299 ulong a = 1; 300 int* a = &a; 301 Foo* a = &a; 302 int[] a = &a; 303 int[] a = [0]; 304 string a = "sdf"; 305 string a = "sdg"c; 306 wstring a = "sdg"w; 307 dstring a = "fgh"d; 308 string a = q{int a;}; 309 size_t a = 1; 310 ptrdiff_t a; 311 ubyte a; 312 int a; 313 ulong a; 314 int* a; 315 Foo* a; 316 int[] a; 317 string a; 318 wstring a; 319 dstring a; 320 string a = ['a']; 321 string a = ""; 322 string a = ""c; 323 wstring a = ""w; 324 dstring a = ""d; 325 string a = q{}; 326 char[] a = "ze"; 327 S s = S(0,1); 328 S s = s.call(); 329 enum {a} 330 enum ubyte a = 0; 331 static assert(is(typeof((){T t = T.init;}))); 332 void foo(){__traits(compiles, (){int a = 0;}).writeln;} 333 bool a; 334 D d = D.init; 335 E e = E.init; 336 NotKnown nk = NotKnown.init; 337 }, sac); 338 339 stderr.writeln("Unittest for UselessInitializerChecker passed."); 340 } 341