Detect unnecessary imports instead of hardcoding

pull/515/head
lu4p 2 years ago committed by Daniel Martí
parent 1a0b028db7
commit 237e0b7b7c

@ -748,6 +748,7 @@ func transformCompile(args []string) ([]string, error) {
if curPkg.ImportPath == "runtime" && flagTiny { if curPkg.ImportPath == "runtime" && flagTiny {
// strip unneeded runtime code // strip unneeded runtime code
stripRuntime(filename, file) stripRuntime(filename, file)
tf.removeUnnecessaryImports(file)
} }
tf.handleDirectives(file.Comments) tf.handleDirectives(file.Comments)
file = tf.transformGo(file) file = tf.transformGo(file)
@ -1441,6 +1442,51 @@ func recordedAsNotObfuscated(obj types.Object) bool {
return ok return ok
} }
func (tf *transformer) removeUnnecessaryImports(file *ast.File) {
usedImports := make(map[string]bool)
ast.Inspect(file, func(n ast.Node) bool {
node, ok := n.(*ast.Ident)
if !ok {
return true
}
uses, ok := tf.info.Uses[node].(*types.PkgName)
if !ok {
return true
}
usedImports[uses.Imported().Path()] = true
return true
})
for _, imp := range file.Imports {
if imp.Name != nil && (imp.Name.Name == "_" || imp.Name.Name == ".") {
continue
}
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
panic(err)
}
// The import path can't be used directly here, because the actual
// path resolved via go/types might be different from the naive path.
lpkg, err := listPackage(path)
if err != nil {
panic(err)
}
if usedImports[lpkg.ImportPath] {
continue
}
if !astutil.DeleteImport(fset, file, path) {
panic(fmt.Sprintf("cannot delete unused import: %q", path))
}
}
}
// transformGo obfuscates the provided Go syntax file. // transformGo obfuscates the provided Go syntax file.
func (tf *transformer) transformGo(file *ast.File) *ast.File { func (tf *transformer) transformGo(file *ast.File) *ast.File {
// Only obfuscate the literals here if the flag is on // Only obfuscate the literals here if the flag is on
@ -1453,48 +1499,7 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
file = literals.Obfuscate(file, tf.info, fset, tf.linkerVariableStrings) file = literals.Obfuscate(file, tf.info, fset, tf.linkerVariableStrings)
// some imported constants might not be needed anymore, remove unnecessary imports // some imported constants might not be needed anymore, remove unnecessary imports
usedImports := make(map[string]bool) tf.removeUnnecessaryImports(file)
ast.Inspect(file, func(n ast.Node) bool {
node, ok := n.(*ast.Ident)
if !ok {
return true
}
uses, ok := tf.info.Uses[node].(*types.PkgName)
if !ok {
return true
}
usedImports[uses.Imported().Path()] = true
return true
})
for _, imp := range file.Imports {
if imp.Name != nil && (imp.Name.Name == "_" || imp.Name.Name == ".") {
continue
}
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
panic(err)
}
// The import path can't be used directly here, because the actual
// path resolved via go/types might be different from the naive path.
lpkg, err := listPackage(path)
if err != nil {
panic(err)
}
if usedImports[lpkg.ImportPath] {
continue
}
if !astutil.DeleteImport(fset, file, path) {
panic(fmt.Sprintf("cannot delete unused import: %q", path))
}
}
} }
pre := func(cursor *astutil.Cursor) bool { pre := func(cursor *astutil.Cursor) bool {

@ -5,7 +5,6 @@ package main
import ( import (
"go/ast" "go/ast"
"go/token"
"strings" "strings"
ah "mvdan.cc/garble/internal/asthelper" ah "mvdan.cc/garble/internal/asthelper"
@ -35,121 +34,93 @@ func stripRuntime(filename string, file *ast.File) {
} }
for _, decl := range file.Decls { for _, decl := range file.Decls {
switch x := decl.(type) { funcDecl, ok := decl.(*ast.FuncDecl)
case *ast.FuncDecl: if !ok {
switch filename { continue
case "error.go": }
// only used in panics
switch x.Name.Name { switch filename {
case "printany", "printanycustomtype": case "error.go":
x.Body.List = nil // only used in panics
} switch funcDecl.Name.Name {
case "mgcscavenge.go": case "printany", "printanycustomtype":
// used in tracing the scavenger funcDecl.Body.List = nil
if x.Name.Name == "printScavTrace" { }
x.Body.List = nil case "mgcscavenge.go":
break // used in tracing the scavenger
} if funcDecl.Name.Name == "printScavTrace" {
case "mprof.go": funcDecl.Body.List = nil
// remove all functions that print debug/tracing info }
// of the runtime case "mprof.go":
if strings.HasPrefix(x.Name.Name, "trace") { // remove all functions that print debug/tracing info
x.Body.List = nil // of the runtime
} if strings.HasPrefix(funcDecl.Name.Name, "trace") {
case "panic.go": funcDecl.Body.List = nil
// used for printing panics }
switch x.Name.Name { case "panic.go":
case "preprintpanics", "printpanics": // used for printing panics
x.Body.List = nil switch funcDecl.Name.Name {
} case "preprintpanics", "printpanics":
case "print.go": funcDecl.Body.List = nil
// only used in tracebacks }
if x.Name.Name == "hexdumpWords" { case "print.go":
x.Body.List = nil // only used in tracebacks
break if funcDecl.Name.Name == "hexdumpWords" {
} funcDecl.Body.List = nil
case "proc.go": }
// used in tracing the scheduler case "proc.go":
if x.Name.Name == "schedtrace" { // used in tracing the scheduler
x.Body.List = nil if funcDecl.Name.Name == "schedtrace" {
break funcDecl.Body.List = nil
} }
case "runtime1.go": case "runtime1.go":
usesEnv := func(node ast.Node) bool { usesEnv := func(node ast.Node) bool {
seen := false seen := false
ast.Inspect(node, func(node ast.Node) bool { ast.Inspect(node, func(node ast.Node) bool {
ident, ok := node.(*ast.Ident) ident, ok := node.(*ast.Ident)
if ok && ident.Name == "gogetenv" { if ok && ident.Name == "gogetenv" {
seen = true seen = true
return false return false
}
return true
})
return seen
}
filenames:
switch x.Name.Name {
case "parsedebugvars":
// keep defaults for GODEBUG cgocheck and invalidptr,
// remove code that reads GODEBUG via gogetenv
for i, stmt := range x.Body.List {
if usesEnv(stmt) {
x.Body.List = x.Body.List[:i]
break filenames
}
} }
panic("did not see any gogetenv call in parsedebugvars") return true
case "setTraceback": })
// tracebacks are completely hidden, no return seen
// sense keeping this function }
x.Body.List = nil filenames:
} switch funcDecl.Name.Name {
case "traceback.go": case "parsedebugvars":
// only used for printing tracebacks // keep defaults for GODEBUG cgocheck and invalidptr,
switch x.Name.Name { // remove code that reads GODEBUG via gogetenv
case "tracebackdefers", "printcreatedby", "printcreatedby1", "traceback", "tracebacktrap", "traceback1", "printAncestorTraceback", for i, stmt := range funcDecl.Body.List {
"printAncestorTracebackFuncInfo", "goroutineheader", "tracebackothers", "tracebackHexdump", "printCgoTraceback": if usesEnv(stmt) {
x.Body.List = nil funcDecl.Body.List = funcDecl.Body.List[:i]
case "printOneCgoTraceback": break filenames
x.Body = ah.BlockStmt(ah.ReturnStmt(ah.IntLit(0)))
default:
if strings.HasPrefix(x.Name.Name, "print") {
x.Body.List = nil
} }
} }
default: panic("did not see any gogetenv call in parsedebugvars")
break case "setTraceback":
} // tracebacks are completely hidden, no
case *ast.GenDecl: // sense keeping this function
if x.Tok != token.IMPORT { funcDecl.Body.List = nil
continue
} }
case "traceback.go":
switch filename { // only used for printing tracebacks
case "print.go": switch funcDecl.Name.Name {
// was used in hexdumpWords case "tracebackdefers", "printcreatedby", "printcreatedby1", "traceback", "tracebacktrap", "traceback1", "printAncestorTraceback",
x.Specs = removeImport(`"internal/goarch"`, x.Specs) "printAncestorTracebackFuncInfo", "goroutineheader", "tracebackothers", "tracebackHexdump", "printCgoTraceback":
case "traceback.go": funcDecl.Body.List = nil
// was used in traceback1 case "printOneCgoTraceback":
x.Specs = removeImport(`"runtime/internal/atomic"`, x.Specs) funcDecl.Body = ah.BlockStmt(ah.ReturnStmt(ah.IntLit(0)))
default:
if strings.HasPrefix(funcDecl.Name.Name, "print") {
funcDecl.Body.List = nil
}
} }
} }
} }
switch filename { if filename == "print.go" {
case "runtime1.go":
// On Go 1.17.x, the code above results in runtime1.go having an
// unused import. Make it an underscore import.
// If this is a recurring problem, we could go for a more
// generic solution like x/tools/imports.
for _, imp := range file.Imports {
if imp.Path.Value == `"internal/bytealg"` {
imp.Name = &ast.Ident{Name: "_"}
break
}
}
case "print.go":
file.Decls = append(file.Decls, hidePrintDecl) file.Decls = append(file.Decls, hidePrintDecl)
return return
} }
@ -160,18 +131,6 @@ func stripRuntime(filename string, file *ast.File) {
ast.Inspect(file, stripPrints) 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{ var hidePrintDecl = &ast.FuncDecl{
Name: ast.NewIdent("hidePrint"), Name: ast.NewIdent("hidePrint"),
Type: &ast.FuncType{Params: &ast.FieldList{ Type: &ast.FuncType{Params: &ast.FieldList{

Loading…
Cancel
Save