Source file src/cmd/cgo/internal/testout/out_test.go

     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  }
   145  

View as plain text