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 }