Skip to content

Commit ddf99bc

Browse files
Mauricio GomesMauricio Gomes
authored andcommitted
Traverse class and instance values for capability contracts
1 parent d3c9b14 commit ddf99bc

2 files changed

Lines changed: 155 additions & 4 deletions

File tree

vibes/capability_contracts.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ import (
66
)
77

88
type capabilityContractScanner struct {
9-
seenArrays map[sliceIdentity]struct{}
10-
seenMaps map[uintptr]struct{}
9+
seenArrays map[sliceIdentity]struct{}
10+
seenMaps map[uintptr]struct{}
11+
seenClasses map[*ClassDef]struct{}
12+
seenInstances map[*Instance]struct{}
1113
}
1214

1315
func newCapabilityContractScanner() *capabilityContractScanner {
1416
return &capabilityContractScanner{
15-
seenArrays: make(map[sliceIdentity]struct{}),
16-
seenMaps: make(map[uintptr]struct{}),
17+
seenArrays: make(map[sliceIdentity]struct{}),
18+
seenMaps: make(map[uintptr]struct{}),
19+
seenClasses: make(map[*ClassDef]struct{}),
20+
seenInstances: make(map[*Instance]struct{}),
1721
}
1822
}
1923

@@ -103,5 +107,32 @@ func (s *capabilityContractScanner) bindContracts(val Value, contractsByName map
103107
for _, item := range entries {
104108
s.bindContracts(item, contractsByName, target)
105109
}
110+
case KindClass:
111+
classDef := val.Class()
112+
if classDef == nil {
113+
return
114+
}
115+
if _, seen := s.seenClasses[classDef]; seen {
116+
return
117+
}
118+
s.seenClasses[classDef] = struct{}{}
119+
for _, item := range classDef.ClassVars {
120+
s.bindContracts(item, contractsByName, target)
121+
}
122+
case KindInstance:
123+
instance := val.Instance()
124+
if instance == nil {
125+
return
126+
}
127+
if _, seen := s.seenInstances[instance]; seen {
128+
return
129+
}
130+
s.seenInstances[instance] = struct{}{}
131+
for _, item := range instance.Ivars {
132+
s.bindContracts(item, contractsByName, target)
133+
}
134+
if instance.Class != nil {
135+
s.bindContracts(NewClass(instance.Class), contractsByName, target)
136+
}
106137
}
107138
}

vibes/capability_contracts_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,74 @@ func (unrelatedNamedContractCapability) CapabilityContracts() map[string]Capabil
8282
}
8383
}
8484

85+
type instanceIvarContractCapability struct {
86+
invokeCount *int
87+
}
88+
89+
func (c instanceIvarContractCapability) Bind(binding CapabilityBinding) (map[string]Value, error) {
90+
classDef := &ClassDef{
91+
Name: "CapabilityBox",
92+
Methods: map[string]*ScriptFunction{},
93+
ClassMethods: map[string]*ScriptFunction{},
94+
ClassVars: map[string]Value{},
95+
}
96+
instance := &Instance{
97+
Class: classDef,
98+
Ivars: map[string]Value{
99+
"call": NewBuiltin("probe.call", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
100+
*c.invokeCount = *c.invokeCount + 1
101+
return NewString("ok"), nil
102+
}),
103+
},
104+
}
105+
return map[string]Value{"box": NewInstance(instance)}, nil
106+
}
107+
108+
func (c instanceIvarContractCapability) CapabilityContracts() map[string]CapabilityMethodContract {
109+
return map[string]CapabilityMethodContract{
110+
"probe.call": {
111+
ValidateArgs: func(args []Value, kwargs map[string]Value, block Value) error {
112+
if len(args) != 1 || args[0].Kind() != KindInt {
113+
return fmt.Errorf("probe.call expects int")
114+
}
115+
return nil
116+
},
117+
},
118+
}
119+
}
120+
121+
type classVarContractCapability struct {
122+
invokeCount *int
123+
}
124+
125+
func (c classVarContractCapability) Bind(binding CapabilityBinding) (map[string]Value, error) {
126+
classDef := &ClassDef{
127+
Name: "CapabilityHolder",
128+
Methods: map[string]*ScriptFunction{},
129+
ClassMethods: map[string]*ScriptFunction{},
130+
ClassVars: map[string]Value{
131+
"call": NewBuiltin("probe.class_call", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
132+
*c.invokeCount = *c.invokeCount + 1
133+
return NewString("ok"), nil
134+
}),
135+
},
136+
}
137+
return map[string]Value{"holder": NewClass(classDef)}, nil
138+
}
139+
140+
func (c classVarContractCapability) CapabilityContracts() map[string]CapabilityMethodContract {
141+
return map[string]CapabilityMethodContract{
142+
"probe.class_call": {
143+
ValidateArgs: func(args []Value, kwargs map[string]Value, block Value) error {
144+
if len(args) != 1 || args[0].Kind() != KindInt {
145+
return fmt.Errorf("probe.class_call expects int")
146+
}
147+
return nil
148+
},
149+
},
150+
}
151+
}
152+
85153
func TestCapabilityContractRejectsInvalidArguments(t *testing.T) {
86154
engine := MustNewEngine(Config{})
87155
script, err := engine.Compile(`def run()
@@ -184,3 +252,55 @@ end`)
184252
t.Fatalf("unexpected merge result: %#v", result.Hash())
185253
}
186254
}
255+
256+
func TestCapabilityContractsTraverseInstanceValues(t *testing.T) {
257+
engine := MustNewEngine(Config{})
258+
script, err := engine.Compile(`def run()
259+
box.call("bad")
260+
end`)
261+
if err != nil {
262+
t.Fatalf("compile failed: %v", err)
263+
}
264+
265+
invocations := 0
266+
_, err = script.Call(context.Background(), "run", nil, CallOptions{
267+
Capabilities: []CapabilityAdapter{
268+
instanceIvarContractCapability{invokeCount: &invocations},
269+
},
270+
})
271+
if err == nil {
272+
t.Fatalf("expected instance contract validation error")
273+
}
274+
if got := err.Error(); !strings.Contains(got, "probe.call expects int") {
275+
t.Fatalf("unexpected error: %s", got)
276+
}
277+
if invocations != 0 {
278+
t.Fatalf("instance capability should not execute when contract fails")
279+
}
280+
}
281+
282+
func TestCapabilityContractsTraverseClassValues(t *testing.T) {
283+
engine := MustNewEngine(Config{})
284+
script, err := engine.Compile(`def run()
285+
holder.call("bad")
286+
end`)
287+
if err != nil {
288+
t.Fatalf("compile failed: %v", err)
289+
}
290+
291+
invocations := 0
292+
_, err = script.Call(context.Background(), "run", nil, CallOptions{
293+
Capabilities: []CapabilityAdapter{
294+
classVarContractCapability{invokeCount: &invocations},
295+
},
296+
})
297+
if err == nil {
298+
t.Fatalf("expected class contract validation error")
299+
}
300+
if got := err.Error(); !strings.Contains(got, "probe.class_call expects int") {
301+
t.Fatalf("unexpected error: %s", got)
302+
}
303+
if invocations != 0 {
304+
t.Fatalf("class capability should not execute when contract fails")
305+
}
306+
}

0 commit comments

Comments
 (0)