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 }