From b72f0588d7e0dd29ece87cb25e25502b7bd04ed8 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 26 Sep 2026 09:34:41 +0100 Subject: [PATCH 1/2] Support TinyGo binary inspection --- .github/workflows/ci.yml | 19 ++++++++++++ binary/binary.go | 29 +++--------------- binary/buildinfo_std.go | 34 +++++++++++++++++++++ binary/buildinfo_tinygo.go | 13 ++++++++ binary/macho.go | 11 +++---- binary/reader_test.go | 62 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 binary/buildinfo_std.go create mode 100644 binary/buildinfo_tinygo.go create mode 100644 binary/reader_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5eb1a14..028140e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,25 @@ jobs: - name: Test run: go test -v -race ./... + - name: Test TinyGo build-info fallback + run: go test -tags=tinygo -run TestInspectReaderWithoutGoMetadata ./binary + + - name: Check debug/buildinfo stays out of the TinyGo build + if: runner.os == 'Linux' + run: | + if go list -tags=tinygo -deps . ./binary | grep -qx debug/buildinfo; then + echo "debug/buildinfo reached the tinygo build; keep it behind //go:build !tinygo" >&2 + exit 1 + fi + + - name: Build WebAssembly library + if: runner.os == 'Linux' + run: GOOS=js GOARCH=wasm go build . ./binary + + - name: Build WASI library + if: runner.os == 'Linux' + run: GOOS=wasip1 GOARCH=wasm go build . ./binary + lint: runs-on: ubuntu-latest steps: diff --git a/binary/binary.go b/binary/binary.go index 36bce93..217c4f9 100644 --- a/binary/binary.go +++ b/binary/binary.go @@ -4,7 +4,6 @@ package binary import ( - "debug/buildinfo" "errors" "fmt" "io" @@ -39,8 +38,8 @@ type Object struct { // is present. Producer []string `json:"producer,omitempty"` - // Go is set when the object was produced by the Go toolchain and - // carries embedded build metadata. + // Go is set when the object carries Go build metadata. It is unavailable + // under TinyGo. Go *GoBuild `json:"go,omitempty"` // Static lists libraries that appear to be statically linked into the @@ -132,9 +131,9 @@ func InspectReader(r io.ReaderAt, size int64) (*Object, error) { // The fat Mach-O path reads build info from its first slice because // debug/buildinfo cannot open fat containers itself. if obj.Go == nil { - if bi, err := buildinfo.Read(sr); err == nil { - obj.Go = goBuildFrom(bi) - obj.Producer = append(obj.Producer, bi.GoVersion) + if bi, err := readGoBuild(sr); err == nil { + obj.Go = bi + obj.Producer = append(obj.Producer, bi.Version) } } @@ -159,21 +158,3 @@ func dispatch(r *io.SectionReader, size int64, head [4]byte) (*Object, []byte, e return nil, nil, ErrUnrecognized } } - -func goBuildFrom(bi *buildinfo.BuildInfo) *GoBuild { - g := &GoBuild{ - Version: bi.GoVersion, - Path: bi.Path, - } - if bi.Main.Path != "" { - g.Main = bi.Main.Path + "@" + bi.Main.Version - } - for _, dep := range bi.Deps { - if dep.Replace != nil { - g.Deps = append(g.Deps, dep.Path+" => "+dep.Replace.Path+"@"+dep.Replace.Version) - } else { - g.Deps = append(g.Deps, dep.Path+"@"+dep.Version) - } - } - return g -} diff --git a/binary/buildinfo_std.go b/binary/buildinfo_std.go new file mode 100644 index 0000000..c3686b4 --- /dev/null +++ b/binary/buildinfo_std.go @@ -0,0 +1,34 @@ +//go:build !tinygo + +package binary + +import ( + "debug/buildinfo" + "io" +) + +func readGoBuild(r io.ReaderAt) (*GoBuild, error) { + bi, err := buildinfo.Read(r) + if err != nil { + return nil, err + } + return goBuildFrom(bi), nil +} + +func goBuildFrom(bi *buildinfo.BuildInfo) *GoBuild { + g := &GoBuild{ + Version: bi.GoVersion, + Path: bi.Path, + } + if bi.Main.Path != "" { + g.Main = bi.Main.Path + "@" + bi.Main.Version + } + for _, dep := range bi.Deps { + if dep.Replace != nil { + g.Deps = append(g.Deps, dep.Path+" => "+dep.Replace.Path+"@"+dep.Replace.Version) + } else { + g.Deps = append(g.Deps, dep.Path+"@"+dep.Version) + } + } + return g +} diff --git a/binary/buildinfo_tinygo.go b/binary/buildinfo_tinygo.go new file mode 100644 index 0000000..0d0f9d5 --- /dev/null +++ b/binary/buildinfo_tinygo.go @@ -0,0 +1,13 @@ +//go:build tinygo + +package binary + +import ( + "errors" + "fmt" + "io" +) + +func readGoBuild(r io.ReaderAt) (*GoBuild, error) { + return nil, fmt.Errorf("brief/binary: Go buildinfo inspection not available under tinygo: %w", errors.ErrUnsupported) +} diff --git a/binary/macho.go b/binary/macho.go index 6329df2..4fe1a92 100644 --- a/binary/macho.go +++ b/binary/macho.go @@ -2,7 +2,6 @@ package binary import ( "bytes" - "debug/buildinfo" "debug/macho" stdbin "encoding/binary" "errors" @@ -110,13 +109,13 @@ func inspectMachOFat(r io.ReaderAt, size int64, head [4]byte) (*Object, []byte, rodata.Write(sliceROData) } - if bi, err := buildinfo.Read(sr); err == nil { + if bi, err := readGoBuild(sr); err == nil { if obj.Go == nil { - obj.Go = goBuildFrom(bi) + obj.Go = bi } - if !producerSeen[bi.GoVersion] { - producerSeen[bi.GoVersion] = true - obj.Producer = append(obj.Producer, bi.GoVersion) + if !producerSeen[bi.Version] { + producerSeen[bi.Version] = true + obj.Producer = append(obj.Producer, bi.Version) } } } diff --git a/binary/reader_test.go b/binary/reader_test.go new file mode 100644 index 0000000..3dc5ce5 --- /dev/null +++ b/binary/reader_test.go @@ -0,0 +1,62 @@ +package binary_test + +import ( + "bytes" + "debug/elf" + "debug/macho" + "encoding/binary" + "testing" + + briefbinary "github.com/git-pkgs/brief/binary" +) + +func TestInspectReaderWithoutGoMetadata(t *testing.T) { + var elfData bytes.Buffer + elfHeader := elf.Header64{ + Ident: [elf.EI_NIDENT]byte{0x7f, 'E', 'L', 'F', byte(elf.ELFCLASS64), byte(elf.ELFDATA2LSB), byte(elf.EV_CURRENT)}, + Type: uint16(elf.ET_REL), Machine: uint16(elf.EM_X86_64), Version: uint32(elf.EV_CURRENT), + Ehsize: 64, + } + if err := binary.Write(&elfData, binary.LittleEndian, elfHeader); err != nil { + t.Fatal(err) + } + + var machData bytes.Buffer + machHeader := macho.FileHeader{Magic: macho.Magic64, Cpu: macho.CpuArm64, Type: macho.TypeObj} + if err := binary.Write(&machData, binary.LittleEndian, machHeader); err != nil { + t.Fatal(err) + } + if err := binary.Write(&machData, binary.LittleEndian, uint32(0)); err != nil { + t.Fatal(err) + } + + var fatData bytes.Buffer + fatHeader := []uint32{macho.MagicFat, 1, uint32(macho.CpuArm64), 0, 28, uint32(machData.Len()), 0} + if err := binary.Write(&fatData, binary.BigEndian, fatHeader); err != nil { + t.Fatal(err) + } + fatData.Write(machData.Bytes()) + + for _, tt := range []struct { + format string + arch string + data []byte + }{ + {"elf", "amd64", elfData.Bytes()}, + {"mach-o", "arm64", machData.Bytes()}, + {"mach-o-universal", "arm64", fatData.Bytes()}, + } { + t.Run(tt.format, func(t *testing.T) { + obj, err := briefbinary.InspectReader(bytes.NewReader(tt.data), int64(len(tt.data))) + if err != nil { + t.Fatal(err) + } + if obj.Format != tt.format || obj.Arch != tt.arch { + t.Fatalf("object = %+v, want %s/%s", obj, tt.format, tt.arch) + } + if obj.Go != nil || len(obj.Producer) != 0 { + t.Fatalf("unexpected Go metadata: Go=%+v, Producer=%v", obj.Go, obj.Producer) + } + }) + } +} From e3102485022a9d877a8edcd7497597dd6e4e391e Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 26 Sep 2026 20:50:26 +0100 Subject: [PATCH 2/2] Run binary inspection tests with TinyGo in CI --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 028140e..f80a058 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,44 @@ jobs: if: runner.os == 'Linux' run: GOOS=wasip1 GOARCH=wasm go build . ./binary + tinygo: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + + - name: Set up Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + package-manager-cache: false + + - name: Install TinyGo and Wasmtime + working-directory: ${{ runner.temp }} + run: | + curl --fail --location --silent --show-error --output tinygo.tar.gz https://github.com/tinygo-org/tinygo/releases/download/v0.42.0/tinygo0.42.0.linux-amd64.tar.gz + echo 'b87688fa2e19cee7d813cad7fd7dadb71dff3198e47125aba66ba4af5e490438 tinygo.tar.gz' | sha256sum --check + tar -xzf tinygo.tar.gz + echo "$RUNNER_TEMP/tinygo/bin" >> "$GITHUB_PATH" + curl --fail --location --silent --show-error --output wasmtime.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux.tar.xz + echo 'afd58715f105e3a7f454169daed22168c5736ec5f225fb04c4ac62c54c9508a3 wasmtime.tar.xz' | sha256sum --check + tar -xJf wasmtime.tar.xz + echo "$RUNNER_TEMP/wasmtime-v44.0.1-x86_64-linux" >> "$GITHUB_PATH" + + - name: Test TinyGo WebAssembly binary inspection + run: tinygo test -target=wasm -run '^TestInspectReaderWithoutGoMetadata$' -v ./binary + + - name: Test TinyGo WASI binary inspection + run: tinygo test -target=wasip1 -run '^TestInspectReaderWithoutGoMetadata$' -v ./binary + lint: runs-on: ubuntu-latest steps: