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