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 }