position.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. // Copyright (c) 2020, The Garble Authors.
  2. // See LICENSE for licensing information.
  3. package main
  4. import (
  5. "bytes"
  6. "fmt"
  7. "go/ast"
  8. "go/printer"
  9. "go/scanner"
  10. "go/token"
  11. "path/filepath"
  12. "strings"
  13. )
  14. var printBuf1, printBuf2 bytes.Buffer
  15. // printFile prints a Go file to a buffer, while also removing non-directive
  16. // comments and adding extra compiler directives to obfuscate position
  17. // information.
  18. func printFile(file *ast.File) ([]byte, error) {
  19. printConfig := printer.Config{Mode: printer.RawFormat}
  20. printBuf1.Reset()
  21. if err := printConfig.Fprint(&printBuf1, fset, file); err != nil {
  22. return nil, err
  23. }
  24. src := printBuf1.Bytes()
  25. if !curPkg.ToObfuscate {
  26. // We lightly transform packages which shouldn't be obfuscated,
  27. // such as when rewriting go:linkname directives to obfuscated packages.
  28. // We still need to print the files, but without obfuscating positions.
  29. return src, nil
  30. }
  31. fsetFile := fset.File(file.Pos())
  32. filename := filepath.Base(fsetFile.Name())
  33. newPrefix := ""
  34. if strings.HasPrefix(filename, "_cgo_") {
  35. newPrefix = "_cgo_"
  36. }
  37. // Many parts of garble, notably the literal obfuscator, modify the AST.
  38. // Unfortunately, comments are free-floating in File.Comments,
  39. // and those are the only source of truth that go/printer uses.
  40. // So the positions of the comments in the given file are wrong.
  41. // The only way we can get the final ones is to tokenize again.
  42. // Using go/scanner is slightly awkward, but cheaper than parsing again.
  43. // We want to use the original positions for the hashed positions.
  44. // Since later we'll iterate on tokens rather than walking an AST,
  45. // we use a list of offsets indexed by identifiers in source order.
  46. var origCallOffsets []int
  47. nextOffset := -1
  48. ast.Inspect(file, func(node ast.Node) bool {
  49. switch node := node.(type) {
  50. case *ast.CallExpr:
  51. nextOffset = fsetFile.Position(node.Pos()).Offset
  52. case *ast.Ident:
  53. origCallOffsets = append(origCallOffsets, nextOffset)
  54. nextOffset = -1
  55. }
  56. return true
  57. })
  58. copied := 0
  59. printBuf2.Reset()
  60. // Make sure the entire file gets a zero filename by default,
  61. // in case we miss any positions below.
  62. // We use a //-style comment, because there might be build tags.
  63. // toAdd is for /*-style comments, so add it to printBuf2 directly.
  64. fmt.Fprintf(&printBuf2, "//line %s:1\n", newPrefix)
  65. // We use an empty filename when tokenizing below.
  66. // We use a nil go/scanner.ErrorHandler because src comes from go/printer.
  67. // Syntax errors should be rare, and when they do happen,
  68. // we don't want to point to the original source file on disk.
  69. // That would be confusing, as we've changed the source in memory.
  70. var s scanner.Scanner
  71. fsetFile = fset.AddFile("", fset.Base(), len(src))
  72. s.Init(fsetFile, src, nil, scanner.ScanComments)
  73. identIndex := 0
  74. for {
  75. pos, tok, lit := s.Scan()
  76. switch tok {
  77. case token.EOF:
  78. // Copy the rest and return.
  79. printBuf2.Write(src[copied:])
  80. return printBuf2.Bytes(), nil
  81. case token.COMMENT:
  82. // Omit comments from the final Go code.
  83. // Keep directives, as they affect the build.
  84. // This is superior to removing the comments before printing,
  85. // because then the final source would have different line numbers.
  86. if strings.HasPrefix(lit, "//go:") {
  87. continue // directives are kept
  88. }
  89. offset := fsetFile.Position(pos).Offset
  90. printBuf2.Write(src[copied:offset])
  91. copied = offset + len(lit)
  92. case token.IDENT:
  93. origOffset := origCallOffsets[identIndex]
  94. identIndex++
  95. if origOffset == -1 {
  96. continue // identifiers which don't start func calls are left untouched
  97. }
  98. newName := ""
  99. if !flagTiny {
  100. origPos := fmt.Sprintf("%s:%d", filename, origOffset)
  101. newName = hashWithPackage(curPkg, origPos) + ".go"
  102. // log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)
  103. }
  104. offset := fsetFile.Position(pos).Offset
  105. printBuf2.Write(src[copied:offset])
  106. copied = offset
  107. // We use the "/*text*/" form, since we can use multiple of them
  108. // on a single line, and they don't require extra newlines.
  109. // Make sure there is whitespace at either side of a comment.
  110. // Otherwise, we could change the syntax of the program.
  111. // Inserting "/*text*/" in "a/b" // must be "a/ /*text*/ b",
  112. // as "a//*text*/b" is tokenized as a "//" comment.
  113. fmt.Fprintf(&printBuf2, " /*line %s%s:1*/ ", newPrefix, newName)
  114. }
  115. }
  116. }