mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
//pkg/state fixes for VFS2.
- When encodeState.resolve() determines that the resolved reflect.Value is contained by a previously-resolved object, set wire.Ref.Type to the containing object's type (existing.obj.Type()) rather than the contained value's type (obj.Type()). - When encodeState.resolve() determines that the resolved reflect.Value contains a previously-resolved object, handle cases where the new object contains *multiple* previously-resolved objects. (This may cause previously-allocated object IDs to become unused; to facilitate this, change encodeState.pending to a map, and change the wire format to prefix each object with its object ID.) - Add encodeState.encodedStructs to avoid redundant encoding of structs, since deduplication of objects via encodeState.resolve() doesn't work for objects instantiated by StateSave() and passed to SaveValue() (i.e. fields tagged `state:".(whatever)"`). - Make unexported array fields deserializable via slices that refer to them by casting away their unexportedness in decodeState.decodeObject(). Updates #1663 PiperOrigin-RevId: 338727687
This commit is contained in:
@@ -3,19 +3,6 @@ load("//tools/go_generics:defs.bzl", "go_template_instance")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_template_instance(
|
||||
name = "pending_list",
|
||||
out = "pending_list.go",
|
||||
package = "state",
|
||||
prefix = "pending",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*objectEncodeState",
|
||||
"ElementMapper": "pendingMapper",
|
||||
"Linker": "*pendingEntry",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "deferred_list",
|
||||
out = "deferred_list.go",
|
||||
@@ -83,7 +70,6 @@ go_library(
|
||||
"deferred_list.go",
|
||||
"encode.go",
|
||||
"encode_unsafe.go",
|
||||
"pending_list.go",
|
||||
"state.go",
|
||||
"state_norace.go",
|
||||
"state_race.go",
|
||||
|
||||
+45
-35
@@ -21,6 +21,7 @@ import (
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
)
|
||||
|
||||
@@ -258,7 +259,7 @@ func (ds *decodeState) waitObject(ods *objectDecodeState, encoded wire.Object, c
|
||||
// For the purposes of this function, a child object is either a field within a
|
||||
// struct or an array element, with one such indirection per element in
|
||||
// path. The returned value may be an unexported field, so it may not be
|
||||
// directly assignable. See unsafePointerTo.
|
||||
// directly assignable. See decode_unsafe.go.
|
||||
func walkChild(path []wire.Dot, obj reflect.Value) reflect.Value {
|
||||
// See wire.Ref.Dots. The path here is specified in reverse order.
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
@@ -519,9 +520,7 @@ func (ds *decodeState) decodeObject(ods *objectDecodeState, obj reflect.Value, e
|
||||
|
||||
// Normal assignment: authoritative only if no dots.
|
||||
v := ds.register(x, obj.Type().Elem())
|
||||
if v.IsValid() {
|
||||
obj.Set(unsafePointerTo(v))
|
||||
}
|
||||
obj.Set(reflectValueRWAddr(v))
|
||||
case wire.Bool:
|
||||
obj.SetBool(bool(x))
|
||||
case wire.Int:
|
||||
@@ -559,7 +558,7 @@ func (ds *decodeState) decodeObject(ods *objectDecodeState, obj reflect.Value, e
|
||||
// contents will still be filled in later on.
|
||||
typ := reflect.ArrayOf(int(x.Capacity), obj.Type().Elem()) // The object type.
|
||||
v := ds.register(&x.Ref, typ)
|
||||
obj.Set(v.Slice3(0, int(x.Length), int(x.Capacity)))
|
||||
obj.Set(reflectValueRWSlice3(v, 0, int(x.Length), int(x.Capacity)))
|
||||
case *wire.Array:
|
||||
ds.decodeArray(ods, obj, x)
|
||||
case *wire.Struct:
|
||||
@@ -592,7 +591,7 @@ func (ds *decodeState) Load(obj reflect.Value) {
|
||||
ds.pending.PushBack(rootOds)
|
||||
|
||||
// Read the number of objects.
|
||||
lastID, object, err := ReadHeader(ds.r)
|
||||
numObjects, object, err := ReadHeader(ds.r)
|
||||
if err != nil {
|
||||
Failf("header error: %w", err)
|
||||
}
|
||||
@@ -604,42 +603,44 @@ func (ds *decodeState) Load(obj reflect.Value) {
|
||||
var (
|
||||
encoded wire.Object
|
||||
ods *objectDecodeState
|
||||
id = objectID(1)
|
||||
id objectID
|
||||
tid = typeID(1)
|
||||
)
|
||||
if err := safely(func() {
|
||||
// Decode all objects in the stream.
|
||||
//
|
||||
// Note that the structure of this decoding loop should match
|
||||
// the raw decoding loop in printer.go.
|
||||
for id <= objectID(lastID) {
|
||||
// Unmarshal the object.
|
||||
// Note that the structure of this decoding loop should match the raw
|
||||
// decoding loop in state/pretty/pretty.printer.printStream().
|
||||
for i := uint64(0); i < numObjects; {
|
||||
// Unmarshal either a type object or object ID.
|
||||
encoded = wire.Load(ds.r)
|
||||
|
||||
// Is this a type object? Handle inline.
|
||||
if wt, ok := encoded.(*wire.Type); ok {
|
||||
ds.types.Register(wt)
|
||||
switch we := encoded.(type) {
|
||||
case *wire.Type:
|
||||
ds.types.Register(we)
|
||||
tid++
|
||||
encoded = nil
|
||||
continue
|
||||
case wire.Uint:
|
||||
id = objectID(we)
|
||||
i++
|
||||
// Unmarshal and resolve the actual object.
|
||||
encoded = wire.Load(ds.r)
|
||||
ods = ds.lookup(id)
|
||||
if ods != nil {
|
||||
// Decode the object.
|
||||
ds.decodeObject(ods, ods.obj, encoded)
|
||||
} else {
|
||||
// If an object hasn't had interest registered
|
||||
// previously or isn't yet valid, we deferred
|
||||
// decoding until interest is registered.
|
||||
ds.deferred[id] = encoded
|
||||
}
|
||||
// For error handling.
|
||||
ods = nil
|
||||
encoded = nil
|
||||
default:
|
||||
Failf("wanted type or object ID, got %#v", encoded)
|
||||
}
|
||||
|
||||
// Actually resolve the object.
|
||||
ods = ds.lookup(id)
|
||||
if ods != nil {
|
||||
// Decode the object.
|
||||
ds.decodeObject(ods, ods.obj, encoded)
|
||||
} else {
|
||||
// If an object hasn't had interest registered
|
||||
// previously or isn't yet valid, we deferred
|
||||
// decoding until interest is registered.
|
||||
ds.deferred[id] = encoded
|
||||
}
|
||||
|
||||
// For error handling.
|
||||
ods = nil
|
||||
encoded = nil
|
||||
id++
|
||||
}
|
||||
}); err != nil {
|
||||
// Include as much information as we can, taking into account
|
||||
@@ -647,16 +648,25 @@ func (ds *decodeState) Load(obj reflect.Value) {
|
||||
if ods != nil {
|
||||
Failf("error decoding object ID %d (%T) from %#v: %w", id, ods.obj.Interface(), encoded, err)
|
||||
} else if encoded != nil {
|
||||
Failf("lookup error decoding object ID %d from %#v: %w", id, encoded, err)
|
||||
Failf("error decoding from %#v: %w", encoded, err)
|
||||
} else {
|
||||
Failf("general decoding error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have any deferred objects.
|
||||
numDeferred := 0
|
||||
for id, encoded := range ds.deferred {
|
||||
// Shoud never happen, the graph was bogus.
|
||||
Failf("still have deferred objects: one is ID %d, %#v", id, encoded)
|
||||
numDeferred++
|
||||
if s, ok := encoded.(*wire.Struct); ok && s.TypeID != 0 {
|
||||
typ := ds.types.LookupType(typeID(s.TypeID))
|
||||
log.Warningf("unused deferred object: ID %d, type %v", id, typ)
|
||||
} else {
|
||||
log.Warningf("unused deferred object: ID %d, %#v", id, encoded)
|
||||
}
|
||||
}
|
||||
if numDeferred != 0 {
|
||||
Failf("still had %d deferred objects", numDeferred)
|
||||
}
|
||||
|
||||
// Scan and fire all callbacks. We iterate over the list of incomplete
|
||||
|
||||
@@ -15,13 +15,62 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// unsafePointerTo is logically equivalent to reflect.Value.Addr, but works on
|
||||
// values representing unexported fields. This bypasses visibility, but not
|
||||
// type safety.
|
||||
func unsafePointerTo(obj reflect.Value) reflect.Value {
|
||||
// reflectValueRWAddr is equivalent to obj.Addr(), except that the returned
|
||||
// reflect.Value is usable in assignments even if obj was obtained by the use
|
||||
// of unexported struct fields.
|
||||
//
|
||||
// Preconditions: obj.CanAddr().
|
||||
func reflectValueRWAddr(obj reflect.Value) reflect.Value {
|
||||
return reflect.NewAt(obj.Type(), unsafe.Pointer(obj.UnsafeAddr()))
|
||||
}
|
||||
|
||||
// reflectValueRWSlice3 is equivalent to arr.Slice3(i, j, k), except that the
|
||||
// returned reflect.Value is usable in assignments even if obj was obtained by
|
||||
// the use of unexported struct fields.
|
||||
//
|
||||
// Preconditions:
|
||||
// * arr.Kind() == reflect.Array.
|
||||
// * i, j, k >= 0.
|
||||
// * i <= j <= k <= arr.Len().
|
||||
func reflectValueRWSlice3(arr reflect.Value, i, j, k int) reflect.Value {
|
||||
if arr.Kind() != reflect.Array {
|
||||
panic(fmt.Sprintf("arr has kind %v, wanted %v", arr.Kind(), reflect.Array))
|
||||
}
|
||||
if i < 0 || j < 0 || k < 0 {
|
||||
panic(fmt.Sprintf("negative subscripts (%d, %d, %d)", i, j, k))
|
||||
}
|
||||
if i > j {
|
||||
panic(fmt.Sprintf("subscript i (%d) > j (%d)", i, j))
|
||||
}
|
||||
if j > k {
|
||||
panic(fmt.Sprintf("subscript j (%d) > k (%d)", j, k))
|
||||
}
|
||||
if k > arr.Len() {
|
||||
panic(fmt.Sprintf("subscript k (%d) > array length (%d)", k, arr.Len()))
|
||||
}
|
||||
|
||||
sliceTyp := reflect.SliceOf(arr.Type().Elem())
|
||||
if i == arr.Len() {
|
||||
// By precondition, i == j == k == arr.Len().
|
||||
return reflect.MakeSlice(sliceTyp, 0, 0)
|
||||
}
|
||||
slh := reflect.SliceHeader{
|
||||
// reflect.Value.CanAddr() == false for arrays, so we need to get the
|
||||
// address from the first element of the array.
|
||||
Data: arr.Index(i).UnsafeAddr(),
|
||||
Len: j - i,
|
||||
Cap: k - i,
|
||||
}
|
||||
slobj := reflect.NewAt(sliceTyp, unsafe.Pointer(&slh)).Elem()
|
||||
// Before slobj is constructed, arr holds the only pointer-typed pointer to
|
||||
// the array since reflect.SliceHeader.Data is a uintptr, so arr must be
|
||||
// kept alive.
|
||||
runtime.KeepAlive(arr)
|
||||
return slobj
|
||||
}
|
||||
|
||||
+130
-93
@@ -17,13 +17,14 @@ package state
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
)
|
||||
|
||||
// objectEncodeState the type and identity of an object occupying a memory
|
||||
// address range. This is the value type for addrSet, and the intrusive entry
|
||||
// for the pending and deferred lists.
|
||||
// for the deferred list.
|
||||
type objectEncodeState struct {
|
||||
// id is the assigned ID for this object.
|
||||
id objectID
|
||||
@@ -47,7 +48,6 @@ type objectEncodeState struct {
|
||||
// references may be updated directly and automatically.
|
||||
refs []*wire.Ref
|
||||
|
||||
pendingEntry
|
||||
deferredEntry
|
||||
}
|
||||
|
||||
@@ -93,9 +93,15 @@ type encodeState struct {
|
||||
// serialized.
|
||||
pendingTypes []wire.Type
|
||||
|
||||
// pending is the list of objects to be serialized. Serialization does
|
||||
// pending maps object IDs to objects to be serialized. Serialization does
|
||||
// not actually occur until the full object graph is computed.
|
||||
pending pendingList
|
||||
pending map[objectID]*objectEncodeState
|
||||
|
||||
// encodedStructs maps reflect.Values representing structs to previous
|
||||
// encodings of those structs. This is necessary to avoid duplicate calls
|
||||
// to SaverLoader.StateSave() that may result in multiple calls to
|
||||
// Sink.SaveValue() for a given field, resulting in object duplication.
|
||||
encodedStructs map[reflect.Value]*wire.Struct
|
||||
|
||||
// stats tracks time data.
|
||||
stats Stats
|
||||
@@ -189,7 +195,8 @@ func (es *encodeState) resolve(obj reflect.Value, ref *wire.Ref) {
|
||||
// depending on this value knows there's nothing there.
|
||||
return
|
||||
}
|
||||
if seg, _ := es.values.Find(addr); seg.Ok() {
|
||||
seg, gap := es.values.Find(addr)
|
||||
if seg.Ok() {
|
||||
// Ensure the map types match.
|
||||
existing := seg.Value()
|
||||
if existing.obj.Type() != obj.Type() {
|
||||
@@ -203,13 +210,20 @@ func (es *encodeState) resolve(obj reflect.Value, ref *wire.Ref) {
|
||||
}
|
||||
|
||||
// Record the map.
|
||||
r := addrRange{addr, addr + 1}
|
||||
oes := &objectEncodeState{
|
||||
id: es.nextID(),
|
||||
obj: obj,
|
||||
how: encodeMapAsValue,
|
||||
}
|
||||
es.values.Add(addrRange{addr, addr + 1}, oes)
|
||||
es.pending.PushBack(oes)
|
||||
// Use Insert instead of InsertWithoutMergingUnchecked when race
|
||||
// detection is enabled to get additional sanity-checking from Merge.
|
||||
if !raceEnabled {
|
||||
es.values.InsertWithoutMergingUnchecked(gap, r, oes)
|
||||
} else {
|
||||
es.values.Insert(gap, r, oes)
|
||||
}
|
||||
es.pending[oes.id] = oes
|
||||
es.deferred.PushBack(oes)
|
||||
|
||||
// See above: no ref recording.
|
||||
@@ -245,7 +259,7 @@ func (es *encodeState) resolve(obj reflect.Value, ref *wire.Ref) {
|
||||
obj: obj,
|
||||
}
|
||||
es.zeroValues[typ] = oes
|
||||
es.pending.PushBack(oes)
|
||||
es.pending[oes.id] = oes
|
||||
es.deferred.PushBack(oes)
|
||||
}
|
||||
|
||||
@@ -258,86 +272,112 @@ func (es *encodeState) resolve(obj reflect.Value, ref *wire.Ref) {
|
||||
size = 1 // See above.
|
||||
}
|
||||
|
||||
// Calculate the container.
|
||||
end := addr + size
|
||||
r := addrRange{addr, end}
|
||||
if seg, _ := es.values.Find(addr); seg.Ok() {
|
||||
seg := es.values.LowerBoundSegment(addr)
|
||||
var (
|
||||
oes *objectEncodeState
|
||||
gap addrGapIterator
|
||||
)
|
||||
|
||||
// Does at least one previously-registered object overlap this one?
|
||||
if seg.Ok() && seg.Start() < end {
|
||||
existing := seg.Value()
|
||||
switch {
|
||||
case seg.Start() == addr && seg.End() == end && obj.Type() == existing.obj.Type():
|
||||
// The object is a perfect match. Happy path. Avoid the
|
||||
// traversal and just return directly. We don't need to
|
||||
// encode the type information or any dots here.
|
||||
|
||||
if seg.Range() == r && typ == existing.obj.Type() {
|
||||
// This exact object is already registered. Avoid the traversal and
|
||||
// just return directly. We don't need to encode the type
|
||||
// information or any dots here.
|
||||
ref.Root = wire.Uint(existing.id)
|
||||
existing.refs = append(existing.refs, ref)
|
||||
return
|
||||
}
|
||||
|
||||
case (seg.Start() < addr && seg.End() >= end) || (seg.Start() <= addr && seg.End() > end):
|
||||
// The previously registered object is larger than
|
||||
// this, no need to update. But we expect some
|
||||
// traversal below.
|
||||
if seg.Range().IsSupersetOf(r) && (seg.Range() != r || isSameSizeParent(existing.obj, typ)) {
|
||||
// This object is contained within a previously-registered object.
|
||||
// Perform traversal from the container to the new object.
|
||||
ref.Root = wire.Uint(existing.id)
|
||||
ref.Dots = traverse(existing.obj.Type(), typ, seg.Start(), addr)
|
||||
ref.Type = es.findType(existing.obj.Type())
|
||||
existing.refs = append(existing.refs, ref)
|
||||
return
|
||||
}
|
||||
|
||||
case seg.Start() == addr && seg.End() == end:
|
||||
if !isSameSizeParent(obj, existing.obj.Type()) {
|
||||
break // Needs traversal.
|
||||
// This object contains one or more previously-registered objects.
|
||||
// Remove them and update existing references to use the new one.
|
||||
oes := &objectEncodeState{
|
||||
// Reuse the root ID of the first contained element.
|
||||
id: existing.id,
|
||||
obj: obj,
|
||||
}
|
||||
type elementEncodeState struct {
|
||||
addr uintptr
|
||||
typ reflect.Type
|
||||
refs []*wire.Ref
|
||||
}
|
||||
var (
|
||||
elems []elementEncodeState
|
||||
gap addrGapIterator
|
||||
)
|
||||
for {
|
||||
// Each contained object should be completely contained within
|
||||
// this one.
|
||||
if raceEnabled && !r.IsSupersetOf(seg.Range()) {
|
||||
Failf("containing object %#v does not contain existing object %#v", obj, existing.obj)
|
||||
}
|
||||
fallthrough // Needs update.
|
||||
|
||||
case (seg.Start() > addr && seg.End() <= end) || (seg.Start() >= addr && seg.End() < end):
|
||||
// Update the object and redo the encoding.
|
||||
old := existing.obj
|
||||
existing.obj = obj
|
||||
elems = append(elems, elementEncodeState{
|
||||
addr: seg.Start(),
|
||||
typ: existing.obj.Type(),
|
||||
refs: existing.refs,
|
||||
})
|
||||
delete(es.pending, existing.id)
|
||||
es.deferred.Remove(existing)
|
||||
es.deferred.PushBack(existing)
|
||||
|
||||
// The previously registered object is superseded by
|
||||
// this new object. We are guaranteed to not have any
|
||||
// mergeable neighbours in this segment set.
|
||||
if !raceEnabled {
|
||||
seg.SetRangeUnchecked(r)
|
||||
} else {
|
||||
// Add extra paranoid. This will be statically
|
||||
// removed at compile time unless a race build.
|
||||
es.values.Remove(seg)
|
||||
es.values.Add(r, existing)
|
||||
seg = es.values.LowerBoundSegment(addr)
|
||||
gap = es.values.Remove(seg)
|
||||
seg = gap.NextSegment()
|
||||
if !seg.Ok() || seg.Start() >= end {
|
||||
break
|
||||
}
|
||||
|
||||
// Compute the traversal required & update references.
|
||||
dots := traverse(obj.Type(), old.Type(), addr, seg.Start())
|
||||
wt := es.findType(obj.Type())
|
||||
for _, ref := range existing.refs {
|
||||
existing = seg.Value()
|
||||
}
|
||||
wt := es.findType(typ)
|
||||
for _, elem := range elems {
|
||||
dots := traverse(typ, elem.typ, addr, elem.addr)
|
||||
for _, ref := range elem.refs {
|
||||
ref.Root = wire.Uint(oes.id)
|
||||
ref.Dots = append(ref.Dots, dots...)
|
||||
ref.Type = wt
|
||||
}
|
||||
default:
|
||||
// There is a non-sensical overlap.
|
||||
Failf("overlapping objects: [new object] %#v [existing object] %#v", obj, existing.obj)
|
||||
oes.refs = append(oes.refs, elem.refs...)
|
||||
}
|
||||
|
||||
// Compute the new reference, record and return it.
|
||||
ref.Root = wire.Uint(existing.id)
|
||||
ref.Dots = traverse(existing.obj.Type(), obj.Type(), seg.Start(), addr)
|
||||
ref.Type = es.findType(obj.Type())
|
||||
existing.refs = append(existing.refs, ref)
|
||||
// Finally register the new containing object.
|
||||
if !raceEnabled {
|
||||
es.values.InsertWithoutMergingUnchecked(gap, r, oes)
|
||||
} else {
|
||||
es.values.Insert(gap, r, oes)
|
||||
}
|
||||
es.pending[oes.id] = oes
|
||||
es.deferred.PushBack(oes)
|
||||
ref.Root = wire.Uint(oes.id)
|
||||
oes.refs = append(oes.refs, ref)
|
||||
return
|
||||
}
|
||||
|
||||
// The only remaining case is a pointer value that doesn't overlap with
|
||||
// any registered addresses. Create a new entry for it, and start
|
||||
// tracking the first reference we just created.
|
||||
oes := &objectEncodeState{
|
||||
// No existing object overlaps this one. Register a new object.
|
||||
oes = &objectEncodeState{
|
||||
id: es.nextID(),
|
||||
obj: obj,
|
||||
}
|
||||
if !raceEnabled {
|
||||
es.values.AddWithoutMerging(r, oes)
|
||||
if seg.Ok() {
|
||||
gap = seg.PrevGap()
|
||||
} else {
|
||||
// Merges should never happen. This is just enabled extra
|
||||
// sanity checks because the Merge function below will panic.
|
||||
es.values.Add(r, oes)
|
||||
gap = es.values.LastGap()
|
||||
}
|
||||
es.pending.PushBack(oes)
|
||||
if !raceEnabled {
|
||||
es.values.InsertWithoutMergingUnchecked(gap, r, oes)
|
||||
} else {
|
||||
es.values.Insert(gap, r, oes)
|
||||
}
|
||||
es.pending[oes.id] = oes
|
||||
es.deferred.PushBack(oes)
|
||||
ref.Root = wire.Uint(oes.id)
|
||||
oes.refs = append(oes.refs, ref)
|
||||
@@ -439,6 +479,14 @@ func (oe *objectEncoder) save(slot int, obj reflect.Value) {
|
||||
|
||||
// encodeStruct encodes a composite object.
|
||||
func (es *encodeState) encodeStruct(obj reflect.Value, dest *wire.Object) {
|
||||
if s, ok := es.encodedStructs[obj]; ok {
|
||||
*dest = s
|
||||
return
|
||||
}
|
||||
s := &wire.Struct{}
|
||||
*dest = s
|
||||
es.encodedStructs[obj] = s
|
||||
|
||||
// Ensure that the obj is addressable. There are two cases when it is
|
||||
// not. First, is when this is dispatched via SaveValue. Second, when
|
||||
// this is a map key as a struct. Either way, we need to make a copy to
|
||||
@@ -449,10 +497,6 @@ func (es *encodeState) encodeStruct(obj reflect.Value, dest *wire.Object) {
|
||||
obj = localObj.Elem()
|
||||
}
|
||||
|
||||
// Prepare the value.
|
||||
s := &wire.Struct{}
|
||||
*dest = s
|
||||
|
||||
// Look the type up in the database.
|
||||
te, ok := es.types.Lookup(obj.Type())
|
||||
if te == nil {
|
||||
@@ -730,45 +774,43 @@ func (es *encodeState) Save(obj reflect.Value) {
|
||||
Failf("encoding error at object %#v: %w", oes.obj.Interface(), err)
|
||||
}
|
||||
|
||||
// Check that items are pending.
|
||||
if es.pending.Front() == nil {
|
||||
// Check that we have objects to serialize.
|
||||
if len(es.pending) == 0 {
|
||||
Failf("pending is empty?")
|
||||
}
|
||||
|
||||
// Write the header with the number of objects. Note that there is no
|
||||
// way that es.lastID could conflict with objectID, which would
|
||||
// indicate that an impossibly large encoding.
|
||||
if err := WriteHeader(es.w, uint64(es.lastID), true); err != nil {
|
||||
// Write the header with the number of objects.
|
||||
if err := WriteHeader(es.w, uint64(len(es.pending)), true); err != nil {
|
||||
Failf("error writing header: %w", err)
|
||||
}
|
||||
|
||||
// Serialize all pending types and pending objects. Note that we don't
|
||||
// bother removing from this list as we walk it because that just
|
||||
// wastes time. It will not change after this point.
|
||||
var id objectID
|
||||
if err := safely(func() {
|
||||
for _, wt := range es.pendingTypes {
|
||||
// Encode the type.
|
||||
wire.Save(es.w, &wt)
|
||||
}
|
||||
for oes = es.pending.Front(); oes != nil; oes = oes.pendingEntry.Next() {
|
||||
id++ // First object is 1.
|
||||
if oes.id != id {
|
||||
Failf("expected id %d, got %d", id, oes.id)
|
||||
}
|
||||
|
||||
// Marshall the object.
|
||||
// Emit objects in ID order.
|
||||
ids := make([]objectID, 0, len(es.pending))
|
||||
for id := range es.pending {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool {
|
||||
return ids[i] < ids[j]
|
||||
})
|
||||
for _, id := range ids {
|
||||
// Encode the id.
|
||||
wire.Save(es.w, wire.Uint(id))
|
||||
// Marshal the object.
|
||||
oes := es.pending[id]
|
||||
wire.Save(es.w, oes.encoded)
|
||||
}
|
||||
}); err != nil {
|
||||
// Include the object and the error.
|
||||
Failf("error serializing object %#v: %w", oes.encoded, err)
|
||||
}
|
||||
|
||||
// Check what we wrote.
|
||||
if id != es.lastID {
|
||||
Failf("expected %d objects, wrote %d", es.lastID, id)
|
||||
}
|
||||
}
|
||||
|
||||
// objectFlag indicates that the length is a # of objects, rather than a raw
|
||||
@@ -797,11 +839,6 @@ func WriteHeader(w wire.Writer, length uint64, object bool) error {
|
||||
})
|
||||
}
|
||||
|
||||
// pendingMapper is for the pending list.
|
||||
type pendingMapper struct{}
|
||||
|
||||
func (pendingMapper) linkerFor(oes *objectEncodeState) *pendingEntry { return &oes.pendingEntry }
|
||||
|
||||
// deferredMapper is for the deferred list.
|
||||
type deferredMapper struct{}
|
||||
|
||||
|
||||
+23
-18
@@ -42,6 +42,7 @@ func (p *printer) formatRef(x *wire.Ref, graph uint64) string {
|
||||
buf.WriteString(typ)
|
||||
buf.WriteString(")(")
|
||||
buf.WriteString(baseRef)
|
||||
buf.WriteString(")")
|
||||
for _, component := range x.Dots {
|
||||
switch v := component.(type) {
|
||||
case *wire.FieldName:
|
||||
@@ -53,7 +54,6 @@ func (p *printer) formatRef(x *wire.Ref, graph uint64) string {
|
||||
panic(fmt.Sprintf("unreachable: switch should be exhaustive, unhandled case %v", reflect.TypeOf(component)))
|
||||
}
|
||||
}
|
||||
buf.WriteString(")")
|
||||
fullRef = buf.String()
|
||||
}
|
||||
if p.html {
|
||||
@@ -242,19 +242,22 @@ func (p *printer) printStream(w io.Writer, r wire.Reader) (err error) {
|
||||
// Note that this loop must match the general structure of the
|
||||
// loop in decode.go. But we don't register type information,
|
||||
// etc. and just print the raw structures.
|
||||
type objectAndID struct {
|
||||
id uint64
|
||||
obj wire.Object
|
||||
}
|
||||
var (
|
||||
tid uint64 = 1
|
||||
objects []wire.Object
|
||||
objects []objectAndID
|
||||
)
|
||||
for oid := uint64(1); oid <= length; {
|
||||
// Unmarshal the object.
|
||||
for i := uint64(0); i < length; {
|
||||
// Unmarshal either a type object or object ID.
|
||||
encoded := wire.Load(r)
|
||||
|
||||
// Is this a type?
|
||||
if typ, ok := encoded.(*wire.Type); ok {
|
||||
switch we := encoded.(type) {
|
||||
case *wire.Type:
|
||||
str, _ := p.format(graph, 0, encoded)
|
||||
tag := fmt.Sprintf("g%dt%d", graph, tid)
|
||||
p.typeSpecs[tag] = typ
|
||||
p.typeSpecs[tag] = we
|
||||
if p.html {
|
||||
// See below.
|
||||
tag = fmt.Sprintf("<a name=\"%s\">%s</a><a href=\"#%s\">⚓</a>", tag, tag, tag)
|
||||
@@ -263,20 +266,22 @@ func (p *printer) printStream(w io.Writer, r wire.Reader) (err error) {
|
||||
return err
|
||||
}
|
||||
tid++
|
||||
continue
|
||||
case wire.Uint:
|
||||
// Unmarshal the actual object.
|
||||
objects = append(objects, objectAndID{
|
||||
id: uint64(we),
|
||||
obj: wire.Load(r),
|
||||
})
|
||||
i++
|
||||
default:
|
||||
return fmt.Errorf("wanted type or object ID, got %#v", encoded)
|
||||
}
|
||||
|
||||
// Otherwise, it is a node.
|
||||
objects = append(objects, encoded)
|
||||
oid++
|
||||
}
|
||||
|
||||
for i, encoded := range objects {
|
||||
// oid starts at 1.
|
||||
oid := i + 1
|
||||
for _, objAndID := range objects {
|
||||
// Format the node.
|
||||
str, _ := p.format(graph, 0, encoded)
|
||||
tag := fmt.Sprintf("g%dr%d", graph, oid)
|
||||
str, _ := p.format(graph, 0, objAndID.obj)
|
||||
tag := fmt.Sprintf("g%dr%d", graph, objAndID.id)
|
||||
if p.html {
|
||||
// Create a little tag with an anchor next to it for linking.
|
||||
tag = fmt.Sprintf("<a name=\"%s\">%s</a><a href=\"#%s\">⚓</a>", tag, tag, tag)
|
||||
|
||||
+6
-4
@@ -90,10 +90,12 @@ func (e *ErrState) Unwrap() error {
|
||||
func Save(ctx context.Context, w wire.Writer, rootPtr interface{}) (Stats, error) {
|
||||
// Create the encoding state.
|
||||
es := encodeState{
|
||||
ctx: ctx,
|
||||
w: w,
|
||||
types: makeTypeEncodeDatabase(),
|
||||
zeroValues: make(map[reflect.Type]*objectEncodeState),
|
||||
ctx: ctx,
|
||||
w: w,
|
||||
types: makeTypeEncodeDatabase(),
|
||||
zeroValues: make(map[reflect.Type]*objectEncodeState),
|
||||
pending: make(map[objectID]*objectEncodeState),
|
||||
encodedStructs: make(map[reflect.Value]*wire.Struct),
|
||||
}
|
||||
|
||||
// Perform the encoding.
|
||||
|
||||
@@ -53,13 +53,48 @@ type outerArray struct {
|
||||
inner [2]inner
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type outerSlice struct {
|
||||
inner []inner
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type inner struct {
|
||||
v int64
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type outerFieldValue struct {
|
||||
inner innerFieldValue
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type innerFieldValue struct {
|
||||
v int64 `state:".(*savedFieldValue)"`
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type savedFieldValue struct {
|
||||
v int64
|
||||
}
|
||||
|
||||
func (ifv *innerFieldValue) saveV() *savedFieldValue {
|
||||
return &savedFieldValue{ifv.v}
|
||||
}
|
||||
|
||||
func (ifv *innerFieldValue) loadV(sfv *savedFieldValue) {
|
||||
ifv.v = sfv.v
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type system struct {
|
||||
v1 interface{}
|
||||
v2 interface{}
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type system3 struct {
|
||||
v1 interface{}
|
||||
v2 interface{}
|
||||
v3 interface{}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/state"
|
||||
@@ -67,12 +68,23 @@ func TestRegisterTypeOnlyStruct(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmbeddedPointers(t *testing.T) {
|
||||
var (
|
||||
ofs outerSame
|
||||
of1 outerFieldFirst
|
||||
of2 outerFieldSecond
|
||||
oa outerArray
|
||||
)
|
||||
// Give each int64 a random value to prevent Go from using
|
||||
// runtime.staticuint64s, which confounds tests for struct duplication.
|
||||
magic := func() int64 {
|
||||
for {
|
||||
n := rand.Int63()
|
||||
if n < 0 || n > 255 {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ofs := outerSame{inner{magic()}}
|
||||
of1 := outerFieldFirst{inner{magic()}, magic()}
|
||||
of2 := outerFieldSecond{magic(), inner{magic()}}
|
||||
oa := outerArray{[2]inner{{magic()}, {magic()}}}
|
||||
osl := outerSlice{oa.inner[:]}
|
||||
ofv := outerFieldValue{innerFieldValue{magic()}}
|
||||
|
||||
runTestCases(t, false, "embedded-pointers", []interface{}{
|
||||
system{&ofs, &ofs.inner},
|
||||
@@ -85,5 +97,15 @@ func TestEmbeddedPointers(t *testing.T) {
|
||||
system{&oa, &oa.inner[1]},
|
||||
system{&oa.inner[0], &oa},
|
||||
system{&oa.inner[1], &oa},
|
||||
system3{&oa, &oa.inner[0], &oa.inner[1]},
|
||||
system3{&oa, &oa.inner[1], &oa.inner[0]},
|
||||
system3{&oa.inner[0], &oa, &oa.inner[1]},
|
||||
system3{&oa.inner[1], &oa, &oa.inner[0]},
|
||||
system3{&oa.inner[0], &oa.inner[1], &oa},
|
||||
system3{&oa.inner[1], &oa.inner[0], &oa},
|
||||
system{&oa, &osl},
|
||||
system{&osl, &oa},
|
||||
system{&ofv, &ofv.inner},
|
||||
system{&ofv.inner, &ofv},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ var analyzerConfig = map[*analysis.Analyzer]matcher{
|
||||
"pkg/sentry/platform/systrap/stub_unsafe.go", // Special case.
|
||||
"pkg/sentry/platform/systrap/switchto_google_unsafe.go", // Special case.
|
||||
"pkg/sentry/platform/systrap/sysmsg_thread_unsafe.go", // Special case.
|
||||
"pkg/state/decode_unsafe.go", // Special case.
|
||||
),
|
||||
),
|
||||
unusedresult.Analyzer: alwaysMatches(),
|
||||
|
||||
Reference in New Issue
Block a user