Skip to content

Commit 523a091

Browse files
authored
Fix race cond in variabels (#523)
* Add race cond test * Move variables array to vm * Resize vars array for different programs
1 parent b2f6fb8 commit 523a091

File tree

6 files changed

+66
-10
lines changed

6 files changed

+66
-10
lines changed

.github/workflows/test.yml

+11
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,14 @@ jobs:
3131
go-version: 1.18
3232
- name: Test
3333
run: go test -tags=expr_debug -run=TestDebugger -v ./vm
34+
35+
race:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v3
39+
- name: Setup Go 1.21
40+
uses: actions/setup-go@v4
41+
with:
42+
go-version: 1.21
43+
- name: Test
44+
run: go test -race .

compiler/compiler.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ type compiler struct {
6464
config *conf.Config
6565
locations []file.Location
6666
bytecode []Opcode
67-
variables []any
67+
variables int
6868
scopes []scope
6969
constants []any
7070
constantsIndex map[any]int
@@ -137,10 +137,9 @@ func (c *compiler) addConstant(constant any) int {
137137
}
138138

139139
func (c *compiler) addVariable(name string) int {
140-
c.variables = append(c.variables, nil)
141-
p := len(c.variables) - 1
142-
c.debugInfo[fmt.Sprintf("var_%d", p)] = name
143-
return p
140+
c.variables++
141+
c.debugInfo[fmt.Sprintf("var_%d", c.variables-1)] = name
142+
return c.variables - 1
144143
}
145144

146145
// emitFunction adds builtin.Function.Func to the program.functions and emits call opcode.

expr_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"reflect"
9+
"sync"
910
"testing"
1011
"time"
1112

@@ -2383,3 +2384,22 @@ func TestIssue474(t *testing.T) {
23832384
})
23842385
}
23852386
}
2387+
2388+
func TestRaceCondition_variables(t *testing.T) {
2389+
program, err := expr.Compile(`let foo = 1; foo + 1`, expr.Env(mock.Env{}))
2390+
require.NoError(t, err)
2391+
2392+
var wg sync.WaitGroup
2393+
2394+
for i := 0; i < 10; i++ {
2395+
wg.Add(1)
2396+
go func() {
2397+
defer wg.Done()
2398+
out, err := expr.Run(program, mock.Env{})
2399+
require.NoError(t, err)
2400+
require.Equal(t, 2, out)
2401+
}()
2402+
}
2403+
2404+
wg.Wait()
2405+
}

vm/program.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type Program struct {
2222

2323
source *file.Source
2424
locations []file.Location
25-
variables []any
25+
variables int
2626
functions []Function
2727
debugInfo map[string]string
2828
}
@@ -31,7 +31,7 @@ type Program struct {
3131
func NewProgram(
3232
source *file.Source,
3333
locations []file.Location,
34-
variables []any,
34+
variables int,
3535
constants []any,
3636
bytecode []Opcode,
3737
arguments []int,

vm/vm.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func Debug() *VM {
3434
type VM struct {
3535
Stack []any
3636
Scopes []*Scope
37+
Variables []any
3738
ip int
3839
memory uint
3940
memoryBudget uint
@@ -74,10 +75,12 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
7475
} else {
7576
vm.Stack = vm.Stack[0:0]
7677
}
77-
7878
if vm.Scopes != nil {
7979
vm.Scopes = vm.Scopes[0:0]
8080
}
81+
if len(vm.Variables) < program.variables {
82+
vm.Variables = make([]any, program.variables)
83+
}
8184

8285
vm.memoryBudget = MemoryBudget
8386
vm.memory = 0
@@ -107,10 +110,10 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
107110
vm.pop()
108111

109112
case OpStore:
110-
program.variables[arg] = vm.pop()
113+
vm.Variables[arg] = vm.pop()
111114

112115
case OpLoadVar:
113-
vm.push(program.variables[arg])
116+
vm.push(vm.Variables[arg])
114117

115118
case OpLoadConst:
116119
vm.push(runtime.Fetch(env, program.Constants[arg]))

vm/vm_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/stretchr/testify/require"
1010

11+
"github.com/expr-lang/expr"
1112
"github.com/expr-lang/expr/checker"
1213
"github.com/expr-lang/expr/compiler"
1314
"github.com/expr-lang/expr/conf"
@@ -34,6 +35,28 @@ func TestRun_ReuseVM(t *testing.T) {
3435
require.NoError(t, err)
3536
}
3637

38+
func TestRun_ReuseVM_for_different_variables(t *testing.T) {
39+
v := vm.VM{}
40+
41+
program, err := expr.Compile(`let a = 1; a + 1`)
42+
require.NoError(t, err)
43+
out, err := v.Run(program, nil)
44+
require.NoError(t, err)
45+
require.Equal(t, 2, out)
46+
47+
program, err = expr.Compile(`let a = 2; a + 1`)
48+
require.NoError(t, err)
49+
out, err = v.Run(program, nil)
50+
require.NoError(t, err)
51+
require.Equal(t, 3, out)
52+
53+
program, err = expr.Compile(`let a = 2; let b = 2; a + b`)
54+
require.NoError(t, err)
55+
out, err = v.Run(program, nil)
56+
require.NoError(t, err)
57+
require.Equal(t, 4, out)
58+
}
59+
3760
func TestRun_Cast(t *testing.T) {
3861
input := `1`
3962

0 commit comments

Comments
 (0)