mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00
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: 46ae8b9688
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>
This commit is contained in:
parent
53845004d6
commit
d5b950399d
3 changed files with 219 additions and 1 deletions
144
src/cmd/cgo/internal/testout/out_test.go
Normal file
144
src/cmd/cgo/internal/testout/out_test.go
Normal file
|
@ -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
|
||||||
|
}
|
63
src/cmd/cgo/internal/testout/testdata/aligned.go
vendored
Normal file
63
src/cmd/cgo/internal/testout/testdata/aligned.go
vendored
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -949,6 +949,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
|
||||||
fmt.Fprintf(gotype, "struct {\n")
|
fmt.Fprintf(gotype, "struct {\n")
|
||||||
off := int64(0)
|
off := int64(0)
|
||||||
npad := 0
|
npad := 0
|
||||||
|
// the align is at least 1 (for char)
|
||||||
|
maxAlign := int64(1)
|
||||||
argField := func(typ ast.Expr, namePat string, args ...interface{}) {
|
argField := func(typ ast.Expr, namePat string, args ...interface{}) {
|
||||||
name := fmt.Sprintf(namePat, args...)
|
name := fmt.Sprintf(namePat, args...)
|
||||||
t := p.cgoType(typ)
|
t := p.cgoType(typ)
|
||||||
|
@ -963,6 +965,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
|
||||||
noSourceConf.Fprint(gotype, fset, typ)
|
noSourceConf.Fprint(gotype, fset, typ)
|
||||||
fmt.Fprintf(gotype, "\n")
|
fmt.Fprintf(gotype, "\n")
|
||||||
off += t.Size
|
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 {
|
if fn.Recv != nil {
|
||||||
argField(fn.Recv.List[0].Type, "recv")
|
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++
|
// string.h for memset, and is also robust to C++
|
||||||
// types with constructors. Both GCC and LLVM optimize
|
// types with constructors. Both GCC and LLVM optimize
|
||||||
// this into just zeroing _cgo_a.
|
// 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, "\tstatic _cgo_argtype _cgo_zero;\n")
|
||||||
fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _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) {
|
if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue