diff --git a/syft/file/cataloger/executable/elf_test.go b/syft/file/cataloger/executable/elf_test.go index 728526f88..69fc0e6c0 100644 --- a/syft/file/cataloger/executable/elf_test.go +++ b/syft/file/cataloger/executable/elf_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -265,6 +266,38 @@ func Test_elfGoToolchainDetection(t *testing.T) { } } +func Test_elfCgoToolchainDetection(t *testing.T) { + readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { + t.Helper() + f, err := os.Open(filepath.Join("test-fixtures/golang", fixture)) + require.NoError(t, err) + return f + } + + t.Run("cgo binary has both go and c toolchains", func(t *testing.T) { + reader := readerForFixture(t, "bin/hello_linux_cgo") + f, err := elf.NewFile(reader) + require.NoError(t, err) + + toolchains := elfToolchains(reader, f) + + // versions are dynamic based on Docker image, so we ignore them in comparison + want := []file.Toolchain{ + {Name: "go", Kind: file.ToolchainKindCompiler}, + {Name: "gcc", Kind: file.ToolchainKindCompiler}, + } + + if d := cmp.Diff(want, toolchains, cmpopts.IgnoreFields(file.Toolchain{}, "Version")); d != "" { + t.Errorf("elfToolchains() mismatch (-want +got):\n%s", d) + } + + // verify versions are populated + for _, tc := range toolchains { + assert.NotEmpty(t, tc.Version, "expected version to be set for %s toolchain", tc.Name) + } + }) +} + func Test_elfGoSymbolCapture(t *testing.T) { readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader { t.Helper() diff --git a/syft/file/cataloger/executable/test-fixtures/golang/Dockerfile b/syft/file/cataloger/executable/test-fixtures/golang/Dockerfile index b827f2d93..557085672 100644 --- a/syft/file/cataloger/executable/test-fixtures/golang/Dockerfile +++ b/syft/file/cataloger/executable/test-fixtures/golang/Dockerfile @@ -5,10 +5,14 @@ WORKDIR /app COPY go.mod go.sum ./ RUN go mod download -COPY main.go ./ +COPY main.go cgo_main.go ./ +# pure Go builds (no CGO) RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /hello_linux . RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o /hello_mac . RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o /hello.exe . + +# CGO-enabled build (Linux only, uses gcc) +RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o /hello_linux_cgo ./cgo_main.go diff --git a/syft/file/cataloger/executable/test-fixtures/golang/Makefile b/syft/file/cataloger/executable/test-fixtures/golang/Makefile index 71a5c73a0..8cc87fe2f 100644 --- a/syft/file/cataloger/executable/test-fixtures/golang/Makefile +++ b/syft/file/cataloger/executable/test-fixtures/golang/Makefile @@ -19,12 +19,12 @@ tools-check: tools: @(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || \ - (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) + (docker build --platform=linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) build: tools @mkdir -p $(BIN) docker run -i -v $(shell pwd)/$(BIN):/out $(TOOL_IMAGE) sh -c \ - "cp /hello_linux /hello_mac /hello.exe /out/" + "cp /hello_linux /hello_mac /hello.exe /hello_linux_cgo /out/" debug: docker run -it --rm -v $(shell pwd):/mount -w /mount $(TOOL_IMAGE) sh diff --git a/syft/file/cataloger/executable/test-fixtures/golang/cgo_main.go b/syft/file/cataloger/executable/test-fixtures/golang/cgo_main.go new file mode 100644 index 000000000..26a51030d --- /dev/null +++ b/syft/file/cataloger/executable/test-fixtures/golang/cgo_main.go @@ -0,0 +1,18 @@ +package main + +/* +#include +#include + +int get_length(const char* s) { + return strlen(s); +} +*/ +import "C" +import "fmt" + +func main() { + msg := C.CString("Hello from CGO!") + length := C.get_length(msg) + fmt.Printf("String length: %d\n", int(length)) +}