Skip to content

Commit 2315778

Browse files
Mauricio GomesMauricio Gomes
authored andcommitted
Rebind contracts after capability argument mutation
1 parent 1cd0f3b commit 2315778

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

vibes/capability_contracts_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,40 @@ func (c importingContractCapability) CapabilityContracts() map[string]Capability
350350
}
351351
}
352352

353+
type argMutationContractCapability struct {
354+
invokeCount *int
355+
}
356+
357+
func (c argMutationContractCapability) Bind(binding CapabilityBinding) (map[string]Value, error) {
358+
return map[string]Value{
359+
"cap": NewObject(map[string]Value{
360+
"install": NewBuiltin("cap.install", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
361+
if len(args) != 1 || (args[0].Kind() != KindHash && args[0].Kind() != KindObject) {
362+
return NewNil(), fmt.Errorf("cap.install expects target hash")
363+
}
364+
args[0].Hash()["call"] = NewBuiltin("cap.call", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
365+
*c.invokeCount = *c.invokeCount + 1
366+
return NewString("ok"), nil
367+
})
368+
return NewString("installed"), nil
369+
}),
370+
}),
371+
}, nil
372+
}
373+
374+
func (c argMutationContractCapability) CapabilityContracts() map[string]CapabilityMethodContract {
375+
return map[string]CapabilityMethodContract{
376+
"cap.call": {
377+
ValidateArgs: func(args []Value, kwargs map[string]Value, block Value) error {
378+
if len(args) != 1 || args[0].Kind() != KindInt {
379+
return fmt.Errorf("cap.call expects int")
380+
}
381+
return nil
382+
},
383+
},
384+
}
385+
}
386+
353387
func TestCapabilityContractRejectsInvalidArguments(t *testing.T) {
354388
engine := MustNewEngine(Config{})
355389
script, err := engine.Compile(`def run()
@@ -641,3 +675,31 @@ end`)
641675
t.Fatalf("unexpected result: %#v", result)
642676
}
643677
}
678+
679+
func TestCapabilityContractsBindAfterArgumentMutation(t *testing.T) {
680+
engine := MustNewEngine(Config{})
681+
script, err := engine.Compile(`def run()
682+
target = {}
683+
cap.install(target)
684+
target.call("bad")
685+
end`)
686+
if err != nil {
687+
t.Fatalf("compile failed: %v", err)
688+
}
689+
690+
invocations := 0
691+
_, err = script.Call(context.Background(), "run", nil, CallOptions{
692+
Capabilities: []CapabilityAdapter{
693+
argMutationContractCapability{invokeCount: &invocations},
694+
},
695+
})
696+
if err == nil {
697+
t.Fatalf("expected argument-mutation contract validation error")
698+
}
699+
if got := err.Error(); !strings.Contains(got, "cap.call expects int") {
700+
t.Fatalf("unexpected error: %s", got)
701+
}
702+
if invocations != 0 {
703+
t.Fatalf("argument mutation capability should not execute when contract fails")
704+
}
705+
}

vibes/execution.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,14 @@ func (exec *Execution) invokeCallable(callee Value, receiver Value, args []Value
644644
for _, root := range scope.roots {
645645
bindCapabilityContracts(root, scope, exec.capabilityContracts, exec.capabilityContractScopes)
646646
}
647+
// Methods can also publish builtins by mutating positional or keyword
648+
// argument objects supplied by script code.
649+
for _, arg := range args {
650+
bindCapabilityContracts(arg, scope, exec.capabilityContracts, exec.capabilityContractScopes)
651+
}
652+
for _, kwarg := range kwargs {
653+
bindCapabilityContracts(kwarg, scope, exec.capabilityContracts, exec.capabilityContractScopes)
654+
}
647655
}
648656
return result, nil
649657
default:

0 commit comments

Comments
 (0)