Add internal nogo analysis & checkescape tool.

See tools/nogo/README.md.

The checkescape tool is able to perform recursive escape analysis, using the
actual generated binary to confirm the results produced by the compiler itself.

As an initial use case, this replaces the manual escape analysis tests used for
go_marshal, and validates that the CopyIn and CopyOut paths will not require
any allocation or stack splits.

Updates #2243

PiperOrigin-RevId: 307532986
This commit is contained in:
Adin Scannell
2020-04-20 20:58:20 -07:00
committed by gVisor bot
parent 782041509f
commit c615aafa21
34 changed files with 2269 additions and 242 deletions
-33
View File
@@ -44,39 +44,6 @@ go_path(
# bazel run //:gazelle -- update-repos -from_file=go.mod
gazelle(name = "gazelle")
# nogo applies checks to all Go source in this repository, enforcing code
# guidelines and restrictions. Note that the tool libraries themselves should
# live in the tools subdirectory (unless they are standard).
nogo(
name = "nogo",
config = "//tools:nogo.json",
visibility = ["//visibility:public"],
deps = [
"//tools/checkunsafe",
"@org_golang_x_tools//go/analysis/passes/asmdecl:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/assign:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/atomic:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/atomicalign:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/bools:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/buildtag:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/cgocall:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/copylock:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/deepequalerrors:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/loopclosure:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/lostcancel:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/nilfunc:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/nilness:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/printf:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/shift:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/stdmethods:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/structtag:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/tests:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/unmarshal:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/unsafeptr:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/unusedresult:go_tool_library",
],
)
# We need to define a bazel platform and toolchain to specify dockerPrivileged
# and dockerRunAsRoot options, they are required to run tests on the RBE
# cluster in Kokoro.
+21 -12
View File
@@ -2,8 +2,16 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
# Load go bazel rules and gazelle.
#
# Note that this repository actually patches some other Go repositories as it
# loads it, in order to limit visibility. We hack this process by patching the
# patch used by the Go rules, turning the trick against itself.
http_archive(
name = "io_bazel_rules_go",
patch_args = ["-p1"],
patches = [
"//tools/nogo:io_bazel_rules_go-visibility.patch",
],
sha256 = "db2b2d35293f405430f553bc7a865a8749a8ef60c30287e90d2b278c32771afe",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.22.3/rules_go-v0.22.3.tar.gz",
@@ -24,10 +32,7 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe
go_rules_dependencies()
go_register_toolchains(
go_version = "1.14.2",
nogo = "@//:nogo",
)
go_register_toolchains(go_version = "1.14.2")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
@@ -170,9 +175,13 @@ http_archive(
"https://github.com/grpc/grpc/archive/v1.26.0.tar.gz",
],
)
load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
grpc_deps()
load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps")
grpc_extra_deps()
# External repositories, in sorted order.
@@ -221,8 +230,8 @@ go_repository(
go_repository(
name = "com_github_imdario_mergo",
importpath = "github.com/imdario/mergo",
version = "v0.3.8",
sum = "h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=",
version = "v0.3.8",
)
go_repository(
@@ -248,8 +257,8 @@ go_repository(
go_repository(
name = "com_github_mohae_deepcopy",
importpath = "github.com/mohae/deepcopy",
commit = "c48cc78d482608239f6c4c92a4abd87eb8761c90",
importpath = "github.com/mohae/deepcopy",
)
go_repository(
@@ -298,8 +307,8 @@ go_repository(
go_repository(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",
sum = "h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=",
version = "v0.0.0-20191011191535-87dc89f01550",
sum = "h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=",
version = "v0.0.0-20190308221718-c2843e01d9a2",
)
go_repository(
@@ -340,15 +349,15 @@ go_repository(
go_repository(
name = "org_golang_x_tools",
importpath = "golang.org/x/tools",
sum = "h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ=",
version = "v0.0.0-20191119224855-298f0cb1881e",
sum = "h1:Uglradbb4KfUWaYasZhlsDsGRwHHvRsHoNAEONef0W8=",
version = "v0.0.0-20200131233409-575de47986ce",
)
go_repository(
name = "org_golang_x_xerrors",
importpath = "golang.org/x/xerrors",
sum = "h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=",
version = "v0.0.0-20191204190536-9bdfabe68543",
sum = "h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=",
version = "v0.0.0-20190717185122-a985d3407aa7",
)
go_repository(
+1 -1
View File
@@ -244,6 +244,6 @@ func EmitMetricUpdate() {
return
}
log.Debugf("Emitting metrics: %v", m)
log.Debugf("Emitting metrics: %v", &m)
eventchannel.Emit(&m)
}
-2
View File
@@ -1,3 +1 @@
package(licenses = ["notice"])
exports_files(["nogo.json"])
+33 -8
View File
@@ -1,7 +1,7 @@
"""Bazel implementations of standard rules."""
load("@bazel_tools//tools/cpp:cc_flags_supplier.bzl", _cc_flags_supplier = "cc_flags_supplier")
load("@io_bazel_rules_go//go:def.bzl", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_library = "go_library", _go_test = "go_test", _go_tool_library = "go_tool_library")
load("@io_bazel_rules_go//go:def.bzl", "GoLibrary", _go_binary = "go_binary", _go_context = "go_context", _go_embed_data = "go_embed_data", _go_library = "go_library", _go_test = "go_test")
load("@io_bazel_rules_go//proto:def.bzl", _go_grpc_library = "go_grpc_library", _go_proto_library = "go_proto_library")
load("@rules_cc//cc:defs.bzl", _cc_binary = "cc_binary", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test")
load("@rules_pkg//:pkg.bzl", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar")
@@ -99,6 +99,10 @@ def go_binary(name, static = False, pure = False, **kwargs):
**kwargs
)
def go_importpath(target):
"""Returns the importpath for the target."""
return target[GoLibrary].importpath
def go_library(name, **kwargs):
_go_library(
name = name,
@@ -106,13 +110,6 @@ def go_library(name, **kwargs):
**kwargs
)
def go_tool_library(name, **kwargs):
_go_tool_library(
name = name,
importpath = "gvisor.dev/gvisor/" + native.package_name(),
**kwargs
)
def go_test(name, pure = False, library = None, **kwargs):
"""Build a go test.
@@ -131,6 +128,34 @@ def go_test(name, pure = False, library = None, **kwargs):
**kwargs
)
def go_rule(rule, implementation, **kwargs):
"""Wraps a rule definition with Go attributes.
Args:
rule: rule function (typically rule or aspect).
implementation: implementation function.
**kwargs: other arguments to pass to rule.
Returns:
The result of invoking the rule.
"""
attrs = kwargs.pop("attrs", [])
attrs["_go_context_data"] = attr.label(default = "@io_bazel_rules_go//:go_context_data")
attrs["_stdlib"] = attr.label(default = "@io_bazel_rules_go//:stdlib")
toolchains = kwargs.get("toolchains", []) + ["@io_bazel_rules_go//go:toolchain"]
return rule(implementation, attrs = attrs, toolchains = toolchains, **kwargs)
def go_context(ctx):
go_ctx = _go_context(ctx)
return struct(
go = go_ctx.go,
env = go_ctx.env,
runfiles = depset([go_ctx.go] + go_ctx.sdk.tools + go_ctx.stdlib.libs),
goos = go_ctx.sdk.goos,
goarch = go_ctx.sdk.goarch,
tags = go_ctx.tags,
)
def py_requirement(name, direct = True):
return _py_requirement(name)
+16
View File
@@ -0,0 +1,16 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "checkescape",
srcs = ["checkescape.go"],
nogo = False,
visibility = ["//tools/nogo:__subpackages__"],
deps = [
"//tools/nogo/data",
"@org_golang_x_tools//go/analysis:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/buildssa:go_tool_library",
"@org_golang_x_tools//go/ssa:go_tool_library",
],
)
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "test1",
srcs = ["test1.go"],
visibility = ["//tools/checkescape/test2:__pkg__"],
)
+195
View File
@@ -0,0 +1,195 @@
// 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 test1 is a test package.
package test1
import (
"fmt"
"reflect"
)
// Interface is a generic interface.
type Interface interface {
Foo()
}
// Type is a concrete implementation of Interface.
type Type struct {
A uint64
B uint64
}
// Foo implements Interface.Foo.
//go:nosplit
func (t Type) Foo() {
fmt.Printf("%v", t) // Never executed.
}
// +checkescape:all,hard
//go:nosplit
func InterfaceFunction(i Interface) {
// Do nothing; exported for tests.
}
// +checkesacape:all,hard
//go:nosplit
func TypeFunction(t *Type) {
}
// +mustescape:local,builtin
//go:noinline
//go:nosplit
func BuiltinMap(x int) map[string]bool {
return make(map[string]bool)
}
// +mustescape:builtin
//go:noinline
//go:nosplit
func builtinMapRec(x int) map[string]bool {
return BuiltinMap(x)
}
// +temustescapestescape:local,builtin
//go:noinline
//go:nosplit
func BuiltinClosure(x int) func() {
return func() {
fmt.Printf("%v", x)
}
}
// +mustescape:builtin
//go:noinline
//go:nosplit
func builtinClosureRec(x int) func() {
return BuiltinClosure(x)
}
// +mustescape:local,builtin
//go:noinline
//go:nosplit
func BuiltinMakeSlice(x int) []byte {
return make([]byte, x)
}
// +mustescape:builtin
//go:noinline
//go:nosplit
func builtinMakeSliceRec(x int) []byte {
return BuiltinMakeSlice(x)
}
// +mustescape:local,builtin
//go:noinline
//go:nosplit
func BuiltinAppend(x []byte) []byte {
return append(x, 0)
}
// +mustescape:builtin
//go:noinline
//go:nosplit
func builtinAppendRec() []byte {
return BuiltinAppend(nil)
}
// +mustescape:local,builtin
//go:noinline
//go:nosplit
func BuiltinChan() chan int {
return make(chan int)
}
// +mustescape:builtin
//go:noinline
//go:nosplit
func builtinChanRec() chan int {
return BuiltinChan()
}
// +mustescape:local,heap
//go:noinline
//go:nosplit
func Heap() *Type {
var t Type
return &t
}
// +mustescape:heap
//go:noinline
//go:nosplit
func heapRec() *Type {
return Heap()
}
// +mustescape:local,interface
//go:noinline
//go:nosplit
func Dispatch(i Interface) {
i.Foo()
}
// +mustescape:interface
//go:noinline
//go:nosplit
func dispatchRec(i Interface) {
Dispatch(i)
}
// +mustescape:local,dynamic
//go:noinline
//go:nosplit
func Dynamic(f func()) {
f()
}
// +mustescape:dynamic
//go:noinline
//go:nosplit
func dynamicRec(f func()) {
Dynamic(f)
}
// +mustescape:local,unknown
//go:noinline
//go:nosplit
func Unknown() {
_ = reflect.TypeOf((*Type)(nil)) // Does not actually escape.
}
// +mustescape:unknown
//go:noinline
//go:nosplit
func unknownRec() {
Unknown()
}
//go:noinline
//go:nosplit
func internalFunc() {
}
// +mustescape:local,stack
//go:noinline
func Split() {
internalFunc()
}
// +mustescape:stack
//go:noinline
func splitRec() {
Split()
}
+9
View File
@@ -0,0 +1,9 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "test2",
srcs = ["test2.go"],
deps = ["//tools/checkescape/test1"],
)
+94
View File
@@ -0,0 +1,94 @@
// 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 test2 is a test package that imports test1.
package test2
import (
"gvisor.dev/gvisor/tools/checkescape/test1"
)
// +checkescape:all
//go:nosplit
func interfaceFunctionCrossPkg() {
var i test1.Interface
test1.InterfaceFunction(i)
}
// +checkesacape:all
//go:nosplit
func typeFunctionCrossPkg() {
var t test1.Type
test1.TypeFunction(&t)
}
// +mustescape:builtin
//go:noinline
func builtinMapCrossPkg(x int) map[string]bool {
return test1.BuiltinMap(x)
}
// +mustescape:builtin
//go:noinline
func builtinClosureCrossPkg(x int) func() {
return test1.BuiltinClosure(x)
}
// +mustescape:builtin
//go:noinline
func builtinMakeSliceCrossPkg(x int) []byte {
return test1.BuiltinMakeSlice(x)
}
// +mustescape:builtin
//go:noinline
func builtinAppendCrossPkg() []byte {
return test1.BuiltinAppend(nil)
}
// +mustescape:builtin
//go:noinline
func builtinChanCrossPkg() chan int {
return test1.BuiltinChan()
}
// +mustescape:heap
//go:noinline
func heapCrossPkg() *test1.Type {
return test1.Heap()
}
// +mustescape:interface
//go:noinline
func dispatchCrossPkg(i test1.Interface) {
test1.Dispatch(i)
}
// +mustescape:dynamic
//go:noinline
func dynamicCrossPkg(f func()) {
test1.Dynamic(f)
}
// +mustescape:unknown
//go:noinline
func unknownCrossPkg() {
test1.Unknown()
}
// +mustescape:stack
//go:noinline
func splitCrosssPkt() {
test1.Split()
}
+4 -3
View File
@@ -1,11 +1,12 @@
load("//tools:defs.bzl", "go_tool_library")
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_tool_library(
go_library(
name = "checkunsafe",
srcs = ["check_unsafe.go"],
visibility = ["//:sandbox"],
nogo = False,
visibility = ["//tools/nogo:__subpackages__"],
deps = [
"@org_golang_x_tools//go/analysis:go_tool_library",
],
+9 -3
View File
@@ -7,9 +7,10 @@ change for Google-internal and bazel-compatible rules.
load("//tools/go_stateify:defs.bzl", "go_stateify")
load("//tools/go_marshal:defs.bzl", "go_marshal", "marshal_deps", "marshal_test_deps")
load("//tools/bazeldefs:defs.bzl", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _container_image = "container_image", _default_installer = "default_installer", _default_net_util = "default_net_util", _gbenchmark = "gbenchmark", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_image = "go_image", _go_library = "go_library", _go_proto_library = "go_proto_library", _go_test = "go_test", _go_tool_library = "go_tool_library", _grpcpp = "grpcpp", _gtest = "gtest", _loopback = "loopback", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar", _proto_library = "proto_library", _py_binary = "py_binary", _py_library = "py_library", _py_requirement = "py_requirement", _py_test = "py_test", _select_arch = "select_arch", _select_system = "select_system")
load("//tools/bazeldefs:defs.bzl", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _container_image = "container_image", _default_installer = "default_installer", _default_net_util = "default_net_util", _gbenchmark = "gbenchmark", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_image = "go_image", _go_library = "go_library", _go_proto_library = "go_proto_library", _go_test = "go_test", _grpcpp = "grpcpp", _gtest = "gtest", _loopback = "loopback", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar", _proto_library = "proto_library", _py_binary = "py_binary", _py_library = "py_library", _py_requirement = "py_requirement", _py_test = "py_test", _select_arch = "select_arch", _select_system = "select_system")
load("//tools/bazeldefs:platforms.bzl", _default_platform = "default_platform", _platforms = "platforms")
load("//tools/bazeldefs:tags.bzl", "go_suffixes")
load("//tools/nogo:defs.bzl", "nogo_test")
# Delegate directly.
cc_binary = _cc_binary
@@ -25,7 +26,6 @@ gbenchmark = _gbenchmark
go_embed_data = _go_embed_data
go_image = _go_image
go_test = _go_test
go_tool_library = _go_tool_library
gtest = _gtest
grpcpp = _grpcpp
loopback = _loopback
@@ -38,6 +38,7 @@ py_test = _py_test
select_arch = _select_arch
select_system = _select_system
# Platform options.
default_platform = _default_platform
platforms = _platforms
@@ -91,7 +92,7 @@ def go_imports(name, src, out):
cmd = ("$(location @org_golang_x_tools//cmd/goimports:goimports) $(SRCS) > $@"),
)
def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = False, marshal_debug = False, **kwargs):
def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = False, marshal_debug = False, nogo = True, **kwargs):
"""Wraps the standard go_library and does stateification and marshalling.
The recommended way is to use this rule with mostly identical configuration as the native
@@ -177,6 +178,11 @@ def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = F
deps = all_deps,
**kwargs
)
if nogo:
nogo_test(
name = name + "_nogo",
deps = [":" + name],
)
if marshal:
# Ignore importpath for go_test.
@@ -44,6 +44,7 @@ func (g *interfaceGenerator) emitMarshallableForArrayNewtype(n *ast.Ident, a *as
lenExpr := g.arrayLenExpr(a)
g.emit("// SizeBytes implements marshal.Marshallable.SizeBytes.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) SizeBytes() int {\n", g.r, g.typeName())
g.inIndent(func() {
if size, dynamic := g.scalarSize(elt); !dynamic {
@@ -77,6 +78,7 @@ func (g *interfaceGenerator) emitMarshallableForArrayNewtype(n *ast.Ident, a *as
g.emit("}\n\n")
g.emit("// Packed implements marshal.Marshallable.Packed.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) Packed() bool {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("// Array newtypes are always packed.\n")
@@ -99,17 +101,19 @@ func (g *interfaceGenerator) emitMarshallableForArrayNewtype(n *ast.Ident, a *as
g.emit("}\n\n")
g.emit("// CopyOutN implements marshal.Marshallable.CopyOutN.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyOutN(task marshal.Task, addr usermem.Addr, limit int) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit])\n")
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit]) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
})
g.emit("}\n\n")
g.emit("// CopyOut implements marshal.Marshallable.CopyOut.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyOut(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("return %s.CopyOutN(task, addr, %s.SizeBytes())\n", g.r, g.r)
@@ -117,11 +121,12 @@ func (g *interfaceGenerator) emitMarshallableForArrayNewtype(n *ast.Ident, a *as
g.emit("}\n\n")
g.emit("// CopyIn implements marshal.Marshallable.CopyIn.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyIn(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyInBytes(addr, buf)\n")
g.emit("length, err := task.CopyInBytes(addr, buf) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
})
@@ -104,6 +104,7 @@ func (g *interfaceGenerator) emitMarshallableForPrimitiveNewtype(nt *ast.Ident)
g.recordUsedImport("usermem")
g.emit("// SizeBytes implements marshal.Marshallable.SizeBytes.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) SizeBytes() int {\n", g.r, g.typeName())
g.inIndent(func() {
if size, dynamic := g.scalarSize(nt); !dynamic {
@@ -129,6 +130,7 @@ func (g *interfaceGenerator) emitMarshallableForPrimitiveNewtype(nt *ast.Ident)
g.emit("}\n\n")
g.emit("// Packed implements marshal.Marshallable.Packed.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) Packed() bool {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("// Scalar newtypes are always packed.\n")
@@ -151,17 +153,19 @@ func (g *interfaceGenerator) emitMarshallableForPrimitiveNewtype(nt *ast.Ident)
g.emit("}\n\n")
g.emit("// CopyOutN implements marshal.Marshallable.CopyOutN.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyOutN(task marshal.Task, addr usermem.Addr, limit int) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit])\n")
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit]) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
})
g.emit("}\n\n")
g.emit("// CopyOut implements marshal.Marshallable.CopyOut.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyOut(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("return %s.CopyOutN(task, addr, %s.SizeBytes())\n", g.r, g.r)
@@ -169,11 +173,12 @@ func (g *interfaceGenerator) emitMarshallableForPrimitiveNewtype(nt *ast.Ident)
g.emit("}\n\n")
g.emit("// CopyIn implements marshal.Marshallable.CopyIn.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) CopyIn(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyInBytes(addr, buf)\n")
g.emit("length, err := task.CopyInBytes(addr, buf) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
})
@@ -205,6 +210,7 @@ func (g *interfaceGenerator) emitMarshallableSliceForPrimitiveNewtype(nt *ast.Id
}
g.emit("// Copy%sIn copies in a slice of %s objects from the task's memory.\n", slice.ident, eltType)
g.emit("//go:nosplit\n")
g.emit("func Copy%sIn(task marshal.Task, addr usermem.Addr, dst []%s) (int, error) {\n", slice.ident, eltType)
g.inIndent(func() {
g.emit("count := len(dst)\n")
@@ -217,13 +223,14 @@ func (g *interfaceGenerator) emitMarshallableSliceForPrimitiveNewtype(nt *ast.Id
g.emitCastSliceToByteSlice("&dst", "buf", "size * count")
g.emit("length, err := task.CopyInBytes(addr, buf)\n")
g.emit("length, err := task.CopyInBytes(addr, buf) // escapes: okay.\n")
g.emitKeepAlive("dst")
g.emit("return length, err\n")
})
g.emit("}\n\n")
g.emit("// Copy%sOut copies a slice of %s objects to the task's memory.\n", slice.ident, eltType)
g.emit("//go:nosplit\n")
g.emit("func Copy%sOut(task marshal.Task, addr usermem.Addr, src []%s) (int, error) {\n", slice.ident, eltType)
g.inIndent(func() {
g.emit("count := len(src)\n")
@@ -236,7 +243,7 @@ func (g *interfaceGenerator) emitMarshallableSliceForPrimitiveNewtype(nt *ast.Id
g.emitCastSliceToByteSlice("&src", "buf", "size * count")
g.emit("length, err := task.CopyOutBytes(addr, buf)\n")
g.emit("length, err := task.CopyOutBytes(addr, buf) // escapes: okay.\n")
g.emitKeepAlive("src")
g.emit("return length, err\n")
})
@@ -249,6 +249,7 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
g.emit("}\n\n")
g.emit("// Packed implements marshal.Marshallable.Packed.\n")
g.emit("//go:nosplit\n")
g.emit("func (%s *%s) Packed() bool {\n", g.r, g.typeName())
g.inIndent(func() {
expr, fieldsMaybePacked := g.areFieldsPackedExpression()
@@ -317,15 +318,16 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
g.emit("}\n\n")
g.emit("// CopyOutN implements marshal.Marshallable.CopyOutN.\n")
g.emit("//go:nosplit\n")
g.recordUsedImport("marshal")
g.recordUsedImport("usermem")
g.emit("func (%s *%s) CopyOutN(task marshal.Task, addr usermem.Addr, limit int) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
fallback := func() {
g.emit("// Type %s doesn't have a packed layout in memory, fall back to MarshalBytes.\n", g.typeName())
g.emit("buf := task.CopyScratchBuffer(%s.SizeBytes())\n", g.r)
g.emit("%s.MarshalBytes(buf)\n", g.r)
g.emit("return task.CopyOutBytes(addr, buf[:limit])\n")
g.emit("buf := task.CopyScratchBuffer(%s.SizeBytes()) // escapes: okay.\n", g.r)
g.emit("%s.MarshalBytes(buf) // escapes: fallback.\n", g.r)
g.emit("return task.CopyOutBytes(addr, buf[:limit]) // escapes: okay.\n")
}
if thisPacked {
g.recordUsedImport("reflect")
@@ -339,7 +341,7 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
// Fast serialization.
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit])\n")
g.emit("length, err := task.CopyOutBytes(addr, buf[:limit]) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
} else {
@@ -349,6 +351,7 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
g.emit("}\n\n")
g.emit("// CopyOut implements marshal.Marshallable.CopyOut.\n")
g.emit("//go:nosplit\n")
g.recordUsedImport("marshal")
g.recordUsedImport("usermem")
g.emit("func (%s *%s) CopyOut(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
@@ -358,17 +361,18 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
g.emit("}\n\n")
g.emit("// CopyIn implements marshal.Marshallable.CopyIn.\n")
g.emit("//go:nosplit\n")
g.recordUsedImport("marshal")
g.recordUsedImport("usermem")
g.emit("func (%s *%s) CopyIn(task marshal.Task, addr usermem.Addr) (int, error) {\n", g.r, g.typeName())
g.inIndent(func() {
fallback := func() {
g.emit("// Type %s doesn't have a packed layout in memory, fall back to UnmarshalBytes.\n", g.typeName())
g.emit("buf := task.CopyScratchBuffer(%s.SizeBytes())\n", g.r)
g.emit("length, err := task.CopyInBytes(addr, buf)\n")
g.emit("buf := task.CopyScratchBuffer(%s.SizeBytes()) // escapes: okay.\n", g.r)
g.emit("length, err := task.CopyInBytes(addr, buf) // escapes: okay.\n")
g.emit("// Unmarshal unconditionally. If we had a short copy-in, this results in a\n")
g.emit("// partially unmarshalled struct.\n")
g.emit("%s.UnmarshalBytes(buf)\n", g.r)
g.emit("%s.UnmarshalBytes(buf) // escapes: fallback.\n", g.r)
g.emit("return length, err\n")
}
if thisPacked {
@@ -383,7 +387,7 @@ func (g *interfaceGenerator) emitMarshallableForStruct(st *ast.StructType) {
// Fast deserialization.
g.emitCastToByteSlice(g.r, "buf", fmt.Sprintf("%s.SizeBytes()", g.r))
g.emit("length, err := task.CopyInBytes(addr, buf)\n")
g.emit("length, err := task.CopyInBytes(addr, buf) // escapes: okay.\n")
g.emitKeepAlive(g.r)
g.emit("return length, err\n")
} else {
+2 -13
View File
@@ -1,4 +1,4 @@
load("//tools:defs.bzl", "go_binary", "go_library", "go_test")
load("//tools:defs.bzl", "go_library", "go_test")
licenses(["notice"])
@@ -25,21 +25,10 @@ go_library(
testonly = 1,
srcs = ["test.go"],
marshal = True,
visibility = ["//tools/go_marshal/test:__subpackages__"],
deps = ["//tools/go_marshal/test/external"],
)
go_binary(
name = "escape",
testonly = 1,
srcs = ["escape.go"],
gc_goopts = ["-m"],
deps = [
":test",
"//pkg/usermem",
"//tools/go_marshal/marshal",
],
)
go_test(
name = "marshal_test",
size = "small",
-114
View File
@@ -1,114 +0,0 @@
// 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.
// This binary provides a convienient target for analyzing how the go-marshal
// API causes its various arguments to escape to the heap. To use, build and
// observe the output from the go compiler's escape analysis:
//
// $ bazel build :escape
// ...
// escape.go:67:2: moved to heap: task
// escape.go:77:31: make([]byte, size) escapes to heap
// escape.go:87:31: make([]byte, size) escapes to heap
// escape.go:96:6: moved to heap: stat
// ...
//
// This is not an automated test, but simply a minimal binary for easy analysis.
package main
import (
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/tools/go_marshal/marshal"
"gvisor.dev/gvisor/tools/go_marshal/test"
)
// dummyTask implements marshal.Task.
type dummyTask struct {
}
func (*dummyTask) CopyScratchBuffer(size int) []byte {
return make([]byte, size)
}
func (*dummyTask) CopyOutBytes(addr usermem.Addr, b []byte) (int, error) {
return len(b), nil
}
func (*dummyTask) CopyInBytes(addr usermem.Addr, b []byte) (int, error) {
return len(b), nil
}
func (task *dummyTask) MarshalBytes(addr usermem.Addr, marshallable marshal.Marshallable) {
buf := task.CopyScratchBuffer(marshallable.SizeBytes())
marshallable.MarshalBytes(buf)
task.CopyOutBytes(addr, buf)
}
func (task *dummyTask) MarshalUnsafe(addr usermem.Addr, marshallable marshal.Marshallable) {
buf := task.CopyScratchBuffer(marshallable.SizeBytes())
marshallable.MarshalUnsafe(buf)
task.CopyOutBytes(addr, buf)
}
// Expected escapes:
// - task: passed to marshal.Marshallable.CopyOut as the marshal.Task interface.
func doCopyOut() {
task := dummyTask{}
var stat test.Stat
stat.CopyOut(&task, usermem.Addr(0xf000ba12))
}
// Expected escapes:
// - buf: make allocates on the heap.
func doMarshalBytesDirect() {
task := dummyTask{}
var stat test.Stat
buf := task.CopyScratchBuffer(stat.SizeBytes())
stat.MarshalBytes(buf)
task.CopyOutBytes(usermem.Addr(0xf000ba12), buf)
}
// Expected escapes:
// - buf: make allocates on the heap.
func doMarshalUnsafeDirect() {
task := dummyTask{}
var stat test.Stat
buf := task.CopyScratchBuffer(stat.SizeBytes())
stat.MarshalUnsafe(buf)
task.CopyOutBytes(usermem.Addr(0xf000ba12), buf)
}
// Expected escapes:
// - stat: passed to dummyTask.MarshalBytes as the marshal.Marshallable interface.
func doMarshalBytesViaMarshallable() {
task := dummyTask{}
var stat test.Stat
task.MarshalBytes(usermem.Addr(0xf000ba12), &stat)
}
// Expected escapes:
// - stat: passed to dummyTask.MarshalUnsafe as the marshal.Marshallable interface.
func doMarshalUnsafeViaMarshallable() {
task := dummyTask{}
var stat test.Stat
task.MarshalUnsafe(usermem.Addr(0xf000ba12), &stat)
}
func main() {
doCopyOut()
doMarshalBytesDirect()
doMarshalUnsafeDirect()
doMarshalBytesViaMarshallable()
doMarshalUnsafeViaMarshallable()
}
+14
View File
@@ -0,0 +1,14 @@
load("//tools:defs.bzl", "go_library")
licenses(["notice"])
go_library(
name = "escape",
testonly = 1,
srcs = ["escape.go"],
deps = [
"//pkg/usermem",
"//tools/go_marshal/marshal",
"//tools/go_marshal/test",
],
)
+95
View File
@@ -0,0 +1,95 @@
// 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 escape
import (
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/tools/go_marshal/marshal"
"gvisor.dev/gvisor/tools/go_marshal/test"
)
// dummyTask implements marshal.Task.
type dummyTask struct {
}
func (*dummyTask) CopyScratchBuffer(size int) []byte {
return make([]byte, size)
}
func (*dummyTask) CopyOutBytes(addr usermem.Addr, b []byte) (int, error) {
return len(b), nil
}
func (*dummyTask) CopyInBytes(addr usermem.Addr, b []byte) (int, error) {
return len(b), nil
}
func (t *dummyTask) MarshalBytes(addr usermem.Addr, marshallable marshal.Marshallable) {
buf := t.CopyScratchBuffer(marshallable.SizeBytes())
marshallable.MarshalBytes(buf)
t.CopyOutBytes(addr, buf)
}
func (t *dummyTask) MarshalUnsafe(addr usermem.Addr, marshallable marshal.Marshallable) {
buf := t.CopyScratchBuffer(marshallable.SizeBytes())
marshallable.MarshalUnsafe(buf)
t.CopyOutBytes(addr, buf)
}
// +checkescape:all
//go:nosplit
func doCopyIn(t *dummyTask) {
var stat test.Stat
stat.CopyIn(t, usermem.Addr(0xf000ba12))
}
// +checkescape:all
//go:nosplit
func doCopyOut(t *dummyTask) {
var stat test.Stat
stat.CopyOut(t, usermem.Addr(0xf000ba12))
}
// +mustescape:builtin
// +mustescape:stack
func doMarshalBytesDirect(t *dummyTask) {
var stat test.Stat
buf := t.CopyScratchBuffer(stat.SizeBytes())
stat.MarshalBytes(buf)
t.CopyOutBytes(usermem.Addr(0xf000ba12), buf)
}
// +mustescape:builtin
// +mustescape:stack
func doMarshalUnsafeDirect(t *dummyTask) {
var stat test.Stat
buf := t.CopyScratchBuffer(stat.SizeBytes())
stat.MarshalUnsafe(buf)
t.CopyOutBytes(usermem.Addr(0xf000ba12), buf)
}
// +mustescape:local,heap
// +mustescape:stack
func doMarshalBytesViaMarshallable(t *dummyTask) {
var stat test.Stat
t.MarshalBytes(usermem.Addr(0xf000ba12), &stat)
}
// +mustescape:local,heap
// +mustescape:stack
func doMarshalUnsafeViaMarshallable(t *dummyTask) {
var stat test.Stat
t.MarshalUnsafe(usermem.Addr(0xf000ba12), &stat)
}

Some files were not shown because too many files have changed in this diff Show More