mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
go_marshal: Implement automatic generation of ABI marshalling code.
This CL implements go_marshal, a code generation utility for automatically serializing and deserializing ABI structs. The go_marshal tool automatically generates implementations of the new marshal interface. Unlike binary.Marshal/Unmarshal, the generated interface implementations use no runtime reflection, and translates to a single memcpy for most structs. See go_marshal/README.md for details. PiperOrigin-RevId: 268065475
This commit is contained in:
committed by
gVisor bot
parent
6af9a9850a
commit
3733b9b893
@@ -8,5 +8,7 @@ package_group(
|
||||
packages = [
|
||||
"//pkg/sentry/...",
|
||||
"//runsc/...",
|
||||
# Code generated by go_marshal relies on go_marshal libraries.
|
||||
"//tools/go_marshal/...",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_binary(
|
||||
name = "go_marshal",
|
||||
srcs = ["main.go"],
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
deps = [
|
||||
"//tools/go_marshal/gomarshal",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,164 @@
|
||||
This package implements the go_marshal utility.
|
||||
|
||||
# Overview
|
||||
|
||||
`go_marshal` is a code generation utility similar to `go_stateify` for
|
||||
automatically generating code to marshal go data structures to memory.
|
||||
|
||||
`go_marshal` attempts to improve on `binary.Write` and the sentry's
|
||||
`binary.Marshal` by moving the go runtime reflection necessary to marshal a
|
||||
struct to compile-time.
|
||||
|
||||
`go_marshal` automatically generates implementations for `abi.Marshallable` and
|
||||
`safemem.{Reader,Writer}`. Call-sites for serialization (typically syscall
|
||||
implementations) can directly invoke `safemem.Reader.ReadToBlocks` and
|
||||
`safemem.Writer.WriteFromBlocks`. Data structures that require custom
|
||||
serialization will have manual implementations for these interfaces.
|
||||
|
||||
Data structures can be flagged for code generation by adding a struct-level
|
||||
comment `// +marshal`.
|
||||
|
||||
# Usage
|
||||
|
||||
See `defs.bzl`: two new rules are provided, `go_marshal` and `go_library`.
|
||||
|
||||
The recommended way to generate a go library with marshalling is to use the
|
||||
`go_library` with mostly identical configuration as the native go_library rule.
|
||||
|
||||
```
|
||||
load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = ["foo.go"],
|
||||
)
|
||||
```
|
||||
|
||||
Under the hood, the `go_marshal` rule is used to generate a file that will
|
||||
appear in a Go target; the output file should appear explicitly in a srcs list.
|
||||
For example (note that the above is the preferred method):
|
||||
|
||||
```
|
||||
load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_marshal")
|
||||
|
||||
go_marshal(
|
||||
name = "foo_abi",
|
||||
srcs = ["foo.go"],
|
||||
out = "foo_abi.go",
|
||||
package = "foo",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = [
|
||||
"foo.go",
|
||||
"foo_abi.go",
|
||||
],
|
||||
deps = [
|
||||
"<PKGPATH>/gvisor/pkg/abi",
|
||||
"<PKGPATH>/gvisor/pkg/sentry/safemem/safemem",
|
||||
"<PKGPATH>/gvisor/pkg/sentry/usermem/usermem",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
As part of the interface generation, `go_marshal` also generates some tests for
|
||||
sanity checking the struct definitions for potential alignment issues, and a
|
||||
simple round-trip test through Marshal/Unmarshal to verify the implementation.
|
||||
These tests use reflection to verify properties of the ABI struct, and should be
|
||||
considered part of the generated interfaces (but are too expensive to execute at
|
||||
runtime). Ensure these tests run at some point.
|
||||
|
||||
```
|
||||
$ cat BUILD
|
||||
load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = ["foo.go"],
|
||||
)
|
||||
$ blaze build :foo
|
||||
$ blaze query ...
|
||||
<path-to-dir>:foo_abi_autogen
|
||||
<path-to-dir>:foo_abi_autogen_test
|
||||
$ blaze test :foo_abi_autogen_test
|
||||
<test-output>
|
||||
```
|
||||
|
||||
# Restrictions
|
||||
|
||||
Not all valid go type definitions can be used with `go_marshal`. `go_marshal` is
|
||||
intended for ABI structs, which have these additional restrictions:
|
||||
|
||||
- At the moment, `go_marshal` only supports struct declarations.
|
||||
|
||||
- Structs are marshalled as packed types. This means no implicit padding is
|
||||
inserted between fields shorter than the platform register size. For
|
||||
alignment, manually insert padding fields.
|
||||
|
||||
- Structs used with `go_marshal` must have a compile-time static size. This
|
||||
means no dynamically sizes fields like slices or strings. Use statically
|
||||
sized array (byte arrays for strings) instead.
|
||||
|
||||
- No pointers, channel, map or function pointer fields, and no fields that are
|
||||
arrays of these types. These don't make sense in an ABI data structure.
|
||||
|
||||
- We could support opaque pointers as `uintptr`, but this is currently not
|
||||
implemented. Implementing this would require handling the architecture
|
||||
dependent native pointer size.
|
||||
|
||||
- Fields must either be a primitive integer type (`byte`,
|
||||
`[u]int{8,16,32,64}`), or of a type that implements abi.Marshallable.
|
||||
|
||||
- `int` and `uint` fields are not allowed. Use an explicitly-sized numeric
|
||||
type.
|
||||
|
||||
- `float*` fields are currently not supported, but could be if necessary.
|
||||
|
||||
# Appendix
|
||||
|
||||
## Working with Non-Packed Structs
|
||||
|
||||
ABI structs must generally be packed types, meaning they should have no implicit
|
||||
padding between short fields. However, if a field is tagged
|
||||
`marshal:"unaligned"`, `go_marshal` will fall back to a safer but slower
|
||||
mechanism to deal with potentially unaligned fields.
|
||||
|
||||
Note that the non-packed property is inheritted by any other struct that embeds
|
||||
this struct, since the `go_marshal` tool currently can't reason about alignments
|
||||
for embedded structs that are not aligned.
|
||||
|
||||
Because of this, it's generally best to avoid using `marshal:"unaligned"` and
|
||||
insert explicit padding fields instead.
|
||||
|
||||
## Debugging go_marshal
|
||||
|
||||
To enable debugging output from the go marshal tool, pass the `-debug` flag to
|
||||
the tool. When using the build rules from above, add a `debug = True` field to
|
||||
the build rule like this:
|
||||
|
||||
```
|
||||
load("<PKGPATH>/gvisor/tools/go_marshal:defs.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = ["foo.go"],
|
||||
debug = True,
|
||||
)
|
||||
```
|
||||
|
||||
## Modifying the `go_marshal` Tool
|
||||
|
||||
The following are some guidelines for modifying the `go_marshal` tool:
|
||||
|
||||
- The `go_marshal` tool currently does a single pass over all types requesting
|
||||
code generation, in arbitrary order. This means the generated code can't
|
||||
directly obtain information about embedded marshallable types at
|
||||
compile-time. One way to work around this restriction is to add a new
|
||||
Marshallable interface method providing this piece of information, and
|
||||
calling it from the generated code. Use this sparingly, as we want to rely
|
||||
on compile-time information as much as possible for performance.
|
||||
|
||||
- No runtime reflection in the code generated for the marshallable interface.
|
||||
The entire point of the tool is to avoid runtime reflection. The generated
|
||||
tests may use reflection.
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "analysis",
|
||||
testonly = 1,
|
||||
srcs = ["analysis_unsafe.go"],
|
||||
importpath = "gvisor.dev/gvisor/tools/go_marshal/analysis",
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2019 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 analysis implements common functionality used by generated
|
||||
// go_marshal tests.
|
||||
package analysis
|
||||
|
||||
// All functions in this package are unsafe and are not intended for general
|
||||
// consumption. They contain sharp edge cases and the caller is responsible for
|
||||
// ensuring none of them are hit. Callers must be carefully to pass in only sane
|
||||
// arguments. Failure to do so may cause panics at best and arbitrary memory
|
||||
// corruption at worst.
|
||||
//
|
||||
// Never use outside of tests.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// RandomizeValue assigns random value(s) to an abitrary type. This is intended
|
||||
// for used with ABI structs from go_marshal, meaning the typical restrictions
|
||||
// apply (fixed-size types, no pointers, maps, channels, etc), and should only
|
||||
// be used on zeroed values to avoid overwriting pointers to active go objects.
|
||||
//
|
||||
// Internally, we populate the type with random data by doing an unsafe cast to
|
||||
// access the underlying memory of the type and filling it as if it were a byte
|
||||
// slice. This almost gets us what we want, but padding fields named "_" are
|
||||
// normally not accessible, so we walk the type and recursively zero all "_"
|
||||
// fields.
|
||||
//
|
||||
// Precondition: x must be a pointer. x must not contain any valid
|
||||
// pointers to active go objects (pointer fields aren't allowed in ABI
|
||||
// structs anyways), or we'd be violating the go runtime contract and
|
||||
// the GC may malfunction.
|
||||
func RandomizeValue(x interface{}) {
|
||||
v := reflect.Indirect(reflect.ValueOf(x))
|
||||
if !v.CanSet() {
|
||||
panic("RandomizeType() called with an unaddressable value. You probably need to pass a pointer to the argument")
|
||||
}
|
||||
|
||||
// Cast the underlying memory for the type into a byte slice.
|
||||
var b []byte
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
// Note: v.UnsafeAddr panics if x is passed by value. x should be a pointer.
|
||||
hdr.Data = v.UnsafeAddr()
|
||||
hdr.Len = int(v.Type().Size())
|
||||
hdr.Cap = hdr.Len
|
||||
|
||||
// Fill the byte slice with random data, which in effect fills the type with
|
||||
// random values.
|
||||
n, err := rand.Read(b)
|
||||
if err != nil || n != len(b) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Normally, padding fields are not accessible, so zero them out.
|
||||
reflectZeroPaddingFields(v.Type(), b, false)
|
||||
}
|
||||
|
||||
// reflectZeroPaddingFields assigns zero values to padding fields for the value
|
||||
// of type r, represented by the memory in data. Padding fields are defined as
|
||||
// fields with the name "_". If zero is true, the immediate value itself is
|
||||
// zeroed. In addition, the type is recursively scanned for padding fields in
|
||||
// inner types.
|
||||
//
|
||||
// This is used for zeroing padding fields after calling RandomizeValue.
|
||||
func reflectZeroPaddingFields(r reflect.Type, data []byte, zero bool) {
|
||||
if zero {
|
||||
for i, _ := range data {
|
||||
data[i] = 0
|
||||
}
|
||||
}
|
||||
switch r.Kind() {
|
||||
case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
|
||||
// These types are explicitly allowed in an ABI type, but we don't need
|
||||
// to recurse further as they're scalar types.
|
||||
case reflect.Struct:
|
||||
for i, numFields := 0, r.NumField(); i < numFields; i++ {
|
||||
f := r.Field(i)
|
||||
off := f.Offset
|
||||
len := f.Type.Size()
|
||||
window := data[off : off+len]
|
||||
reflectZeroPaddingFields(f.Type, window, f.Name == "_")
|
||||
}
|
||||
case reflect.Array:
|
||||
eLen := int(r.Elem().Size())
|
||||
if int(r.Size()) != eLen*r.Len() {
|
||||
panic("Array has unexpected size?")
|
||||
}
|
||||
for i, n := 0, r.Len(); i < n; i++ {
|
||||
reflectZeroPaddingFields(r.Elem(), data[i*eLen:(i+1)*eLen], false)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Type %v not allowed in ABI struct", r.Kind()))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// AlignmentCheck ensures the definition of the type represented by typ doesn't
|
||||
// cause the go compiler to emit implicit padding between elements of the type
|
||||
// (i.e. fields in a struct).
|
||||
//
|
||||
// AlignmentCheck doesn't explicitly recurse for embedded structs because any
|
||||
// struct present in an ABI struct must also be Marshallable, and therefore
|
||||
// they're aligned by definition (or their alignment check would have failed).
|
||||
func AlignmentCheck(t *testing.T, typ reflect.Type) (ok bool, delta uint64) {
|
||||
switch typ.Kind() {
|
||||
case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
|
||||
// Primitive types are always considered well aligned. Primitive types
|
||||
// that are fields in structs are checked independently, this branch
|
||||
// exists to handle recursive calls to alignmentCheck.
|
||||
case reflect.Struct:
|
||||
xOff := 0
|
||||
nextXOff := 0
|
||||
skipNext := false
|
||||
for i, numFields := 0, typ.NumField(); i < numFields; i++ {
|
||||
xOff = nextXOff
|
||||
f := typ.Field(i)
|
||||
fmt.Printf("Checking alignment of %s.%s @ %d [+%d]...\n", typ.Name(), f.Name, f.Offset, f.Type.Size())
|
||||
nextXOff = int(f.Offset + f.Type.Size())
|
||||
|
||||
if f.Name == "_" {
|
||||
// Padding fields need not be aligned.
|
||||
fmt.Printf("Padding field of type %v\n", f.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
if tag, ok := f.Tag.Lookup("marshal"); ok && tag == "unaligned" {
|
||||
skipNext = true
|
||||
continue
|
||||
}
|
||||
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
fmt.Printf("Skipping alignment check for field %s.%s explicitly marked as unaligned.\n", typ.Name(), f.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if xOff != int(f.Offset) {
|
||||
implicitPad := int(f.Offset) - xOff
|
||||
t.Fatalf("Suspect offset for field %s.%s, detected an implicit %d byte padding from offset %d to %d; either add %d bytes of explicit padding before this field or tag it as `marshal:\"unaligned\"`.", typ.Name(), f.Name, implicitPad, xOff, f.Offset, implicitPad)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure structs end on a byte explicitly defined by the type.
|
||||
if typ.NumField() > 0 && nextXOff != int(typ.Size()) {
|
||||
implicitPad := int(typ.Size()) - nextXOff
|
||||
f := typ.Field(typ.NumField() - 1) // Final field
|
||||
t.Fatalf("Suspect offset for field %s.%s at the end of %s, detected an implicit %d byte padding from offset %d to %d at the end of the struct; either add %d bytes of explict padding at end of the struct or tag the final field %s as `marshal:\"unaligned\"`.",
|
||||
typ.Name(), f.Name, typ.Name(), implicitPad, nextXOff, typ.Size(), implicitPad, f.Name)
|
||||
}
|
||||
case reflect.Array:
|
||||
// Independent arrays are also always considered well aligned. We only
|
||||
// need to worry about their alignment when they're embedded in structs,
|
||||
// which we handle above.
|
||||
default:
|
||||
t.Fatalf("Unsupported type in ABI struct while checking for field alignment for type: %v", typ.Kind())
|
||||
}
|
||||
return true, uint64(typ.Size())
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Marshal is a tool for generating marshalling interfaces for Go types.
|
||||
|
||||
The recommended way is to use the go_library rule defined below with mostly
|
||||
identical configuration as the native go_library rule.
|
||||
|
||||
load("//tools/go_marshal:defs.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = ["foo.go"],
|
||||
)
|
||||
|
||||
Under the hood, the go_marshal rule is used to generate a file that will
|
||||
appear in a Go target; the output file should appear explicitly in a srcs list.
|
||||
For example (the above is still the preferred way):
|
||||
|
||||
load("//tools/go_marshal:defs.bzl", "go_marshal")
|
||||
|
||||
go_marshal(
|
||||
name = "foo_abi",
|
||||
srcs = ["foo.go"],
|
||||
out = "foo_abi.go",
|
||||
package = "foo",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "foo",
|
||||
srcs = [
|
||||
"foo.go",
|
||||
"foo_abi.go",
|
||||
],
|
||||
deps = [
|
||||
"//tools/go_marshal:marshal",
|
||||
"//pkg/sentry/platform/safecopy",
|
||||
"//pkg/sentry/usermem",
|
||||
],
|
||||
)
|
||||
"""
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", _go_library = "go_library", _go_test = "go_test")
|
||||
|
||||
def _go_marshal_impl(ctx):
|
||||
"""Execute the go_marshal tool."""
|
||||
output = ctx.outputs.lib
|
||||
output_test = ctx.outputs.test
|
||||
(build_dir, _, _) = ctx.build_file_path.rpartition("/BUILD")
|
||||
|
||||
decl = "/".join(["gvisor.dev/gvisor", build_dir])
|
||||
|
||||
# Run the marshal command.
|
||||
args = ["-output=%s" % output.path]
|
||||
args += ["-pkg=%s" % ctx.attr.package]
|
||||
args += ["-output_test=%s" % output_test.path]
|
||||
args += ["-declarationPkg=%s" % decl]
|
||||
|
||||
if ctx.attr.debug:
|
||||
args += ["-debug"]
|
||||
|
||||
args += ["--"]
|
||||
for src in ctx.attr.srcs:
|
||||
args += [f.path for f in src.files.to_list()]
|
||||
ctx.actions.run(
|
||||
inputs = ctx.files.srcs,
|
||||
outputs = [output, output_test],
|
||||
mnemonic = "GoMarshal",
|
||||
progress_message = "go_marshal: %s" % ctx.label,
|
||||
arguments = args,
|
||||
executable = ctx.executable._tool,
|
||||
)
|
||||
|
||||
# Generates save and restore logic from a set of Go files.
|
||||
#
|
||||
# Args:
|
||||
# name: the name of the rule.
|
||||
# srcs: the input source files. These files should include all structs in the
|
||||
# package that need to be saved.
|
||||
# imports: an optional list of extra, non-aliased, Go-style absolute import
|
||||
# paths.
|
||||
# out: the name of the generated file output. This must not conflict with any
|
||||
# other files and must be added to the srcs of the relevant go_library.
|
||||
# package: the package name for the input sources.
|
||||
go_marshal = rule(
|
||||
implementation = _go_marshal_impl,
|
||||
attrs = {
|
||||
"srcs": attr.label_list(mandatory = True, allow_files = True),
|
||||
"libname": attr.string(mandatory = True),
|
||||
"imports": attr.string_list(mandatory = False),
|
||||
"package": attr.string(mandatory = True),
|
||||
"debug": attr.bool(doc = "enable debugging output from the go_marshal tool"),
|
||||
"_tool": attr.label(executable = True, cfg = "host", default = Label("//tools/go_marshal:go_marshal")),
|
||||
},
|
||||
outputs = {
|
||||
"lib": "%{name}_unsafe.go",
|
||||
"test": "%{name}_test.go",
|
||||
},
|
||||
)
|
||||
|
||||
def go_library(name, srcs, deps = [], imports = [], debug = False, **kwargs):
|
||||
"""wraps the standard go_library and does mashalling interface generation.
|
||||
|
||||
Args:
|
||||
name: Same as native go_library.
|
||||
srcs: Same as native go_library.
|
||||
deps: Same as native go_library.
|
||||
imports: Extra import paths to pass to the go_marshal tool.
|
||||
debug: Enables debugging output from the go_marshal tool.
|
||||
**kwargs: Remaining args to pass to the native go_library rule unmodified.
|
||||
"""
|
||||
go_marshal(
|
||||
name = name + "_abi_autogen",
|
||||
libname = name,
|
||||
srcs = [src for src in srcs if src.endswith(".go")],
|
||||
debug = debug,
|
||||
imports = imports,
|
||||
package = name,
|
||||
)
|
||||
|
||||
extra_deps = [
|
||||
"//tools/go_marshal/marshal",
|
||||
"//pkg/sentry/platform/safecopy",
|
||||
"//pkg/sentry/usermem",
|
||||
]
|
||||
|
||||
all_srcs = srcs + [name + "_abi_autogen_unsafe.go"]
|
||||
all_deps = deps + [] # + extra_deps
|
||||
|
||||
for extra in extra_deps:
|
||||
if extra not in deps:
|
||||
all_deps.append(extra)
|
||||
|
||||
_go_library(
|
||||
name = name,
|
||||
srcs = all_srcs,
|
||||
deps = all_deps,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# Don't pass importpath arg to go_test.
|
||||
kwargs.pop("importpath", "")
|
||||
|
||||
_go_test(
|
||||
name = name + "_abi_autogen_test",
|
||||
srcs = [name + "_abi_autogen_test.go"],
|
||||
# Generated test has a fixed set of dependencies since we generate these
|
||||
# tests. They should only depend on the library generated above, and the
|
||||
# Marshallable interface.
|
||||
deps = [
|
||||
":" + name,
|
||||
"//tools/go_marshal/analysis",
|
||||
],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def go_test(**kwargs):
|
||||
"""Wraps the standard go_test."""
|
||||
_go_test(
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "gomarshal",
|
||||
srcs = [
|
||||
"generator.go",
|
||||
"generator_interfaces.go",
|
||||
"generator_tests.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/tools/go_marshal/gomarshal",
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright 2019 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 gomarshal implements the go_marshal code generator. See README.md.
|
||||
package gomarshal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
marshalImport = "gvisor.dev/gvisor/tools/go_marshal/marshal"
|
||||
usermemImport = "gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
safecopyImport = "gvisor.dev/gvisor/pkg/sentry/platform/safecopy"
|
||||
)
|
||||
|
||||
// List of identifiers we use in generated code, that may conflict a
|
||||
// similarly-named source identifier. Avoid problems by refusing the generate
|
||||
// code when we see these.
|
||||
//
|
||||
// This only applies to import aliases at the moment. All other identifiers
|
||||
// are qualified by a receiver argument, since they're struct fields.
|
||||
//
|
||||
// All recievers are single letters, so we don't allow import aliases to be a
|
||||
// single letter.
|
||||
var badIdents = []string{
|
||||
"src", "srcs", "dst", "dsts", "blk", "buf", "err",
|
||||
// All single-letter identifiers.
|
||||
}
|
||||
|
||||
// Generator drives code generation for a single invocation of the go_marshal
|
||||
// utility.
|
||||
//
|
||||
// The Generator holds arguments passed to the tool, and drives parsing,
|
||||
// processing and code Generator for all types marked with +marshal declared in
|
||||
// the input files.
|
||||
//
|
||||
// See Generator.run() as the entry point.
|
||||
type Generator struct {
|
||||
// Paths to input go source files.
|
||||
inputs []string
|
||||
// Output file to write generated go source.
|
||||
output *os.File
|
||||
// Output file to write generated tests.
|
||||
outputTest *os.File
|
||||
// Package name for the generated file.
|
||||
pkg string
|
||||
// Go import path for package we're processing. This package should directly
|
||||
// declare the type we're generating code for.
|
||||
declaration string
|
||||
// Set of extra packages to import in the generated file.
|
||||
imports *importTable
|
||||
}
|
||||
|
||||
// NewGenerator creates a new code Generator.
|
||||
func NewGenerator(srcs []string, out, outTest, pkg, declaration string, imports []string) (*Generator, error) {
|
||||
f, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't open output file %q: %v", out, err)
|
||||
}
|
||||
fTest, err := os.OpenFile(outTest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't open test output file %q: %v", out, err)
|
||||
}
|
||||
g := Generator{
|
||||
inputs: srcs,
|
||||
output: f,
|
||||
outputTest: fTest,
|
||||
pkg: pkg,
|
||||
declaration: declaration,
|
||||
imports: newImportTable(),
|
||||
}
|
||||
for _, i := range imports {
|
||||
// All imports on the extra imports list are unconditionally marked as
|
||||
// used, so they're always added to the generated code.
|
||||
g.imports.add(i).markUsed()
|
||||
}
|
||||
g.imports.add(marshalImport).markUsed()
|
||||
// The follow imports may or may not be used by the generated
|
||||
// code, depending what's required for the target types. Don't
|
||||
// mark these imports as used by default.
|
||||
g.imports.add(usermemImport)
|
||||
g.imports.add(safecopyImport)
|
||||
g.imports.add("unsafe")
|
||||
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
// writeHeader writes the header for the generated source file. The header
|
||||
// includes the package name, package level comments and import statements.
|
||||
func (g *Generator) writeHeader() error {
|
||||
var b sourceBuffer
|
||||
b.emit("// Automatically generated marshal implementation. See tools/go_marshal.\n\n")
|
||||
b.emit("package %s\n\n", g.pkg)
|
||||
if err := b.write(g.output); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return g.imports.write(g.output)
|
||||
}
|
||||
|
||||
// writeTypeChecks writes a statement to force the compiler to perform a type
|
||||
// check for all Marshallable types referenced by the generated code.
|
||||
func (g *Generator) writeTypeChecks(ms map[string]struct{}) error {
|
||||
if len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
msl := make([]string, 0, len(ms))
|
||||
for m, _ := range ms {
|
||||
msl = append(msl, m)
|
||||
}
|
||||
sort.Strings(msl)
|
||||
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "// Marshallable types used by this file.\n")
|
||||
|
||||
for _, m := range msl {
|
||||
fmt.Fprintf(&buf, "var _ marshal.Marshallable = (*%s)(nil)\n", m)
|
||||
}
|
||||
fmt.Fprint(&buf, "\n")
|
||||
|
||||
_, err := fmt.Fprint(g.output, buf.String())
|
||||
return err
|
||||
}
|
||||
|
||||
// parse processes all input files passed this generator and produces a set of
|
||||
// parsed go ASTs.
|
||||
func (g *Generator) parse() ([]*ast.File, []*token.FileSet, error) {
|
||||
debugf("go_marshal invoked with %d input files:\n", len(g.inputs))
|
||||
for _, path := range g.inputs {
|
||||
debugf(" %s\n", path)
|
||||
}
|
||||
|
||||
files := make([]*ast.File, 0, len(g.inputs))
|
||||
fsets := make([]*token.FileSet, 0, len(g.inputs))
|
||||
|
||||
for _, path := range g.inputs {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
// Not a valid input file?
|
||||
return nil, nil, fmt.Errorf("Input %q can't be parsed: %v", path, err)
|
||||
}
|
||||
|
||||
if debugEnabled() {
|
||||
debugf("AST for %q:\n", path)
|
||||
ast.Print(fset, f)
|
||||
}
|
||||
|
||||
files = append(files, f)
|
||||
fsets = append(fsets, fset)
|
||||
}
|
||||
|
||||
return files, fsets, nil
|
||||
}
|
||||
|
||||
// collectMarshallabeTypes walks the parsed AST and collects a list of type
|
||||
// declarations for which we need to generate the Marshallable interface.
|
||||
func (g *Generator) collectMarshallabeTypes(a *ast.File, f *token.FileSet) []*ast.TypeSpec {
|
||||
var types []*ast.TypeSpec
|
||||
for _, decl := range a.Decls {
|
||||
gdecl, ok := decl.(*ast.GenDecl)
|
||||
// Type declaration?
|
||||
if !ok || gdecl.Tok != token.TYPE {
|
||||
debugfAt(f.Position(decl.Pos()), "Skipping declaration since it's not a type declaration.\n")
|
||||
continue
|
||||
}
|
||||
// Does it have a comment?
|
||||
if gdecl.Doc == nil {
|
||||
debugfAt(f.Position(gdecl.Pos()), "Skipping declaration since it doesn't have a comment.\n")
|
||||
continue
|
||||
}
|
||||
// Does the comment contain a "+marshal" line?
|
||||
marked := false
|
||||
for _, c := range gdecl.Doc.List {
|
||||
if c.Text == "// +marshal" {
|
||||
marked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !marked {
|
||||
debugfAt(f.Position(gdecl.Pos()), "Skipping declaration since it doesn't have a comment containing +marshal line.\n")
|
||||
continue
|
||||
}
|
||||
for _, spec := range gdecl.Specs {
|
||||
// We already confirmed we're in a type declaration earlier.
|
||||
t := spec.(*ast.TypeSpec)
|
||||
if _, ok := t.Type.(*ast.StructType); ok {
|
||||
debugfAt(f.Position(t.Pos()), "Collected marshallable type %s.\n", t.Name.Name)
|
||||
types = append(types, t)
|
||||
continue
|
||||
}
|
||||
debugf("Skipping declaration %v since it's not a struct declaration.\n", gdecl)
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
// collectImports collects all imports from all input source files. Some of
|
||||
// these imports are copied to the generated output, if they're referenced by
|
||||
// the generated code.
|
||||
//
|
||||
// collectImports de-duplicates imports while building the list, and ensures
|
||||
// identifiers in the generated code don't conflict with any imported package
|
||||
// names.
|
||||
func (g *Generator) collectImports(a *ast.File, f *token.FileSet) map[string]importStmt {
|
||||
badImportNames := make(map[string]bool)
|
||||
for _, i := range badIdents {
|
||||
badImportNames[i] = true
|
||||
}
|
||||
|
||||
is := make(map[string]importStmt)
|
||||
for _, decl := range a.Decls {
|
||||
gdecl, ok := decl.(*ast.GenDecl)
|
||||
// Import statement?
|
||||
if !ok || gdecl.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for _, spec := range gdecl.Specs {
|
||||
i := g.imports.addFromSpec(spec.(*ast.ImportSpec), f)
|
||||
debugf("Collected import '%s' as '%s'\n", i.path, i.name)
|
||||
|
||||
// Make sure we have an import that doesn't use any local names that
|
||||
// would conflict with identifiers in the generated code.
|
||||
if len(i.name) == 1 {
|
||||
abortAt(f.Position(spec.Pos()), fmt.Sprintf("Import has a single character local name '%s'; this may conflict with code generated by go_marshal, use a multi-character import alias", i.name))
|
||||
}
|
||||
if badImportNames[i.name] {
|
||||
abortAt(f.Position(spec.Pos()), fmt.Sprintf("Import name '%s' is likely to conflict with code generated by go_marshal, use a different import alias", i.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return is
|
||||
|
||||
}
|
||||
|
||||
func (g *Generator) generateOne(t *ast.TypeSpec, fset *token.FileSet) *interfaceGenerator {
|
||||
// We're guaranteed to have only struct type specs by now. See
|
||||
// Generator.collectMarshallabeTypes.
|
||||
i := newInterfaceGenerator(t, fset)
|
||||
i.validate()
|
||||
i.emitMarshallable()
|
||||
return i
|
||||
}
|
||||
|
||||
// generateOneTestSuite generates a test suite for the automatically generated
|
||||
// implementations type t.
|
||||
func (g *Generator) generateOneTestSuite(t *ast.TypeSpec) *testGenerator {
|
||||
i := newTestGenerator(t, g.declaration)
|
||||
i.emitTests()
|
||||
return i
|
||||
}
|
||||
|
||||
// Run is the entry point to code generation using g.
|
||||
//
|
||||
// Run parses all input source files specified in g and emits generated code.
|
||||
func (g *Generator) Run() error {
|
||||
// Parse our input source files into ASTs and token sets.
|
||||
asts, fsets, err := g.parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(asts) != len(fsets) {
|
||||
panic("ASTs and FileSets don't match")
|
||||
}
|
||||
|
||||
// Map of imports in source files; key = local package name, value = import
|
||||
// path.
|
||||
is := make(map[string]importStmt)
|
||||
for i, a := range asts {
|
||||
// Collect all imports from the source files. We may need to copy some
|
||||
// of these to the generated code if they're referenced. This has to be
|
||||
// done before the loop below because we need to process all ASTs before
|
||||
// we start requesting imports to be copied one by one as we encounter
|
||||
// them in each generated source.
|
||||
for name, i := range g.collectImports(a, fsets[i]) {
|
||||
is[name] = i
|
||||
}
|
||||
}
|
||||
|
||||
var impls []*interfaceGenerator
|
||||
var ts []*testGenerator
|
||||
// Set of Marshallable types referenced by generated code.
|
||||
ms := make(map[string]struct{})
|
||||
for i, a := range asts {
|
||||
// Collect type declarations marked for code generation and generate
|
||||
// Marshallable interfaces.
|
||||
for _, t := range g.collectMarshallabeTypes(a, fsets[i]) {
|
||||
impl := g.generateOne(t, fsets[i])
|
||||
// Collect Marshallable types referenced by the generated code.
|
||||
for ref, _ := range impl.ms {
|
||||
ms[ref] = struct{}{}
|
||||
}
|
||||
impls = append(impls, impl)
|
||||
// Collect imports referenced by the generated code and add them to
|
||||
// the list of imports we need to copy to the generated code.
|
||||
for name, _ := range impl.is {
|
||||
if !g.imports.markUsed(name) {
|
||||
panic(fmt.Sprintf("Generated code for '%s' referenced a non-existent import with local name '%s'", impl.typeName(), name))
|
||||
}
|
||||
}
|
||||
ts = append(ts, g.generateOneTestSuite(t))
|
||||
}
|
||||
}
|
||||
|
||||
// Tool was invoked with input files with no data structures marked for code
|
||||
// generation. This is probably not what the user intended.
|
||||
if len(impls) == 0 {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "go_marshal invoked on these files, but they don't contain any types requiring code generation. Perhaps mark some with \"// +marshal\"?:\n")
|
||||
for _, i := range g.inputs {
|
||||
fmt.Fprintf(&buf, " %s\n", i)
|
||||
}
|
||||
abort(buf.String())
|
||||
}
|
||||
|
||||
// Write output file header. These include things like package name and
|
||||
// import statements.
|
||||
if err := g.writeHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write type checks for referenced marshallable types to output file.
|
||||
if err := g.writeTypeChecks(ms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write generated interfaces to output file.
|
||||
for _, i := range impls {
|
||||
if err := i.write(g.output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write generated tests to test file.
|
||||
return g.writeTests(ts)
|
||||
}
|
||||
|
||||
// writeTests outputs tests for the generated interface implementations to a go
|
||||
// source file.
|
||||
func (g *Generator) writeTests(ts []*testGenerator) error {
|
||||
var b sourceBuffer
|
||||
b.emit("package %s_test\n\n", g.pkg)
|
||||
if err := b.write(g.outputTest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imports := newImportTable()
|
||||
for _, t := range ts {
|
||||
imports.merge(t.imports)
|
||||
}
|
||||
|
||||
if err := imports.write(g.outputTest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range ts {
|
||||
if err := t.write(g.outputTest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,154 @@
|
||||
// Copyright 2019 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 gomarshal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var standardImports = []string{
|
||||
"fmt",
|
||||
"reflect",
|
||||
"testing",
|
||||
"gvisor.dev/gvisor/tools/go_marshal/analysis",
|
||||
}
|
||||
|
||||
type testGenerator struct {
|
||||
sourceBuffer
|
||||
|
||||
// The type we're serializing.
|
||||
t *ast.TypeSpec
|
||||
|
||||
// Receiver argument for generated methods.
|
||||
r string
|
||||
|
||||
// Imports used by generated code.
|
||||
imports *importTable
|
||||
|
||||
// Import statement for the package declaring the type we generated code
|
||||
// for. We need this to construct test instances for the type, since the
|
||||
// tests aren't written in the same package.
|
||||
decl *importStmt
|
||||
}
|
||||
|
||||
func newTestGenerator(t *ast.TypeSpec, declaration string) *testGenerator {
|
||||
if _, ok := t.Type.(*ast.StructType); !ok {
|
||||
panic(fmt.Sprintf("Attempting to generate code for a not struct type %v", t))
|
||||
}
|
||||
g := &testGenerator{
|
||||
t: t,
|
||||
r: receiverName(t),
|
||||
imports: newImportTable(),
|
||||
}
|
||||
|
||||
for _, i := range standardImports {
|
||||
g.imports.add(i).markUsed()
|
||||
}
|
||||
g.decl = g.imports.add(declaration)
|
||||
g.decl.markUsed()
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *testGenerator) typeName() string {
|
||||
return fmt.Sprintf("%s.%s", g.decl.name, g.t.Name.Name)
|
||||
}
|
||||
|
||||
func (g *testGenerator) forEachField(fn func(f *ast.Field)) {
|
||||
// This is guaranteed to succeed because g.t is always a struct.
|
||||
st := g.t.Type.(*ast.StructType)
|
||||
for _, field := range st.Fields.List {
|
||||
fn(field)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *testGenerator) testFuncName(base string) string {
|
||||
return fmt.Sprintf("%s%s", base, strings.Title(g.t.Name.Name))
|
||||
}
|
||||
|
||||
func (g *testGenerator) inTestFunction(name string, body func()) {
|
||||
g.emit("func %s(t *testing.T) {\n", g.testFuncName(name))
|
||||
g.inIndent(body)
|
||||
g.emit("}\n\n")
|
||||
}
|
||||
|
||||
func (g *testGenerator) emitTestNonZeroSize() {
|
||||
g.inTestFunction("TestSizeNonZero", func() {
|
||||
g.emit("x := &%s{}\n", g.typeName())
|
||||
g.emit("if x.SizeBytes() == 0 {\n")
|
||||
g.inIndent(func() {
|
||||
g.emit("t.Fatal(\"Marshallable.Size() should not return zero\")\n")
|
||||
})
|
||||
g.emit("}\n")
|
||||
})
|
||||
}
|
||||
|
||||
func (g *testGenerator) emitTestSuspectAlignment() {
|
||||
g.inTestFunction("TestSuspectAlignment", func() {
|
||||
g.emit("x := %s{}\n", g.typeName())
|
||||
g.emit("analysis.AlignmentCheck(t, reflect.TypeOf(x))\n")
|
||||
})
|
||||
}
|
||||
|
||||
func (g *testGenerator) emitTestMarshalUnmarshalPreservesData() {
|
||||
g.inTestFunction("TestSafeMarshalUnmarshalPreservesData", func() {
|
||||
g.emit("var x, y, z, yUnsafe, zUnsafe %s\n", g.typeName())
|
||||
g.emit("analysis.RandomizeValue(&x)\n\n")
|
||||
|
||||
g.emit("buf := make([]byte, x.SizeBytes())\n")
|
||||
g.emit("x.MarshalBytes(buf)\n")
|
||||
g.emit("bufUnsafe := make([]byte, x.SizeBytes())\n")
|
||||
g.emit("x.MarshalUnsafe(bufUnsafe)\n\n")
|
||||
|
||||
g.emit("y.UnmarshalBytes(buf)\n")
|
||||
g.emit("if !reflect.DeepEqual(x, y) {\n")
|
||||
g.inIndent(func() {
|
||||
g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across Marshal/Unmarshal cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, y))\n")
|
||||
})
|
||||
g.emit("}\n")
|
||||
g.emit("yUnsafe.UnmarshalBytes(bufUnsafe)\n")
|
||||
g.emit("if !reflect.DeepEqual(x, yUnsafe) {\n")
|
||||
g.inIndent(func() {
|
||||
g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/Unmarshal cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, yUnsafe))\n")
|
||||
})
|
||||
g.emit("}\n\n")
|
||||
|
||||
g.emit("z.UnmarshalUnsafe(buf)\n")
|
||||
g.emit("if !reflect.DeepEqual(x, z) {\n")
|
||||
g.inIndent(func() {
|
||||
g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across Marshal/UnmarshalUnsafe cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, z))\n")
|
||||
})
|
||||
g.emit("}\n")
|
||||
g.emit("zUnsafe.UnmarshalUnsafe(bufUnsafe)\n")
|
||||
g.emit("if !reflect.DeepEqual(x, zUnsafe) {\n")
|
||||
g.inIndent(func() {
|
||||
g.emit("t.Fatal(fmt.Sprintf(\"Data corrupted across MarshalUnsafe/UnmarshalUnsafe cycle:\\nBefore: %%+v\\nAfter: %%+v\\n\", x, zUnsafe))\n")
|
||||
})
|
||||
g.emit("}\n")
|
||||
})
|
||||
}
|
||||
|
||||
func (g *testGenerator) emitTests() {
|
||||
g.emitTestNonZeroSize()
|
||||
g.emitTestSuspectAlignment()
|
||||
g.emitTestMarshalUnmarshalPreservesData()
|
||||
}
|
||||
|
||||
func (g *testGenerator) write(out io.Writer) error {
|
||||
return g.sourceBuffer.write(out)
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
// Copyright 2019 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 gomarshal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var debug = flag.Bool("debug", false, "enables debugging output")
|
||||
|
||||
// receiverName returns an appropriate receiver name given a type spec.
|
||||
func receiverName(t *ast.TypeSpec) string {
|
||||
if len(t.Name.Name) < 1 {
|
||||
// Zero length type name?
|
||||
panic("unreachable")
|
||||
}
|
||||
return strings.ToLower(t.Name.Name[:1])
|
||||
}
|
||||
|
||||
// kindString returns a user-friendly representation of an AST expr type.
|
||||
func kindString(e ast.Expr) string {
|
||||
switch e.(type) {
|
||||
case *ast.Ident:
|
||||
return "scalar"
|
||||
case *ast.ArrayType:
|
||||
return "array"
|
||||
case *ast.StructType:
|
||||
return "struct"
|
||||
case *ast.StarExpr:
|
||||
return "pointer"
|
||||
case *ast.FuncType:
|
||||
return "function"
|
||||
case *ast.InterfaceType:
|
||||
return "interface"
|
||||
case *ast.MapType:
|
||||
return "map"
|
||||
case *ast.ChanType:
|
||||
return "channel"
|
||||
default:
|
||||
return reflect.TypeOf(e).String()
|
||||
}
|
||||
}
|
||||
|
||||
// fieldDispatcher is a collection of callbacks for handling different types of
|
||||
// fields in a struct declaration.
|
||||
type fieldDispatcher struct {
|
||||
primitive func(n, t *ast.Ident)
|
||||
selector func(n, tX, tSel *ast.Ident)
|
||||
array func(n, t *ast.Ident, size int)
|
||||
unhandled func(n *ast.Ident)
|
||||
}
|
||||
|
||||
// Precondition: All dispatch callbacks that will be invoked must be
|
||||
// provided. Embedded fields are not allowed, len(f.Names) >= 1.
|
||||
func (fd fieldDispatcher) dispatch(f *ast.Field) {
|
||||
// Each field declaration may actually be multiple declarations of the same
|
||||
// type. For example, consider:
|
||||
//
|
||||
// type Point struct {
|
||||
// x, y, z int
|
||||
// }
|
||||
//
|
||||
// We invoke the call-backs once per such instance. Embedded fields are not
|
||||
// allowed, and results in a panic.
|
||||
if len(f.Names) < 1 {
|
||||
panic("Precondition not met: attempted to dispatch on embedded field")
|
||||
}
|
||||
|
||||
for _, name := range f.Names {
|
||||
switch v := f.Type.(type) {
|
||||
case *ast.Ident:
|
||||
fd.primitive(name, v)
|
||||
case *ast.SelectorExpr:
|
||||
fd.selector(name, v.X.(*ast.Ident), v.Sel)
|
||||
case *ast.ArrayType:
|
||||
len := 0
|
||||
if v.Len != nil {
|
||||
// Non-literal array length is handled by generatorInterfaces.validate().
|
||||
if lenLit, ok := v.Len.(*ast.BasicLit); ok {
|
||||
var err error
|
||||
len, err = strconv.Atoi(lenLit.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch t := v.Elt.(type) {
|
||||
case *ast.Ident:
|
||||
fd.array(name, t, len)
|
||||
default:
|
||||
fd.array(name, nil, len)
|
||||
}
|
||||
default:
|
||||
fd.unhandled(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// debugEnabled indicates whether debugging is enabled for gomarshal.
|
||||
func debugEnabled() bool {
|
||||
return *debug
|
||||
}
|
||||
|
||||
// abort aborts the go_marshal tool with the given error message.
|
||||
func abort(msg string) {
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg += "\n"
|
||||
}
|
||||
fmt.Print(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// abortAt aborts the go_marshal tool with the given error message, with
|
||||
// a reference position to the input source.
|
||||
func abortAt(p token.Position, msg string) {
|
||||
abort(fmt.Sprintf("%v:\n %s\n", p, msg))
|
||||
}
|
||||
|
||||
// debugf conditionally prints a debug message.
|
||||
func debugf(f string, a ...interface{}) {
|
||||
if debugEnabled() {
|
||||
fmt.Printf(f, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// debugfAt conditionally prints a debug message with a reference to a position
|
||||
// in the input source.
|
||||
func debugfAt(p token.Position, f string, a ...interface{}) {
|
||||
if debugEnabled() {
|
||||
fmt.Printf("%s:\n %s", p, fmt.Sprintf(f, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// emit generates a line of code in the output file.
|
||||
//
|
||||
// emit is a wrapper around writing a formatted string to the output
|
||||
// buffer. emit can be invoked in one of two ways:
|
||||
//
|
||||
// (1) emit("some string")
|
||||
// When emit is called with a single string argument, it is simply copied to
|
||||
// the output buffer without any further formatting.
|
||||
// (2) emit(fmtString, args...)
|
||||
// emit can also be invoked in a similar fashion to *Printf() functions,
|
||||
// where the first argument is a format string.
|
||||
//
|
||||
// Calling emit with a single argument that is not a string will result in a
|
||||
// panic, as the caller's intent is ambiguous.
|
||||
func emit(out io.Writer, indent int, a ...interface{}) {
|
||||
const spacesPerIndentLevel = 4
|
||||
|
||||
if len(a) < 1 {
|
||||
panic("emit() called with no arguments")
|
||||
}
|
||||
|
||||
if indent > 0 {
|
||||
if _, err := fmt.Fprint(out, strings.Repeat(" ", indent*spacesPerIndentLevel)); err != nil {
|
||||
// Writing to the emit output should not fail. Typically the output
|
||||
// is a byte.Buffer; writes to these never fail.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
first, ok := a[0].(string)
|
||||
if !ok {
|
||||
// First argument must be either the string to emit (case 1 from
|
||||
// function-level comment), or a format string (case 2).
|
||||
panic(fmt.Sprintf("First argument to emit() is not a string: %+v", a[0]))
|
||||
}
|
||||
|
||||
if len(a) == 1 {
|
||||
// Single string argument. Assume no formatting requested.
|
||||
if _, err := fmt.Fprint(out, first); err != nil {
|
||||
// Writing to out should not fail.
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// Formatting requested.
|
||||
if _, err := fmt.Fprintf(out, first, a[1:]...); err != nil {
|
||||
// Writing to out should not fail.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// sourceBuffer represents fragments of generated go source code.
|
||||
//
|
||||
// sourceBuffer provides a convenient way to build up go souce fragments in
|
||||
// memory. May be safely zero-value initialized. Not thread-safe.
|
||||
type sourceBuffer struct {
|
||||
// Current indentation level.
|
||||
indent int
|
||||
|
||||
// Memory buffer containing contents while they're being generated.
|
||||
b bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) incIndent() {
|
||||
b.indent++
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) decIndent() {
|
||||
if b.indent <= 0 {
|
||||
panic("decIndent() without matching incIndent()")
|
||||
}
|
||||
b.indent--
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) emit(a ...interface{}) {
|
||||
emit(&b.b, b.indent, a...)
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) emitNoIndent(a ...interface{}) {
|
||||
emit(&b.b, 0 /*indent*/, a...)
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) inIndent(body func()) {
|
||||
b.incIndent()
|
||||
body()
|
||||
b.decIndent()
|
||||
}
|
||||
|
||||
func (b *sourceBuffer) write(out io.Writer) error {
|
||||
_, err := fmt.Fprint(out, b.b.String())
|
||||
return err
|
||||
}
|
||||
|
||||
// Write implements io.Writer.Write.
|
||||
func (b *sourceBuffer) Write(buf []byte) (int, error) {
|
||||
return (b.b.Write(buf))
|
||||
}
|
||||
|
||||
// importStmt represents a single import statement.
|
||||
type importStmt struct {
|
||||
// Local name of the imported package.
|
||||
name string
|
||||
// Import path.
|
||||
path string
|
||||
// Indicates whether the local name is an alias, or simply the final
|
||||
// component of the path.
|
||||
aliased bool
|
||||
// Indicates whether this import was referenced by generated code.
|
||||
used bool
|
||||
}
|
||||
|
||||
func newImport(p string) *importStmt {
|
||||
name := path.Base(p)
|
||||
return &importStmt{
|
||||
name: name,
|
||||
path: p,
|
||||
aliased: false,
|
||||
}
|
||||
}
|
||||
|
||||
func newImportFromSpec(spec *ast.ImportSpec, f *token.FileSet) *importStmt {
|
||||
p := spec.Path.Value[1 : len(spec.Path.Value)-1] // Strip the " quotes around path.
|
||||
name := path.Base(p)
|
||||
if name == "" || name == "/" || name == "." {
|
||||
panic(fmt.Sprintf("Couldn't process local package name for import at %s, (processed as %s)",
|
||||
f.Position(spec.Path.Pos()), name))
|
||||
}
|
||||
if spec.Name != nil {
|
||||
name = spec.Name.Name
|
||||
}
|
||||
return &importStmt{
|
||||
name: name,
|
||||
path: p,
|
||||
aliased: spec.Name != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importStmt) String() string {
|
||||
if i.aliased {
|
||||
return fmt.Sprintf("%s \"%s\"", i.name, i.path)
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", i.path)
|
||||
}
|
||||
|
||||
func (i *importStmt) markUsed() {
|
||||
i.used = true
|
||||
}
|
||||
|
||||
func (i *importStmt) equivalent(other *importStmt) bool {
|
||||
return i == other
|
||||
}
|
||||
|
||||
// importTable represents a collection of importStmts.
|
||||
type importTable struct {
|
||||
// Map of imports and whether they should be copied to the output.
|
||||
is map[string]*importStmt
|
||||
}
|
||||
|
||||
func newImportTable() *importTable {
|
||||
return &importTable{
|
||||
is: make(map[string]*importStmt),
|
||||
}
|
||||
}
|
||||
|
||||
// Merges import statements from other into i. Collisions in import statements
|
||||
// result in a panic.
|
||||
func (i *importTable) merge(other *importTable) {
|
||||
for name, im := range other.is {
|
||||
if dup, ok := i.is[name]; ok && dup.equivalent(im) {
|
||||
panic(fmt.Sprintf("Found colliding import statements: ours: %+v, other's: %+v", dup, im))
|
||||
}
|
||||
|
||||
i.is[name] = im
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importTable) add(s string) *importStmt {
|
||||
n := newImport(s)
|
||||
i.is[n.name] = n
|
||||
return n
|
||||
}
|
||||
|
||||
func (i *importTable) addFromSpec(spec *ast.ImportSpec, f *token.FileSet) *importStmt {
|
||||
n := newImportFromSpec(spec, f)
|
||||
i.is[n.name] = n
|
||||
return n
|
||||
}
|
||||
|
||||
// Marks the import named n as used. If no such import is in the table, returns
|
||||
// false.
|
||||
func (i *importTable) markUsed(n string) bool {
|
||||
if n, ok := i.is[n]; ok {
|
||||
n.markUsed()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *importTable) clear() {
|
||||
for _, i := range i.is {
|
||||
i.used = false
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importTable) write(out io.Writer) error {
|
||||
if len(i.is) == 0 {
|
||||
// Nothing to import, we're done.
|
||||
return nil
|
||||
}
|
||||
|
||||
imports := make([]string, 0, len(i.is))
|
||||
for _, i := range i.is {
|
||||
if i.used {
|
||||
imports = append(imports, i.String())
|
||||
}
|
||||
}
|
||||
sort.Strings(imports)
|
||||
|
||||
var b sourceBuffer
|
||||
b.emit("import (\n")
|
||||
b.incIndent()
|
||||
for _, i := range imports {
|
||||
b.emit("%s\n", i)
|
||||
}
|
||||
b.decIndent()
|
||||
b.emit(")\n\n")
|
||||
|
||||
return b.write(out)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// go_marshal is a code generation utility for automatically generating code to
|
||||
// marshal go data structures to memory.
|
||||
//
|
||||
// This binary is typically run as part of the build process, and is invoked by
|
||||
// the go_marshal bazel rule defined in defs.bzl.
|
||||
//
|
||||
// See README.md.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/tools/go_marshal/gomarshal"
|
||||
)
|
||||
|
||||
var (
|
||||
pkg = flag.String("pkg", "", "output package")
|
||||
output = flag.String("output", "", "output file")
|
||||
outputTest = flag.String("output_test", "", "output file for tests")
|
||||
imports = flag.String("imports", "", "comma-separated list of extra packages to import in generated code")
|
||||
declarationPkg = flag.String("declarationPkg", "", "import path of target declaring the types we're generating on")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s <input go src files>\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
if len(flag.Args()) == 0 {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *pkg == "" {
|
||||
flag.Usage()
|
||||
fmt.Fprint(os.Stderr, "Flag -pkg must be provided.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var extraImports []string
|
||||
if len(*imports) > 0 {
|
||||
// Note: strings.Split(s, sep) returns s if sep doesn't exist in s. Thus
|
||||
// we check for an empty imports list to avoid emitting an empty string
|
||||
// as an import.
|
||||
extraImports = strings.Split(*imports, ",")
|
||||
}
|
||||
g, err := gomarshal.NewGenerator(flag.Args(), *output, *outputTest, *pkg, *declarationPkg, extraImports)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := g.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "marshal",
|
||||
srcs = [
|
||||
"marshal.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/tools/go_marshal/marshal",
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2019 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 marshal defines the Marshallable interface for
|
||||
// serialize/deserializing go data structures to/from memory, according to the
|
||||
// Linux ABI.
|
||||
//
|
||||
// Implementations of this interface are typically automatically generated by
|
||||
// tools/go_marshal. See the go_marshal README for details.
|
||||
package marshal
|
||||
|
||||
// Marshallable represents a type that can be marshalled to and from memory.
|
||||
type Marshallable interface {
|
||||
// SizeBytes is the size of the memory representation of a type in
|
||||
// marshalled form.
|
||||
SizeBytes() int
|
||||
|
||||
// MarshalBytes serializes a copy of a type to dst. dst must be at least
|
||||
// SizeBytes() long.
|
||||
MarshalBytes(dst []byte)
|
||||
|
||||
// UnmarshalBytes deserializes a type from src. src must be at least
|
||||
// SizeBytes() long.
|
||||
UnmarshalBytes(src []byte)
|
||||
|
||||
// Packed returns true if the marshalled size of the type is the same as the
|
||||
// size it occupies in memory. This happens when the type has no fields
|
||||
// starting at unaligned addresses (should always be true by default for ABI
|
||||
// structs, verified by automatically generated tests when using
|
||||
// go_marshal), and has no fields marked `marshal:"unaligned"`.
|
||||
Packed() bool
|
||||
|
||||
// MarshalUnsafe serializes a type by bulk copying its in-memory
|
||||
// representation to the dst buffer. This is only safe to do when the type
|
||||
// has no implicit padding, see Marshallable.Packed. When Packed would
|
||||
// return false, MarshalUnsafe should fall back to the safer but slower
|
||||
// MarshalBytes.
|
||||
MarshalUnsafe(dst []byte)
|
||||
|
||||
// UnmarshalUnsafe deserializes a type directly to the underlying memory
|
||||
// allocated for the object by the runtime.
|
||||
//
|
||||
// This allows much faster unmarshalling of types which have no implicit
|
||||
// padding, see Marshallable.Packed. When Packed would return false,
|
||||
// UnmarshalUnsafe should fall back to the safer but slower unmarshal
|
||||
// mechanism implemented in UnmarshalBytes (usually by calling
|
||||
// UnmarshalBytes directly).
|
||||
UnmarshalUnsafe(src []byte)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package(licenses = ["notice"])
|
||||
|
||||
load("//tools/go_marshal:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package_group(
|
||||
name = "gomarshal_test",
|
||||
packages = [
|
||||
"//tools/go_marshal/test/...",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "benchmark_test",
|
||||
srcs = ["benchmark_test.go"],
|
||||
deps = [
|
||||
":test",
|
||||
"//pkg/binary",
|
||||
"//pkg/sentry/usermem",
|
||||
"//tools/go_marshal/analysis",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "test",
|
||||
testonly = 1,
|
||||
srcs = ["test.go"],
|
||||
importpath = "gvisor.dev/gvisor/tools/go_marshal/test",
|
||||
deps = ["//tools/go_marshal/test/external"],
|
||||
)
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright 2019 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 benchmark_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
encbin "encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/tools/go_marshal/analysis"
|
||||
test "gvisor.dev/gvisor/tools/go_marshal/test"
|
||||
)
|
||||
|
||||
// Marshalling using the standard encoding/binary package.
|
||||
func BenchmarkEncodingBinary(b *testing.B) {
|
||||
var s1, s2 test.Stat
|
||||
analysis.RandomizeValue(&s1)
|
||||
|
||||
size := encbin.Size(&s1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf := bytes.NewBuffer(make([]byte, size))
|
||||
buf.Reset()
|
||||
if err := encbin.Write(buf, usermem.ByteOrder, &s1); err != nil {
|
||||
b.Error("Write:", err)
|
||||
}
|
||||
if err := encbin.Read(buf, usermem.ByteOrder, &s2); err != nil {
|
||||
b.Error("Read:", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
// Sanity check, make sure the values were preserved.
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshalling using the sentry's binary.Marshal.
|
||||
func BenchmarkBinary(b *testing.B) {
|
||||
var s1, s2 test.Stat
|
||||
analysis.RandomizeValue(&s1)
|
||||
|
||||
size := binary.Size(s1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf := make([]byte, 0, size)
|
||||
buf = binary.Marshal(buf, usermem.ByteOrder, &s1)
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &s2)
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
// Sanity check, make sure the values were preserved.
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshalling field-by-field with manually-written code.
|
||||
func BenchmarkMarshalManual(b *testing.B) {
|
||||
var s1, s2 test.Stat
|
||||
analysis.RandomizeValue(&s1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf := make([]byte, 0, s1.SizeBytes())
|
||||
|
||||
// Marshal
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Dev)
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Ino)
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Nlink)
|
||||
buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.Mode)
|
||||
buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.UID)
|
||||
buf = binary.AppendUint32(buf, usermem.ByteOrder, s1.GID)
|
||||
buf = binary.AppendUint32(buf, usermem.ByteOrder, 0)
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, s1.Rdev)
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Size))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Blksize))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.Blocks))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.ATime.Sec))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.ATime.Nsec))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.MTime.Sec))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.MTime.Nsec))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.CTime.Sec))
|
||||
buf = binary.AppendUint64(buf, usermem.ByteOrder, uint64(s1.CTime.Nsec))
|
||||
|
||||
// Unmarshal
|
||||
s2.Dev = usermem.ByteOrder.Uint64(buf[0:8])
|
||||
s2.Ino = usermem.ByteOrder.Uint64(buf[8:16])
|
||||
s2.Nlink = usermem.ByteOrder.Uint64(buf[16:24])
|
||||
s2.Mode = usermem.ByteOrder.Uint32(buf[24:28])
|
||||
s2.UID = usermem.ByteOrder.Uint32(buf[28:32])
|
||||
s2.GID = usermem.ByteOrder.Uint32(buf[32:36])
|
||||
// Padding: buf[36:40]
|
||||
s2.Rdev = usermem.ByteOrder.Uint64(buf[40:48])
|
||||
s2.Size = int64(usermem.ByteOrder.Uint64(buf[48:56]))
|
||||
s2.Blksize = int64(usermem.ByteOrder.Uint64(buf[56:64]))
|
||||
s2.Blocks = int64(usermem.ByteOrder.Uint64(buf[64:72]))
|
||||
s2.ATime.Sec = int64(usermem.ByteOrder.Uint64(buf[72:80]))
|
||||
s2.ATime.Nsec = int64(usermem.ByteOrder.Uint64(buf[80:88]))
|
||||
s2.MTime.Sec = int64(usermem.ByteOrder.Uint64(buf[88:96]))
|
||||
s2.MTime.Nsec = int64(usermem.ByteOrder.Uint64(buf[96:104]))
|
||||
s2.CTime.Sec = int64(usermem.ByteOrder.Uint64(buf[104:112]))
|
||||
s2.CTime.Nsec = int64(usermem.ByteOrder.Uint64(buf[112:120]))
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
// Sanity check, make sure the values were preserved.
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshalling with the go_marshal safe API.
|
||||
func BenchmarkGoMarshalSafe(b *testing.B) {
|
||||
var s1, s2 test.Stat
|
||||
analysis.RandomizeValue(&s1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf := make([]byte, s1.SizeBytes())
|
||||
s1.MarshalBytes(buf)
|
||||
s2.UnmarshalBytes(buf)
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
// Sanity check, make sure the values were preserved.
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshalling with the go_marshal unsafe API.
|
||||
func BenchmarkGoMarshalUnsafe(b *testing.B) {
|
||||
var s1, s2 test.Stat
|
||||
analysis.RandomizeValue(&s1)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf := make([]byte, s1.SizeBytes())
|
||||
s1.MarshalUnsafe(buf)
|
||||
s2.UnmarshalUnsafe(buf)
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
// Sanity check, make sure the values were preserved.
|
||||
if !reflect.DeepEqual(s1, s2) {
|
||||
panic(fmt.Sprintf("Data corruption across marshal/unmarshal cycle:\nBefore: %+v\nAfter: %+v\n", s1, s2))
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
package(licenses = ["notice"])
|
||||
|
||||
load("//tools/go_marshal:defs.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "external",
|
||||
testonly = 1,
|
||||
srcs = ["external.go"],
|
||||
importpath = "gvisor.dev/gvisor/tools/go_marshal/test/external",
|
||||
visibility = ["//tools/go_marshal/test:gomarshal_test"],
|
||||
)
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 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 external defines types we can import for testing.
|
||||
package external
|
||||
|
||||
// External is a public Marshallable type for use in testing.
|
||||
//
|
||||
// +marshal
|
||||
type External struct {
|
||||
j int64
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2019 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 test contains data structures for testing the go_marshal tool.
|
||||
package test
|
||||
|
||||
import (
|
||||
// We're intentionally using a package name alias here even though it's not
|
||||
// necessary to test the code generator's ability to handle package aliases.
|
||||
ex "gvisor.dev/gvisor/tools/go_marshal/test/external"
|
||||
)
|
||||
|
||||
// Type1 is a test data type.
|
||||
//
|
||||
// +marshal
|
||||
type Type1 struct {
|
||||
a Type2
|
||||
x, y int64 // Multiple field names.
|
||||
b byte `marshal:"unaligned"` // Short field.
|
||||
c uint64
|
||||
_ uint32 // Unnamed scalar field.
|
||||
_ [6]byte // Unnamed vector field, typical padding.
|
||||
_ [2]byte
|
||||
xs [8]int32
|
||||
as [10]Type2 `marshal:"unaligned"` // Array of Marshallable objects.
|
||||
ss Type3
|
||||
}
|
||||
|
||||
// Type2 is a test data type.
|
||||
//
|
||||
// +marshal
|
||||
type Type2 struct {
|
||||
n int64
|
||||
c byte
|
||||
_ [7]byte
|
||||
m int64
|
||||
a int64
|
||||
}
|
||||
|
||||
// Type3 is a test data type.
|
||||
//
|
||||
// +marshal
|
||||
type Type3 struct {
|
||||
s int64
|
||||
x ex.External // Type defined in another package.
|
||||
}
|
||||
|
||||
// Type4 is a test data type.
|
||||
//
|
||||
// +marshal
|
||||
type Type4 struct {
|
||||
c byte
|
||||
x int64 `marshal:"unaligned"`
|
||||
d byte
|
||||
_ [7]byte
|
||||
}
|
||||
|
||||
// Type5 is a test data type.
|
||||
//
|
||||
// +marshal
|
||||
type Type5 struct {
|
||||
n int64
|
||||
t Type4
|
||||
m int64
|
||||
}
|
||||
|
||||
// Timespec represents struct timespec in <time.h>.
|
||||
//
|
||||
// +marshal
|
||||
type Timespec struct {
|
||||
Sec int64
|
||||
Nsec int64
|
||||
}
|
||||
|
||||
// Stat represents struct stat.
|
||||
//
|
||||
// +marshal
|
||||
type Stat struct {
|
||||
Dev uint64
|
||||
Ino uint64
|
||||
Nlink uint64
|
||||
Mode uint32
|
||||
UID uint32
|
||||
GID uint32
|
||||
_ int32
|
||||
Rdev uint64
|
||||
Size int64
|
||||
Blksize int64
|
||||
Blocks int64
|
||||
ATime Timespec
|
||||
MTime Timespec
|
||||
CTime Timespec
|
||||
_ [3]int64
|
||||
}
|
||||
Reference in New Issue
Block a user