remove unnecessary data from runtime if -tiny is passed
Fixes #127. Saves an additional ~1-2% binary size in my testing.pull/138/head
parent
d679944408
commit
0d182a3dbd
@ -1,146 +0,0 @@
|
||||
// Copyright (c) 2020, The Garble Authors.
|
||||
// See LICENSE for licensing information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
ah "mvdan.cc/garble/internal/asthelper"
|
||||
)
|
||||
|
||||
// addRuntimeAPI exposes additional functions in the runtime
|
||||
// package that may be helpful when hiding information
|
||||
// during execution is required.
|
||||
func addRuntimeAPI(filename string, file *ast.File) {
|
||||
switchPanicPrints := func(node ast.Node) bool {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
id, ok := call.Fun.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch id.Name {
|
||||
case "print":
|
||||
id.Name = "panicprint"
|
||||
return false
|
||||
case "println":
|
||||
id.Name = "panicprint"
|
||||
call.Args = append(call.Args, &ast.BasicLit{Kind: token.STRING, Value: `"\n"`})
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch filename {
|
||||
case "debug.go":
|
||||
// Add hideFatalErrors function and internal fatalErrorsHidden variable
|
||||
file.Decls = append(file.Decls, hideFatalErrorsDecls...)
|
||||
case "error.go":
|
||||
// Add a function panicprint, that does nothing if panics are
|
||||
// hidden, otherwise forwards arguments to printany to print them
|
||||
// as normal. Although we add an if statement to printany to do
|
||||
// nothing if panics are hidden, printany only takes one argument,
|
||||
// and both print and printany are used to print panic messages.
|
||||
// panicprint's entire purpose is to act as a replacement to print
|
||||
// that respects hideFatalErrors, and print is variadic, so print
|
||||
// must be replaced by a variadic function, hence panicprint.
|
||||
//
|
||||
// We will also add two statements to printany:
|
||||
// 1. An if statement that returns early if panics are hidden
|
||||
// 2. An additional case statement that handles printing runtime.hex
|
||||
// values. Without this case statement, the default case will print
|
||||
// the runtime.hex values in a way not consistent with normal panic
|
||||
// outputs
|
||||
for _, decl := range file.Decls {
|
||||
decl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok || decl.Name.Name != "printany" {
|
||||
continue
|
||||
}
|
||||
for _, stmt := range decl.Body.List {
|
||||
if stmt, ok := stmt.(*ast.TypeSwitchStmt); ok {
|
||||
stmt.Body.List = append(stmt.Body.List, printanyHexCase)
|
||||
break
|
||||
}
|
||||
}
|
||||
decl.Body.List = append([]ast.Stmt{fatalErrorsHiddenCheckStmt}, decl.Body.List...)
|
||||
break
|
||||
}
|
||||
|
||||
file.Decls = append(file.Decls, panicprintDecl)
|
||||
default:
|
||||
// Change all calls to print, which we don't control, to
|
||||
// panicprint, which we do control and does the same thing.
|
||||
ast.Inspect(file, switchPanicPrints)
|
||||
}
|
||||
}
|
||||
|
||||
var fatalErrorsHiddenCheckStmt = &ast.IfStmt{
|
||||
Cond: ah.Ident("fatalErrorsHidden"),
|
||||
Body: ah.BlockStmt(ah.ReturnStmt()),
|
||||
}
|
||||
|
||||
var hideFatalErrorsDecls = []ast.Decl{
|
||||
&ast.GenDecl{
|
||||
Tok: token.VAR,
|
||||
Specs: []ast.Spec{&ast.ValueSpec{
|
||||
Names: []*ast.Ident{ah.Ident("fatalErrorsHidden")},
|
||||
Type: ah.Ident("bool"),
|
||||
}},
|
||||
},
|
||||
&ast.FuncDecl{
|
||||
Name: ah.Ident("hideFatalErrors"),
|
||||
Type: &ast.FuncType{Params: &ast.FieldList{
|
||||
List: []*ast.Field{{
|
||||
Names: []*ast.Ident{ah.Ident("hide")},
|
||||
Type: ah.Ident("bool"),
|
||||
}},
|
||||
}},
|
||||
Body: ah.BlockStmt(
|
||||
&ast.AssignStmt{
|
||||
Lhs: []ast.Expr{ah.Ident("fatalErrorsHidden")},
|
||||
Tok: token.ASSIGN,
|
||||
Rhs: []ast.Expr{ah.Ident("hide")},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
var printanyHexCase = &ast.CaseClause{
|
||||
List: []ast.Expr{ah.Ident("hex")},
|
||||
Body: []ast.Stmt{
|
||||
ah.ExprStmt(ah.CallExpr(ah.Ident("print"), ah.Ident("v"))),
|
||||
},
|
||||
}
|
||||
|
||||
var panicprintDecl = &ast.FuncDecl{
|
||||
Name: ah.Ident("panicprint"),
|
||||
Type: &ast.FuncType{Params: &ast.FieldList{
|
||||
List: []*ast.Field{{
|
||||
Names: []*ast.Ident{{Name: "args"}},
|
||||
Type: &ast.Ellipsis{Elt: &ast.InterfaceType{
|
||||
Methods: &ast.FieldList{},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
Body: ah.BlockStmt(
|
||||
&ast.IfStmt{
|
||||
Cond: ah.Ident("fatalErrorsHidden"),
|
||||
Body: ah.BlockStmt(ah.ReturnStmt()),
|
||||
},
|
||||
&ast.RangeStmt{
|
||||
Key: ah.Ident("_"),
|
||||
Value: ah.Ident("arg"),
|
||||
Tok: token.DEFINE,
|
||||
X: ah.Ident("args"),
|
||||
Body: ah.BlockStmt(
|
||||
ah.ExprStmt(ah.CallExpr(ah.Ident("printany"), ah.Ident("arg"))),
|
||||
),
|
||||
},
|
||||
),
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2020, The Garble Authors.
|
||||
// See LICENSE for licensing information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
ah "mvdan.cc/garble/internal/asthelper"
|
||||
)
|
||||
|
||||
// stripRuntime removes unnecessary code from the runtime,
|
||||
// such as panic and fatal error printing, and code that
|
||||
// prints trace/debug info of the runtime.
|
||||
func stripRuntime(filename string, file *ast.File) {
|
||||
stripPrints := func(node ast.Node) bool {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
id, ok := call.Fun.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch id.Name {
|
||||
case "print", "println":
|
||||
id.Name = "hidePrint"
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, decl := range file.Decls {
|
||||
switch x := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
switch filename {
|
||||
case "error.go":
|
||||
// only used in panics
|
||||
switch x.Name.Name {
|
||||
case "printany", "printanycustomtype":
|
||||
x.Body.List = nil
|
||||
}
|
||||
case "mgcscavenge.go":
|
||||
// used in tracing the scavenger
|
||||
if x.Name.Name == "printScavTrace" {
|
||||
x.Body.List = nil
|
||||
break
|
||||
}
|
||||
case "mprof.go":
|
||||
// remove all functions that print debug/tracing info
|
||||
// of the runtime
|
||||
if strings.HasPrefix(x.Name.Name, "trace") {
|
||||
x.Body.List = nil
|
||||
}
|
||||
case "panic.go":
|
||||
// used for printing panics
|
||||
switch x.Name.Name {
|
||||
case "preprintpanics", "printpanics":
|
||||
x.Body.List = nil
|
||||
}
|
||||
case "print.go":
|
||||
// only used in tracebacks
|
||||
if x.Name.Name == "hexdumpWords" {
|
||||
x.Body.List = nil
|
||||
break
|
||||
}
|
||||
case "proc.go":
|
||||
// used in tracing the scheduler
|
||||
if x.Name.Name == "schedtrace" {
|
||||
x.Body.List = nil
|
||||
break
|
||||
}
|
||||
case "runtime1.go":
|
||||
switch x.Name.Name {
|
||||
case "parsedebugvars":
|
||||
// set defaults for GODEBUG cgocheck and
|
||||
// invalidptr, remove code that reads in
|
||||
// GODEBUG
|
||||
x.Body = parsedebugvarsStmts
|
||||
case "setTraceback":
|
||||
// tracebacks are completely hidden, no
|
||||
// sense keeping this function
|
||||
x.Body.List = nil
|
||||
}
|
||||
case "traceback.go":
|
||||
// only used for printing tracebacks
|
||||
switch x.Name.Name {
|
||||
case "tracebackdefers", "traceback", "tracebacktrap", "traceback1",
|
||||
"goroutineheader", "tracebackothers", "tracebackHexdump":
|
||||
x.Body.List = nil
|
||||
case "printOneCgoTraceback":
|
||||
x.Body = ah.BlockStmt(ah.ReturnStmt(ah.IntLit(0)))
|
||||
default:
|
||||
if strings.HasPrefix(x.Name.Name, "print") {
|
||||
x.Body.List = nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
case *ast.GenDecl:
|
||||
if x.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
|
||||
switch filename {
|
||||
case "print.go":
|
||||
// was used in hexdumpWords
|
||||
x.Specs = removeImport(`"runtime/internal/sys"`, x.Specs)
|
||||
case "traceback.go":
|
||||
// was used in traceback1
|
||||
x.Specs = removeImport(`"runtime/internal/atomic"`, x.Specs)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if filename == "print.go" {
|
||||
file.Decls = append(file.Decls, hidePrintDecl)
|
||||
}
|
||||
|
||||
// replace all 'print' and 'println' statements in
|
||||
// the runtime with an empty func, which will be
|
||||
// optimized out by the compiler
|
||||
ast.Inspect(file, stripPrints)
|
||||
}
|
||||
|
||||
func removeImport(importPath string, specs []ast.Spec) []ast.Spec {
|
||||
for i, spec := range specs {
|
||||
imp := spec.(*ast.ImportSpec)
|
||||
if imp.Path.Value == importPath {
|
||||
specs = append(specs[:i], specs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return specs
|
||||
}
|
||||
|
||||
var hidePrintDecl = &ast.FuncDecl{
|
||||
Name: ah.Ident("hidePrint"),
|
||||
Type: &ast.FuncType{Params: &ast.FieldList{
|
||||
List: []*ast.Field{{
|
||||
Names: []*ast.Ident{{Name: "args"}},
|
||||
Type: &ast.Ellipsis{Elt: &ast.InterfaceType{
|
||||
Methods: &ast.FieldList{},
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
Body: &ast.BlockStmt{},
|
||||
}
|
||||
|
||||
var parsedebugvarsStmts = ah.BlockStmt(
|
||||
&ast.AssignStmt{
|
||||
Lhs: []ast.Expr{&ast.SelectorExpr{
|
||||
X: ah.Ident("debug"),
|
||||
Sel: ah.Ident("cgocheck"),
|
||||
}},
|
||||
Tok: token.ASSIGN,
|
||||
Rhs: []ast.Expr{ah.IntLit(1)},
|
||||
},
|
||||
&ast.AssignStmt{
|
||||
Lhs: []ast.Expr{&ast.SelectorExpr{
|
||||
X: ah.Ident("debug"),
|
||||
Sel: ah.Ident("invalidptr"),
|
||||
}},
|
||||
Tok: token.ASSIGN,
|
||||
Rhs: []ast.Expr{ah.IntLit(1)},
|
||||
},
|
||||
)
|
@ -1,23 +0,0 @@
|
||||
env GOPRIVATE=test/main
|
||||
|
||||
garble build
|
||||
! exec ./main
|
||||
cmp stderr main.stderr
|
||||
|
||||
-- go.mod --
|
||||
module test/main
|
||||
-- main.go --
|
||||
package main
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//go:linkname hideFatalErrors runtime.hideFatalErrors
|
||||
func hideFatalErrors(hide bool)
|
||||
|
||||
func init() { hideFatalErrors(true) }
|
||||
|
||||
func main() {
|
||||
panic("ya like jazz?")
|
||||
}
|
||||
|
||||
-- main.stderr --
|
Loading…
Reference in New Issue