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 /// 40 this(string fileName, bool skipTests = false) 41 { 42 super(fileName, null, skipTests); 43 } 44 45 override void visit(const(FunctionDeclaration) decl) 46 { 47 _returns.length += 1; 48 scope(exit) _returns.length -= 1; 49 _returns[$-1] = false; 50 51 const bool autoFun = decl.storageClasses 52 .any!(a => a.token.type == tok!"auto"); 53 54 decl.accept(this); 55 56 if (decl.functionBody && autoFun && !_returns[$-1]) 57 addErrorMessage(decl.name.line, decl.name.column, KEY, MESSAGE); 58 } 59 60 override void visit(const(ReturnStatement) rst) 61 { 62 if (_returns.length) 63 _returns[$-1] = true; 64 rst.accept(this); 65 } 66 67 override void visit(const(AssertExpression) exp) 68 { 69 exp.accept(this); 70 if (_returns.length) 71 { 72 const UnaryExpression u = cast(UnaryExpression) exp.assertion; 73 if (!u) 74 return; 75 const PrimaryExpression p = u.primaryExpression; 76 if (!p) 77 return; 78 79 immutable token = p.primary; 80 if (token.type == tok!"false") 81 _returns[$-1] = true; 82 else if (token.text == "0") 83 _returns[$-1] = true; 84 } 85 } 86 87 override void visit(const(MixinExpression) mix) 88 { 89 ++_mixinDepth; 90 mix.accept(this); 91 --_mixinDepth; 92 } 93 94 override void visit(const(PrimaryExpression) exp) 95 { 96 exp.accept(this); 97 98 import std.algorithm.searching : canFind; 99 100 if (_returns.length && _mixinDepth) 101 { 102 if (findReturnInLiteral(exp.primary.text)) 103 _returns[$-1] = true; 104 else if (exp.identifierOrTemplateInstance && 105 _literalWithReturn.canFind(exp.identifierOrTemplateInstance.identifier.text)) 106 _returns[$-1] = true; 107 } 108 } 109 110 bool findReturnInLiteral(const(string) value) 111 { 112 import std.algorithm.searching : find; 113 import std.range : empty; 114 115 return value == "return" || !value.find("return ").empty; 116 } 117 118 bool stringliteralHasReturn(const(NonVoidInitializer) nvi) 119 { 120 bool result; 121 if (!nvi.assignExpression || (cast(UnaryExpression) nvi.assignExpression) is null) 122 return result; 123 124 const(UnaryExpression) u = cast(UnaryExpression) nvi.assignExpression; 125 if (u.primaryExpression && 126 u.primaryExpression.primary.type.isStringLiteral && 127 findReturnInLiteral(u.primaryExpression.primary.text)) 128 result = true; 129 130 return result; 131 } 132 133 override void visit(const(AutoDeclaration) decl) 134 { 135 decl.accept(this); 136 137 foreach(const(AutoDeclarationPart) p; decl.parts) 138 if (p.initializer && 139 p.initializer.nonVoidInitializer && 140 stringliteralHasReturn(p.initializer.nonVoidInitializer)) 141 _literalWithReturn ~= p.identifier.text.idup; 142 } 143 144 override void visit(const(VariableDeclaration) decl) 145 { 146 decl.accept(this); 147 148 foreach(const(Declarator) d; decl.declarators) 149 if (d.initializer && 150 d.initializer.nonVoidInitializer && 151 stringliteralHasReturn(d.initializer.nonVoidInitializer)) 152 _literalWithReturn ~= d.name.text.idup; 153 } 154 } 155 156 unittest 157 { 158 import std.stdio : stderr; 159 import std.format : format; 160 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 161 import dscanner.analysis.helpers : assertAnalyzerWarnings; 162 163 StaticAnalysisConfig sac = disabledConfig(); 164 sac.auto_function_check = Check.enabled; 165 assertAnalyzerWarnings(q{ 166 auto ref doStuff(){} // [warn]: %s 167 auto doStuff(){} // [warn]: %s 168 int doStuff(){auto doStuff(){}} // [warn]: %s 169 auto doStuff(){return 0;} 170 int doStuff(){/*error but not the aim*/} 171 }}.format( 172 AutoFunctionChecker.MESSAGE, 173 AutoFunctionChecker.MESSAGE, 174 AutoFunctionChecker.MESSAGE, 175 ), sac); 176 177 assertAnalyzerWarnings(q{ 178 auto doStuff(){assert(true);} // [warn]: %s 179 auto doStuff(){assert(false);} 180 }}.format( 181 AutoFunctionChecker.MESSAGE, 182 ), sac); 183 184 assertAnalyzerWarnings(q{ 185 auto doStuff(){assert(1);} // [warn]: %s 186 auto doStuff(){assert(0);} 187 }}.format( 188 AutoFunctionChecker.MESSAGE, 189 ), sac); 190 191 assertAnalyzerWarnings(q{ 192 auto doStuff(){mixin("0+0");} // [warn]: %s 193 auto doStuff(){mixin("return 0;");} 194 }}.format( 195 AutoFunctionChecker.MESSAGE, 196 ), sac); 197 198 assertAnalyzerWarnings(q{ 199 auto doStuff(){mixin("0+0");} // [warn]: %s 200 auto doStuff(){mixin("static if (true)" ~ " return " ~ 0.stringof ~ ";");} 201 }}.format( 202 AutoFunctionChecker.MESSAGE, 203 ), sac); 204 205 assertAnalyzerWarnings(q{ 206 auto doStuff(){} // [warn]: %s 207 extern(C) auto doStuff(); 208 }}.format( 209 AutoFunctionChecker.MESSAGE, 210 ), sac); 211 212 assertAnalyzerWarnings(q{ 213 auto doStuff(){} // [warn]: %s 214 @disable auto doStuff(); 215 }}.format( 216 AutoFunctionChecker.MESSAGE, 217 ), sac); 218 219 assertAnalyzerWarnings(q{ 220 enum _genSave = "return true;"; 221 auto doStuff(){ mixin(_genSave);} 222 }, sac); 223 224 stderr.writeln("Unittest for AutoFunctionChecker passed."); 225 }