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