blob: e8ea5092a3562b2fe0d9b26298c989c254706a25 [file] [log] [blame]
// 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/goarch"
"internal/testenv"
"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
}