]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/cgo: fix unaligned arguments typedmemmove crash on iOS
authorTim Cooijmans <timcooijmans@gmail.com>
Tue, 30 Sep 2025 21:53:11 +0000 (21:53 +0000)
committert hepudds <thepudds1460@gmail.com>
Fri, 3 Oct 2025 11:04:57 +0000 (04:04 -0700)
Irregularly typedmemmove and bulkBarrierPreWrite crashes on unaligned
arguments. By aligning the arguments this is fixed.

Fixes #46893

Change-Id: I7beb9fdc31053fcb71bee6c6cb906dea31718c56
GitHub-Last-Rev: 46ae8b96889644aab60ea4284cf447a740354c6a
GitHub-Pull-Request: golang/go#74868
Reviewed-on: https://go-review.googlesource.com/c/go/+/692935
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/cgo/internal/testout/out_test.go [new file with mode: 0644]
src/cmd/cgo/internal/testout/testdata/aligned.go [new file with mode: 0644]
src/cmd/cgo/out.go

diff --git a/src/cmd/cgo/internal/testout/out_test.go b/src/cmd/cgo/internal/testout/out_test.go
new file mode 100644 (file)
index 0000000..81dfa36
--- /dev/null
@@ -0,0 +1,144 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package out_test
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "internal/testenv"
+       "internal/goarch"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strconv"
+       "strings"
+       "testing"
+)
+
+type methodAlign struct {
+       Method string
+       Align  int
+}
+
+var wantAligns = map[string]int{
+       "ReturnEmpty":         1,
+       "ReturnOnlyUint8":     1,
+       "ReturnOnlyUint16":    2,
+       "ReturnOnlyUint32":    4,
+       "ReturnOnlyUint64":    goarch.PtrSize,
+       "ReturnOnlyInt":       goarch.PtrSize,
+       "ReturnOnlyPtr":       goarch.PtrSize,
+       "ReturnByteSlice":     goarch.PtrSize,
+       "ReturnString":        goarch.PtrSize,
+       "InputAndReturnUint8": 1,
+       "MixedTypes":          goarch.PtrSize,
+}
+
+// TestAligned tests that the generated _cgo_export.c file has the wanted
+// align attributes for struct types used as arguments or results of
+// //exported functions.
+func TestAligned(t *testing.T) {
+       testenv.MustHaveGoRun(t)
+       testenv.MustHaveCGO(t)
+
+       testdata, err := filepath.Abs("testdata")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       objDir := t.TempDir()
+
+       cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo",
+               "-objdir", objDir,
+               filepath.Join(testdata, "aligned.go"))
+       cmd.Stderr = new(bytes.Buffer)
+
+       err = cmd.Run()
+       if err != nil {
+               t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr)
+       }
+
+       haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c"))
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       // Check that we have all the wanted methods
+       if len(haveAligns) != len(wantAligns) {
+               t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns))
+       }
+
+       for i := range haveAligns {
+               method := haveAligns[i].Method
+               haveAlign := haveAligns[i].Align
+
+               wantAlign, ok := wantAligns[method]
+               if !ok {
+                       t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign)
+               } else if haveAlign != wantAlign {
+                       t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign)
+               }
+       }
+}
+
+func parseAlign(filename string) ([]methodAlign, error) {
+       file, err := os.Open(filename)
+       if err != nil {
+               return nil, fmt.Errorf("failed to open file: %w", err)
+       }
+       defer file.Close()
+
+       var results []methodAlign
+       scanner := bufio.NewScanner(file)
+
+       // Regex to match function declarations like "struct MethodName_return MethodName("
+       funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`)
+       // Regex to match simple function declarations like "GoSlice MethodName("
+       simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`)
+       // Regex to match void-returning exported functions like "void ReturnEmpty("
+       voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`)
+       // Regex to match align attributes like "__attribute__((aligned(8)))"
+       alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`)
+
+       var currentMethod string
+
+       for scanner.Scan() {
+               line := strings.TrimSpace(scanner.Text())
+
+               // Check if this line declares a function with struct return type
+               if matches := funcRegex.FindStringSubmatch(line); matches != nil {
+                       currentMethod = matches[2] // Extract the method name
+               } else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil {
+                       // Check if this line declares a function with simple return type (like GoSlice)
+                       currentMethod = matches[1] // Extract the method name
+               } else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil {
+                       // Check if this line declares a void-returning function
+                       currentMethod = matches[1] // Extract the method name
+               }
+
+               // Check if this line contains align information
+               if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" {
+                       alignStr := alignMatches[1]
+                       align, err := strconv.Atoi(alignStr)
+                       if err != nil {
+                               // Skip this entry if we can't parse the align as integer
+                               currentMethod = ""
+                               continue
+                       }
+                       results = append(results, methodAlign{
+                               Method: currentMethod,
+                               Align:  align,
+                       })
+                       currentMethod = "" // Reset for next method
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, fmt.Errorf("error reading file: %w", err)
+       }
+
+       return results, nil
+}
diff --git a/src/cmd/cgo/internal/testout/testdata/aligned.go b/src/cmd/cgo/internal/testout/testdata/aligned.go
new file mode 100644 (file)
index 0000000..cea6f28
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "C"
+
+//export ReturnEmpty
+func ReturnEmpty() {
+       return
+}
+
+//export ReturnOnlyUint8
+func ReturnOnlyUint8() (uint8, uint8, uint8) {
+       return 1, 2, 3
+}
+
+//export ReturnOnlyUint16
+func ReturnOnlyUint16() (uint16, uint16, uint16) {
+       return 1, 2, 3
+}
+
+//export ReturnOnlyUint32
+func ReturnOnlyUint32() (uint32, uint32, uint32) {
+       return 1, 2, 3
+}
+
+//export ReturnOnlyUint64
+func ReturnOnlyUint64() (uint64, uint64, uint64) {
+       return 1, 2, 3
+}
+
+//export ReturnOnlyInt
+func ReturnOnlyInt() (int, int, int) {
+       return 1, 2, 3
+}
+
+//export ReturnOnlyPtr
+func ReturnOnlyPtr() (*int, *int, *int) {
+       a, b, c := 1, 2, 3
+       return &a, &b, &c
+}
+
+//export ReturnString
+func ReturnString() string {
+       return "hello"
+}
+
+//export ReturnByteSlice
+func ReturnByteSlice() []byte {
+       return []byte{1, 2, 3}
+}
+
+//export InputAndReturnUint8
+func InputAndReturnUint8(a, b, c uint8) (uint8, uint8, uint8) {
+       return a, b, c
+}
+
+//export MixedTypes
+func MixedTypes(a uint8, b uint16, c uint32, d uint64, e int, f *int) (uint8, uint16, uint32, uint64, int, *int) {
+       return a, b, c, d, e, f
+}
index 622d35ac7b3babbe5140386a5a89933843f6c1b7..a2bcdf89c5ad44cca1d3ad0fb645259c057428f1 100644 (file)
@@ -949,6 +949,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
                fmt.Fprintf(gotype, "struct {\n")
                off := int64(0)
                npad := 0
+               // the align is at least 1 (for char)
+               maxAlign := int64(1)
                argField := func(typ ast.Expr, namePat string, args ...interface{}) {
                        name := fmt.Sprintf(namePat, args...)
                        t := p.cgoType(typ)
@@ -963,6 +965,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
                        noSourceConf.Fprint(gotype, fset, typ)
                        fmt.Fprintf(gotype, "\n")
                        off += t.Size
+                       // keep track of the maximum alignment among all fields
+                       // so that we can align the struct correctly
+                       if t.Align > maxAlign {
+                               maxAlign = t.Align
+                       }
                }
                if fn.Recv != nil {
                        argField(fn.Recv.List[0].Type, "recv")
@@ -1047,7 +1054,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
                // string.h for memset, and is also robust to C++
                // types with constructors. Both GCC and LLVM optimize
                // this into just zeroing _cgo_a.
-               fmt.Fprintf(fgcc, "\ttypedef %s %v _cgo_argtype;\n", ctype.String(), p.packedAttribute())
+               //
+               // The struct should be aligned to the maximum alignment
+               // of any of its fields. This to avoid alignment
+               // issues.
+               fmt.Fprintf(fgcc, "\ttypedef %s %v __attribute__((aligned(%d))) _cgo_argtype;\n", ctype.String(), p.packedAttribute(), maxAlign)
                fmt.Fprintf(fgcc, "\tstatic _cgo_argtype _cgo_zero;\n")
                fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _cgo_zero;\n")
                if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {