Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a5fb656

Browse files
griesemergopherbot
authored andcommittedJan 31, 2024
go/token: correct out-of-bounds token offsets and positions
Per the discussion on the issue, make methods that depend on incoming offsets or positions tolerant in the presence of out-of-bounds values by adjusting the values as needed. Add an internal flag debug that can be set to enable the old (not fault-tolerant) behavior. Fixes #57490. Change-Id: I8a7d422b9fd1d6f0980fd4e64da2f0489056d71e Reviewed-on: https://go-review.googlesource.com/c/go/+/559436 Reviewed-by: Alan Donovan <[email protected]> TryBot-Bypass: Robert Griesemer <[email protected]> Auto-Submit: Robert Griesemer <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 7973821 commit a5fb656

File tree

3 files changed

+130
-17
lines changed

3 files changed

+130
-17
lines changed
 

‎src/go/parser/parser_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,3 +800,24 @@ func TestGoVersion(t *testing.T) {
800800
}
801801
}
802802
}
803+
804+
func TestIssue57490(t *testing.T) {
805+
src := `package p; func f() { var x struct` // program not correctly terminated
806+
fset := token.NewFileSet()
807+
file, err := ParseFile(fset, "", src, 0)
808+
if err == nil {
809+
t.Fatalf("syntax error expected, but no error reported")
810+
}
811+
812+
// Because of the syntax error, the end position of the function declaration
813+
// is past the end of the file's position range.
814+
funcEnd := file.Decls[0].End()
815+
816+
// Offset(funcEnd) must not panic (to test panic, set debug=true in token package)
817+
// (panic: offset 35 out of bounds [0, 34] (position 36 out of bounds [1, 35]))
818+
tokFile := fset.File(file.Pos())
819+
offset := tokFile.Offset(funcEnd)
820+
if offset != tokFile.Size() {
821+
t.Fatalf("offset = %d, want %d", offset, tokFile.Size())
822+
}
823+
}

‎src/go/token/position.go

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
"sync/atomic"
1313
)
1414

15+
// If debug is set, invalid offset and position values cause a panic
16+
// (go.dev/issue/57490).
17+
const debug = false
18+
1519
// -----------------------------------------------------------------------------
1620
// Positions
1721

@@ -261,24 +265,54 @@ func (f *File) AddLineColumnInfo(offset int, filename string, line, column int)
261265
f.mutex.Unlock()
262266
}
263267

264-
// Pos returns the Pos value for the given file offset;
265-
// the offset must be <= f.Size().
268+
// fixOffset fixes an out-of-bounds offset such that 0 <= offset <= f.size.
269+
func (f *File) fixOffset(offset int) int {
270+
switch {
271+
case offset < 0:
272+
if !debug {
273+
return 0
274+
}
275+
case offset > f.size:
276+
if !debug {
277+
return f.size
278+
}
279+
default:
280+
return offset
281+
}
282+
283+
// only generate this code if needed
284+
if debug {
285+
panic(fmt.Sprintf("offset %d out of bounds [%d, %d] (position %d out of bounds [%d, %d])",
286+
0 /* for symmetry */, offset, f.size,
287+
f.base+offset, f.base, f.base+f.size))
288+
}
289+
return 0
290+
}
291+
292+
// Pos returns the Pos value for the given file offset.
293+
//
294+
// If offset is negative, the result is the file's start
295+
// position; if the offset is too large, the result is
296+
// the file's end position (see also go.dev/issue/57490).
297+
//
298+
// The following invariant, though not true for Pos values
299+
// in general, holds for the result p:
266300
// f.Pos(f.Offset(p)) == p.
267301
func (f *File) Pos(offset int) Pos {
268-
if offset > f.size {
269-
panic(fmt.Sprintf("invalid file offset %d (should be <= %d)", offset, f.size))
270-
}
271-
return Pos(f.base + offset)
302+
return Pos(f.base + f.fixOffset(offset))
272303
}
273304

274-
// Offset returns the offset for the given file position p;
275-
// p must be a valid [Pos] value in that file.
276-
// f.Offset(f.Pos(offset)) == offset.
305+
// Offset returns the offset for the given file position p.
306+
//
307+
// If p is before the file's start position (or if p is NoPos),
308+
// the result is 0; if p is past the file's end position, the
309+
// the result is the file size (see also go.dev/issue/57490).
310+
//
311+
// The following invariant, though not true for offset values
312+
// in general, holds for the result offset:
313+
// f.Offset(f.Pos(offset)) == offset
277314
func (f *File) Offset(p Pos) int {
278-
if int(p) < f.base || int(p) > f.base+f.size {
279-
panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size))
280-
}
281-
return int(p) - f.base
315+
return f.fixOffset(int(p) - f.base)
282316
}
283317

284318
// Line returns the line number for the given file position p;
@@ -330,27 +364,26 @@ func (f *File) unpack(offset int, adjusted bool) (filename string, line, column
330364
}
331365

332366
func (f *File) position(p Pos, adjusted bool) (pos Position) {
333-
offset := int(p) - f.base
367+
offset := f.fixOffset(int(p) - f.base)
334368
pos.Offset = offset
335369
pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted)
336370
return
337371
}
338372

339373
// PositionFor returns the Position value for the given file position p.
374+
// If p is out of bounds, it is adjusted to match the File.Offset behavior.
340375
// If adjusted is set, the position may be adjusted by position-altering
341376
// //line comments; otherwise those comments are ignored.
342377
// p must be a Pos value in f or NoPos.
343378
func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) {
344379
if p != NoPos {
345-
if int(p) < f.base || int(p) > f.base+f.size {
346-
panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d])", p, f.base, f.base+f.size))
347-
}
348380
pos = f.position(p, adjusted)
349381
}
350382
return
351383
}
352384

353385
// Position returns the Position value for the given file position p.
386+
// If p is out of bounds, it is adjusted to match the File.Offset behavior.
354387
// Calling f.Position(p) is equivalent to calling f.PositionFor(p, true).
355388
func (f *File) Position(p Pos) (pos Position) {
356389
return f.PositionFor(p, true)

‎src/go/token/position_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,62 @@ func TestFileAddLineColumnInfo(t *testing.T) {
478478
})
479479
}
480480
}
481+
482+
func TestIssue57490(t *testing.T) {
483+
// If debug is set, this test is expected to panic.
484+
if debug {
485+
defer func() {
486+
if recover() == nil {
487+
t.Errorf("got no panic")
488+
}
489+
}()
490+
}
491+
492+
const fsize = 5
493+
fset := NewFileSet()
494+
base := fset.Base()
495+
f := fset.AddFile("f", base, fsize)
496+
497+
// out-of-bounds positions must not lead to a panic when calling f.Offset
498+
if got := f.Offset(NoPos); got != 0 {
499+
t.Errorf("offset = %d, want %d", got, 0)
500+
}
501+
if got := f.Offset(Pos(-1)); got != 0 {
502+
t.Errorf("offset = %d, want %d", got, 0)
503+
}
504+
if got := f.Offset(Pos(base + fsize + 1)); got != fsize {
505+
t.Errorf("offset = %d, want %d", got, fsize)
506+
}
507+
508+
// out-of-bounds offsets must not lead to a panic when calling f.Pos
509+
if got := f.Pos(-1); got != Pos(base) {
510+
t.Errorf("pos = %d, want %d", got, base)
511+
}
512+
if got := f.Pos(fsize + 1); got != Pos(base+fsize) {
513+
t.Errorf("pos = %d, want %d", got, base+fsize)
514+
}
515+
516+
// out-of-bounds Pos values must not lead to a panic when calling f.Position
517+
want := fmt.Sprintf("%s:1:1", f.Name())
518+
if got := f.Position(Pos(-1)).String(); got != want {
519+
t.Errorf("position = %s, want %s", got, want)
520+
}
521+
want = fmt.Sprintf("%s:1:%d", f.Name(), fsize+1)
522+
if got := f.Position(Pos(fsize + 1)).String(); got != want {
523+
t.Errorf("position = %s, want %s", got, want)
524+
}
525+
526+
// check invariants
527+
const xsize = fsize + 5
528+
for offset := -xsize; offset < xsize; offset++ {
529+
want1 := f.Offset(Pos(f.base + offset))
530+
if got := f.Offset(f.Pos(offset)); got != want1 {
531+
t.Errorf("offset = %d, want %d", got, want1)
532+
}
533+
534+
want2 := f.Pos(offset)
535+
if got := f.Pos(f.Offset(want2)); got != want2 {
536+
t.Errorf("pos = %d, want %d", got, want2)
537+
}
538+
}
539+
}

0 commit comments

Comments
 (0)
Please sign in to comment.