Object invariants (#62)

* Object invariants
This commit is contained in:
Stanisław Barzowski 2017-09-22 18:02:05 -04:00 committed by Dave Cunningham
parent 9047718ad0
commit 8638a21287
52 changed files with 2615 additions and 22 deletions

View File

@ -110,26 +110,23 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
// Simplify asserts // Simplify asserts
// TODO(dcunnin): this // TODO(dcunnin): this
for _, field := range *fields { for i := range *fields {
field := &(*fields)[i]
if field.Kind != ast.ObjectAssert { if field.Kind != ast.ObjectAssert {
continue continue
} }
/* msg := field.Expr3
AST *msg = field.expr3 if msg == nil {
field.expr3 = nil msg = buildLiteralString("Object assertion failed.")
if (msg == nil) {
auto msg_str = U"Object assertion failed."
msg = alloc->make<ast.LiteralString>(field.expr2->location, msg_str,
ast.LiteralString::DOUBLE, "")
} }
field.Expr3 = nil
// if expr2 then true else error msg onFailure := &ast.Error{Expr: msg}
field.expr2 = alloc->make<ast.Conditional>( assertion := &ast.Conditional{
ast->location, Cond: field.Expr2,
field.expr2, BranchTrue: &ast.LiteralBoolean{Value: true}, // ignored anyway
alloc->make<ast.LiteralBoolean>(E, true), BranchFalse: onFailure,
alloc->make<Error>(msg->location, msg)) }
*/ field.Expr2 = assertion
} }
// Remove methods // Remove methods

View File

@ -326,8 +326,12 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error)
} }
fields[fieldName] = valueSimpleObjectField{field.Hide, &codeUnboundField{field.Body}} fields[fieldName] = valueSimpleObjectField{field.Hide, &codeUnboundField{field.Body}}
} }
var asserts []unboundField
for _, assert := range ast.Asserts {
asserts = append(asserts, &codeUnboundField{assert})
}
upValues := i.capture(ast.FreeVariables()) upValues := i.capture(ast.FreeVariables())
return makeValueSimpleObject(upValues, fields, ast.Asserts), nil return makeValueSimpleObject(upValues, fields, asserts), nil
case *ast.Error: case *ast.Error:
msgVal, err := e.evalInCurrentContext(ast.Expr) msgVal, err := e.evalInCurrentContext(ast.Expr)
@ -570,6 +574,11 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool,
fieldNames := objectFields(v, true) fieldNames := objectFields(v, true)
sort.Strings(fieldNames) sort.Strings(fieldNames)
err := checkAssertions(e, v)
if err != nil {
return err
}
if len(fieldNames) == 0 { if len(fieldNames) == 0 {
buf.WriteString("{ }") buf.WriteString("{ }")
} else { } else {

1
testdata/object_invariant.golden vendored Normal file
View File

@ -0,0 +1 @@
{ }

1
testdata/object_invariant.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert true }

1
testdata/object_invariant10.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant10.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert true, assert false }

1
testdata/object_invariant11.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant11.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert false }.x

1
testdata/object_invariant12.golden vendored Normal file
View File

@ -0,0 +1 @@
{ }

1
testdata/object_invariant12.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert 5 == 5 }

1
testdata/object_invariant13.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: x

1
testdata/object_invariant13.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert error "x" }

1
testdata/object_invariant14.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: xxx

1
testdata/object_invariant14.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert false: "xxx" }

1
testdata/object_invariant2.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant2.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert false }

1
testdata/object_invariant3.golden vendored Normal file
View File

@ -0,0 +1 @@
[ ]

1
testdata/object_invariant3.input vendored Normal file
View File

@ -0,0 +1 @@
std.objectFields({assert false})

1
testdata/object_invariant4.golden vendored Normal file
View File

@ -0,0 +1 @@
false

1
testdata/object_invariant4.input vendored Normal file
View File

@ -0,0 +1 @@
std.objectHas({assert false}, "x")

1
testdata/object_invariant5.golden vendored Normal file
View File

@ -0,0 +1 @@
"object"

1
testdata/object_invariant5.input vendored Normal file
View File

@ -0,0 +1 @@
std.type({assert false})

3
testdata/object_invariant6.golden vendored Normal file
View File

@ -0,0 +1,3 @@
{
"x": 5
}

1
testdata/object_invariant6.input vendored Normal file
View File

@ -0,0 +1 @@
{assert self.x == 5, x: 5}

1
testdata/object_invariant7.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Field does not exist: x

1
testdata/object_invariant7.input vendored Normal file
View File

@ -0,0 +1 @@
{ x: 5, assert super.x == 5 }

1
testdata/object_invariant8.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant8.input vendored Normal file
View File

@ -0,0 +1 @@
{ x: 5, assert self.x == 4 }

1
testdata/object_invariant9.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant9.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert true, assert false }

455
testdata/object_invariant_perf.golden vendored Normal file
View File

@ -0,0 +1,455 @@
[
{
"v": -1
},
{
"v": 0
},
{
"v": 1
},
{
"v": 2
},
{
"v": 3
},
{
"v": 4
},
{
"v": 5
},
{
"v": 6
},
{
"v": 7
},
{
"v": 8
},
{
"v": 9
},
{
"v": 10
},
{
"v": 11
},
{
"v": 12
},
{
"v": 13
},
{
"v": 14
},
{
"v": 15
},
{
"v": 16
},
{
"v": 17
},
{
"v": 18
},
{
"v": 19
},
{
"v": 20
},
{
"v": 21
},
{
"v": 22
},
{
"v": 23
},
{
"v": 24
},
{
"v": 25
},
{
"v": 26
},
{
"v": 27
},
{
"v": 28
},
{
"v": 29
},
{
"v": 30
},
{
"v": 31
},
{
"v": 32
},
{
"v": 33
},
{
"v": 34
},
{
"v": 35
},
{
"v": 36
},
{
"v": 37
},
{
"v": 38
},
{
"v": 39
},
{
"v": 40
},
{
"v": 41
},
{
"v": 42
},
{
"v": 43
},
{
"v": 44
},
{
"v": 45
},
{
"v": 46
},
{
"v": 47
},
{
"v": 48
},
{
"v": 49
},
{
"v": 50
},
{
"v": 51
},
{
"v": 52
},
{
"v": 53
},
{
"v": 54
},
{
"v": 55
},
{
"v": 56
},
{
"v": 57
},
{
"v": 58
},
{
"v": 59
},
{
"v": 60
},
{
"v": 61
},
{
"v": 62
},
{
"v": 63
},
{
"v": 64
},
{
"v": 65
},
{
"v": 66
},
{
"v": 67
},
{
"v": 68
},
{
"v": 69
},
{
"v": 70
},
{
"v": 71
},
{
"v": 72
},
{
"v": 73
},
{
"v": 74
},
{
"v": 75
},
{
"v": 76
},
{
"v": 77
},
{
"v": 78
},
{
"v": 79
},
{
"v": 80
},
{
"v": 81
},
{
"v": 82
},
{
"v": 83
},
{
"v": 84
},
{
"v": 85
},
{
"v": 86
},
{
"v": 87
},
{
"v": 88
},
{
"v": 89
},
{
"v": 90
},
{
"v": 91
},
{
"v": 92
},
{
"v": 93
},
{
"v": 94
},
{
"v": 95
},
{
"v": 96
},
{
"v": 97
},
{
"v": 98
},
{
"v": 99
},
{
"v": 100
},
{
"v": 101
},
{
"v": 102
},
{
"v": 103
},
{
"v": 104
},
{
"v": 105
},
{
"v": 106
},
{
"v": 107
},
{
"v": 108
},
{
"v": 109
},
{
"v": 110
},
{
"v": 111
},
{
"v": 112
},
{
"v": 113
},
{
"v": 114
},
{
"v": 115
},
{
"v": 116
},
{
"v": 117
},
{
"v": 118
},
{
"v": 119
},
{
"v": 120
},
{
"v": 121
},
{
"v": 122
},
{
"v": 123
},
{
"v": 124
},
{
"v": 125
},
{
"v": 126
},
{
"v": 127
},
{
"v": 128
},
{
"v": 129
},
{
"v": 130
},
{
"v": 131
},
{
"v": 132
},
{
"v": 133
},
{
"v": 134
},
{
"v": 135
},
{
"v": 136
},
{
"v": 137
},
{
"v": 138
},
{
"v": 139
},
{
"v": 140
},
{
"v": 141
},
{
"v": 142
},
{
"v": 143
},
{
"v": 144
},
{
"v": 145
},
{
"v": 146
},
{
"v": 147
},
{
"v": 148
},
{
"v": 149
}
]

2
testdata/object_invariant_perf.input vendored Normal file
View File

@ -0,0 +1,2 @@
local arr = [{v: -1}] + std.makeArray(150, function(x) { v: x, assert arr[x].v == self.v - 1 });
arr

1
testdata/object_invariant_plus.golden vendored Normal file
View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant_plus.input vendored Normal file
View File

@ -0,0 +1 @@
{assert false} + {assert true}

View File

@ -0,0 +1 @@
RUNTIME ERROR: Object assertion failed.

1
testdata/object_invariant_plus2.input vendored Normal file
View File

@ -0,0 +1 @@
{assert true} + {assert false}

View File

@ -0,0 +1 @@
{ }

1
testdata/object_invariant_plus3.input vendored Normal file
View File

@ -0,0 +1 @@
{assert true} + {assert true}

View File

@ -0,0 +1,3 @@
{
"x": 5
}

1
testdata/object_invariant_plus4.input vendored Normal file
View File

@ -0,0 +1 @@
{assert self.x == 5} { x: 5 }

View File

@ -0,0 +1,3 @@
{
"x": 3
}

1
testdata/object_invariant_plus5.input vendored Normal file
View File

@ -0,0 +1 @@
{x: 1, assert self.x == 3} {x: 2, assert super.x == 1} {x: 3, assert super.x == 2}

View File

@ -0,0 +1 @@
RUNTIME ERROR: yyy

1
testdata/object_invariant_plus6.input vendored Normal file
View File

@ -0,0 +1 @@
{ assert false: "xxx" } { assert false: "yyy" }

View File

@ -0,0 +1,3 @@
{
"x": 4
}

2
testdata/object_invariant_plus7.input vendored Normal file
View File

@ -0,0 +1,2 @@
({x: 1} {x: 2, assert 1 == super.x}) +
({x: 3, assert 2 == super.x} {x: 4, assert 3 == super.x})

2003
testdata/std.makeArray_recursive.golden vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr

View File

@ -0,0 +1 @@
RUNTIME ERROR: Max stack frames exceeded.

View File

@ -0,0 +1 @@
local arr = [0] + std.makeArray(2000, function(i) arr[i] + 1); arr[500]

View File

@ -75,7 +75,7 @@ type callThunk struct {
} }
func makeCallThunk(ec evalCallable, args callArguments) potentialValue { func makeCallThunk(ec evalCallable, args callArguments) potentialValue {
return &callThunk{function: ec, args: args} return makeCachedThunk(&callThunk{function: ec, args: args})
} }
func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error) { func (th *callThunk) getValue(i *interpreter, trace *TraceElement) (value, error) {

View File

@ -16,6 +16,7 @@ limitations under the License.
package jsonnet package jsonnet
import ( import (
"errors"
"fmt" "fmt"
"github.com/google/go-jsonnet/ast" "github.com/google/go-jsonnet/ast"
@ -269,6 +270,9 @@ type valueObject interface {
value value
inheritanceSize() int inheritanceSize() int
index(e *evaluator, field string) (value, error) index(e *evaluator, field string) (value, error)
assertionsChecked() bool
setAssertionsCheckResult(err error)
getAssertionsCheckResult() error
} }
type selfBinding struct { type selfBinding struct {
@ -276,7 +280,7 @@ type selfBinding struct {
// that this is not the same as context, because we could be inside a function, // that this is not the same as context, because we could be inside a function,
// inside an object and then context would be the function, but self would still point // inside an object and then context would be the function, but self would still point
// to the object. // to the object.
self value self valueObject
// superDepth is the "super" level of self. Sometimes, we look upwards in the // superDepth is the "super" level of self. Sometimes, we look upwards in the
// inheritance tree, e.g. via an explicit use of super, or because a given field // inheritance tree, e.g. via an explicit use of super, or because a given field
@ -297,14 +301,43 @@ func makeUnboundSelfBinding() selfBinding {
} }
} }
// Hack - we need to distinguish not-checked-yet and no error situations
// so we have a special value for no error and nil means that we don't know yet.
var errNoErrorInObjectInvariants = errors.New("No error - assertions passed")
type valueObjectBase struct { type valueObjectBase struct {
valueBase valueBase
assertionError error
} }
func (*valueObjectBase) typename() string { func (*valueObjectBase) typename() string {
return "object" return "object"
} }
func (obj *valueObjectBase) assertionsChecked() bool {
// nil - not checked yet
// errNoErrorInObjectInvariants - we checked and there is no error (or checking in progress)
return obj.assertionError != nil
}
func (obj *valueObjectBase) setAssertionsCheckResult(err error) {
if err != nil {
obj.assertionError = err
} else {
obj.assertionError = errNoErrorInObjectInvariants
}
}
func (obj *valueObjectBase) getAssertionsCheckResult() error {
if obj.assertionError == nil {
panic("Assertions not checked yet")
}
if obj.assertionError == errNoErrorInObjectInvariants {
return nil
}
return obj.assertionError
}
// valueSimpleObject represents a flat object (no inheritance). // valueSimpleObject represents a flat object (no inheritance).
// Note that it can be used as part of extended objects // Note that it can be used as part of extended objects
// in inheritance using operator +. // in inheritance using operator +.
@ -317,7 +350,43 @@ type valueSimpleObject struct {
valueObjectBase valueObjectBase
upValues bindingFrame upValues bindingFrame
fields valueSimpleObjectFieldMap fields valueSimpleObjectFieldMap
asserts []ast.Node asserts []unboundField
}
func checkAssertionsHelper(e *evaluator, obj valueObject, curr valueObject, superDepth int) error {
switch curr := curr.(type) {
case *valueExtendedObject:
err := checkAssertionsHelper(e, obj, curr.right, superDepth)
if err != nil {
return err
}
err = checkAssertionsHelper(e, obj, curr.left, superDepth+curr.right.inheritanceSize())
if err != nil {
return err
}
return nil
case *valueSimpleObject:
for _, assert := range curr.asserts {
_, err := e.evaluate(assert.bindToObject(selfBinding{self: obj, superDepth: superDepth}, curr.upValues))
if err != nil {
return err
}
}
return nil
default:
panic(fmt.Sprintf("Unknown object type %#v", obj))
}
}
func checkAssertions(e *evaluator, obj valueObject) error {
if !obj.assertionsChecked() {
// Assertions may refer to the object that will normally
// trigger checking of assertions, resulting in an endless recursion.
// To avoid that, while we check them, we treat them as already passed.
obj.setAssertionsCheckResult(errNoErrorInObjectInvariants)
obj.setAssertionsCheckResult(checkAssertionsHelper(e, obj, obj, 0))
}
return obj.getAssertionsCheckResult()
} }
func (o *valueSimpleObject) index(e *evaluator, field string) (value, error) { func (o *valueSimpleObject) index(e *evaluator, field string) (value, error) {
@ -328,7 +397,7 @@ func (*valueSimpleObject) inheritanceSize() int {
return 1 return 1
} }
func makeValueSimpleObject(b bindingFrame, fields valueSimpleObjectFieldMap, asserts ast.Nodes) *valueSimpleObject { func makeValueSimpleObject(b bindingFrame, fields valueSimpleObjectFieldMap, asserts []unboundField) *valueSimpleObject {
return &valueSimpleObject{ return &valueSimpleObject{
upValues: b, upValues: b,
fields: fields, fields: fields,
@ -425,6 +494,10 @@ func superIndex(e *evaluator, currentSB selfBinding, field string) (value, error
} }
func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) { func objectIndex(e *evaluator, sb selfBinding, fieldName string) (value, error) {
err := checkAssertions(e, sb.self)
if err != nil {
return nil, err
}
field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName) field, upValues, foundAt := findField(sb.self, sb.superDepth, fieldName)
if field == nil { if field == nil {
return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName)) return nil, e.Error(fmt.Sprintf("Field does not exist: %s", fieldName))