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