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 }c.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 }c.format(
181 AutoFunctionChecker.MESSAGE,
182 ), sac);
183
184 assertAnalyzerWarnings(q{
185 auto doStuff(){assert(1);} // [warn]: %s
186 auto doStuff(){assert(0);}
187 }c.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 }c.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 }c.format(
202 AutoFunctionChecker.MESSAGE,
203 ), sac);
204
205 assertAnalyzerWarnings(q{
206 auto doStuff(){} // [warn]: %s
207 extern(C) auto doStuff();
208 }c.format(
209 AutoFunctionChecker.MESSAGE,
210 ), sac);
211
212 assertAnalyzerWarnings(q{
213 auto doStuff(){} // [warn]: %s
214 @disable auto doStuff();
215 }c.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 }