Skip to content

Commit cb57836

Browse files
committed
Flags for false positive
1 parent 742089c commit cb57836

File tree

4 files changed

+185
-2
lines changed

4 files changed

+185
-2
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ func main() {
135135
}
136136
```
137137

138+
### Suppressing false positives
139+
140+
Add a comment marker on the same line as the operation or the line immediately above to mark a bug as false positive, so that the compiler won't panic on the arithmetic or truncation issue.:
141+
142+
- Overflow/underflow: `overflow_false_positive`
143+
- Truncation: `truncation_false_positive`
144+
145+
Example:
146+
147+
```go
148+
// overflow_false_positive
149+
sum := a + b
150+
151+
// truncation_false_positive
152+
x := uint8(big)
153+
154+
sum2 := a + b // overflow_false_positive
155+
x2 := uint8(big) // truncation_false_positive
156+
```
157+
158+
138159
**Expected output:**
139160

140161
```bash

src/cmd/compile/internal/ssagen/ssa.go

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"path/filepath"
1717
"slices"
1818
"strings"
19+
"sync"
1920

2021
"cmd/compile/internal/abi"
2122
"cmd/compile/internal/base"
@@ -5429,6 +5430,118 @@ func isStandardLibraryFile(filename string) bool {
54295430
return false
54305431
}
54315432

5433+
// Suppression directives to skip integer overflow checks on specific source lines.
5434+
// If a line contains one of these markers, or the immediately preceding line contains it,
5435+
// arithmetic overflow checks for that line will be skipped.
5436+
const (
5437+
// Match either with or without a leading '@' in comments
5438+
overflowSuppressionDirectivePrimary = "overflow_false_positive"
5439+
)
5440+
5441+
// cache mapping filename -> set of line numbers that contain the suppression directive
5442+
var overflowSuppressionCache sync.Map // map[string]map[int]struct{}
5443+
5444+
// hasOverflowSuppression reports whether the given position has a nearby suppression directive.
5445+
// It returns true if the exact line or the immediately preceding line contains the directive.
5446+
func hasOverflowSuppression(pos src.XPos) bool {
5447+
if !pos.IsKnown() {
5448+
return false
5449+
}
5450+
p := base.Ctxt.PosTable.Pos(pos)
5451+
filename := p.Filename()
5452+
intLine := int(p.Line())
5453+
if filename == "" || intLine <= 0 {
5454+
return false
5455+
}
5456+
5457+
// Load or populate the per-file directive line set.
5458+
var lineSet map[int]struct{}
5459+
if v, ok := overflowSuppressionCache.Load(filename); ok {
5460+
lineSet, _ = v.(map[int]struct{})
5461+
} else {
5462+
// Build the set once per file.
5463+
data, err := os.ReadFile(filename)
5464+
if err != nil {
5465+
return false
5466+
}
5467+
lines := strings.Split(string(data), "\n")
5468+
set := make(map[int]struct{}, 8)
5469+
for i, s := range lines {
5470+
if strings.Contains(s, overflowSuppressionDirectivePrimary) {
5471+
// Lines are 1-indexed in src.Pos
5472+
set[i+1] = struct{}{}
5473+
}
5474+
}
5475+
overflowSuppressionCache.Store(filename, set)
5476+
lineSet = set
5477+
}
5478+
5479+
if lineSet == nil {
5480+
return false
5481+
}
5482+
if _, ok := lineSet[intLine]; ok {
5483+
return true
5484+
}
5485+
if intLine > 1 {
5486+
if _, ok := lineSet[intLine-1]; ok {
5487+
return true
5488+
}
5489+
}
5490+
return false
5491+
}
5492+
5493+
// Suppress truncation detection when a directive is present on the same or previous line.
5494+
const (
5495+
// Match either with or without a leading '@' in comments
5496+
truncationSuppressionDirectivePrimary = "truncation_false_positive"
5497+
)
5498+
5499+
var truncationSuppressionCache sync.Map // map[string]map[int]struct{}
5500+
5501+
func hasTruncationSuppression(pos src.XPos) bool {
5502+
if !pos.IsKnown() {
5503+
return false
5504+
}
5505+
p := base.Ctxt.PosTable.Pos(pos)
5506+
filename := p.Filename()
5507+
intLine := int(p.Line())
5508+
if filename == "" || intLine <= 0 {
5509+
return false
5510+
}
5511+
5512+
var lineSet map[int]struct{}
5513+
if v, ok := truncationSuppressionCache.Load(filename); ok {
5514+
lineSet, _ = v.(map[int]struct{})
5515+
} else {
5516+
data, err := os.ReadFile(filename)
5517+
if err != nil {
5518+
return false
5519+
}
5520+
lines := strings.Split(string(data), "\n")
5521+
set := make(map[int]struct{}, 8)
5522+
for i, s := range lines {
5523+
if strings.Contains(s, truncationSuppressionDirectivePrimary) {
5524+
set[i+1] = struct{}{}
5525+
}
5526+
}
5527+
truncationSuppressionCache.Store(filename, set)
5528+
lineSet = set
5529+
}
5530+
5531+
if lineSet == nil {
5532+
return false
5533+
}
5534+
if _, ok := lineSet[intLine]; ok {
5535+
return true
5536+
}
5537+
if intLine > 1 {
5538+
if _, ok := lineSet[intLine-1]; ok {
5539+
return true
5540+
}
5541+
}
5542+
return false
5543+
}
5544+
54325545
// shouldCheckOverflow returns true if overflow detection should be applied for this operation.
54335546
// It checks if the package should be excluded from overflow detection and if the type is supported.
54345547
func (s *state) shouldCheckOverflow(typ *types.Type) bool {
@@ -5484,7 +5597,6 @@ func (s *state) shouldCheckTruncation(n ir.Node, fromType, toType *types.Type) b
54845597
return false
54855598
}
54865599

5487-
54885600
// Check truncation for integer types in these cases:
54895601
// 1. Target type is smaller than source type (traditional truncation)
54905602
// 2. Same size but different signedness (problematic conversions)
@@ -5539,6 +5651,11 @@ func (s *state) checkTypeTruncation(n ir.Node, value *ssa.Value, fromType, toTyp
55395651
}
55405652
}
55415653

5654+
// Allow source-level suppression using the same directive key.
5655+
if hasTruncationSuppression(n.Pos()) {
5656+
return s.newValue1(op, toType, value)
5657+
}
5658+
55425659
// Perform the conversion first
55435660
result := s.newValue1(op, toType, value)
55445661

@@ -5631,6 +5748,10 @@ func (s *state) intAdd(n ir.Node, a, b *ssa.Value) *ssa.Value {
56315748
}
56325749
}
56335750

5751+
if hasOverflowSuppression(n.Pos()) {
5752+
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
5753+
}
5754+
56345755
if !s.shouldCheckOverflow(n.Type()) {
56355756
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
56365757
}
@@ -5702,6 +5823,10 @@ func (s *state) intSub(n ir.Node, a, b *ssa.Value) *ssa.Value {
57025823
}
57035824
}
57045825

5826+
if hasOverflowSuppression(n.Pos()) {
5827+
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
5828+
}
5829+
57055830
if !s.shouldCheckOverflow(n.Type()) {
57065831
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
57075832
}
@@ -5773,6 +5898,10 @@ func (s *state) intMul(n ir.Node, a, b *ssa.Value) *ssa.Value {
57735898
}
57745899
}
57755900

5901+
if hasOverflowSuppression(n.Pos()) {
5902+
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
5903+
}
5904+
57765905
if !s.shouldCheckOverflow(n.Type()) {
57775906
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
57785907
}
@@ -5845,7 +5974,10 @@ func (s *state) intDiv(n ir.Node, a, b *ssa.Value) *ssa.Value {
58455974
s.check(cmp, ir.Syms.Panicdivide)
58465975
}
58475976

5848-
// If overflow detection is disabled, just perform the division
5977+
// If overflow detection is suppressed/disabled, just perform the division
5978+
if hasOverflowSuppression(n.Pos()) {
5979+
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
5980+
}
58495981
if !s.shouldCheckOverflow(n.Type()) {
58505982
return s.newValue2(s.ssaOp(n.Op(), n.Type()), a.Type, a, b)
58515983
}

tests/arithmetic_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,20 @@ func TestSafeArithmetic(t *testing.T) {
372372
if result2 != 150 {
373373
t.Fatalf("Expected 150, got %d", result2)
374374
}
375+
}
376+
377+
// Suppression directive tests for overflow/underflow
378+
func TestOverflowSuppression_LineAbove(t *testing.T) {
379+
// Expect no panic due to suppression marker on previous line
380+
var a int8 = 120
381+
var b int8 = 10
382+
// overflow_false_positive
383+
_ = a + b
384+
}
385+
386+
func TestOverflowSuppression_SameLine(t *testing.T) {
387+
// Expect no panic due to suppression marker on same line
388+
var a int8 = 120
389+
var b int8 = 10
390+
_ = a + b // overflow_false_positive
375391
}

tests/truncation_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,17 @@ func TestSafeTruncation(t *testing.T) {
395395
t.Fatalf("Expected 200, got %d", result2)
396396
}
397397
}
398+
399+
// Suppression directive tests for truncation
400+
func TestTruncationSuppression_LineAbove(t *testing.T) {
401+
// Expect no panic due to suppression marker on previous line
402+
var big uint16 = 300
403+
// truncation_false_positive
404+
_ = uint8(big)
405+
}
406+
407+
func TestTruncationSuppression_SameLine(t *testing.T) {
408+
// Expect no panic due to suppression marker on same line
409+
var big uint16 = 300
410+
_ = uint8(big) // truncation_false_positive
411+
}

0 commit comments

Comments
 (0)