mirror of
https://github.com/burrowers/garble.git
synced 2025-12-24 12:58:05 +08:00
Now that we've dropped support for Go 1.15.x, we can finally rely on this environment variable for toolexec calls, present in Go 1.16. Before, we had hacky ways of trying to figure out the current package's import path, mostly from the -p flag. The biggest rough edge there was that, for main packages, that was simply the package name, and not its full import path. To work around that, we had a restriction on a single main package, so we could work around that issue. That restriction is now gone. The new code is simpler, especially because we can set curPkg in a single place for all toolexec transform funcs. Since we can always rely on curPkg not being nil now, we can also start reusing listedPackage.Private and avoid the majority of repeated calls to isPrivate. The function is cheap, but still not free. isPrivate itself can also get simpler. We no longer have to worry about the "main" edge case. Plus, the sanity check for invalid package paths is now unnecessary; we only got malformed paths from goobj2, and we now require exact matches with the ImportPath field from "go list -json". Another effect of clearing up the "main" edge case is that -debugdir now uses the right directory for main packages. We also start using consistent debugdir paths in the tests, for the sake of being easier to read and maintain. Finally, note that commandReverse did not need the extra call to "go list -toolexec", as the "shared" call stored in the cache is enough. We still call toolexecCmd to get said cache, which should probably be simplified in a future PR. While at it, replace the use of the "-std" compiler flag with the Standard field from "go list -json".
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
// Copyright (c) 2019, The Garble Authors.
|
|
// See LICENSE for licensing information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// commandReverse implements "garble reverse".
|
|
func commandReverse(args []string) error {
|
|
flags, args := splitFlagsFromArgs(args)
|
|
mainPkg := "."
|
|
if len(args) > 0 {
|
|
mainPkg = args[0]
|
|
args = args[1:]
|
|
}
|
|
|
|
listArgs := []string{
|
|
"-json",
|
|
"-deps",
|
|
"-export",
|
|
}
|
|
listArgs = append(listArgs, flags...)
|
|
listArgs = append(listArgs, mainPkg)
|
|
// TODO: We most likely no longer need this "list -toolexec" call, since
|
|
// we use the original build IDs.
|
|
if _, err := toolexecCmd("list", listArgs); err != nil {
|
|
return err
|
|
}
|
|
|
|
// A package's names are generally hashed with the action ID of its
|
|
// obfuscated build. We recorded those action IDs above.
|
|
// Note that we parse Go files directly to obtain the names, since the
|
|
// export data only exposes exported names. Parsing Go files is cheap,
|
|
// so it's unnecessary to try to avoid this cost.
|
|
var replaces []string
|
|
fset := token.NewFileSet()
|
|
|
|
for _, lpkg := range cache.ListedPackages {
|
|
if !lpkg.Private {
|
|
continue
|
|
}
|
|
addReplace := func(str string) {
|
|
replaces = append(replaces, hashWith(lpkg.GarbleActionID, str), str)
|
|
}
|
|
|
|
// Package paths are obfuscated, too.
|
|
addReplace(lpkg.ImportPath)
|
|
|
|
for _, goFile := range lpkg.GoFiles {
|
|
goFile = filepath.Join(lpkg.Dir, goFile)
|
|
file, err := parser.ParseFile(fset, goFile, nil, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, decl := range file.Decls {
|
|
// TODO: Probably do type names too. What else?
|
|
switch decl := decl.(type) {
|
|
case *ast.FuncDecl:
|
|
addReplace(decl.Name.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
repl := strings.NewReplacer(replaces...)
|
|
|
|
// TODO: return a non-zero status code if we could not reverse any string.
|
|
if len(args) == 0 {
|
|
return reverseContent(os.Stdout, os.Stdin, repl)
|
|
}
|
|
for _, path := range args {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
if err := reverseContent(os.Stdout, f, repl); err != nil {
|
|
return err
|
|
}
|
|
f.Close() // since we're in a loop
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func reverseContent(w io.Writer, r io.Reader, repl *strings.Replacer) error {
|
|
// Read line by line.
|
|
// Reading the entire content at once wouldn't be interactive,
|
|
// nor would it support large files well.
|
|
// Reading entire lines ensures we don't cut words in half.
|
|
// We use bufio.Reader instead of bufio.Scanner,
|
|
// to also obtain the newline characters themselves.
|
|
br := bufio.NewReader(r)
|
|
for {
|
|
// Note that ReadString can return a line as well as an error if
|
|
// we hit EOF without a newline.
|
|
// In that case, we still want to process the string.
|
|
line, readErr := br.ReadString('\n')
|
|
if _, err := repl.WriteString(w, line); err != nil {
|
|
return err
|
|
}
|
|
if readErr == io.EOF {
|
|
return nil
|
|
}
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
}
|
|
}
|