blob: 4206758809247eccd5076c0c51e8bdd91a04c86a [file] [log] [blame]
// 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"
"sort"
"gvisor.dev/gvisor/pkg/state/wire"
)
// assertValidType asserts that the type is valid.
func assertValidType(name string, fields []string) {
if name == "" {
Failf("type has empty name")
}
fieldsCopy := make([]string, len(fields))
for i := 0; i < len(fields); i++ {
if fields[i] == "" {
Failf("field has empty name for type %q", name)
}
fieldsCopy[i] = fields[i]
}
sort.Slice(fieldsCopy, func(i, j int) bool {
return fieldsCopy[i] < fieldsCopy[j]
})
for i := range fieldsCopy {
if i > 0 && fieldsCopy[i-1] == fieldsCopy[i] {
Failf("duplicate field %q for type %s", fieldsCopy[i], name)
}
}
}
// typeEntry is an entry in the typeDatabase.
type typeEntry struct {
ID typeID
wire.Type
}
// reconciledTypeEntry is a reconciled entry in the typeDatabase.
type reconciledTypeEntry struct {
wire.Type
LocalType reflect.Type
FieldOrder []int
}
// typeEncodeDatabase is an internal TypeInfo database for encoding.
type typeEncodeDatabase struct {
// byType maps by type to the typeEntry.
byType map[reflect.Type]*typeEntry
// lastID is the last used ID.
lastID typeID
}
// makeTypeEncodeDatabase makes a typeDatabase.
func makeTypeEncodeDatabase() typeEncodeDatabase {
return typeEncodeDatabase{
byType: make(map[reflect.Type]*typeEntry),
}
}
// typeDecodeDatabase is an internal TypeInfo database for decoding.
type typeDecodeDatabase struct {
// byID maps by ID to type.
byID []*reconciledTypeEntry
// pending are entries that are pending validation by Lookup. These
// will be reconciled with actual objects. Note that these will also be
// used to lookup types by name, since they may not be reconciled and
// there's little value to deleting from this map.
pending []*wire.Type
}
// makeTypeDecodeDatabase makes a typeDatabase.
func makeTypeDecodeDatabase() typeDecodeDatabase {
return typeDecodeDatabase{}
}
// lookupNameFields extracts the name and fields from an object.
func lookupNameFields(typ reflect.Type) (string, []string, bool) {
v := reflect.Zero(reflect.PtrTo(typ)).Interface()
t, ok := v.(Type)
if !ok {
// Is this a primitive?
if typ.Kind() == reflect.Interface {
return interfaceType, nil, true
}
name := typ.Name()
if _, ok := primitiveTypeDatabase[name]; !ok {
// This is not a known type, and not a primitive. The
// encoder may proceed for anonymous empty structs, or
// it may deference the type pointer and try again.
return "", nil, false
}
return name, nil, true
}
// Sanity check the type.
if raceEnabled {
if _, ok := reverseTypeDatabase[typ]; !ok {
// The type was not registered? Must be an embedded
// structure or something else.
return "", nil, false
}
}
// Extract the name from the object.
name := t.StateTypeName()
fields := t.StateFields()
assertValidType(name, fields)
return name, fields, true
}
// Lookup looks up or registers the given object.
//
// The bool indicates whether this is an existing entry: false means the entry
// did not exist, and true means the entry did exist. If this bool is false and
// the returned typeEntry are nil, then the obj did not implement the Type
// interface.
func (tdb *typeEncodeDatabase) Lookup(typ reflect.Type) (*typeEntry, bool) {
te, ok := tdb.byType[typ]
if !ok {
// Lookup the type information.
name, fields, ok := lookupNameFields(typ)
if !ok {
// Empty structs may still be encoded, so let the
// caller decide what to do from here.
return nil, false
}
// Register the new type.
tdb.lastID++
te = &typeEntry{
ID: tdb.lastID,
Type: wire.Type{
Name: name,
Fields: fields,
},
}
// All done.
tdb.byType[typ] = te
return te, false
}
return te, true
}
// Register adds a typeID entry.
func (tbd *typeDecodeDatabase) Register(typ *wire.Type) {
assertValidType(typ.Name, typ.Fields)
tbd.pending = append(tbd.pending, typ)
}
// LookupName looks up the type name by ID.
func (tbd *typeDecodeDatabase) LookupName(id typeID) string {
if len(tbd.pending) < int(id) {
// This is likely an encoder error?
Failf("type ID %d not available", id)
}
return tbd.pending[id-1].Name
}
// LookupType looks up the type by ID.
func (tbd *typeDecodeDatabase) LookupType(id typeID) reflect.Type {
name := tbd.LookupName(id)
typ, ok := globalTypeDatabase[name]
if !ok {
// If not available, see if it's primitive.
typ, ok = primitiveTypeDatabase[name]
if !ok && name == interfaceType {
// Matches the built-in interface type.
var i interface{}
return reflect.TypeOf(&i).Elem()
}
if !ok {
// The type is perhaps not registered?
Failf("type name %q is not available", name)
}
return typ // Primitive type.
}
return typ // Registered type.
}
// singleFieldOrder defines the field order for a single field.
var singleFieldOrder = []int{0}
// Lookup looks up or registers the given object.
//
// First, the typeID is searched to see if this has already been appropriately
// reconciled. If no, then a reconcilation will take place that may result in a
// field ordering. If a nil reconciledTypeEntry is returned from this method,
// then the object does not support the Type interface.
//
// This method never returns nil.
func (tbd *typeDecodeDatabase) Lookup(id typeID, typ reflect.Type) *reconciledTypeEntry {
if len(tbd.byID) > int(id) && tbd.byID[id-1] != nil {
// Already reconciled.
return tbd.byID[id-1]
}
// The ID has not been reconciled yet. That's fine. We need to make
// sure it aligns with the current provided object.
if len(tbd.pending) < int(id) {
// This id was never registered. Probably an encoder error?
Failf("typeDatabase does not contain id %d", id)
}
// Extract the pending info.
pending := tbd.pending[id-1]
// Grow the byID list.
if len(tbd.byID) < int(id) {
tbd.byID = append(tbd.byID, make([]*reconciledTypeEntry, int(id)-len(tbd.byID))...)
}
// Reconcile the type.
name, fields, ok := lookupNameFields(typ)
if !ok {
// Empty structs are decoded only when the type is nil. Since
// this isn't the case, we fail here.
Failf("unsupported type %q during decode; can't reconcile", pending.Name)
}
if name != pending.Name {
// Are these the same type? Print a helpful message as this may
// actually happen in practice if types change.
Failf("typeDatabase contains conflicting definitions for id %d: %s->%v (current) and %s->%v (existing)",
id, name, fields, pending.Name, pending.Fields)
}
rte := &reconciledTypeEntry{
Type: wire.Type{
Name: name,
Fields: fields,
},
LocalType: typ,
}
// If there are zero or one fields, then we skip allocating the field
// slice. There is special handling for decoding in this case. If the
// field name does not match, it will be caught in the general purpose
// code below.
if len(fields) != len(pending.Fields) {
Failf("type %q contains different fields: %v (decode) and %v (encode)",
name, fields, pending.Fields)
}
if len(fields) == 0 {
tbd.byID[id-1] = rte // Save.
return rte
}
if len(fields) == 1 && fields[0] == pending.Fields[0] {
tbd.byID[id-1] = rte // Save.
rte.FieldOrder = singleFieldOrder
return rte
}
// For each field in the current object's information, match it to a
// field in the destination object. We know from the assertion above
// and the insertion on insertion to pending that neither field
// contains any duplicates.
fieldOrder := make([]int, len(fields))
for i, name := range fields {
fieldOrder[i] = -1 // Sentinel.
// Is it an exact match?
if pending.Fields[i] == name {
fieldOrder[i] = i
continue
}
// Find the matching field.
for j, otherName := range pending.Fields {
if name == otherName {
fieldOrder[i] = j
break
}
}
if fieldOrder[i] == -1 {
// The type name matches but we are lacking some common fields.
Failf("type %q has mismatched fields: %v (decode) and %v (encode)",
name, fields, pending.Fields)
}
}
// The type has been reeconciled.
rte.FieldOrder = fieldOrder
tbd.byID[id-1] = rte
return rte
}
// interfaceType defines all interfaces.
const interfaceType = "interface"
// primitiveTypeDatabase is a set of fixed types.
var primitiveTypeDatabase = func() map[string]reflect.Type {
r := make(map[string]reflect.Type)
for _, t := range []reflect.Type{
reflect.TypeOf(false),
reflect.TypeOf(int(0)),
reflect.TypeOf(int8(0)),
reflect.TypeOf(int16(0)),
reflect.TypeOf(int32(0)),
reflect.TypeOf(int64(0)),
reflect.TypeOf(uint(0)),
reflect.TypeOf(uintptr(0)),
reflect.TypeOf(uint8(0)),
reflect.TypeOf(uint16(0)),
reflect.TypeOf(uint32(0)),
reflect.TypeOf(uint64(0)),
reflect.TypeOf(""),
reflect.TypeOf(float32(0.0)),
reflect.TypeOf(float64(0.0)),
reflect.TypeOf(complex64(0.0)),
reflect.TypeOf(complex128(0.0)),
} {
r[t.Name()] = t
}
return r
}()
// globalTypeDatabase is used for dispatching interfaces on decode.
var globalTypeDatabase = map[string]reflect.Type{}
// reverseTypeDatabase is a reverse mapping.
var reverseTypeDatabase = map[reflect.Type]string{}
// Register registers a type.
//
// This must be called on init and only done once.
func Register(t Type) {
name := t.StateTypeName()
typ := reflect.TypeOf(t)
if raceEnabled {
assertValidType(name, t.StateFields())
// Register must always be called on pointers.
if typ.Kind() != reflect.Ptr {
Failf("Register must be called on pointers")
}
}
typ = typ.Elem()
if raceEnabled {
if typ.Kind() == reflect.Struct {
// All registered structs must implement SaverLoader. We allow
// the registration is non-struct types with just the Type
// interface, but we need to call StateSave/StateLoad methods
// on aggregate types.
if _, ok := t.(SaverLoader); !ok {
Failf("struct %T does not implement SaverLoader", t)
}
} else {
// Non-structs must not have any fields. We don't support
// calling StateSave/StateLoad methods on any non-struct types.
// If custom behavior is required, these types should be
// wrapped in a structure of some kind.
if fields := t.StateFields(); len(fields) != 0 {
Failf("non-struct %T has non-zero fields %v", t, fields)
}
// We don't allow non-structs to implement StateSave/StateLoad
// methods, because they won't be called and it's confusing.
if _, ok := t.(SaverLoader); ok {
Failf("non-struct %T implements SaverLoader", t)
}
}
if _, ok := primitiveTypeDatabase[name]; ok {
Failf("conflicting primitiveTypeDatabase entry for %T: used by primitive", t)
}
if _, ok := globalTypeDatabase[name]; ok {
Failf("conflicting globalTypeDatabase entries for %T: name conflict", t)
}
if name == interfaceType {
Failf("conflicting name for %T: matches interfaceType", t)
}
reverseTypeDatabase[typ] = name
}
globalTypeDatabase[name] = typ
}