Skip to content

Commit d5b9503

Browse files
timcooijmansthepudds
authored andcommitted
cmd/cgo: fix unaligned arguments typedmemmove crash on iOS
Irregularly typedmemmove and bulkBarrierPreWrite crashes on unaligned arguments. By aligning the arguments this is fixed. Fixes #46893 Change-Id: I7beb9fdc31053fcb71bee6c6cb906dea31718c56 GitHub-Last-Rev: 46ae8b9 GitHub-Pull-Request: #74868 Reviewed-on: https://go-review.googlesource.com/c/go/+/692935 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 5384500 commit d5b9503

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package out_test
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"fmt"
11+
"internal/testenv"
12+
"internal/goarch"
13+
"os"
14+
"path/filepath"
15+
"regexp"
16+
"strconv"
17+
"strings"
18+
"testing"
19+
)
20+
21+
type methodAlign struct {
22+
Method string
23+
Align int
24+
}
25+
26+
var wantAligns = map[string]int{
27+
"ReturnEmpty": 1,
28+
"ReturnOnlyUint8": 1,
29+
"ReturnOnlyUint16": 2,
30+
"ReturnOnlyUint32": 4,
31+
"ReturnOnlyUint64": goarch.PtrSize,
32+
"ReturnOnlyInt": goarch.PtrSize,
33+
"ReturnOnlyPtr": goarch.PtrSize,
34+
"ReturnByteSlice": goarch.PtrSize,
35+
"ReturnString": goarch.PtrSize,
36+
"InputAndReturnUint8": 1,
37+
"MixedTypes": goarch.PtrSize,
38+
}
39+
40+
// TestAligned tests that the generated _cgo_export.c file has the wanted
41+
// align attributes for struct types used as arguments or results of
42+
// //exported functions.
43+
func TestAligned(t *testing.T) {
44+
testenv.MustHaveGoRun(t)
45+
testenv.MustHaveCGO(t)
46+
47+
testdata, err := filepath.Abs("testdata")
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
52+
objDir := t.TempDir()
53+
54+
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo",
55+
"-objdir", objDir,
56+
filepath.Join(testdata, "aligned.go"))
57+
cmd.Stderr = new(bytes.Buffer)
58+
59+
err = cmd.Run()
60+
if err != nil {
61+
t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr)
62+
}
63+
64+
haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c"))
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
// Check that we have all the wanted methods
70+
if len(haveAligns) != len(wantAligns) {
71+
t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns))
72+
}
73+
74+
for i := range haveAligns {
75+
method := haveAligns[i].Method
76+
haveAlign := haveAligns[i].Align
77+
78+
wantAlign, ok := wantAligns[method]
79+
if !ok {
80+
t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign)
81+
} else if haveAlign != wantAlign {
82+
t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign)
83+
}
84+
}
85+
}
86+
87+
func parseAlign(filename string) ([]methodAlign, error) {
88+
file, err := os.Open(filename)
89+
if err != nil {
90+
return nil, fmt.Errorf("failed to open file: %w", err)
91+
}
92+
defer file.Close()
93+
94+
var results []methodAlign
95+
scanner := bufio.NewScanner(file)
96+
97+
// Regex to match function declarations like "struct MethodName_return MethodName("
98+
funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`)
99+
// Regex to match simple function declarations like "GoSlice MethodName("
100+
simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`)
101+
// Regex to match void-returning exported functions like "void ReturnEmpty("
102+
voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`)
103+
// Regex to match align attributes like "__attribute__((aligned(8)))"
104+
alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`)
105+
106+
var currentMethod string
107+
108+
for scanner.Scan() {
109+
line := strings.TrimSpace(scanner.Text())
110+
111+
// Check if this line declares a function with struct return type
112+
if matches := funcRegex.FindStringSubmatch(line); matches != nil {
113+
currentMethod = matches[2] // Extract the method name
114+
} else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil {
115+
// Check if this line declares a function with simple return type (like GoSlice)
116+
currentMethod = matches[1] // Extract the method name
117+
} else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil {
118+
// Check if this line declares a void-returning function
119+
currentMethod = matches[1] // Extract the method name
120+
}
121+
122+
// Check if this line contains align information
123+
if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" {
124+
alignStr := alignMatches[1]
125+
align, err := strconv.Atoi(alignStr)
126+
if err != nil {
127+
// Skip this entry if we can't parse the align as integer
128+
currentMethod = ""
129+
continue
130+
}
131+
results = append(results, methodAlign{
132+
Method: currentMethod,
133+
Align: align,
134+
})
135+
currentMethod = "" // Reset for next method
136+
}
137+
}
138+
139+
if err := scanner.Err(); err != nil {
140+
return nil, fmt.Errorf("error reading file: %w", err)
141+
}
142+
143+
return results, nil
144+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import "C"
8+
9+
//export ReturnEmpty
10+
func ReturnEmpty() {
11+
return
12+
}
13+
14+
//export ReturnOnlyUint8
15+
func ReturnOnlyUint8() (uint8, uint8, uint8) {
16+
return 1, 2, 3
17+
}
18+
19+
//export ReturnOnlyUint16
20+
func ReturnOnlyUint16() (uint16, uint16, uint16) {
21+
return 1, 2, 3
22+
}
23+
24+
//export ReturnOnlyUint32
25+
func ReturnOnlyUint32() (uint32, uint32, uint32) {
26+
return 1, 2, 3
27+
}
28+
29+
//export ReturnOnlyUint64
30+
func ReturnOnlyUint64() (uint64, uint64, uint64) {
31+
return 1, 2, 3
32+
}
33+
34+
//export ReturnOnlyInt
35+
func ReturnOnlyInt() (int, int, int) {
36+
return 1, 2, 3
37+
}
38+
39+
//export ReturnOnlyPtr
40+
func ReturnOnlyPtr() (*int, *int, *int) {
41+
a, b, c := 1, 2, 3
42+
return &a, &b, &c
43+
}
44+
45+
//export ReturnString
46+
func ReturnString() string {
47+
return "hello"
48+
}
49+
50+
//export ReturnByteSlice
51+
func ReturnByteSlice() []byte {
52+
return []byte{1, 2, 3}
53+
}
54+
55+
//export InputAndReturnUint8
56+
func InputAndReturnUint8(a, b, c uint8) (uint8, uint8, uint8) {
57+
return a, b, c
58+
}
59+
60+
//export MixedTypes
61+
func MixedTypes(a uint8, b uint16, c uint32, d uint64, e int, f *int) (uint8, uint16, uint32, uint64, int, *int) {
62+
return a, b, c, d, e, f
63+
}

src/cmd/cgo/out.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
949949
fmt.Fprintf(gotype, "struct {\n")
950950
off := int64(0)
951951
npad := 0
952+
// the align is at least 1 (for char)
953+
maxAlign := int64(1)
952954
argField := func(typ ast.Expr, namePat string, args ...interface{}) {
953955
name := fmt.Sprintf(namePat, args...)
954956
t := p.cgoType(typ)
@@ -963,6 +965,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
963965
noSourceConf.Fprint(gotype, fset, typ)
964966
fmt.Fprintf(gotype, "\n")
965967
off += t.Size
968+
// keep track of the maximum alignment among all fields
969+
// so that we can align the struct correctly
970+
if t.Align > maxAlign {
971+
maxAlign = t.Align
972+
}
966973
}
967974
if fn.Recv != nil {
968975
argField(fn.Recv.List[0].Type, "recv")
@@ -1047,7 +1054,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
10471054
// string.h for memset, and is also robust to C++
10481055
// types with constructors. Both GCC and LLVM optimize
10491056
// this into just zeroing _cgo_a.
1050-
fmt.Fprintf(fgcc, "\ttypedef %s %v _cgo_argtype;\n", ctype.String(), p.packedAttribute())
1057+
//
1058+
// The struct should be aligned to the maximum alignment
1059+
// of any of its fields. This to avoid alignment
1060+
// issues.
1061+
fmt.Fprintf(fgcc, "\ttypedef %s %v __attribute__((aligned(%d))) _cgo_argtype;\n", ctype.String(), p.packedAttribute(), maxAlign)
10511062
fmt.Fprintf(fgcc, "\tstatic _cgo_argtype _cgo_zero;\n")
10521063
fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _cgo_zero;\n")
10531064
if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {

0 commit comments

Comments
 (0)