Skip to content

Commit d3c9b14

Browse files
Mauricio GomesMauricio Gomes
authored andcommitted
Scope capability contracts to bound capability builtins
1 parent aed3b12 commit d3c9b14

4 files changed

Lines changed: 100 additions & 9 deletions

File tree

vibes/capability_contracts.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ func validateCapabilityDataOnlyValue(label string, val Value) error {
2525
return nil
2626
}
2727

28+
func bindCapabilityContracts(val Value, contractsByName map[string]CapabilityMethodContract, target map[*Builtin]CapabilityMethodContract) {
29+
if len(contractsByName) == 0 {
30+
return
31+
}
32+
scanner := newCapabilityContractScanner()
33+
scanner.bindContracts(val, contractsByName, target)
34+
}
35+
2836
func (s *capabilityContractScanner) containsCallable(val Value) bool {
2937
switch val.Kind() {
3038
case KindFunction, KindBuiltin, KindBlock, KindClass, KindInstance:
@@ -63,3 +71,37 @@ func (s *capabilityContractScanner) containsCallable(val Value) bool {
6371
return false
6472
}
6573
}
74+
75+
func (s *capabilityContractScanner) bindContracts(val Value, contractsByName map[string]CapabilityMethodContract, target map[*Builtin]CapabilityMethodContract) {
76+
switch val.Kind() {
77+
case KindBuiltin:
78+
builtin := val.Builtin()
79+
if contract, ok := contractsByName[builtin.Name]; ok {
80+
target[builtin] = contract
81+
}
82+
case KindArray:
83+
values := val.Array()
84+
id := sliceIdentity{
85+
ptr: reflect.ValueOf(values).Pointer(),
86+
len: len(values),
87+
cap: cap(values),
88+
}
89+
if _, seen := s.seenArrays[id]; seen {
90+
return
91+
}
92+
s.seenArrays[id] = struct{}{}
93+
for _, item := range values {
94+
s.bindContracts(item, contractsByName, target)
95+
}
96+
case KindHash, KindObject:
97+
entries := val.Hash()
98+
ptr := reflect.ValueOf(entries).Pointer()
99+
if _, seen := s.seenMaps[ptr]; seen {
100+
return
101+
}
102+
s.seenMaps[ptr] = struct{}{}
103+
for _, item := range entries {
104+
s.bindContracts(item, contractsByName, target)
105+
}
106+
}
107+
}

vibes/capability_contracts_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,28 @@ func (duplicateContractCapability) CapabilityContracts() map[string]CapabilityMe
6060
}
6161
}
6262

63+
type unrelatedNamedContractCapability struct{}
64+
65+
func (unrelatedNamedContractCapability) Bind(binding CapabilityBinding) (map[string]Value, error) {
66+
return map[string]Value{
67+
"probe": NewObject(map[string]Value{
68+
"call": NewBuiltin("probe.call", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
69+
return NewString("ok"), nil
70+
}),
71+
}),
72+
}, nil
73+
}
74+
75+
func (unrelatedNamedContractCapability) CapabilityContracts() map[string]CapabilityMethodContract {
76+
return map[string]CapabilityMethodContract{
77+
"hash.merge": {
78+
ValidateArgs: func(args []Value, kwargs map[string]Value, block Value) error {
79+
return fmt.Errorf("hash.merge contract should not be applied")
80+
},
81+
},
82+
}
83+
}
84+
6385
func TestCapabilityContractRejectsInvalidArguments(t *testing.T) {
6486
engine := MustNewEngine(Config{})
6587
script, err := engine.Compile(`def run()
@@ -137,3 +159,28 @@ end`)
137159
t.Fatalf("unexpected error: %s", got)
138160
}
139161
}
162+
163+
func TestCapabilityContractsDoNotAttachByGlobalBuiltinName(t *testing.T) {
164+
engine := MustNewEngine(Config{})
165+
script, err := engine.Compile(`def run()
166+
base = { a: 1 }
167+
override = { b: 2 }
168+
base.merge(override)
169+
end`)
170+
if err != nil {
171+
t.Fatalf("compile failed: %v", err)
172+
}
173+
174+
result, err := script.Call(context.Background(), "run", nil, CallOptions{
175+
Capabilities: []CapabilityAdapter{unrelatedNamedContractCapability{}},
176+
})
177+
if err != nil {
178+
t.Fatalf("unexpected error: %v", err)
179+
}
180+
if result.Kind() != KindHash {
181+
t.Fatalf("expected hash result, got %v", result.Kind())
182+
}
183+
if got, ok := result.Hash()["b"]; !ok || got.Kind() != KindInt || got.Int() != 2 {
184+
t.Fatalf("unexpected merge result: %#v", result.Hash())
185+
}
186+
}

vibes/execution.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type Execution struct {
5454
moduleLoading map[string]bool
5555
moduleLoadStack []string
5656
moduleStack []moduleContext
57-
capabilityContracts map[string]CapabilityMethodContract
57+
capabilityContracts map[*Builtin]CapabilityMethodContract
5858
receiverStack []Value
5959
envStack []*Env
6060
strictEffects bool
@@ -607,7 +607,7 @@ func (exec *Execution) invokeCallable(callee Value, receiver Value, args []Value
607607
return exec.callFunction(callee.Function(), receiver, args, kwargs, block, pos)
608608
case KindBuiltin:
609609
builtin := callee.Builtin()
610-
contract, hasContract := exec.capabilityContracts[builtin.Name]
610+
contract, hasContract := exec.capabilityContracts[builtin]
611611
if hasContract && contract.ValidateArgs != nil {
612612
if err := contract.ValidateArgs(args, kwargs, block); err != nil {
613613
return NewNil(), exec.wrapError(err, pos)
@@ -3400,7 +3400,7 @@ func (s *Script) Call(ctx context.Context, name string, args []Value, opts CallO
34003400
moduleLoading: make(map[string]bool),
34013401
moduleLoadStack: make([]string, 0, 8),
34023402
moduleStack: make([]moduleContext, 0, 8),
3403-
capabilityContracts: make(map[string]CapabilityMethodContract),
3403+
capabilityContracts: make(map[*Builtin]CapabilityMethodContract),
34043404
receiverStack: make([]Value, 0, 8),
34053405
envStack: make([]*Env, 0, 8),
34063406
strictEffects: s.engine.config.StrictEffects,
@@ -3409,28 +3409,33 @@ func (s *Script) Call(ctx context.Context, name string, args []Value, opts CallO
34093409

34103410
if len(opts.Capabilities) > 0 {
34113411
binding := CapabilityBinding{Context: exec.ctx, Engine: s.engine}
3412+
knownCapabilityContracts := make(map[string]CapabilityMethodContract)
34123413
for _, adapter := range opts.Capabilities {
34133414
if adapter == nil {
34143415
continue
34153416
}
3417+
adapterContracts := map[string]CapabilityMethodContract{}
34163418
if provider, ok := adapter.(CapabilityContractProvider); ok {
34173419
for methodName, contract := range provider.CapabilityContracts() {
34183420
name := strings.TrimSpace(methodName)
34193421
if name == "" {
34203422
return NewNil(), fmt.Errorf("capability contract method name must be non-empty")
34213423
}
3422-
if _, exists := exec.capabilityContracts[name]; exists {
3424+
if _, exists := knownCapabilityContracts[name]; exists {
34233425
return NewNil(), fmt.Errorf("duplicate capability contract for %s", name)
34243426
}
3425-
exec.capabilityContracts[name] = contract
3427+
knownCapabilityContracts[name] = contract
3428+
adapterContracts[name] = contract
34263429
}
34273430
}
34283431
globals, err := adapter.Bind(binding)
34293432
if err != nil {
34303433
return NewNil(), err
34313434
}
34323435
for name, val := range globals {
3433-
root.Define(name, rebinder.rebindValue(val))
3436+
rebound := rebinder.rebindValue(val)
3437+
root.Define(name, rebound)
3438+
bindCapabilityContracts(rebound, adapterContracts, exec.capabilityContracts)
34343439
}
34353440
}
34363441
}

vibes/memory.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ func (exec *Execution) estimateMemoryUsage(extras ...Value) int {
8181
total += estimatedStringHeaderBytes + len(name)
8282
}
8383
total += estimatedMapBaseBytes + len(exec.capabilityContracts)*estimatedMapEntryBytes
84-
for name := range exec.capabilityContracts {
85-
total += estimatedStringHeaderBytes + len(name)
86-
}
8784
total += estimatedSliceBaseBytes + len(exec.moduleLoadStack)*estimatedStringHeaderBytes
8885
for _, key := range exec.moduleLoadStack {
8986
total += len(key)

0 commit comments

Comments
 (0)