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