Skip to content

Commit 6cb766f

Browse files
committed
restore fail-fast memory checks across call argument evaluation
1 parent d7651bf commit 6cb766f

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

vibes/execution_call_expr.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ func (exec *Execution) evalCallArgs(call *CallExpr, env *Env) ([]Value, error) {
6666
if err != nil {
6767
return nil, err
6868
}
69+
if err := exec.checkMemoryWith(val); err != nil {
70+
return nil, err
71+
}
6972
args[i] = val
7073
}
7174
return args, nil
@@ -81,6 +84,9 @@ func (exec *Execution) evalCallKwArgs(call *CallExpr, env *Env) (map[string]Valu
8184
if err != nil {
8285
return nil, err
8386
}
87+
if err := exec.checkMemoryWith(val); err != nil {
88+
return nil, err
89+
}
8490
kwargs[kw.Name] = val
8591
}
8692
return kwargs, nil
@@ -90,7 +96,14 @@ func (exec *Execution) evalCallBlock(call *CallExpr, env *Env) (Value, error) {
9096
if call.Block == nil {
9197
return NewNil(), nil
9298
}
93-
return exec.evalBlockLiteral(call.Block, env)
99+
block, err := exec.evalBlockLiteral(call.Block, env)
100+
if err != nil {
101+
return NewNil(), err
102+
}
103+
if err := exec.checkMemoryWith(block); err != nil {
104+
return NewNil(), err
105+
}
106+
return block, nil
94107
}
95108

96109
func (exec *Execution) checkCallMemoryRoots(receiver Value, args []Value, kwargs map[string]Value, block Value) error {

vibes/memory_quota_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,50 @@ func TestAggregateBuiltinArgumentsAreChecked(t *testing.T) {
568568
requireErrorContains(t, err, "memory quota exceeded")
569569
}
570570

571+
func TestCallArgumentMemoryChecksFailFastBeforeLaterSideEffects(t *testing.T) {
572+
pos := Position{Line: 1, Column: 1}
573+
payload := strings.Repeat("a", 5000)
574+
tickCount := 0
575+
576+
stmt := &ExprStmt{
577+
Expr: &CallExpr{
578+
Callee: &Identifier{Name: "noop", position: pos},
579+
Args: []Expression{
580+
&StringLiteral{Value: payload, position: pos},
581+
&CallExpr{
582+
Callee: &Identifier{Name: "tick", position: pos},
583+
position: pos,
584+
},
585+
},
586+
position: pos,
587+
},
588+
position: pos,
589+
}
590+
591+
exec := &Execution{
592+
quota: 10000,
593+
memoryQuota: 2048,
594+
moduleLoading: make(map[string]bool),
595+
}
596+
env := newEnv(nil)
597+
env.Define("noop", NewBuiltin("noop", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
598+
return NewNil(), nil
599+
}))
600+
env.Define("tick", NewBuiltin("tick", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
601+
tickCount++
602+
return NewInt(1), nil
603+
}))
604+
605+
_, _, err := exec.evalStatements([]Statement{stmt}, env)
606+
if err == nil {
607+
t.Fatalf("expected memory quota error for oversized first argument")
608+
}
609+
requireErrorContains(t, err, "memory quota exceeded")
610+
if tickCount != 0 {
611+
t.Fatalf("expected later argument side effects to be skipped, got %d", tickCount)
612+
}
613+
}
614+
571615
func TestTransientAssignmentValueIsCheckedBeforeAssign(t *testing.T) {
572616
pos := Position{Line: 1, Column: 1}
573617
elements := make([]Expression, 1200)

0 commit comments

Comments
 (0)