Skip to content

[BUG] False positives: heap address reuse, sync.Mutex, and testing.tRunner happens-before not tracked #39

@kolkov

Description

@kolkov

Problem

racedetector v0.8.4 reports ~4,600 false positive DATA RACE warnings on a project with 24 test suites and 1,500+ tests. The standard Go race detector (go test -race with Go 1.25.6) reports zero races on the same codebase.

The false positives fall into three categories:

1. Heap address reuse between sequential test goroutines

Tests run sequentially (no t.Parallel()). Each test allocates a struct via &App{...}. After test N completes, GC collects the struct. Test N+1 allocates a new struct at the same heap address. The racedetector flags the two writes from different tRunner goroutines as a race.

=== RUN   TestNewApp_WithPlatformProvider
==================
WARNING: DATA RACE
Write at 0x000000c000079eb8 by goroutine 4:
  app.New()
      app.go:90                        ← a := &App{theme: t}
  app.TestNewApp_WithPlatformProvider()
  testing.tRunner()
  [epoch: 2@4]

Previous Write at 0x000000c000079eb8 by goroutine 3:
  app.New()
      app.go:90                        ← same line, DIFFERENT test
  [epoch: 2@3]
==================
--- PASS: TestNewApp_WithPlatformProvider (0.00s)

There is no actual race — goroutine 3's test completed before goroutine 4's test started. The testing.tRunner synchronization (goroutine join → next goroutine fork) establishes a happens-before relationship that the racedetector doesn't track.

The standard Go race detector (ThreadSanitizer) handles this correctly because it clears shadow memory state on runtime.mallocgc.

2. sync.Mutex/RWMutex happens-before not tracked

The racedetector flags accesses that are properly protected by sync.RWMutex:

// layout/registry.go — properly synchronized
func (r *Registry) List() []string {
    r.mu.RLock()          // ← Lock acquired
    defer r.mu.RUnlock()  // ← Lock released
    names := make([]string, 0, len(r.algorithms))
    for name := range r.algorithms {
        names = append(names, name)
    }
    sort.Strings(names)
    return names           // ← flagged as race
}

3. Warning count scales with codebase size

CI Run Test Suites Warnings
PR #14 (deps update) 24 4,294
PR #15 (TextField) 24 4,567
PR #16 (Overlay/Dropdown) 24 4,616

Warnings grow with every new test, uniformly across all 3 platforms:

  • Ubuntu: ~1,570
  • macOS: ~1,551
  • Windows: ~1,495

Reproduction

# Clone the project
git clone https://github.com/gogpu/ui
cd ui

# Run with racedetector — thousands of warnings
racedetector test -v ./...

# Run with standard race detector — zero warnings
go test -race ./...

Expected behavior

Sequential tests accessing independent local variables through properly synchronized code should not be flagged as data races.

Environment

  • racedetector v0.8.4 (installed via go install github.com/kolkov/racedetector/cmd/racedetector@latest)
  • Go 1.25.6 on Ubuntu/macOS/Windows (CI)
  • Project: github.com/gogpu/ui (24 packages, 1,500+ tests, 55K LOC)

Suggested root causes

  1. Missing happens-before for testing.tRunner: The tRunner goroutine join (test N done) → fork (test N+1 start) is not modeled as synchronization
  2. Missing shadow memory reset on allocation: When Go's allocator reuses a heap address, the access history for that address should be cleared
  3. Missing happens-before for sync.Mutex: Lock()/Unlock() pairs don't establish happens-before edges in the FastTrack epoch tracking

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions