Skip to content

Commit

Permalink
Merge pull request #3343 from onflow/supun/port-223
Browse files Browse the repository at this point in the history
Add invalidation for storage references
  • Loading branch information
SupunS authored May 15, 2024
2 parents b36c399 + f420d12 commit e7fbb09
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 83 deletions.
13 changes: 13 additions & 0 deletions runtime/interpreter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -1119,3 +1119,16 @@ func (InvalidCapabilityIDError) IsInternalError() {}
func (e InvalidCapabilityIDError) Error() string {
return "capability created with invalid ID"
}

// ReferencedValueChangedError
type ReferencedValueChangedError struct {
LocationRange
}

var _ errors.UserError = ReferencedValueChangedError{}

func (ReferencedValueChangedError) IsUserError() {}

func (e ReferencedValueChangedError) Error() string {
return "referenced value has been changed after taking the reference"
}
18 changes: 16 additions & 2 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -20657,8 +20657,22 @@ func (v *StorageReferenceValue) GetMember(
locationRange LocationRange,
name string,
) Value {
self := v.mustReferencedValue(interpreter, locationRange)
return interpreter.getMember(self, locationRange, name)
referencedValue := v.mustReferencedValue(interpreter, locationRange)

member := interpreter.getMember(referencedValue, locationRange, name)

// If the member is a function, it is always a bound-function.
// By default, bound functions create and hold an ephemeral reference (`selfRef`).
// For storage references, replace this default one with the actual storage reference.
// It is not possible (or a lot of work), to create the bound function with the storage reference
// when it was created originally, because `getMember(referencedValue, ...)` doesn't know
// whether the member was accessed directly, or via a reference.
if boundFunction, isBoundFunction := member.(BoundFunctionValue); isBoundFunction {
boundFunction.selfRef = v
return boundFunction
}

return member
}

func (v *StorageReferenceValue) RemoveMember(
Expand Down
38 changes: 34 additions & 4 deletions runtime/interpreter/value_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ type BoundFunctionValue struct {
Base *EphemeralReferenceValue
Self *Value
BoundAuthorization Authorization
selfRef *EphemeralReferenceValue
selfRef ReferenceValue
}

var _ Value = BoundFunctionValue{}
Expand All @@ -353,8 +353,8 @@ func NewBoundFunctionValue(

// Since 'self' work as an implicit reference, create an explicit one and hold it.
// This reference is later used to check the validity of the referenced value/resource.
var selfRef *EphemeralReferenceValue
if reference, isReference := (*self).(*EphemeralReferenceValue); isReference {
var selfRef ReferenceValue
if reference, isReference := (*self).(ReferenceValue); isReference {
// For attachments, 'self' is already a reference.
// So no need to create a reference again.
selfRef = reference
Expand Down Expand Up @@ -413,12 +413,42 @@ func (f BoundFunctionValue) invoke(invocation Invocation) Value {
invocation.Base = f.Base
invocation.BoundAuthorization = f.BoundAuthorization

locationRange := invocation.LocationRange
inter := invocation.Interpreter

// Check if the 'self' is not invalidated.
invocation.Interpreter.checkInvalidatedResourceOrResourceReference(f.selfRef, invocation.LocationRange)
if storageRef, isStorageRef := f.selfRef.(*StorageReferenceValue); isStorageRef {
inter.checkInvalidatedStorageReference(storageRef, locationRange)
} else {
inter.checkInvalidatedResourceOrResourceReference(f.selfRef, locationRange)
}

return f.Function.invoke(invocation)
}

// checkInvalidatedStorageReference checks whether a storage reference is valid, by
// comparing the referenced-value against the cached-referenced-value.
// A storage reference can be invalid for both resources and non-resource values.
func (interpreter *Interpreter) checkInvalidatedStorageReference(
storageRef *StorageReferenceValue,
locationRange LocationRange,
) {

referencedValue := storageRef.ReferencedValue(
interpreter,
locationRange,
true,
)

// `storageRef.ReferencedValue` above already checks for the type validity, if it's not nil.
// If nil, that means the value has been moved out of storage.
if referencedValue == nil {
panic(ReferencedValueChangedError{
LocationRange: locationRange,
})
}
}

func (f BoundFunctionValue) ConformsToStaticType(
interpreter *Interpreter,
locationRange LocationRange,
Expand Down
Loading

0 comments on commit e7fbb09

Please sign in to comment.