position.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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 information.
  17. func printFile(lpkg *listedPackage, file *ast.File) ([]byte, error) {
  18. if lpkg.ToObfuscate {
  19. // Omit comments from the final Go code.
  20. // Keep directives, as they affect the build.
  21. // We do this before printing to print fewer bytes below.
  22. var newComments []*ast.CommentGroup
  23. for _, group := range file.Comments {
  24. var newGroup ast.CommentGroup
  25. for _, comment := range group.List {
  26. if strings.HasPrefix(comment.Text, "//go:") {
  27. newGroup.List = append(newGroup.List, comment)
  28. }
  29. }
  30. if len(newGroup.List) > 0 {
  31. newComments = append(newComments, &newGroup)
  32. }
  33. }
  34. file.Comments = newComments
  35. }
  36. printBuf1.Reset()
  37. printConfig := printer.Config{Mode: printer.RawFormat}
  38. if err := printConfig.Fprint(&printBuf1, fset, file); err != nil {
  39. return nil, err
  40. }
  41. src := printBuf1.Bytes()
  42. if !lpkg.ToObfuscate {
  43. // We lightly transform packages which shouldn't be obfuscated,
  44. // such as when rewriting go:linkname directives to obfuscated packages.
  45. // We still need to print the files, but without obfuscating positions.
  46. return src, nil
  47. }
  48. fsetFile := fset.File(file.Pos())
  49. filename := filepath.Base(fsetFile.Name())
  50. newPrefix := ""
  51. if strings.HasPrefix(filename, "_cgo_") {
  52. newPrefix = "_cgo_"
  53. }
  54. // Many parts of garble, notably the literal obfuscator, modify the AST.
  55. // Unfortunately, comments are free-floating in File.Comments,
  56. // and those are the only source of truth that go/printer uses.
  57. // So the positions of the comments in the given file are wrong.
  58. // The only way we can get the final ones is to tokenize again.
  59. // Using go/scanner is slightly awkward, but cheaper than parsing again.
  60. // We want to use the original positions for the hashed positions.
  61. // Since later we'll iterate on tokens rather than walking an AST,
  62. // we use a list of offsets indexed by identifiers in source order.
  63. var origCallOffsets []int
  64. nextOffset := -1
  65. ast.Inspect(file, func(node ast.Node) bool {
  66. switch node := node.(type) {
  67. case *ast.CallExpr:
  68. nextOffset = fsetFile.Position(node.Pos()).Offset
  69. case *ast.Ident:
  70. origCallOffsets = append(origCallOffsets, nextOffset)
  71. nextOffset = -1
  72. }
  73. return true
  74. })
  75. copied := 0
  76. printBuf2.Reset()
  77. // Make sure the entire file gets a zero filename by default,
  78. // in case we miss any positions below.
  79. // We use a //-style comment, because there might be build tags.
  80. fmt.Fprintf(&printBuf2, "//line %s:1\n", newPrefix)
  81. // We use an empty filename when tokenizing below.
  82. // We use a nil go/scanner.ErrorHandler because src comes from go/printer.
  83. // Syntax errors should be rare, and when they do happen,
  84. // we don't want to point to the original source file on disk.
  85. // That would be confusing, as we've changed the source in memory.
  86. var s scanner.Scanner
  87. fsetFile = fset.AddFile("", fset.Base(), len(src))
  88. s.Init(fsetFile, src, nil, scanner.ScanComments)
  89. identIndex := 0
  90. for {
  91. pos, tok, lit := s.Scan()
  92. switch tok {
  93. case token.EOF:
  94. // Copy the rest and return.
  95. printBuf2.Write(src[copied:])
  96. return printBuf2.Bytes(), nil
  97. case token.COMMENT:
  98. // Omit comments from the final Go code, again.
  99. // Before we removed the comments from file.Comments,
  100. // but go/printer also grabs comments from some Doc ast.Node fields.
  101. // TODO: is there an easy way to filter all comments at once?
  102. if strings.HasPrefix(lit, "//go:") {
  103. continue // directives are kept
  104. }
  105. offset := fsetFile.Position(pos).Offset
  106. printBuf2.Write(src[copied:offset])
  107. copied = offset + len(lit)
  108. case token.IDENT:
  109. origOffset := origCallOffsets[identIndex]
  110. identIndex++
  111. if origOffset == -1 {
  112. continue // identifiers which don't start func calls are left untouched
  113. }
  114. newName := ""
  115. if !flagTiny {
  116. origPos := fmt.Sprintf("%s:%d", filename, origOffset)
  117. newName = hashWithPackage(lpkg, origPos) + ".go"
  118. // log.Printf("%q hashed with %x to %q", origPos, curPkg.GarbleActionID, newName)
  119. }
  120. offset := fsetFile.Position(pos).Offset
  121. printBuf2.Write(src[copied:offset])
  122. copied = offset
  123. // We use the "/*text*/" form, since we can use multiple of them
  124. // on a single line, and they don't require extra newlines.
  125. // Make sure there is whitespace at either side of a comment.
  126. // Otherwise, we could change the syntax of the program.
  127. // Inserting "/*text*/" in "a/b" // must be "a/ /*text*/ b",
  128. // as "a//*text*/b" is tokenized as a "//" comment.
  129. fmt.Fprintf(&printBuf2, " /*line %s%s:1*/ ", newPrefix, newName)
  130. }
  131. }
  132. }