1 // Copyright Basile Burg 2016. 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 6 module dscanner.analysis.auto_function; 7 8 import dscanner.analysis.base; 9 import dscanner.analysis.helpers; 10 import dparse.ast; 11 import dparse.lexer; 12 13 import std.stdio; 14 import std.algorithm.searching : any; 15 16 /** 17 * Checks for auto functions without return statement. 18 * 19 * Auto function without return statement can be an omission and are not 20 * detected by the compiler. However sometimes they can be used as a trick 21 * to infer attributes. 22 */ 23 final class AutoFunctionChecker : BaseAnalyzer 24 { 25 26 private: 27 28 enum string KEY = "dscanner.suspicious.missing_return"; 29 enum string MESSAGE = "Auto function without return statement, prefer an explicit void"; 30 31 bool[] _returns; 32 size_t _mixinDepth; 33 string[] _literalWithReturn; 34 35 public: 36 37 alias visit = BaseAnalyzer.visit; 38 39 mixin AnalyzerInfo!"auto_function_check"; 40 41 /// 42 this(string fileName, bool skipTests = false) 43 { 44 super(fileName, null, skipTests); 45 } 46 47 override void visit(const(FunctionDeclaration) decl) 48 { 49 _returns.length += 1; 50 scope(exit) _returns.length -= 1; 51 _returns[$-1] = false; 52 53 const bool autoFun = decl.storageClasses 54 .any!(a => a.token.type == tok!"auto" || a.atAttribute !is null); 55 56 decl.accept(this); 57 58 if (decl.functionBody.specifiedFunctionBody && autoFun && !_returns[$-1]) 59 addErrorMessage(decl.name.line, decl.name.column, KEY, MESSAGE); 60 } 61 62 override void visit(const(ReturnStatement) rst) 63 { 64 if (_returns.length) 65 _returns[$-1] = true; 66 rst.accept(this); 67 } 68 69 override void visit(const(AssertArguments) exp) 70 { 71 exp.accept(this); 72 if (_returns.length) 73 { 74 const UnaryExpression u = cast(UnaryExpression) exp.assertion; 75 if (!u) 76 return; 77 const PrimaryExpression p = u.primaryExpression; 78 if (!p) 79 return; 80 81 immutable token = p.primary; 82 if (token.type == tok!"false") 83 _returns[$-1] = true; 84 else if (token.text == "0") 85 _returns[$-1] = true; 86 } 87 } 88 89 override void visit(const(MixinExpression) mix) 90 { 91 ++_mixinDepth; 92 mix.accept(this); 93 --_mixinDepth; 94 } 95 96 override void visit(const(PrimaryExpression) exp) 97 { 98 exp.accept(this); 99 100 import std.algorithm.searching : canFind; 101 102 if (_returns.length && _mixinDepth) 103 { 104 if (findReturnInLiteral(exp.primary.text)) 105 _returns[$-1] = true; 106 else if (exp.identifierOrTemplateInstance && 107 _literalWithReturn.canFind(exp.identifierOrTemplateInstance.identifier.text)) 108 _returns[$-1] = true; 109 } 110 } 111 112 private bool findReturnInLiteral(const(string) value) 113 { 114 import std.algorithm.searching : find; 115 import std.range : empty; 116 117 return value == "return" || !value.find("return ").empty; 118 } 119 120 private bool stringliteralHasReturn(const(NonVoidInitializer) nvi) 121 { 122 bool result; 123 if (!nvi.assignExpression || (cast(UnaryExpression) nvi.assignExpression) is null) 124 return result; 125 126 const(UnaryExpression) u = cast(UnaryExpression) nvi.assignExpression; 127 if (u.primaryExpression && 128 u.primaryExpression.primary.type.isStringLiteral && 129 findReturnInLiteral(u.primaryExpression.primary.text)) 130 result = true; 131 132 return result; 133 } 134 135 override void visit(const(AutoDeclaration) decl) 136 { 137 decl.accept(this); 138 139 foreach(const(AutoDeclarationPart) p; decl.parts) 140 if (p.initializer && 141 p.initializer.nonVoidInitializer && 142 stringliteralHasReturn(p.initializer.nonVoidInitializer)) 143 _literalWithReturn ~= p.identifier.text.idup; 144 } 145 146 override void visit(const(VariableDeclaration) decl) 147 { 148 decl.accept(this); 149 150 foreach(const(Declarator) d; decl.declarators) 151 if (d.initializer && 152 d.initializer.nonVoidInitializer && 153 stringliteralHasReturn(d.initializer.nonVoidInitializer)) 154 _literalWithReturn ~= d.name.text.idup; 155 } 156 } 157 158 unittest 159 { 160 import std.stdio : stderr; 161 import std.format : format; 162 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 163 import dscanner.analysis.helpers : assertAnalyzerWarnings; 164 165 StaticAnalysisConfig sac = disabledConfig(); 166 sac.auto_function_check = Check.enabled; 167 assertAnalyzerWarnings(q{ 168 auto ref doStuff(){} // [warn]: %s 169 auto doStuff(){} // [warn]: %s 170 int doStuff(){auto doStuff(){}} // [warn]: %s 171 auto doStuff(){return 0;} 172 int doStuff(){/*error but not the aim*/} 173 }}.format( 174 AutoFunctionChecker.MESSAGE, 175 AutoFunctionChecker.MESSAGE, 176 AutoFunctionChecker.MESSAGE, 177 ), sac); 178 179 assertAnalyzerWarnings(q{ 180 auto doStuff(){assert(true);} // [warn]: %s 181 auto doStuff(){assert(false);} 182 }}.format( 183 AutoFunctionChecker.MESSAGE, 184 ), sac); 185 186 assertAnalyzerWarnings(q{ 187 auto doStuff(){assert(1);} // [warn]: %s 188 auto doStuff(){assert(0);} 189 }}.format( 190 AutoFunctionChecker.MESSAGE, 191 ), sac); 192 193 assertAnalyzerWarnings(q{ 194 auto doStuff(){mixin("0+0");} // [warn]: %s 195 auto doStuff(){mixin("return 0;");} 196 }}.format( 197 AutoFunctionChecker.MESSAGE, 198 ), sac); 199 200 assertAnalyzerWarnings(q{ 201 auto doStuff(){mixin("0+0");} // [warn]: %s 202 auto doStuff(){mixin("static if (true)" ~ " return " ~ 0.stringof ~ ";");} 203 }}.format( 204 AutoFunctionChecker.MESSAGE, 205 ), sac); 206 207 assertAnalyzerWarnings(q{ 208 auto doStuff(){} // [warn]: %s 209 extern(C) auto doStuff(); 210 }}.format( 211 AutoFunctionChecker.MESSAGE, 212 ), sac); 213 214 assertAnalyzerWarnings(q{ 215 auto doStuff(){} // [warn]: %s 216 @disable auto doStuff(); 217 }}.format( 218 AutoFunctionChecker.MESSAGE, 219 ), sac); 220 221 assertAnalyzerWarnings(q{ 222 @property doStuff(){} // [warn]: %s 223 @safe doStuff(){} // [warn]: %s 224 @disable doStuff(); 225 @safe void doStuff(); 226 }}.format( 227 AutoFunctionChecker.MESSAGE, 228 AutoFunctionChecker.MESSAGE, 229 ), sac); 230 231 assertAnalyzerWarnings(q{ 232 enum _genSave = "return true;"; 233 auto doStuff(){ mixin(_genSave);} 234 }, sac); 235 236 stderr.writeln("Unittest for AutoFunctionChecker passed."); 237 }