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