Skip to content

Orders source files before compilation to ensure reproducible output #1100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
if err != nil {
panic(err)
}
file, err := parser.ParseFile(fileSet, fullPath, r, parser.ParseComments)
// Files should be uniquely named and in the original package directory in order to be
// ordered correctly
newPath := path.Join(pkg.Dir, "gopherjs__"+name)
file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments)
if err != nil {
panic(err)
}
Expand Down
146 changes: 146 additions & 0 deletions compiler/compiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package compiler

import (
"bytes"
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"testing"

"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/loader"
)

func TestOrder(t *testing.T) {
fileA := `
package foo

var Avar = "a"

type Atype struct{}

func Afunc() int {
var varA = 1
var varB = 2
return varA+varB
}
`

fileB := `
package foo

var Bvar = "b"

type Btype struct{}

func Bfunc() int {
var varA = 1
var varB = 2
return varA+varB
}
`
files := []source{{"fileA.go", []byte(fileA)}, {"fileB.go", []byte(fileB)}}

compare(t, "foo", files, false)
compare(t, "foo", files, true)
}

func compare(t *testing.T, path string, sourceFiles []source, minify bool) {
outputNormal, err := compile(path, sourceFiles, minify)
if err != nil {
t.Fatal(err)
}

// reverse the array
for i, j := 0, len(sourceFiles)-1; i < j; i, j = i+1, j-1 {
sourceFiles[i], sourceFiles[j] = sourceFiles[j], sourceFiles[i]
}

outputReversed, err := compile(path, sourceFiles, minify)
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(string(outputNormal), string(outputReversed)); diff != "" {
t.Errorf("files in different order produce different JS:\n%s", diff)
}
}

type source struct {
name string
contents []byte
}

func compile(path string, sourceFiles []source, minify bool) ([]byte, error) {
conf := loader.Config{}
conf.Fset = token.NewFileSet()
conf.ParserMode = parser.ParseComments

context := build.Default // make a copy of build.Default
conf.Build = &context
conf.Build.BuildTags = []string{"js"}

var astFiles []*ast.File
for _, sourceFile := range sourceFiles {
astFile, err := parser.ParseFile(conf.Fset, sourceFile.name, sourceFile.contents, parser.ParseComments)
if err != nil {
return nil, err
}
astFiles = append(astFiles, astFile)
}
conf.CreateFromFiles(path, astFiles...)
prog, err := conf.Load()
if err != nil {
return nil, err
}

archiveCache := map[string]*Archive{}
var importContext *ImportContext
importContext = &ImportContext{
Packages: make(map[string]*types.Package),
Import: func(path string) (*Archive, error) {
// find in local cache
if a, ok := archiveCache[path]; ok {
return a, nil
}

pi := prog.Package(path)
importContext.Packages[path] = pi.Pkg

// compile package
a, err := Compile(path, pi.Files, prog.Fset, importContext, minify)
if err != nil {
return nil, err
}
archiveCache[path] = a
return a, nil
},
}

a, err := importContext.Import(path)
if err != nil {
return nil, err
}
b, err := renderPackage(a)
if err != nil {
return nil, err
}
return b, nil
}

func renderPackage(archive *Archive) ([]byte, error) {
selection := make(map[*Decl]struct{})
for _, d := range archive.Declarations {
selection[d] = struct{}{}
}

buf := &bytes.Buffer{}

if err := WritePkgCode(archive, selection, goLinknameSet{}, false, &SourceMapFilter{Writer: buf}); err != nil {
return nil, err
}

return buf.Bytes(), nil
}
6 changes: 6 additions & 0 deletions compiler/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
// Some other unexpected panic, catch the stack trace and return as an error.
err = bailout(e)
}()

// Files must be in the same order to get reproducible JS
sort.Slice(files, func(i, j int) bool {
return fileSet.File(files[i].Pos()).Name() > fileSet.File(files[j].Pos()).Name()
})

typesInfo := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ require (

require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxr
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
Expand Down