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 }