@@ -246,38 +246,100 @@ func runSliceBounds(pass *analysis.Pass) (interface{}, error) {
246246 return nil , nil
247247}
248248
249- // extractLenBound checks if the binop is of form "Var < Len + Offset"
249+ // extractLenBound checks if the binop is of form "Var < Len + Offset" or equivalent patterns
250+ // (including offsets on the left-hand side like "(Var + Const) < Len")
250251func extractLenBound (binop * ssa.BinOp ) (ssa.Value , int , bool ) {
251252 // Only handle Less Than for now
252253 if binop .Op != token .LSS {
253254 return nil , 0 , false
254255 }
255256
256- // Assume LHS is the loop variable
257- loopVar := binop . X
257+ var loopVar ssa. Value
258+ var lenOffset int
258259
259- // Check RHS. It is either the Len instruction itself, or a BinOp (Len +/- Const)
260- rhs : = binop .Y
260+ // First, try to interpret RHS as the length expression (len +/- const) and LHS as plain loop var
261+ loopVar = binop .X // candidate loop variable
261262
262- if _ , ok := rhs .(* ssa.Const ); ! ok {
263- if rOp , ok := rhs .(* ssa.BinOp ); ok {
264- // Assume Len is on the left of the arithmetic
265- if c , ok := rOp .Y .(* ssa.Const ); ok {
266- val , err := strconv .Atoi (c .Value .String ())
267- if err == nil {
268- switch rOp .Op {
269- case token .ADD :
270- return loopVar , val , true
271- case token .SUB :
272- return loopVar , - val , true
273- }
263+ if _ , isConst := binop .Y .(* ssa.Const ); isConst {
264+ // RHS is a constant → cannot be a length-bound check
265+ return nil , 0 , false
266+ }
267+
268+ // Try to pull an offset from RHS if it is len +/- const
269+ if rhsBinOp , ok := binop .Y .(* ssa.BinOp ); ok && (rhsBinOp .Op == token .ADD || rhsBinOp .Op == token .SUB ) {
270+ var constVal int
271+ var foundConst bool
272+
273+ // Check both sides for the constant (symmetric for ADD, careful for SUB)
274+ if c , ok := rhsBinOp .Y .(* ssa.Const ); ok {
275+ if v , err := strconv .Atoi (c .Value .String ()); err == nil {
276+ constVal = v
277+ foundConst = true
278+ }
279+ } else if c , ok := rhsBinOp .X .(* ssa.Const ); ok {
280+ if v , err := strconv .Atoi (c .Value .String ()); err == nil {
281+ constVal = v
282+ foundConst = true
283+ }
284+ }
285+
286+ if foundConst {
287+ switch rhsBinOp .Op {
288+ case token .ADD :
289+ // len + k or k + len → same meaning
290+ lenOffset = constVal
291+ case token .SUB :
292+ if _ , isConstOnLeft := rhsBinOp .X .(* ssa.Const ); isConstOnLeft {
293+ // k - len → unusual for a strict upper bound, skip this pattern
294+ foundConst = false
295+ } else {
296+ // len - k
297+ lenOffset = - constVal
274298 }
275299 }
300+ if foundConst {
301+ return loopVar , lenOffset , true
302+ }
276303 }
277- // If it's not a BinOp but just a value (result of len call), offset is 0
278- return loopVar , 0 , true
279304 }
280- return nil , 0 , false
305+
306+ // If we get here, RHS is a plain length (no extractable offset) or extraction failed.
307+ // Now try the alternative pattern: LHS is (loopVar +/- const), RHS is plain len
308+ if lhsBinOp , ok := binop .X .(* ssa.BinOp ); ok && (lhsBinOp .Op == token .ADD || lhsBinOp .Op == token .SUB ) {
309+ var constVal int
310+ var varVal ssa.Value
311+ var found bool
312+
313+ if c , ok := lhsBinOp .Y .(* ssa.Const ); ok {
314+ if v , err := strconv .Atoi (c .Value .String ()); err == nil {
315+ constVal = v
316+ varVal = lhsBinOp .X
317+ found = true
318+ }
319+ } else if c , ok := lhsBinOp .X .(* ssa.Const ); ok {
320+ if v , err := strconv .Atoi (c .Value .String ()); err == nil {
321+ constVal = v
322+ varVal = lhsBinOp .Y
323+ found = true
324+ }
325+ }
326+
327+ if found {
328+ loopVar = varVal
329+ switch lhsBinOp .Op {
330+ case token .ADD :
331+ // (i + k) < len → equivalent to i < len - k
332+ lenOffset = - constVal
333+ case token .SUB :
334+ // (i - k) < len → equivalent to i < len + k (rare but safe)
335+ lenOffset = constVal
336+ }
337+ return loopVar , lenOffset , true
338+ }
339+ }
340+
341+ // Fallback: plain i < len (offset 0)
342+ return loopVar , 0 , true
281343}
282344
283345// extractIndexOffset checks if indexVal is "loopVar + C"
0 commit comments