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