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 }