Skip to content
Closed
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
7 changes: 7 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,13 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
for _, v := range contexts {
if len(rr.Refs) > 0 {
for platform, r := range rr.Refs {
if r == nil {
// Skip nil references. This can occur when BuildKit determines
// that the build result is identical across platforms (e.g., with
// "FROM scratch" or other platform-independent content). In such
// cases, BuildKit may not materialize separate refs for each platform.
continue
}
st, err := r.ToState()
if err != nil {
return err
Expand Down
80 changes: 80 additions & 0 deletions build/build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package build

import (
"context"
"testing"

"github.com/docker/buildx/util/waitmap"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/stretchr/testify/require"
fstypes "github.com/tonistiigi/fsutil/types"
)

type mockReference struct{}

func (m *mockReference) ToState() (llb.State, error) { return llb.Scratch(), nil }
func (m *mockReference) Evaluate(ctx context.Context) error { return nil }
func (m *mockReference) ReadFile(ctx context.Context, req gateway.ReadRequest) ([]byte, error) {
return nil, nil
}
func (m *mockReference) StatFile(ctx context.Context, req gateway.StatRequest) (*fstypes.Stat, error) {
return nil, nil
}
func (m *mockReference) ReadDir(ctx context.Context, req gateway.ReadDirRequest) ([]*fstypes.Stat, error) {
return nil, nil
}

// TestWaitContextDepsWithNilRefs reproduces issue #3508 where nil refs in multi-platform
// builds (e.g., FROM scratch) caused a segmentation fault.
func TestWaitContextDepsWithNilRefs(t *testing.T) {
ctx := context.Background()
results := waitmap.New()

result := &gateway.Result{
Refs: map[string]gateway.Reference{
"linux/amd64": &mockReference{},
"linux/arm64": nil, // Nil ref should not panic
},
}
results.Set("0-base", result)

so := &client.SolveOpt{
FrontendAttrs: map[string]string{
"context:base": "target:base",
},
}

err := waitContextDeps(ctx, 0, results, so)
require.NoError(t, err)

// Only non-nil platform should be set
require.Contains(t, so.FrontendAttrs, "context:base::linux/amd64")
require.NotContains(t, so.FrontendAttrs, "context:base::linux/arm64")
}

// TestWaitContextDepsNormal verifies normal multi-platform operation.
func TestWaitContextDepsNormal(t *testing.T) {
ctx := context.Background()
results := waitmap.New()

result := &gateway.Result{
Refs: map[string]gateway.Reference{
"linux/amd64": &mockReference{},
"linux/arm64": &mockReference{},
},
}
results.Set("0-base", result)

so := &client.SolveOpt{
FrontendAttrs: map[string]string{
"context:base": "target:base",
},
}

err := waitContextDeps(ctx, 0, results, so)
require.NoError(t, err)
require.Contains(t, so.FrontendAttrs, "context:base::linux/amd64")
require.Contains(t, so.FrontendAttrs, "context:base::linux/arm64")
}