1 //          Copyright Brian Schott (Hackerpilot) 2014.
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.range;
7 
8 import std.stdio;
9 import std.d.ast;
10 import std.d.lexer;
11 import analysis.base;
12 import analysis.helpers;
13 
14 /**
15  * Checks for .. expressions where the left side is larger than the right. This
16  * is almost always a mistake.
17  */
18 class BackwardsRangeCheck : BaseAnalyzer
19 {
20 	alias visit = BaseAnalyzer.visit;
21 
22 	/// Key for this check in the report output
23 	enum string KEY = "dscanner.bugs.backwards_slices";
24 
25 	/**
26 	 * Params:
27 	 *     fileName = the name of the file being analyzed
28 	 */
29 	this(string fileName)
30 	{
31 		super(fileName);
32 	}
33 
34 	override void visit(const ForeachStatement foreachStatement)
35 	{
36 		if (foreachStatement.low !is null && foreachStatement.high !is null)
37 		{
38 			import std.string : format;
39 			state = State.left;
40 			visit(foreachStatement.low);
41 			state = State.right;
42 			visit(foreachStatement.high);
43 			state = State.ignore;
44 			if (hasLeft && hasRight && left > right)
45 			{
46 				string message = format(
47 					"%d is larger than %d. Did you mean to use 'foreach_reverse( ... ; %d .. %d)'?",
48 					left, right, right, left);
49 				addErrorMessage(line, this.column, KEY, message);
50 			}
51 			hasLeft = false;
52 			hasRight = false;
53 		}
54 		foreachStatement.accept(this);
55 	}
56 
57 	override void visit(const AddExpression add)
58 	{
59 		auto s = state;
60 		state = State.ignore;
61 		add.accept(this);
62 		state = s;
63 	}
64 
65 	override void visit(const UnaryExpression unary)
66 	{
67 		if (state != State.ignore && unary.primaryExpression is null)
68 			return;
69 		else
70 			unary.accept(this);
71 	}
72 
73 	override void visit(const PrimaryExpression primary)
74 	{
75 		import std.conv : to, ConvException;
76 
77 		if (state == State.ignore || !isNumberLiteral(primary.primary.type))
78 			return;
79 		if (state == State.left)
80 		{
81 			line = primary.primary.line;
82 			this.column = primary.primary.column;
83 
84 			try left = parseNumber(primary.primary.text);
85 			catch (ConvException e) return;
86 			hasLeft = true;
87 		}
88 		else
89 		{
90 			try right = parseNumber(primary.primary.text);
91 			catch (ConvException e) return;
92 			hasRight = true;
93 		}
94 	}
95 
96 	override void visit(const SliceExpression sliceExpression)
97 	{
98 		if (sliceExpression.lower !is null && sliceExpression.upper !is null)
99 		{
100 			state = State.left;
101 			visit(sliceExpression.lower);
102 			state = State.right;
103 			visit(sliceExpression.upper);
104 			state = State.ignore;
105 			if (hasLeft && hasRight && left > right)
106 			{
107 				import std.string : format;
108 				string message = format(
109 					"%d is larger than %d. This slice is likely incorrect.",
110 					left, right);
111 				addErrorMessage(line, this.column, KEY, message);
112 			}
113 			hasLeft = false;
114 			hasRight = false;
115 		}
116 		sliceExpression.accept(this);
117 	}
118 
119 private:
120 	bool hasLeft;
121 	bool hasRight;
122 	long left;
123 	long right;
124 	size_t column;
125 	size_t line;
126 	enum State { ignore, left, right }
127 	State state = State.ignore;
128 
129 	long parseNumber(string te)
130 	{
131 		import std.conv : to;
132 		import std.string : removechars;
133 		string t = te.removechars("_uUlL");
134 		if (t.length > 2)
135 		{
136 			if (t[1] == 'x' || t[1] == 'X')
137 				return to!long(t[2..$], 16);
138 			if (t[1] == 'b' || t[1] == 'B')
139 				return to!long(t[2..$], 2);
140 		}
141 		return to!long(t);
142 	}
143 }
144 
145 unittest
146 {
147 	import analysis.config:StaticAnalysisConfig;
148 
149 	StaticAnalysisConfig sac;
150 	sac.backwards_range_check = true;
151 	assertAnalyzerWarnings(q{
152 		void testRange()
153 		{
154 			a = node.tupleof[2..T.length+1]; // ok
155 			foreach (a; 10 .. j + 2) {} // ok
156 
157 			int[] data = [1, 2, 3, 4, 5];
158 
159 			data = data[1 .. 3]; // ok
160 			data = data[3 .. 1]; // [warn]: 3 is larger than 1. This slice is likely incorrect.
161 
162 			foreach (n; 1 .. 3) { } // ok
163 			foreach (n; 3 .. 1) { } // [warn]: 3 is larger than 1. Did you mean to use 'foreach_reverse( ... ; 1 .. 3)'?
164 		}
165 	}}, sac);
166 
167 	stderr.writeln("Unittest for BackwardsRangeCheck passed.");
168 }
169