You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
garble/reverse.go

150 lines
3.5 KiB
Go

// Copyright (c) 2019, The Garble Authors.
// See LICENSE for licensing information.
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"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)
cmd, err := toolexecCmd("list", listArgs)
if err != nil {
return err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
return fmt.Errorf("go list error: %v", err)
}
mainPkgPath := ""
dec := json.NewDecoder(stdout)
var privatePkgPaths []string
for dec.More() {
var pkg listedPackage
if err := dec.Decode(&pkg); err != nil {
return err
}
if pkg.Export == "" {
continue
}
if pkg.Name == "main" {
if mainPkgPath != "" {
return fmt.Errorf("found two main packages: %s %s", mainPkgPath, pkg.ImportPath)
}
mainPkgPath = pkg.ImportPath
}
if isPrivate(pkg.ImportPath) {
privatePkgPaths = append(privatePkgPaths, pkg.ImportPath)
}
buildID, err := buildidOf(pkg.Export)
if err != nil {
return err
}
// Adding it to buildInfo.imports allows us to reuse the
// "if" branch below. Plus, if this edge case triggers
// multiple times in a single package compile, we can
// call "go list" once and cache its result.
buildInfo.imports[pkg.ImportPath] = importedPkg{
packagefile: pkg.Export,
actionID: decodeHash(splitActionID(buildID)),
}
}
if err := cmd.Wait(); err != nil {
return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes())
}
var replaces []string
for _, pkgPath := range privatePkgPaths {
ipkg := buildInfo.imports[pkgPath]
// All original exported names names are hashed with the
// obfuscated package's action ID.
tpkg, err := origImporter.Import(pkgPath)
if err != nil {
return err
}
pkgScope := tpkg.Scope()
for _, name := range pkgScope.Names() {
obj := pkgScope.Lookup(name)
if !obj.Exported() {
continue
}
replaces = append(replaces, hashWith(ipkg.actionID, 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
}
}
}