-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
slice appends sometimes have some of their content zeroed out in between tx calls #1167
Comments
Possibly related: #1170 something-something-slice-allocation |
So I think we can state that the bugs we have in gnolang#960, gnolang#1167 and gnolang#1170, are all related to slice storage when its capacity is different than its length. @deelawn found a great way to overcome that bug, but the bug is still there, somewhere in the VM code I think. I spent the last couple of days trying to find it, unfortunately without success. That said, I found a workaround, that could be also applied: when a slice is stored, ignore any capacity different than the slice's length. I think this is a good workaround because its one-line and because we don't really care about storing slice with capacity higher than their length (unless I'm missing something).
cc/ @deelawn |
I've done an analysis on this and was able to identify the root cause. I'll start from the call to Inside
|
child.IncRefCount() | |
rc := child.GetRefCount() | |
if rc == 1 { | |
if child.GetIsReal() { | |
// a deleted real became undeleted. | |
child.SetOwner(oo) | |
rlm.MarkDirty(child) | |
} else { | |
// a (possibly pre-existing) new object | |
// became real (again). | |
// NOTE: may already be marked for first gen | |
// newCreated or updated. | |
child.SetOwner(oo) | |
rlm.incRefCreatedDescendants(store, child) | |
child.SetIsNewReal(true) | |
} | |
} else if rc > 1 { | |
if child.GetIsEscaped() { | |
// already escaped, do nothing. | |
} else { | |
// NOTE: do not unset owner here, | |
// may become unescaped later | |
// in processNewEscapedMarks(). | |
// NOTE: may already be escaped. | |
rlm.MarkNewEscaped(child) | |
} | |
} else { |
Note that child
is the existing []Move
instance here. It already has an existing reference so after incrementing it again, rc
becomes 2 and the necessary block to persist the new Move
object is skipped. Even if the reference count were correct, this instance of []Move
is currently a real object, so it would be marked as dirty and the newly value would not be persisted.
So there are two things that need to be addressed here:
- How to handle decrementing a reference to an object when it is reassigned and its parent object is overwritten
- How to determine if a real object may be composed of new real objects that could be two or more layers deep due to transitive assignments
I'll dig into this more and come up with one or more proposals for how to fix these issues.
looking at this now. |
Addresses #1167, #960, and #1170 Consider the following situation: - A slice of structs exists with a length of zero and a capacity of one - A new struct literal is appended to the slice - The code panics because the newly allocated struct literal was never marked as "new" ``` go package append import ( "gno.land/p/demo/ufmt" ) type T struct{ i int } var a []T func init() { a = make([]T, 0, 1) } func Append(i int) { a = append(a, T{i: i}) } ``` Invoking the `Append` function will cause a panic. The solution is to traverse each of the array elements after slice append assignment to make sure any new or updated elements are marked as such. This PR also includes a change to ensure that marking an object as dirty and managing references to the object are mutually exclusive. I think this is correct but am not sure. The changes include txtar test cases that incorporate the issue described by @tbruyelle in #1170 --------- Co-authored-by: jaekwon <jae@tendermint.com>
Fixed by #1305 |
…lang#1305) Addresses gnolang#1167, gnolang#960, and gnolang#1170 Consider the following situation: - A slice of structs exists with a length of zero and a capacity of one - A new struct literal is appended to the slice - The code panics because the newly allocated struct literal was never marked as "new" ``` go package append import ( "gno.land/p/demo/ufmt" ) type T struct{ i int } var a []T func init() { a = make([]T, 0, 1) } func Append(i int) { a = append(a, T{i: i}) } ``` Invoking the `Append` function will cause a panic. The solution is to traverse each of the array elements after slice append assignment to make sure any new or updated elements are marked as such. This PR also includes a change to ensure that marking an object as dirty and managing references to the object are mutually exclusive. I think this is correct but am not sure. The changes include txtar test cases that incorporate the issue described by @tbruyelle in gnolang#1170 --------- Co-authored-by: jaekwon <jae@tendermint.com>
Reproducible test: 0e44fca
Without the workaround, test output:
This was originally found in GnoChess: one of the head-banging-against-the-wall issues we had with GnoChess was that the realm was returning
a1a1
as the first move being made (a1a1
is a representation of a Move struct, where if all of its three values are 0 then the String() method will returna1a1
). This could not be replicated using Gno tests, where the behaviour didn't happen, but only with the live chain.Introducing this change fixed the problem. The underlying idea is that of re-creating and reallocating the moves slice, instead of copying it from the previous instance of
Position
.The text was updated successfully, but these errors were encountered: