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