mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support for saving pointers to fields in the state package.
Previously, it was not possible to encode/decode an object graph which contained a pointer to a field within another type. This was because the encoder was previously unable to disambiguate a pointer to an object and a pointer within the object. This CL remedies this by constructing an address map tracking the full memory range object occupy. The encoded Refvalue message has been extended to allow references to children objects within another object. Because the encoding process may learn about object structure over time, we cannot encode any objects under the entire graph has been generated. This CL also updates the state package to use standard interfaces intead of reflection-based dispatch in order to improve performance overall. This includes a custom wire protocol to significantly reduce the number of allocations and take advantage of structure packing. As part of these changes, there are a small number of minor changes in other places of the code base: * The lists used during encoding are changed to use intrusive lists with the objectEncodeState directly, which required that the ilist Len() method is updated to work properly with the ElementMapper mechanism. * A bug is fixed in the list code wherein Remove() called on an element that is already removed can corrupt the list (removing the element if there's only a single element). Now the behavior is correct. * Standard error wrapping is introduced. * Compressio was updated to implement the new wire.Reader and wire.Writer inteface methods directly. The lack of a ReadByte and WriteByte caused issues not due to interface dispatch, but because underlying slices for a Read or Write call through an interface would always escape to the heap! * Statify has been updated to support the new APIs. See README.md for a description of how the new mechanism works. PiperOrigin-RevId: 318010298
This commit is contained in:
committed by
gVisor bot
parent
399c52888d
commit
364ac92baf
@@ -346,20 +346,22 @@ func (p *pool) schedule(c *chunk, callback func(*chunk) error) error {
|
||||
}
|
||||
}
|
||||
|
||||
// reader chunks reads and decompresses.
|
||||
type reader struct {
|
||||
// Reader is a compressed reader.
|
||||
type Reader struct {
|
||||
pool
|
||||
|
||||
// in is the source.
|
||||
in io.Reader
|
||||
}
|
||||
|
||||
var _ io.Reader = (*Reader)(nil)
|
||||
|
||||
// NewReader returns a new compressed reader. If key is non-nil, the data stream
|
||||
// is assumed to contain expected hash values, which will be compared against
|
||||
// hash values computed from the compressed bytes. See package comments for
|
||||
// details.
|
||||
func NewReader(in io.Reader, key []byte) (io.Reader, error) {
|
||||
r := &reader{
|
||||
func NewReader(in io.Reader, key []byte) (*Reader, error) {
|
||||
r := &Reader{
|
||||
in: in,
|
||||
}
|
||||
|
||||
@@ -394,8 +396,19 @@ var errNewBuffer = errors.New("buffer ready")
|
||||
// ErrHashMismatch is returned if the hash does not match.
|
||||
var ErrHashMismatch = errors.New("hash mismatch")
|
||||
|
||||
// ReadByte implements wire.Reader.ReadByte.
|
||||
func (r *Reader) ReadByte() (byte, error) {
|
||||
var p [1]byte
|
||||
n, err := r.Read(p[:])
|
||||
if n != 1 {
|
||||
return p[0], err
|
||||
}
|
||||
// Suppress EOF.
|
||||
return p[0], nil
|
||||
}
|
||||
|
||||
// Read implements io.Reader.Read.
|
||||
func (r *reader) Read(p []byte) (int, error) {
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
@@ -551,8 +564,8 @@ func (r *reader) Read(p []byte) (int, error) {
|
||||
return done, nil
|
||||
}
|
||||
|
||||
// writer chunks and schedules writes.
|
||||
type writer struct {
|
||||
// Writer is a compressed writer.
|
||||
type Writer struct {
|
||||
pool
|
||||
|
||||
// out is the underlying writer.
|
||||
@@ -562,6 +575,8 @@ type writer struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
var _ io.Writer = (*Writer)(nil)
|
||||
|
||||
// NewWriter returns a new compressed writer. If key is non-nil, hash values are
|
||||
// generated and written out for compressed bytes. See package comments for
|
||||
// details.
|
||||
@@ -569,8 +584,8 @@ type writer struct {
|
||||
// The recommended chunkSize is on the order of 1M. Extra memory may be
|
||||
// buffered (in the form of read-ahead, or buffered writes), and is limited to
|
||||
// O(chunkSize * [1+GOMAXPROCS]).
|
||||
func NewWriter(out io.Writer, key []byte, chunkSize uint32, level int) (io.WriteCloser, error) {
|
||||
w := &writer{
|
||||
func NewWriter(out io.Writer, key []byte, chunkSize uint32, level int) (*Writer, error) {
|
||||
w := &Writer{
|
||||
pool: pool{
|
||||
chunkSize: chunkSize,
|
||||
buf: bufPool.Get().(*bytes.Buffer),
|
||||
@@ -597,7 +612,7 @@ func NewWriter(out io.Writer, key []byte, chunkSize uint32, level int) (io.Write
|
||||
}
|
||||
|
||||
// flush writes a single buffer.
|
||||
func (w *writer) flush(c *chunk) error {
|
||||
func (w *Writer) flush(c *chunk) error {
|
||||
// Prefix each chunk with a length; this allows the reader to safely
|
||||
// limit reads while buffering.
|
||||
l := uint32(c.compressed.Len())
|
||||
@@ -624,8 +639,23 @@ func (w *writer) flush(c *chunk) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteByte implements wire.Writer.WriteByte.
|
||||
//
|
||||
// Note that this implementation is necessary on the object itself, as an
|
||||
// interface-based dispatch cannot tell whether the array backing the slice
|
||||
// escapes, therefore the all bytes written will generate an escape.
|
||||
func (w *Writer) WriteByte(b byte) error {
|
||||
var p [1]byte
|
||||
p[0] = b
|
||||
n, err := w.Write(p[:])
|
||||
if n != 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write implements io.Writer.Write.
|
||||
func (w *writer) Write(p []byte) (int, error) {
|
||||
func (w *Writer) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
@@ -710,7 +740,7 @@ func (w *writer) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
// Close implements io.Closer.Close.
|
||||
func (w *writer) Close() error {
|
||||
func (w *Writer) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ go_library(
|
||||
srcs = [
|
||||
"gohacks_unsafe.go",
|
||||
],
|
||||
stateify = False,
|
||||
visibility = ["//:sandbox"],
|
||||
)
|
||||
|
||||
+3
-3
@@ -90,7 +90,7 @@ func (l *List) Back() Element {
|
||||
//
|
||||
// NOTE: This is an O(n) operation.
|
||||
func (l *List) Len() (count int) {
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
for e := l.Front(); e != nil; e = (ElementMapper{}.linkerFor(e)).Next() {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
@@ -182,13 +182,13 @@ func (l *List) Remove(e Element) {
|
||||
|
||||
if prev != nil {
|
||||
ElementMapper{}.linkerFor(prev).SetNext(next)
|
||||
} else {
|
||||
} else if l.head == e {
|
||||
l.head = next
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
ElementMapper{}.linkerFor(next).SetPrev(prev)
|
||||
} else {
|
||||
} else if l.tail == e {
|
||||
l.tail = prev
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ go_library(
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/state",
|
||||
"//pkg/state/statefile",
|
||||
"//pkg/state/wire",
|
||||
"//pkg/sync",
|
||||
"//pkg/syserr",
|
||||
"//pkg/syserror",
|
||||
|
||||
+11
-11
@@ -34,7 +34,6 @@ package kernel
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -73,6 +72,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/uniqueid"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/state"
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
@@ -417,7 +417,7 @@ func (k *Kernel) Init(args InitKernelArgs) error {
|
||||
// SaveTo saves the state of k to w.
|
||||
//
|
||||
// Preconditions: The kernel must be paused throughout the call to SaveTo.
|
||||
func (k *Kernel) SaveTo(w io.Writer) error {
|
||||
func (k *Kernel) SaveTo(w wire.Writer) error {
|
||||
saveStart := time.Now()
|
||||
ctx := k.SupervisorContext()
|
||||
|
||||
@@ -473,18 +473,18 @@ func (k *Kernel) SaveTo(w io.Writer) error {
|
||||
//
|
||||
// N.B. This will also be saved along with the full kernel save below.
|
||||
cpuidStart := time.Now()
|
||||
if err := state.Save(k.SupervisorContext(), w, k.FeatureSet(), nil); err != nil {
|
||||
if _, err := state.Save(k.SupervisorContext(), w, k.FeatureSet()); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("CPUID save took [%s].", time.Since(cpuidStart))
|
||||
|
||||
// Save the kernel state.
|
||||
kernelStart := time.Now()
|
||||
var stats state.Stats
|
||||
if err := state.Save(k.SupervisorContext(), w, k, &stats); err != nil {
|
||||
stats, err := state.Save(k.SupervisorContext(), w, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Kernel save stats: %s", &stats)
|
||||
log.Infof("Kernel save stats: %s", stats.String())
|
||||
log.Infof("Kernel save took [%s].", time.Since(kernelStart))
|
||||
|
||||
// Save the memory file's state.
|
||||
@@ -629,7 +629,7 @@ func (ts *TaskSet) unregisterEpollWaiters() {
|
||||
}
|
||||
|
||||
// LoadFrom returns a new Kernel loaded from args.
|
||||
func (k *Kernel) LoadFrom(r io.Reader, net inet.Stack, clocks sentrytime.Clocks) error {
|
||||
func (k *Kernel) LoadFrom(r wire.Reader, net inet.Stack, clocks sentrytime.Clocks) error {
|
||||
loadStart := time.Now()
|
||||
|
||||
initAppCores := k.applicationCores
|
||||
@@ -640,7 +640,7 @@ func (k *Kernel) LoadFrom(r io.Reader, net inet.Stack, clocks sentrytime.Clocks)
|
||||
// don't need to explicitly install it in the Kernel.
|
||||
cpuidStart := time.Now()
|
||||
var features cpuid.FeatureSet
|
||||
if err := state.Load(k.SupervisorContext(), r, &features, nil); err != nil {
|
||||
if _, err := state.Load(k.SupervisorContext(), r, &features); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("CPUID load took [%s].", time.Since(cpuidStart))
|
||||
@@ -655,11 +655,11 @@ func (k *Kernel) LoadFrom(r io.Reader, net inet.Stack, clocks sentrytime.Clocks)
|
||||
|
||||
// Load the kernel state.
|
||||
kernelStart := time.Now()
|
||||
var stats state.Stats
|
||||
if err := state.Load(k.SupervisorContext(), r, k, &stats); err != nil {
|
||||
stats, err := state.Load(k.SupervisorContext(), r, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Kernel load stats: %s", &stats)
|
||||
log.Infof("Kernel load stats: %s", stats.String())
|
||||
log.Infof("Kernel load took [%s].", time.Since(kernelStart))
|
||||
|
||||
// rootNetworkNamespace should be populated after loading the state file.
|
||||
|
||||
@@ -92,6 +92,7 @@ go_library(
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/usage",
|
||||
"//pkg/state",
|
||||
"//pkg/state/wire",
|
||||
"//pkg/sync",
|
||||
"//pkg/syserror",
|
||||
"//pkg/usermem",
|
||||
|
||||
@@ -26,11 +26,12 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/state"
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// SaveTo writes f's state to the given stream.
|
||||
func (f *MemoryFile) SaveTo(ctx context.Context, w io.Writer) error {
|
||||
func (f *MemoryFile) SaveTo(ctx context.Context, w wire.Writer) error {
|
||||
// Wait for reclaim.
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
@@ -79,10 +80,10 @@ func (f *MemoryFile) SaveTo(ctx context.Context, w io.Writer) error {
|
||||
}
|
||||
|
||||
// Save metadata.
|
||||
if err := state.Save(ctx, w, &f.fileSize, nil); err != nil {
|
||||
if _, err := state.Save(ctx, w, &f.fileSize); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := state.Save(ctx, w, &f.usage, nil); err != nil {
|
||||
if _, err := state.Save(ctx, w, &f.usage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,9 +116,9 @@ func (f *MemoryFile) SaveTo(ctx context.Context, w io.Writer) error {
|
||||
}
|
||||
|
||||
// LoadFrom loads MemoryFile state from the given stream.
|
||||
func (f *MemoryFile) LoadFrom(ctx context.Context, r io.Reader) error {
|
||||
func (f *MemoryFile) LoadFrom(ctx context.Context, r wire.Reader) error {
|
||||
// Load metadata.
|
||||
if err := state.Load(ctx, r, &f.fileSize, nil); err != nil {
|
||||
if _, err := state.Load(ctx, r, &f.fileSize); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.file.Truncate(f.fileSize); err != nil {
|
||||
@@ -125,7 +126,7 @@ func (f *MemoryFile) LoadFrom(ctx context.Context, r io.Reader) error {
|
||||
}
|
||||
newMappings := make([]uintptr, f.fileSize>>chunkShift)
|
||||
f.mappings.Store(newMappings)
|
||||
if err := state.Load(ctx, r, &f.usage, nil); err != nil {
|
||||
if _, err := state.Load(ctx, r, &f.usage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+49
-19
@@ -1,8 +1,46 @@
|
||||
load("//tools:defs.bzl", "go_library", "go_test", "proto_library")
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
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",
|
||||
package = "state",
|
||||
prefix = "deferred",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*objectEncodeState",
|
||||
"ElementMapper": "deferredMapper",
|
||||
"Linker": "*deferredEntry",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "complete_list",
|
||||
out = "complete_list.go",
|
||||
package = "state",
|
||||
prefix = "complete",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*objectDecodeState",
|
||||
"Linker": "*objectDecodeState",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "addr_range",
|
||||
out = "addr_range.go",
|
||||
@@ -29,7 +67,7 @@ go_template_instance(
|
||||
types = {
|
||||
"Key": "uintptr",
|
||||
"Range": "addrRange",
|
||||
"Value": "reflect.Value",
|
||||
"Value": "*objectEncodeState",
|
||||
"Functions": "addrSetFunctions",
|
||||
},
|
||||
)
|
||||
@@ -39,32 +77,24 @@ go_library(
|
||||
srcs = [
|
||||
"addr_range.go",
|
||||
"addr_set.go",
|
||||
"complete_list.go",
|
||||
"decode.go",
|
||||
"decode_unsafe.go",
|
||||
"deferred_list.go",
|
||||
"encode.go",
|
||||
"encode_unsafe.go",
|
||||
"map.go",
|
||||
"printer.go",
|
||||
"pending_list.go",
|
||||
"state.go",
|
||||
"state_norace.go",
|
||||
"state_race.go",
|
||||
"stats.go",
|
||||
"types.go",
|
||||
],
|
||||
marshal = False,
|
||||
stateify = False,
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
":object_go_proto",
|
||||
"@com_github_golang_protobuf//proto:go_default_library",
|
||||
"//pkg/log",
|
||||
"//pkg/state/wire",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "object",
|
||||
srcs = ["object.proto"],
|
||||
visibility = ["//:sandbox"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "state_test",
|
||||
timeout = "long",
|
||||
srcs = ["state_test.go"],
|
||||
library = ":state",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
# State Encoding and Decoding
|
||||
|
||||
The state package implements the encoding and decoding of data structures for
|
||||
`go_stateify`. This package is designed for use cases other than the standard
|
||||
encoding packages, e.g. `gob` and `json`. Principally:
|
||||
|
||||
* This package operates on complex object graphs and accurately serializes and
|
||||
restores all relationships. That is, you can have things like: intrusive
|
||||
pointers, cycles, and pointer chains of arbitrary depths. These are not
|
||||
handled appropriately by existing encoders. This is not an implementation
|
||||
flaw: the formats themselves are not capable of representing these graphs,
|
||||
as they can only generate directed trees.
|
||||
|
||||
* This package allows installing order-dependent load callbacks and then
|
||||
resolves that graph at load time, with cycle detection. Similarly, there is
|
||||
no analogous feature possible in the standard encoders.
|
||||
|
||||
* This package handles the resolution of interfaces, based on a registered
|
||||
type name. For interface objects type information is saved in the serialized
|
||||
format. This is generally true for `gob` as well, but it works differently.
|
||||
|
||||
Here's an overview of how encoding and decoding works.
|
||||
|
||||
## Encoding
|
||||
|
||||
Encoding produces a `statefile`, which contains a list of chunks of the form
|
||||
`(header, payload)`. The payload can either be some raw data, or a series of
|
||||
encoded wire objects representing some object graph. All encoded objects are
|
||||
defined in the `wire` subpackage.
|
||||
|
||||
Encoding of an object graph begins with `encodeState.Save`.
|
||||
|
||||
### 1. Memory Map & Encoding
|
||||
|
||||
To discover relationships between potentially interdependent data structures
|
||||
(for example, a struct may contain pointers to members of other data
|
||||
structures), the encoder first walks the object graph and constructs a memory
|
||||
map of the objects in the input graph. As this walk progresses, objects are
|
||||
queued in the `pending` list and items are placed on the `deferred` list as they
|
||||
are discovered. No single object will be encoded multiple times, but the
|
||||
discovered relationships between objects may change as more parts of the overall
|
||||
object graph are discovered.
|
||||
|
||||
The encoder starts at the root object and recursively visits all reachable
|
||||
objects, recording the address ranges containing the underlying data for each
|
||||
object. This is stored as a segment set (`addrSet`), mapping address ranges to
|
||||
the of the object occupying the range; see `encodeState.values`. Note that there
|
||||
is special handling for zero-sized types and map objects during this process.
|
||||
|
||||
Additionally, the encoder assigns each object a unique identifier which is used
|
||||
to indicate relationships between objects in the statefile; see `objectID` in
|
||||
`encode.go`.
|
||||
|
||||
### 2. Type Serialization
|
||||
|
||||
The enoder will subsequently serialize all information about discovered types,
|
||||
including field names. These are used during decoding to reconcile these types
|
||||
with other internally registered types.
|
||||
|
||||
### 3. Object Serialization
|
||||
|
||||
With a full address map, and all objects correctly encoded, all object encodings
|
||||
are serialized. The assigned `objectID`s aren't explicitly encoded in the
|
||||
statefile. The order of object messages in the stream determine their IDs.
|
||||
|
||||
### Example
|
||||
|
||||
Given the following data structure definitions:
|
||||
|
||||
```go
|
||||
type system struct {
|
||||
o *outer
|
||||
i *inner
|
||||
}
|
||||
|
||||
type outer struct {
|
||||
a int64
|
||||
cn *container
|
||||
}
|
||||
|
||||
type container struct {
|
||||
n uint64
|
||||
elem *inner
|
||||
}
|
||||
|
||||
type inner struct {
|
||||
c container
|
||||
x, y uint64
|
||||
}
|
||||
```
|
||||
|
||||
Initialized like this:
|
||||
|
||||
```go
|
||||
o := outer{
|
||||
a: 10,
|
||||
cn: nil,
|
||||
}
|
||||
i := inner{
|
||||
x: 20,
|
||||
y: 30,
|
||||
c: container{},
|
||||
}
|
||||
s := system{
|
||||
o: &o,
|
||||
i: &i,
|
||||
}
|
||||
|
||||
o.cn = &i.c
|
||||
o.cn.elem = &i
|
||||
|
||||
```
|
||||
|
||||
Encoding will produce an object stream like this:
|
||||
|
||||
```
|
||||
g0r1 = struct{
|
||||
i: g0r3,
|
||||
o: g0r2,
|
||||
}
|
||||
g0r2 = struct{
|
||||
a: 10,
|
||||
cn: g0r3.c,
|
||||
}
|
||||
g0r3 = struct{
|
||||
c: struct{
|
||||
elem: g0r3,
|
||||
n: 0u,
|
||||
},
|
||||
x: 20u,
|
||||
y: 30u,
|
||||
}
|
||||
```
|
||||
|
||||
Note how `g0r3.c` is correctly encoded as the underlying `container` object for
|
||||
`inner.c`, and how the pointer from `outer.cn` points to it, despite `system.i`
|
||||
being discovered after the pointer to it in `system.o.cn`. Also note that
|
||||
decoding isn't strictly reliant on the order of encoded object stream, as long
|
||||
as the relationship between objects are correctly encoded.
|
||||
|
||||
## Decoding
|
||||
|
||||
Decoding reads the statefile and reconstructs the object graph. Decoding begins
|
||||
in `decodeState.Load`. Decoding is performed in a single pass over the object
|
||||
stream in the statefile, and a subsequent pass over all deserialized objects is
|
||||
done to fire off all loading callbacks in the correctly defined order. Note that
|
||||
introducing cycles is possible here, but these are detected and an error will be
|
||||
returned.
|
||||
|
||||
Decoding is relatively straight forward. For most primitive values, the decoder
|
||||
constructs an appropriate object and fills it with the values encoded in the
|
||||
statefile. Pointers need special handling, as they must point to a value
|
||||
allocated elsewhere. When values are constructed, the decoder indexes them by
|
||||
their `objectID`s in `decodeState.objectsByID`. The target of pointers are
|
||||
resolved by searching for the target in this index by their `objectID`; see
|
||||
`decodeState.register`. For pointers to values inside another value (fields in a
|
||||
pointer, elements of an array), the decoder uses the accessor path to walk to
|
||||
the appropriate location; see `walkChild`.
|
||||
+517
-401
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"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 {
|
||||
return reflect.NewAt(obj.Type(), unsafe.Pointer(obj.UnsafeAddr()))
|
||||
}
|
||||
+706
-335
File diff suppressed because it is too large
Load Diff
@@ -31,51 +31,3 @@ func arrayFromSlice(obj reflect.Value) reflect.Value {
|
||||
reflect.ArrayOf(obj.Cap(), obj.Type().Elem()),
|
||||
unsafe.Pointer(obj.Pointer()))
|
||||
}
|
||||
|
||||
// pbSlice returns a protobuf-supported slice of the array and erase the
|
||||
// original element type (which could be a defined type or non-supported type).
|
||||
func pbSlice(obj reflect.Value) reflect.Value {
|
||||
var typ reflect.Type
|
||||
switch obj.Type().Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
typ = reflect.TypeOf(byte(0))
|
||||
case reflect.Uint16:
|
||||
typ = reflect.TypeOf(uint16(0))
|
||||
case reflect.Uint32:
|
||||
typ = reflect.TypeOf(uint32(0))
|
||||
case reflect.Uint64:
|
||||
typ = reflect.TypeOf(uint64(0))
|
||||
case reflect.Uintptr:
|
||||
typ = reflect.TypeOf(uint64(0))
|
||||
case reflect.Int8:
|
||||
typ = reflect.TypeOf(byte(0))
|
||||
case reflect.Int16:
|
||||
typ = reflect.TypeOf(int16(0))
|
||||
case reflect.Int32:
|
||||
typ = reflect.TypeOf(int32(0))
|
||||
case reflect.Int64:
|
||||
typ = reflect.TypeOf(int64(0))
|
||||
case reflect.Bool:
|
||||
typ = reflect.TypeOf(bool(false))
|
||||
case reflect.Float32:
|
||||
typ = reflect.TypeOf(float32(0))
|
||||
case reflect.Float64:
|
||||
typ = reflect.TypeOf(float64(0))
|
||||
default:
|
||||
panic("slice element is not of basic value type")
|
||||
}
|
||||
return reflect.NewAt(
|
||||
reflect.ArrayOf(obj.Len(), typ),
|
||||
unsafe.Pointer(obj.Slice(0, obj.Len()).Pointer()),
|
||||
).Elem().Slice(0, obj.Len())
|
||||
}
|
||||
|
||||
func castSlice(obj reflect.Value, elemTyp reflect.Type) reflect.Value {
|
||||
if obj.Type().Elem().Size() != elemTyp.Size() {
|
||||
panic("cannot cast slice into other element type of different size")
|
||||
}
|
||||
return reflect.NewAt(
|
||||
reflect.ArrayOf(obj.Len(), elemTyp),
|
||||
unsafe.Pointer(obj.Slice(0, obj.Len()).Pointer()),
|
||||
).Elem()
|
||||
}
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright 2018 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
pb "gvisor.dev/gvisor/pkg/state/object_go_proto"
|
||||
)
|
||||
|
||||
// entry is a single map entry.
|
||||
type entry struct {
|
||||
name string
|
||||
object *pb.Object
|
||||
}
|
||||
|
||||
// internalMap is the internal Map state.
|
||||
//
|
||||
// These are recycled via a pool to avoid churn.
|
||||
type internalMap struct {
|
||||
// es is encodeState.
|
||||
es *encodeState
|
||||
|
||||
// ds is decodeState.
|
||||
ds *decodeState
|
||||
|
||||
// os is current object being decoded.
|
||||
//
|
||||
// This will always be nil during encode.
|
||||
os *objectState
|
||||
|
||||
// data stores the encoded values.
|
||||
data []entry
|
||||
}
|
||||
|
||||
var internalMapPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(internalMap)
|
||||
},
|
||||
}
|
||||
|
||||
// newInternalMap returns a cached map.
|
||||
func newInternalMap(es *encodeState, ds *decodeState, os *objectState) *internalMap {
|
||||
m := internalMapPool.Get().(*internalMap)
|
||||
m.es = es
|
||||
m.ds = ds
|
||||
m.os = os
|
||||
if m.data != nil {
|
||||
m.data = m.data[:0]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Map is a generic state container.
|
||||
//
|
||||
// This is the object passed to Save and Load in order to store their state.
|
||||
//
|
||||
// Detailed documentation is available in individual methods.
|
||||
type Map struct {
|
||||
*internalMap
|
||||
}
|
||||
|
||||
// Save adds the given object to the map.
|
||||
//
|
||||
// You should pass always pointers to the object you are saving. For example:
|
||||
//
|
||||
// type X struct {
|
||||
// A int
|
||||
// B *int
|
||||
// }
|
||||
//
|
||||
// func (x *X) Save(m Map) {
|
||||
// m.Save("A", &x.A)
|
||||
// m.Save("B", &x.B)
|
||||
// }
|
||||
//
|
||||
// func (x *X) Load(m Map) {
|
||||
// m.Load("A", &x.A)
|
||||
// m.Load("B", &x.B)
|
||||
// }
|
||||
func (m Map) Save(name string, objPtr interface{}) {
|
||||
m.save(name, reflect.ValueOf(objPtr).Elem(), ".%s")
|
||||
}
|
||||
|
||||
// SaveValue adds the given object value to the map.
|
||||
//
|
||||
// This should be used for values where pointers are not available, or casts
|
||||
// are required during Save/Load.
|
||||
//
|
||||
// For example, if we want to cast external package type P.Foo to int64:
|
||||
//
|
||||
// type X struct {
|
||||
// A P.Foo
|
||||
// }
|
||||
//
|
||||
// func (x *X) Save(m Map) {
|
||||
// m.SaveValue("A", int64(x.A))
|
||||
// }
|
||||
//
|
||||
// func (x *X) Load(m Map) {
|
||||
// m.LoadValue("A", new(int64), func(x interface{}) {
|
||||
// x.A = P.Foo(x.(int64))
|
||||
// })
|
||||
// }
|
||||
func (m Map) SaveValue(name string, obj interface{}) {
|
||||
m.save(name, reflect.ValueOf(obj), ".(value %s)")
|
||||
}
|
||||
|
||||
// save is helper for the above. It takes the name of value to save the field
|
||||
// to, the field object (obj), and a format string that specifies how the
|
||||
// field's saving logic is dispatched from the struct (normal, value, etc.). The
|
||||
// format string should expect one string parameter, which is the name of the
|
||||
// field.
|
||||
func (m Map) save(name string, obj reflect.Value, format string) {
|
||||
if m.es == nil {
|
||||
// Not currently encoding.
|
||||
m.Failf("no encode state for %q", name)
|
||||
}
|
||||
|
||||
// Attempt the encode.
|
||||
//
|
||||
// These are sorted at the end, after all objects are added and will be
|
||||
// sorted and checked for duplicates (see encodeStruct).
|
||||
m.data = append(m.data, entry{
|
||||
name: name,
|
||||
object: m.es.encodeObject(obj, false, format, name),
|
||||
})
|
||||
}
|
||||
|
||||
// Load loads the given object from the map.
|
||||
//
|
||||
// See Save for an example.
|
||||
func (m Map) Load(name string, objPtr interface{}) {
|
||||
m.load(name, reflect.ValueOf(objPtr), false, nil, ".%s")
|
||||
}
|
||||
|
||||
// LoadWait loads the given objects from the map, and marks it as requiring all
|
||||
// AfterLoad executions to complete prior to running this object's AfterLoad.
|
||||
//
|
||||
// See Save for an example.
|
||||
func (m Map) LoadWait(name string, objPtr interface{}) {
|
||||
m.load(name, reflect.ValueOf(objPtr), true, nil, ".(wait %s)")
|
||||
}
|
||||
|
||||
// LoadValue loads the given object value from the map.
|
||||
//
|
||||
// See SaveValue for an example.
|
||||
func (m Map) LoadValue(name string, objPtr interface{}, fn func(interface{})) {
|
||||
o := reflect.ValueOf(objPtr)
|
||||
m.load(name, o, true, func() { fn(o.Elem().Interface()) }, ".(value %s)")
|
||||
}
|
||||
|
||||
// load is helper for the above. It takes the name of value to load the field
|
||||
// from, the target field pointer (objPtr), whether load completion of the
|
||||
// struct depends on the field's load completion (wait), the load completion
|
||||
// logic (fn), and a format string that specifies how the field's loading logic
|
||||
// is dispatched from the struct (normal, wait, value, etc.). The format string
|
||||
// should expect one string parameter, which is the name of the field.
|
||||
func (m Map) load(name string, objPtr reflect.Value, wait bool, fn func(), format string) {
|
||||
if m.ds == nil {
|
||||
// Not currently decoding.
|
||||
m.Failf("no decode state for %q", name)
|
||||
}
|
||||
|
||||
// Find the object.
|
||||
//
|
||||
// These are sorted up front (and should appear in the state file
|
||||
// sorted as well), so we can do a binary search here to ensure that
|
||||
// large structs don't behave badly.
|
||||
i := sort.Search(len(m.data), func(i int) bool {
|
||||
return m.data[i].name >= name
|
||||
})
|
||||
if i >= len(m.data) || m.data[i].name != name {
|
||||
// There is no data for this name?
|
||||
m.Failf("no data found for %q", name)
|
||||
}
|
||||
|
||||
// Perform the decode.
|
||||
m.ds.decodeObject(m.os, objPtr.Elem(), m.data[i].object, format, name)
|
||||
if wait {
|
||||
// Mark this individual object a blocker.
|
||||
m.ds.waitObject(m.os, m.data[i].object, fn)
|
||||
}
|
||||
}
|
||||
|
||||
// Failf fails the save or restore with the provided message. Processing will
|
||||
// stop after calling Failf, as the state package uses a panic & recover
|
||||
// mechanism for state errors. You should defer any cleanup required.
|
||||
func (m Map) Failf(format string, args ...interface{}) {
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// AfterLoad schedules a function execution when all objects have been allocated
|
||||
// and their automated loading and customized load logic have been executed. fn
|
||||
// will not be executed until all of current object's dependencies' AfterLoad()
|
||||
// logic, if exist, have been executed.
|
||||
func (m Map) AfterLoad(fn func()) {
|
||||
if m.ds == nil {
|
||||
// Not currently decoding.
|
||||
m.Failf("not decoding")
|
||||
}
|
||||
|
||||
// Queue the local callback; this will execute when all of the above
|
||||
// data dependencies have been cleared.
|
||||
m.os.callbacks = append(m.os.callbacks, fn)
|
||||
}
|
||||
|
||||
// Context returns the current context object.
|
||||
func (m Map) Context() context.Context {
|
||||
if m.es != nil {
|
||||
return m.es.ctx
|
||||
} else if m.ds != nil {
|
||||
return m.ds.ctx
|
||||
}
|
||||
return context.Background() // No context.
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright 2018 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package gvisor.state.statefile;
|
||||
|
||||
// Slice is a slice value.
|
||||
message Slice {
|
||||
uint32 length = 1;
|
||||
uint32 capacity = 2;
|
||||
uint64 ref_value = 3;
|
||||
}
|
||||
|
||||
// Array is an array value.
|
||||
message Array {
|
||||
repeated Object contents = 1;
|
||||
}
|
||||
|
||||
// Map is a map value.
|
||||
message Map {
|
||||
repeated Object keys = 1;
|
||||
repeated Object values = 2;
|
||||
}
|
||||
|
||||
// Interface is an interface value.
|
||||
message Interface {
|
||||
string type = 1;
|
||||
Object value = 2;
|
||||
}
|
||||
|
||||
// Struct is a basic composite value.
|
||||
message Struct {
|
||||
repeated Field fields = 1;
|
||||
}
|
||||
|
||||
// Field encodes a single field.
|
||||
message Field {
|
||||
string name = 1;
|
||||
Object value = 2;
|
||||
}
|
||||
|
||||
// Uint16s encodes an uint16 array. To be used inside oneof structure.
|
||||
message Uint16s {
|
||||
// There is no 16-bit type in protobuf so we use variable length 32-bit here.
|
||||
repeated uint32 values = 1;
|
||||
}
|
||||
|
||||
// Uint32s encodes an uint32 array. To be used inside oneof structure.
|
||||
message Uint32s {
|
||||
repeated fixed32 values = 1;
|
||||
}
|
||||
|
||||
// Uint64s encodes an uint64 array. To be used inside oneof structure.
|
||||
message Uint64s {
|
||||
repeated fixed64 values = 1;
|
||||
}
|
||||
|
||||
// Uintptrs encodes an uintptr array. To be used inside oneof structure.
|
||||
message Uintptrs {
|
||||
repeated fixed64 values = 1;
|
||||
}
|
||||
|
||||
// Int8s encodes an int8 array. To be used inside oneof structure.
|
||||
message Int8s {
|
||||
bytes values = 1;
|
||||
}
|
||||
|
||||
// Int16s encodes an int16 array. To be used inside oneof structure.
|
||||
message Int16s {
|
||||
// There is no 16-bit type in protobuf so we use variable length 32-bit here.
|
||||
repeated int32 values = 1;
|
||||
}
|
||||
|
||||
// Int32s encodes an int32 array. To be used inside oneof structure.
|
||||
message Int32s {
|
||||
repeated sfixed32 values = 1;
|
||||
}
|
||||
|
||||
// Int64s encodes an int64 array. To be used inside oneof structure.
|
||||
message Int64s {
|
||||
repeated sfixed64 values = 1;
|
||||
}
|
||||
|
||||
// Bools encodes a boolean array. To be used inside oneof structure.
|
||||
message Bools {
|
||||
repeated bool values = 1;
|
||||
}
|
||||
|
||||
// Float64s encodes a float64 array. To be used inside oneof structure.
|
||||
message Float64s {
|
||||
repeated double values = 1;
|
||||
}
|
||||
|
||||
// Float32s encodes a float32 array. To be used inside oneof structure.
|
||||
message Float32s {
|
||||
repeated float values = 1;
|
||||
}
|
||||
|
||||
// Object are primitive encodings.
|
||||
//
|
||||
// Note that ref_value references an Object.id, below.
|
||||
message Object {
|
||||
oneof value {
|
||||
bool bool_value = 1;
|
||||
bytes string_value = 2;
|
||||
int64 int64_value = 3;
|
||||
uint64 uint64_value = 4;
|
||||
double double_value = 5;
|
||||
uint64 ref_value = 6;
|
||||
Slice slice_value = 7;
|
||||
Array array_value = 8;
|
||||
Interface interface_value = 9;
|
||||
Struct struct_value = 10;
|
||||
Map map_value = 11;
|
||||
bytes byte_array_value = 12;
|
||||
Uint16s uint16_array_value = 13;
|
||||
Uint32s uint32_array_value = 14;
|
||||
Uint64s uint64_array_value = 15;
|
||||
Uintptrs uintptr_array_value = 16;
|
||||
Int8s int8_array_value = 17;
|
||||
Int16s int16_array_value = 18;
|
||||
Int32s int32_array_value = 19;
|
||||
Int64s int64_array_value = 20;
|
||||
Bools bool_array_value = 21;
|
||||
Float64s float64_array_value = 22;
|
||||
Float32s float32_array_value = 23;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "pretty",
|
||||
srcs = ["pretty.go"],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
"//pkg/state",
|
||||
"//pkg/state/wire",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright 2018 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pretty is a pretty-printer for state streams.
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/state"
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
)
|
||||
|
||||
func formatRef(x *wire.Ref, graph uint64, html bool) string {
|
||||
baseRef := fmt.Sprintf("g%dr%d", graph, x.Root)
|
||||
fullRef := baseRef
|
||||
if len(x.Dots) > 0 {
|
||||
// See wire.Ref; Type valid if Dots non-zero.
|
||||
typ, _ := formatType(x.Type, graph, html)
|
||||
var buf strings.Builder
|
||||
buf.WriteString("(*")
|
||||
buf.WriteString(typ)
|
||||
buf.WriteString(")(")
|
||||
buf.WriteString(baseRef)
|
||||
for _, component := range x.Dots {
|
||||
switch v := component.(type) {
|
||||
case *wire.FieldName:
|
||||
buf.WriteString(".")
|
||||
buf.WriteString(string(*v))
|
||||
case wire.Index:
|
||||
buf.WriteString(fmt.Sprintf("[%d]", v))
|
||||
default:
|
||||
panic(fmt.Sprintf("unreachable: switch should be exhaustive, unhandled case %v", reflect.TypeOf(component)))
|
||||
}
|
||||
}
|
||||
buf.WriteString(")")
|
||||
fullRef = buf.String()
|
||||
}
|
||||
if html {
|
||||
return fmt.Sprintf("<a href=\"#%s\">%s</a>", baseRef, fullRef)
|
||||
}
|
||||
return fullRef
|
||||
}
|
||||
|
||||
func formatType(t wire.TypeSpec, graph uint64, html bool) (string, bool) {
|
||||
switch x := t.(type) {
|
||||
case wire.TypeID:
|
||||
base := fmt.Sprintf("g%dt%d", graph, x)
|
||||
if html {
|
||||
return fmt.Sprintf("<a href=\"#%s\">%s</a>", base, base), true
|
||||
}
|
||||
return fmt.Sprintf("%s", base), true
|
||||
case wire.TypeSpecNil:
|
||||
return "", false // Only nil type.
|
||||
case *wire.TypeSpecPointer:
|
||||
element, _ := formatType(x.Type, graph, html)
|
||||
return fmt.Sprintf("(*%s)", element), true
|
||||
case *wire.TypeSpecArray:
|
||||
element, _ := formatType(x.Type, graph, html)
|
||||
return fmt.Sprintf("[%d](%s)", x.Count, element), true
|
||||
case *wire.TypeSpecSlice:
|
||||
element, _ := formatType(x.Type, graph, html)
|
||||
return fmt.Sprintf("([]%s)", element), true
|
||||
case *wire.TypeSpecMap:
|
||||
key, _ := formatType(x.Key, graph, html)
|
||||
value, _ := formatType(x.Value, graph, html)
|
||||
return fmt.Sprintf("(map[%s]%s)", key, value), true
|
||||
default:
|
||||
panic(fmt.Sprintf("unreachable: unknown type %T", t))
|
||||
}
|
||||
}
|
||||
|
||||
// format formats a single object, for pretty-printing. It also returns whether
|
||||
// the value is a non-zero value.
|
||||
func format(graph uint64, depth int, encoded wire.Object, html bool) (string, bool) {
|
||||
switch x := encoded.(type) {
|
||||
case wire.Nil:
|
||||
return "nil", false
|
||||
case *wire.String:
|
||||
return fmt.Sprintf("%q", *x), *x != ""
|
||||
case *wire.Complex64:
|
||||
return fmt.Sprintf("%f+%fi", real(*x), imag(*x)), *x != 0.0
|
||||
case *wire.Complex128:
|
||||
return fmt.Sprintf("%f+%fi", real(*x), imag(*x)), *x != 0.0
|
||||
case *wire.Ref:
|
||||
return formatRef(x, graph, html), x.Root != 0
|
||||
case *wire.Type:
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
items := make([]string, 0, len(x.Fields)+2)
|
||||
items = append(items, fmt.Sprintf("type %s {", x.Name))
|
||||
for i := 0; i < len(x.Fields); i++ {
|
||||
items = append(items, fmt.Sprintf("\t%d: %s,", i, x.Fields[i]))
|
||||
}
|
||||
items = append(items, "}")
|
||||
return strings.Join(items, tabs), true // No zero value.
|
||||
case *wire.Slice:
|
||||
return fmt.Sprintf("%s{len:%d,cap:%d}", formatRef(&x.Ref, graph, html), x.Length, x.Capacity), x.Capacity != 0
|
||||
case *wire.Array:
|
||||
if len(x.Contents) == 0 {
|
||||
return "[]", false
|
||||
}
|
||||
items := make([]string, 0, len(x.Contents)+2)
|
||||
zeros := make([]string, 0) // used to eliminate zero entries.
|
||||
items = append(items, "[")
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
for i := 0; i < len(x.Contents); i++ {
|
||||
item, ok := format(graph, depth+1, x.Contents[i], html)
|
||||
if !ok {
|
||||
zeros = append(zeros, fmt.Sprintf("\t%s,", item))
|
||||
continue
|
||||
}
|
||||
if len(zeros) > 0 {
|
||||
items = append(items, zeros...)
|
||||
zeros = nil
|
||||
}
|
||||
items = append(items, fmt.Sprintf("\t%s,", item))
|
||||
}
|
||||
if len(zeros) > 0 {
|
||||
items = append(items, fmt.Sprintf("\t... (%d zeros),", len(zeros)))
|
||||
}
|
||||
items = append(items, "]")
|
||||
return strings.Join(items, tabs), len(zeros) < len(x.Contents)
|
||||
case *wire.Struct:
|
||||
typ, _ := formatType(x.TypeID, graph, html)
|
||||
if x.Fields() == 0 {
|
||||
return fmt.Sprintf("struct[%s]{}", typ), false
|
||||
}
|
||||
items := make([]string, 0, 2)
|
||||
items = append(items, fmt.Sprintf("struct[%s]{", typ))
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
allZero := true
|
||||
for i := 0; i < x.Fields(); i++ {
|
||||
element, ok := format(graph, depth+1, *x.Field(i), html)
|
||||
allZero = allZero && !ok
|
||||
items = append(items, fmt.Sprintf("\t%d: %s,", i, element))
|
||||
i++
|
||||
}
|
||||
items = append(items, "}")
|
||||
return strings.Join(items, tabs), !allZero
|
||||
case *wire.Map:
|
||||
if len(x.Keys) == 0 {
|
||||
return "map{}", false
|
||||
}
|
||||
items := make([]string, 0, len(x.Keys)+2)
|
||||
items = append(items, "map{")
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
for i := 0; i < len(x.Keys); i++ {
|
||||
key, _ := format(graph, depth+1, x.Keys[i], html)
|
||||
value, _ := format(graph, depth+1, x.Values[i], html)
|
||||
items = append(items, fmt.Sprintf("\t%s: %s,", key, value))
|
||||
}
|
||||
items = append(items, "}")
|
||||
return strings.Join(items, tabs), true
|
||||
case *wire.Interface:
|
||||
typ, typOk := formatType(x.Type, graph, html)
|
||||
element, elementOk := format(graph, depth+1, x.Value, html)
|
||||
return fmt.Sprintf("interface[%s]{%s}", typ, element), typOk || elementOk
|
||||
default:
|
||||
// Must be a primitive; use reflection.
|
||||
return fmt.Sprintf("%v", encoded), true
|
||||
}
|
||||
}
|
||||
|
||||
// printStream is the basic print implementation.
|
||||
func printStream(w io.Writer, r wire.Reader, html bool) (err error) {
|
||||
// current graph ID.
|
||||
var graph uint64
|
||||
|
||||
if html {
|
||||
fmt.Fprintf(w, "<pre>")
|
||||
defer fmt.Fprintf(w, "</pre>")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if rErr, ok := r.(error); ok {
|
||||
err = rErr // Override return.
|
||||
return
|
||||
}
|
||||
panic(r) // Propagate.
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// Find the first object to begin generation.
|
||||
length, object, err := state.ReadHeader(r)
|
||||
if err == io.EOF {
|
||||
// Nothing else to do.
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if !object {
|
||||
graph++ // Increment the graph.
|
||||
if length > 0 {
|
||||
fmt.Fprintf(w, "(%d bytes non-object data)\n", length)
|
||||
io.Copy(ioutil.Discard, &io.LimitedReader{
|
||||
R: r,
|
||||
N: int64(length),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Read & unmarshal the object.
|
||||
//
|
||||
// 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.
|
||||
var (
|
||||
oid uint64 = 1
|
||||
tid uint64 = 1
|
||||
)
|
||||
for oid <= length {
|
||||
// Unmarshal the object.
|
||||
encoded := wire.Load(r)
|
||||
|
||||
// Is this a type?
|
||||
if _, ok := encoded.(*wire.Type); ok {
|
||||
str, _ := format(graph, 0, encoded, html)
|
||||
tag := fmt.Sprintf("g%dt%d", graph, tid)
|
||||
if html {
|
||||
// See below.
|
||||
tag = fmt.Sprintf("<a name=\"%s\">%s</a><a href=\"#%s\">⚓</a>", tag, tag, tag)
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s = %s\n", tag, str); err != nil {
|
||||
return err
|
||||
}
|
||||
tid++
|
||||
continue
|
||||
}
|
||||
|
||||
// Format the node.
|
||||
str, _ := format(graph, 0, encoded, html)
|
||||
tag := fmt.Sprintf("g%dr%d", graph, oid)
|
||||
if 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)
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s = %s\n", tag, str); err != nil {
|
||||
return err
|
||||
}
|
||||
oid++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintText reads the stream from r and prints text to w.
|
||||
func PrintText(w io.Writer, r wire.Reader) error {
|
||||
return printStream(w, r, false /* html */)
|
||||
}
|
||||
|
||||
// PrintHTML reads the stream from r and prints html to w.
|
||||
func PrintHTML(w io.Writer, r wire.Reader) error {
|
||||
return printStream(w, r, true /* html */)
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
// Copyright 2018 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "gvisor.dev/gvisor/pkg/state/object_go_proto"
|
||||
)
|
||||
|
||||
// format formats a single object, for pretty-printing. It also returns whether
|
||||
// the value is a non-zero value.
|
||||
func format(graph uint64, depth int, object *pb.Object, html bool) (string, bool) {
|
||||
switch x := object.GetValue().(type) {
|
||||
case *pb.Object_BoolValue:
|
||||
return fmt.Sprintf("%t", x.BoolValue), x.BoolValue != false
|
||||
case *pb.Object_StringValue:
|
||||
return fmt.Sprintf("\"%s\"", string(x.StringValue)), len(x.StringValue) != 0
|
||||
case *pb.Object_Int64Value:
|
||||
return fmt.Sprintf("%d", x.Int64Value), x.Int64Value != 0
|
||||
case *pb.Object_Uint64Value:
|
||||
return fmt.Sprintf("%du", x.Uint64Value), x.Uint64Value != 0
|
||||
case *pb.Object_DoubleValue:
|
||||
return fmt.Sprintf("%f", x.DoubleValue), x.DoubleValue != 0.0
|
||||
case *pb.Object_RefValue:
|
||||
if x.RefValue == 0 {
|
||||
return "nil", false
|
||||
}
|
||||
ref := fmt.Sprintf("g%dr%d", graph, x.RefValue)
|
||||
if html {
|
||||
ref = fmt.Sprintf("<a href=#%s>%s</a>", ref, ref)
|
||||
}
|
||||
return ref, true
|
||||
case *pb.Object_SliceValue:
|
||||
if x.SliceValue.RefValue == 0 {
|
||||
return "nil", false
|
||||
}
|
||||
ref := fmt.Sprintf("g%dr%d", graph, x.SliceValue.RefValue)
|
||||
if html {
|
||||
ref = fmt.Sprintf("<a href=#%s>%s</a>", ref, ref)
|
||||
}
|
||||
return fmt.Sprintf("%s[:%d:%d]", ref, x.SliceValue.Length, x.SliceValue.Capacity), true
|
||||
case *pb.Object_ArrayValue:
|
||||
if len(x.ArrayValue.Contents) == 0 {
|
||||
return "[]", false
|
||||
}
|
||||
items := make([]string, 0, len(x.ArrayValue.Contents)+2)
|
||||
zeros := make([]string, 0) // used to eliminate zero entries.
|
||||
items = append(items, "[")
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
for i := 0; i < len(x.ArrayValue.Contents); i++ {
|
||||
item, ok := format(graph, depth+1, x.ArrayValue.Contents[i], html)
|
||||
if ok {
|
||||
if len(zeros) > 0 {
|
||||
items = append(items, zeros...)
|
||||
zeros = nil
|
||||
}
|
||||
items = append(items, fmt.Sprintf("\t%s,", item))
|
||||
} else {
|
||||
zeros = append(zeros, fmt.Sprintf("\t%s,", item))
|
||||
}
|
||||
}
|
||||
if len(zeros) > 0 {
|
||||
items = append(items, fmt.Sprintf("\t... (%d zeros),", len(zeros)))
|
||||
}
|
||||
items = append(items, "]")
|
||||
return strings.Join(items, tabs), len(zeros) < len(x.ArrayValue.Contents)
|
||||
case *pb.Object_StructValue:
|
||||
if len(x.StructValue.Fields) == 0 {
|
||||
return "struct{}", false
|
||||
}
|
||||
items := make([]string, 0, len(x.StructValue.Fields)+2)
|
||||
items = append(items, "struct{")
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
allZero := true
|
||||
for _, field := range x.StructValue.Fields {
|
||||
element, ok := format(graph, depth+1, field.Value, html)
|
||||
allZero = allZero && !ok
|
||||
items = append(items, fmt.Sprintf("\t%s: %s,", field.Name, element))
|
||||
}
|
||||
items = append(items, "}")
|
||||
return strings.Join(items, tabs), !allZero
|
||||
case *pb.Object_MapValue:
|
||||
if len(x.MapValue.Keys) == 0 {
|
||||
return "map{}", false
|
||||
}
|
||||
items := make([]string, 0, len(x.MapValue.Keys)+2)
|
||||
items = append(items, "map{")
|
||||
tabs := "\n" + strings.Repeat("\t", depth)
|
||||
for i := 0; i < len(x.MapValue.Keys); i++ {
|
||||
key, _ := format(graph, depth+1, x.MapValue.Keys[i], html)
|
||||
value, _ := format(graph, depth+1, x.MapValue.Values[i], html)
|
||||
items = append(items, fmt.Sprintf("\t%s: %s,", key, value))
|
||||
}
|
||||
items = append(items, "}")
|
||||
return strings.Join(items, tabs), true
|
||||
case *pb.Object_InterfaceValue:
|
||||
if x.InterfaceValue.Type == "" {
|
||||
return "interface(nil){}", false
|
||||
}
|
||||
element, _ := format(graph, depth+1, x.InterfaceValue.Value, html)
|
||||
return fmt.Sprintf("interface(\"%s\"){%s}", x.InterfaceValue.Type, element), true
|
||||
case *pb.Object_ByteArrayValue:
|
||||
return printArray(reflect.ValueOf(x.ByteArrayValue))
|
||||
case *pb.Object_Uint16ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Uint16ArrayValue.Values))
|
||||
case *pb.Object_Uint32ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Uint32ArrayValue.Values))
|
||||
case *pb.Object_Uint64ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Uint64ArrayValue.Values))
|
||||
case *pb.Object_UintptrArrayValue:
|
||||
return printArray(castSlice(reflect.ValueOf(x.UintptrArrayValue.Values), reflect.TypeOf(uintptr(0))))
|
||||
case *pb.Object_Int8ArrayValue:
|
||||
return printArray(castSlice(reflect.ValueOf(x.Int8ArrayValue.Values), reflect.TypeOf(int8(0))))
|
||||
case *pb.Object_Int16ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Int16ArrayValue.Values))
|
||||
case *pb.Object_Int32ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Int32ArrayValue.Values))
|
||||
case *pb.Object_Int64ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Int64ArrayValue.Values))
|
||||
case *pb.Object_BoolArrayValue:
|
||||
return printArray(reflect.ValueOf(x.BoolArrayValue.Values))
|
||||
case *pb.Object_Float64ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Float64ArrayValue.Values))
|
||||
case *pb.Object_Float32ArrayValue:
|
||||
return printArray(reflect.ValueOf(x.Float32ArrayValue.Values))
|
||||
}
|
||||
|
||||
// Should not happen, but tolerate.
|
||||
return fmt.Sprintf("(unknown proto type: %T)", object.GetValue()), true
|
||||
}
|
||||
|
||||
// PrettyPrint reads the state stream from r, and pretty prints to w.
|
||||
func PrettyPrint(w io.Writer, r io.Reader, html bool) error {
|
||||
var (
|
||||
// current graph ID.
|
||||
graph uint64
|
||||
|
||||
// current object ID.
|
||||
id uint64
|
||||
)
|
||||
|
||||
if html {
|
||||
fmt.Fprintf(w, "<pre>")
|
||||
defer fmt.Fprintf(w, "</pre>")
|
||||
}
|
||||
|
||||
for {
|
||||
// Find the first object to begin generation.
|
||||
length, object, err := ReadHeader(r)
|
||||
if err == io.EOF {
|
||||
// Nothing else to do.
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if !object {
|
||||
// Increment the graph number & reset the ID.
|
||||
graph++
|
||||
id = 0
|
||||
if length > 0 {
|
||||
fmt.Fprintf(w, "(%d bytes non-object data)\n", length)
|
||||
io.Copy(ioutil.Discard, &io.LimitedReader{
|
||||
R: r,
|
||||
N: int64(length),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Read & unmarshal the object.
|
||||
buf := make([]byte, length)
|
||||
for done := 0; done < len(buf); {
|
||||
n, err := r.Read(buf[done:])
|
||||
done += n
|
||||
if n == 0 && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
obj := new(pb.Object)
|
||||
if err := proto.Unmarshal(buf, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id++ // First object must be one.
|
||||
str, _ := format(graph, 0, obj, html)
|
||||
tag := fmt.Sprintf("g%dr%d", graph, id)
|
||||
if html {
|
||||
tag = fmt.Sprintf("<a name=%s>%s</a>", tag, tag)
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s = %s\n", tag, str); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printArray(s reflect.Value) (string, bool) {
|
||||
zero := reflect.Zero(s.Type().Elem()).Interface()
|
||||
z := "0"
|
||||
switch s.Type().Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
z = "false"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
case reflect.Float32, reflect.Float64:
|
||||
default:
|
||||
return fmt.Sprintf("unexpected non-primitive type array: %#v", s.Interface()), true
|
||||
}
|
||||
|
||||
zeros := 0
|
||||
items := make([]string, 0, s.Len())
|
||||
for i := 0; i <= s.Len(); i++ {
|
||||
if i < s.Len() && reflect.DeepEqual(s.Index(i).Interface(), zero) {
|
||||
zeros++
|
||||
continue
|
||||
}
|
||||
if zeros > 0 {
|
||||
if zeros <= 4 {
|
||||
for ; zeros > 0; zeros-- {
|
||||
items = append(items, z)
|
||||
}
|
||||
} else {
|
||||
items = append(items, fmt.Sprintf("(%d %ss)", zeros, z))
|
||||
zeros = 0
|
||||
}
|
||||
}
|
||||
if i < s.Len() {
|
||||
items = append(items, fmt.Sprintf("%v", s.Index(i).Interface()))
|
||||
}
|
||||
}
|
||||
return "[" + strings.Join(items, ",") + "]", zeros < s.Len()
|
||||
}
|
||||
+176
-214
@@ -31,210 +31,226 @@
|
||||
// Uint64 default
|
||||
// Float32 default
|
||||
// Float64 default
|
||||
// Complex64 custom
|
||||
// Complex128 custom
|
||||
// Complex64 default
|
||||
// Complex128 default
|
||||
// Array default
|
||||
// Chan custom
|
||||
// Func custom
|
||||
// Interface custom
|
||||
// Map default (*)
|
||||
// Interface default
|
||||
// Map default
|
||||
// Ptr default
|
||||
// Slice default
|
||||
// String default
|
||||
// Struct custom
|
||||
// Struct custom (*) Unless zero-sized.
|
||||
// UnsafePointer custom
|
||||
//
|
||||
// (*) Maps are treated as value types by this package, even if they are
|
||||
// pointers internally. If you want to save two independent references
|
||||
// to the same map value, you must explicitly use a pointer to a map.
|
||||
// See README.md for an overview of how encoding and decoding works.
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
pb "gvisor.dev/gvisor/pkg/state/object_go_proto"
|
||||
"gvisor.dev/gvisor/pkg/state/wire"
|
||||
)
|
||||
|
||||
// objectID is a unique identifier assigned to each object to be serialized.
|
||||
// Each instance of an object is considered separately, i.e. if there are two
|
||||
// objects of the same type in the object graph being serialized, they'll be
|
||||
// assigned unique objectIDs.
|
||||
type objectID uint32
|
||||
|
||||
// typeID is the identifier for a type. Types are serialized and tracked
|
||||
// alongside objects in order to avoid the overhead of encoding field names in
|
||||
// all objects.
|
||||
type typeID uint32
|
||||
|
||||
// ErrState is returned when an error is encountered during encode/decode.
|
||||
type ErrState struct {
|
||||
// err is the underlying error.
|
||||
err error
|
||||
|
||||
// path is the visit path from root to the current object.
|
||||
path string
|
||||
|
||||
// trace is the stack trace.
|
||||
trace string
|
||||
}
|
||||
|
||||
// Error returns a sensible description of the state error.
|
||||
func (e *ErrState) Error() string {
|
||||
return fmt.Sprintf("%v:\nstate path: %s\n%s", e.err, e.path, e.trace)
|
||||
return fmt.Sprintf("%v:\n%s", e.err, e.trace)
|
||||
}
|
||||
|
||||
// UnwrapErrState returns the underlying error in ErrState.
|
||||
//
|
||||
// If err is not *ErrState, err is returned directly.
|
||||
func UnwrapErrState(err error) error {
|
||||
if e, ok := err.(*ErrState); ok {
|
||||
return e.err
|
||||
}
|
||||
return err
|
||||
// Unwrap implements standard unwrapping.
|
||||
func (e *ErrState) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Save saves the given object state.
|
||||
func Save(ctx context.Context, w io.Writer, rootPtr interface{}, stats *Stats) error {
|
||||
func Save(ctx context.Context, w wire.Writer, rootPtr interface{}) (Stats, error) {
|
||||
// Create the encoding state.
|
||||
es := &encodeState{
|
||||
ctx: ctx,
|
||||
idsByObject: make(map[uintptr]uint64),
|
||||
w: w,
|
||||
stats: stats,
|
||||
es := encodeState{
|
||||
ctx: ctx,
|
||||
w: w,
|
||||
types: makeTypeEncodeDatabase(),
|
||||
zeroValues: make(map[reflect.Type]*objectEncodeState),
|
||||
}
|
||||
|
||||
// Perform the encoding.
|
||||
return es.safely(func() {
|
||||
es.Serialize(reflect.ValueOf(rootPtr).Elem())
|
||||
err := safely(func() {
|
||||
es.Save(reflect.ValueOf(rootPtr).Elem())
|
||||
})
|
||||
return es.stats, err
|
||||
}
|
||||
|
||||
// Load loads a checkpoint.
|
||||
func Load(ctx context.Context, r io.Reader, rootPtr interface{}, stats *Stats) error {
|
||||
func Load(ctx context.Context, r wire.Reader, rootPtr interface{}) (Stats, error) {
|
||||
// Create the decoding state.
|
||||
ds := &decodeState{
|
||||
ctx: ctx,
|
||||
objectsByID: make(map[uint64]*objectState),
|
||||
deferred: make(map[uint64]*pb.Object),
|
||||
r: r,
|
||||
stats: stats,
|
||||
ds := decodeState{
|
||||
ctx: ctx,
|
||||
r: r,
|
||||
types: makeTypeDecodeDatabase(),
|
||||
deferred: make(map[objectID]wire.Object),
|
||||
}
|
||||
|
||||
// Attempt our decode.
|
||||
return ds.safely(func() {
|
||||
ds.Deserialize(reflect.ValueOf(rootPtr).Elem())
|
||||
err := safely(func() {
|
||||
ds.Load(reflect.ValueOf(rootPtr).Elem())
|
||||
})
|
||||
return ds.stats, err
|
||||
}
|
||||
|
||||
// Fns are the state dispatch functions.
|
||||
type Fns struct {
|
||||
// Save is a function like Save(concreteType, Map).
|
||||
Save interface{}
|
||||
|
||||
// Load is a function like Load(concreteType, Map).
|
||||
Load interface{}
|
||||
// Sink is used for Type.StateSave.
|
||||
type Sink struct {
|
||||
internal objectEncoder
|
||||
}
|
||||
|
||||
// Save executes the save function.
|
||||
func (fns *Fns) invokeSave(obj reflect.Value, m Map) {
|
||||
reflect.ValueOf(fns.Save).Call([]reflect.Value{obj, reflect.ValueOf(m)})
|
||||
}
|
||||
|
||||
// Load executes the load function.
|
||||
func (fns *Fns) invokeLoad(obj reflect.Value, m Map) {
|
||||
reflect.ValueOf(fns.Load).Call([]reflect.Value{obj, reflect.ValueOf(m)})
|
||||
}
|
||||
|
||||
// validateStateFn ensures types are correct.
|
||||
func validateStateFn(fn interface{}, typ reflect.Type) bool {
|
||||
fnTyp := reflect.TypeOf(fn)
|
||||
if fnTyp.Kind() != reflect.Func {
|
||||
return false
|
||||
}
|
||||
if fnTyp.NumIn() != 2 {
|
||||
return false
|
||||
}
|
||||
if fnTyp.NumOut() != 0 {
|
||||
return false
|
||||
}
|
||||
if fnTyp.In(0) != typ {
|
||||
return false
|
||||
}
|
||||
if fnTyp.In(1) != reflect.TypeOf(Map{}) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Validate validates all state functions.
|
||||
func (fns *Fns) Validate(typ reflect.Type) bool {
|
||||
return validateStateFn(fns.Save, typ) && validateStateFn(fns.Load, typ)
|
||||
}
|
||||
|
||||
type typeDatabase struct {
|
||||
// nameToType is a forward lookup table.
|
||||
nameToType map[string]reflect.Type
|
||||
|
||||
// typeToName is the reverse lookup table.
|
||||
typeToName map[reflect.Type]string
|
||||
|
||||
// typeToFns is the function lookup table.
|
||||
typeToFns map[reflect.Type]Fns
|
||||
}
|
||||
|
||||
// registeredTypes is a database used for SaveInterface and LoadInterface.
|
||||
var registeredTypes = typeDatabase{
|
||||
nameToType: make(map[string]reflect.Type),
|
||||
typeToName: make(map[reflect.Type]string),
|
||||
typeToFns: make(map[reflect.Type]Fns),
|
||||
}
|
||||
|
||||
// register registers a type under the given name. This will generally be
|
||||
// called via init() methods, and therefore uses panic to propagate errors.
|
||||
func (t *typeDatabase) register(name string, typ reflect.Type, fns Fns) {
|
||||
// We can't allow name collisions.
|
||||
if ot, ok := t.nameToType[name]; ok {
|
||||
panic(fmt.Sprintf("type %q can't use name %q, already in use by type %q", typ.Name(), name, ot.Name()))
|
||||
}
|
||||
|
||||
// Or multiple registrations.
|
||||
if on, ok := t.typeToName[typ]; ok {
|
||||
panic(fmt.Sprintf("type %q can't be registered as %q, already registered as %q", typ.Name(), name, on))
|
||||
}
|
||||
|
||||
t.nameToType[name] = typ
|
||||
t.typeToName[typ] = name
|
||||
t.typeToFns[typ] = fns
|
||||
}
|
||||
|
||||
// lookupType finds a type given a name.
|
||||
func (t *typeDatabase) lookupType(name string) (reflect.Type, bool) {
|
||||
typ, ok := t.nameToType[name]
|
||||
return typ, ok
|
||||
}
|
||||
|
||||
// lookupName finds a name given a type.
|
||||
func (t *typeDatabase) lookupName(typ reflect.Type) (string, bool) {
|
||||
name, ok := t.typeToName[typ]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
// lookupFns finds functions given a type.
|
||||
func (t *typeDatabase) lookupFns(typ reflect.Type) (Fns, bool) {
|
||||
fns, ok := t.typeToFns[typ]
|
||||
return fns, ok
|
||||
}
|
||||
|
||||
// Register must be called for any interface implementation types that
|
||||
// implements Loader.
|
||||
// Save adds the given object to the map.
|
||||
//
|
||||
// Register should be called either immediately after startup or via init()
|
||||
// methods. Double registration of either names or types will result in a panic.
|
||||
// You should pass always pointers to the object you are saving. For example:
|
||||
//
|
||||
// No synchronization is provided; this should only be called in init.
|
||||
// type X struct {
|
||||
// A int
|
||||
// B *int
|
||||
// }
|
||||
//
|
||||
// Example usage:
|
||||
// func (x *X) StateTypeInfo(m Sink) state.TypeInfo {
|
||||
// return state.TypeInfo{
|
||||
// Name: "pkg.X",
|
||||
// Fields: []string{
|
||||
// "A",
|
||||
// "B",
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// state.Register("Foo", (*Foo)(nil), state.Fns{
|
||||
// Save: (*Foo).Save,
|
||||
// Load: (*Foo).Load,
|
||||
// func (x *X) StateSave(m Sink) {
|
||||
// m.Save(0, &x.A) // Field is A.
|
||||
// m.Save(1, &x.B) // Field is B.
|
||||
// }
|
||||
//
|
||||
// func (x *X) StateLoad(m Source) {
|
||||
// m.Load(0, &x.A) // Field is A.
|
||||
// m.Load(1, &x.B) // Field is B.
|
||||
// }
|
||||
func (s Sink) Save(slot int, objPtr interface{}) {
|
||||
s.internal.save(slot, reflect.ValueOf(objPtr).Elem())
|
||||
}
|
||||
|
||||
// SaveValue adds the given object value to the map.
|
||||
//
|
||||
// This should be used for values where pointers are not available, or casts
|
||||
// are required during Save/Load.
|
||||
//
|
||||
// For example, if we want to cast external package type P.Foo to int64:
|
||||
//
|
||||
// func (x *X) StateSave(m Sink) {
|
||||
// m.SaveValue(0, "A", int64(x.A))
|
||||
// }
|
||||
//
|
||||
// func (x *X) StateLoad(m Source) {
|
||||
// m.LoadValue(0, new(int64), func(x interface{}) {
|
||||
// x.A = P.Foo(x.(int64))
|
||||
// })
|
||||
// }
|
||||
func (s Sink) SaveValue(slot int, obj interface{}) {
|
||||
s.internal.save(slot, reflect.ValueOf(obj))
|
||||
}
|
||||
|
||||
// Context returns the context object provided at save time.
|
||||
func (s Sink) Context() context.Context {
|
||||
return s.internal.es.ctx
|
||||
}
|
||||
|
||||
// Type is an interface that must be implemented by Struct objects. This allows
|
||||
// these objects to be serialized while minimizing runtime reflection required.
|
||||
//
|
||||
func Register(name string, instance interface{}, fns Fns) {
|
||||
registeredTypes.register(name, reflect.TypeOf(instance), fns)
|
||||
// All these methods can be automatically generated by the go_statify tool.
|
||||
type Type interface {
|
||||
// StateTypeName returns the type's name.
|
||||
//
|
||||
// This is used for matching type information during encoding and
|
||||
// decoding, as well as dynamic interface dispatch. This should be
|
||||
// globally unique.
|
||||
StateTypeName() string
|
||||
|
||||
// StateFields returns information about the type.
|
||||
//
|
||||
// Fields is the set of fields for the object. Calls to Sink.Save and
|
||||
// Source.Load must be made in-order with respect to these fields.
|
||||
//
|
||||
// This will be called at most once per serialization.
|
||||
StateFields() []string
|
||||
}
|
||||
|
||||
// SaverLoader must be implemented by struct types.
|
||||
type SaverLoader interface {
|
||||
// StateSave saves the state of the object to the given Map.
|
||||
StateSave(Sink)
|
||||
|
||||
// StateLoad loads the state of the object.
|
||||
StateLoad(Source)
|
||||
}
|
||||
|
||||
// Source is used for Type.StateLoad.
|
||||
type Source struct {
|
||||
internal objectDecoder
|
||||
}
|
||||
|
||||
// Load loads the given object passed as a pointer..
|
||||
//
|
||||
// See Sink.Save for an example.
|
||||
func (s Source) Load(slot int, objPtr interface{}) {
|
||||
s.internal.load(slot, reflect.ValueOf(objPtr), false, nil)
|
||||
}
|
||||
|
||||
// LoadWait loads the given objects from the map, and marks it as requiring all
|
||||
// AfterLoad executions to complete prior to running this object's AfterLoad.
|
||||
//
|
||||
// See Sink.Save for an example.
|
||||
func (s Source) LoadWait(slot int, objPtr interface{}) {
|
||||
s.internal.load(slot, reflect.ValueOf(objPtr), true, nil)
|
||||
}
|
||||
|
||||
// LoadValue loads the given object value from the map.
|
||||
//
|
||||
// See Sink.SaveValue for an example.
|
||||
func (s Source) LoadValue(slot int, objPtr interface{}, fn func(interface{})) {
|
||||
o := reflect.ValueOf(objPtr)
|
||||
s.internal.load(slot, o, true, func() { fn(o.Elem().Interface()) })
|
||||
}
|
||||
|
||||
// AfterLoad schedules a function execution when all objects have been
|
||||
// allocated and their automated loading and customized load logic have been
|
||||
// executed. fn will not be executed until all of current object's
|
||||
// dependencies' AfterLoad() logic, if exist, have been executed.
|
||||
func (s Source) AfterLoad(fn func()) {
|
||||
s.internal.afterLoad(fn)
|
||||
}
|
||||
|
||||
// Context returns the context object provided at load time.
|
||||
func (s Source) Context() context.Context {
|
||||
return s.internal.ds.ctx
|
||||
}
|
||||
|
||||
// IsZeroValue checks if the given value is the zero value.
|
||||
@@ -244,72 +260,14 @@ func IsZeroValue(val interface{}) bool {
|
||||
return val == nil || reflect.ValueOf(val).Elem().IsZero()
|
||||
}
|
||||
|
||||
// step captures one encoding / decoding step. On each step, there is up to one
|
||||
// choice made, which is captured by non-nil param. We intentionally do not
|
||||
// eagerly create the final path string, as that will only be needed upon panic.
|
||||
type step struct {
|
||||
// dereference indicate if the current object is obtained by
|
||||
// dereferencing a pointer.
|
||||
dereference bool
|
||||
|
||||
// format is the formatting string that takes param below, if
|
||||
// non-nil. For example, in array indexing case, we have "[%d]".
|
||||
format string
|
||||
|
||||
// param stores the choice made at the current encoding / decoding step.
|
||||
// For eaxmple, in array indexing case, param stores the index. When no
|
||||
// choice is made, e.g. dereference, param should be nil.
|
||||
param interface{}
|
||||
// Failf is a wrapper around panic that should be used to generate errors that
|
||||
// can be caught during saving and loading.
|
||||
func Failf(fmtStr string, v ...interface{}) {
|
||||
panic(fmt.Errorf(fmtStr, v...))
|
||||
}
|
||||
|
||||
// recoverable is the state encoding / decoding panic recovery facility. It is
|
||||
// also used to store encoding / decoding steps as well as the reference to the
|
||||
// original queued object from which the current object is dispatched. The
|
||||
// complete encoding / decoding path is synthesised from the steps in all queued
|
||||
// objects leading to the current object.
|
||||
type recoverable struct {
|
||||
from *recoverable
|
||||
steps []step
|
||||
}
|
||||
|
||||
// push enters a new context level.
|
||||
func (sr *recoverable) push(dereference bool, format string, param interface{}) {
|
||||
sr.steps = append(sr.steps, step{dereference, format, param})
|
||||
}
|
||||
|
||||
// pop exits the current context level.
|
||||
func (sr *recoverable) pop() {
|
||||
if len(sr.steps) <= 1 {
|
||||
return
|
||||
}
|
||||
sr.steps = sr.steps[:len(sr.steps)-1]
|
||||
}
|
||||
|
||||
// path returns the complete encoding / decoding path from root. This is only
|
||||
// called upon panic.
|
||||
func (sr *recoverable) path() string {
|
||||
if sr.from == nil {
|
||||
return "root"
|
||||
}
|
||||
p := sr.from.path()
|
||||
for _, s := range sr.steps {
|
||||
if s.dereference {
|
||||
p = fmt.Sprintf("*(%s)", p)
|
||||
}
|
||||
if s.param == nil {
|
||||
p += s.format
|
||||
} else {
|
||||
p += fmt.Sprintf(s.format, s.param)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (sr *recoverable) copy() recoverable {
|
||||
return recoverable{from: sr.from, steps: append([]step(nil), sr.steps...)}
|
||||
}
|
||||
|
||||
// safely executes the given function, catching a panic and unpacking as an error.
|
||||
// safely executes the given function, catching a panic and unpacking as an
|
||||
// error.
|
||||
//
|
||||
// The error flow through the state package uses panic and recover. There are
|
||||
// two important reasons for this:
|
||||
@@ -323,9 +281,15 @@ func (sr *recoverable) copy() recoverable {
|
||||
// method doesn't add a lot of value. If there are specific error conditions
|
||||
// that you'd like to handle, you should add appropriate functionality to
|
||||
// objects themselves prior to calling Save() and Load().
|
||||
func (sr *recoverable) safely(fn func()) (err error) {
|
||||
func safely(fn func()) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if es, ok := r.(*ErrState); ok {
|
||||
err = es // Propagate.
|
||||
return
|
||||
}
|
||||
|
||||
// Build a new state error.
|
||||
es := new(ErrState)
|
||||
if e, ok := r.(error); ok {
|
||||
es.err = e
|
||||
@@ -333,8 +297,6 @@ func (sr *recoverable) safely(fn func()) (err error) {
|
||||
es.err = fmt.Errorf("%v", r)
|
||||
}
|
||||
|
||||
es.path = sr.path()
|
||||
|
||||
// Make a stack. We don't know how big it will be ahead
|
||||
// of time, but want to make sure we get the whole
|
||||
// thing. So we just do a stupid brute force approach.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !race
|
||||
|
||||
package state
|
||||
|
||||
var raceEnabled = false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user