1 //          Copyright Brian Schott (Hackerpilot) 2015.
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 analysis.auto_ref_assignment;
7 
8 import dparse.lexer;
9 import dparse.ast;
10 import analysis.base;
11 
12 /**
13  * Checks for assignment to auto-ref function parameters.
14  */
15 class AutoRefAssignmentCheck : BaseAnalyzer
16 {
17 	///
18 	this(string fileName)
19 	{
20 		super(fileName, null);
21 	}
22 
23 	override void visit(const FunctionDeclaration func)
24 	{
25 		if (func.parameters is null || func.parameters.parameters.length == 0)
26 			return;
27 		pushScope();
28 		scope (exit)
29 			popScope();
30 		func.accept(this);
31 	}
32 
33 	override void visit(const Parameter param)
34 	{
35 		import std.algorithm.searching : canFind;
36 
37 		immutable bool isAuto = param.parameterAttributes.canFind(cast(ubyte) tok!"auto");
38 		immutable bool isRef = param.parameterAttributes.canFind(cast(ubyte) tok!"ref");
39 		if (!isAuto || !isRef)
40 			return;
41 		addSymbol(param.name.text);
42 	}
43 
44 	override void visit(const AssignExpression assign)
45 	{
46 		if (assign.operator == tok!"" || scopes.length == 0)
47 			return;
48 		interest++;
49 		assign.ternaryExpression.accept(this);
50 		interest--;
51 	}
52 
53 	override void visit(const IdentifierOrTemplateInstance ioti)
54 	{
55 		import std.algorithm.searching : canFind;
56 
57 		if (ioti.identifier == tok!"" || interest <= 0)
58 			return;
59 		if (scopes[$ - 1].canFind(ioti.identifier.text))
60 			addErrorMessage(ioti.identifier.line, ioti.identifier.column, KEY,
61 				MESSAGE);
62 	}
63 
64 	override void visit(const IdentifierChain ic)
65 	{
66 		import std.algorithm.searching : canFind;
67 
68 		if (ic.identifiers.length == 0 || interest <= 0)
69 			return;
70 		if (scopes[$ - 1].canFind(ic.identifiers[0].text))
71 			addErrorMessage(ic.identifiers[0].line, ic.identifiers[0].column, KEY,
72 				MESSAGE);
73 	}
74 
75 	alias visit = BaseAnalyzer.visit;
76 
77 private:
78 
79 	enum string MESSAGE = "Assignment to auto-ref function parameter.";
80 	enum string KEY = "dscanner.suspicious.auto_ref_assignment";
81 
82 	invariant
83 	{
84 		assert(interest >= 0);
85 	}
86 
87 	int interest;
88 
89 	void addSymbol(string symbolName)
90 	{
91 		scopes[$ - 1] ~= symbolName;
92 	}
93 
94 	void pushScope()
95 	{
96 		scopes.length++;
97 	}
98 
99 	void popScope()
100 	{
101 		scopes = scopes[0 .. $ - 1];
102 	}
103 
104 	string[][] scopes;
105 }
106 
107 unittest
108 {
109 	import std.stdio : stderr;
110 	import std.format : format;
111 	import analysis.config : StaticAnalysisConfig;
112 	import analysis.helpers : assertAnalyzerWarnings;
113 
114 	StaticAnalysisConfig sac;
115 	sac.auto_ref_assignment_check = true;
116 	assertAnalyzerWarnings(q{
117 		int doStuff(T)(auto ref int a)
118 		{
119 			a = 10; // [warn]: %s
120 		}
121 
122 		int doStuff(T)(ref int a)
123 		{
124 			a = 10;
125 		}
126 	}}.format(AutoRefAssignmentCheck.MESSAGE), sac);
127 	stderr.writeln("Unittest for AutoRefAssignmentCheck passed.");
128 }