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/goarch"
    12  	"internal/testenv"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  // TestDisallowSmuggledCode tests that
    22  // docstrings do not smuggle code into
    23  // files generated by Cgo.
    24  func TestDisallowSmuggledCode(t *testing.T) {
    25  	testenv.MustHaveGoRun(t)
    26  	testenv.MustHaveCGO(t)
    27  	objDir := cgo(t, "comments.go")
    28  
    29  	file, err := os.Open(filepath.Join(objDir, "_cgo_export.h"))
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	defer file.Close()
    34  
    35  	scanner := bufio.NewScanner(file)
    36  	for scanner.Scan() {
    37  		line := strings.TrimSpace(scanner.Text())
    38  		if strings.Contains(line, `"Hello, I am exploiting CVE-2025-61732!\n"`) {
    39  			t.Fatalf(`got %q, want ""`, line)
    40  		}
    41  	}
    42  	if err := scanner.Err(); err != nil {
    43  		t.Fatal(err)
    44  	}
    45  }
    46  
    47  type methodAlign struct {
    48  	Method string
    49  	Align  int
    50  }
    51  
    52  var wantAligns = map[string]int{
    53  	"ReturnEmpty":         1,
    54  	"ReturnOnlyUint8":     1,
    55  	"ReturnOnlyUint16":    2,
    56  	"ReturnOnlyUint32":    4,
    57  	"ReturnOnlyUint64":    goarch.PtrSize,
    58  	"ReturnOnlyInt":       goarch.PtrSize,
    59  	"ReturnOnlyPtr":       goarch.PtrSize,
    60  	"ReturnByteSlice":     goarch.PtrSize,
    61  	"ReturnString":        goarch.PtrSize,
    62  	"InputAndReturnUint8": 1,
    63  	"MixedTypes":          goarch.PtrSize,
    64  }
    65  
    66  // TestAligned tests that the generated _cgo_export.c file has the wanted
    67  // align attributes for struct types used as arguments or results of
    68  // //exported functions.
    69  func TestAligned(t *testing.T) {
    70  	testenv.MustHaveGoRun(t)
    71  	testenv.MustHaveCGO(t)
    72  	objDir := cgo(t, "aligned.go")
    73  
    74  	haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c"))
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	// Check that we have all the wanted methods
    80  	if len(haveAligns) != len(wantAligns) {
    81  		t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns))
    82  	}
    83  
    84  	for i := range haveAligns {
    85  		method := haveAligns[i].Method
    86  		haveAlign := haveAligns[i].Align
    87  
    88  		wantAlign, ok := wantAligns[method]
    89  		if !ok {
    90  			t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign)
    91  		} else if haveAlign != wantAlign {
    92  			t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign)
    93  		}
    94  	}
    95  }
    96  
    97  // cgo executes 'go tool cgo' on testFile
    98  // and returns the objdir containing the
    99  // generated files.
   100  func cgo(t *testing.T, testFile string) string {
   101  	objDir := t.TempDir()
   102  	testdata, err := filepath.Abs("testdata")
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo",
   108  		"-objdir", objDir,
   109  		filepath.Join(testdata, testFile))
   110  
   111  	cmd.Stderr = new(bytes.Buffer)
   112  	if err = cmd.Run(); err != nil {
   113  		t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr)
   114  	}
   115  
   116  	return objDir
   117  }
   118  
   119  func parseAlign(filename string) ([]methodAlign, error) {
   120  	file, err := os.Open(filename)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("failed to open file: %w", err)
   123  	}
   124  	defer file.Close()
   125  
   126  	var results []methodAlign
   127  	scanner := bufio.NewScanner(file)
   128  
   129  	// Regex to match function declarations like "struct MethodName_return MethodName("
   130  	funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`)
   131  	// Regex to match simple function declarations like "GoSlice MethodName("
   132  	simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`)
   133  	// Regex to match void-returning exported functions like "void ReturnEmpty("
   134  	voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`)
   135  	// Regex to match align attributes like "__attribute__((aligned(8)))"
   136  	alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`)
   137  
   138  	var currentMethod string
   139  
   140  	for scanner.Scan() {
   141  		line := strings.TrimSpace(scanner.Text())
   142  
   143  		// Check if this line declares a function with struct return type
   144  		if matches := funcRegex.FindStringSubmatch(line); matches != nil {
   145  			currentMethod = matches[2] // Extract the method name
   146  		} else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil {
   147  			// Check if this line declares a function with simple return type (like GoSlice)
   148  			currentMethod = matches[1] // Extract the method name
   149  		} else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil {
   150  			// Check if this line declares a void-returning function
   151  			currentMethod = matches[1] // Extract the method name
   152  		}
   153  
   154  		// Check if this line contains align information
   155  		if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" {
   156  			alignStr := alignMatches[1]
   157  			align, err := strconv.Atoi(alignStr)
   158  			if err != nil {
   159  				// Skip this entry if we can't parse the align as integer
   160  				currentMethod = ""
   161  				continue
   162  			}
   163  			results = append(results, methodAlign{
   164  				Method: currentMethod,
   165  				Align:  align,
   166  			})
   167  			currentMethod = "" // Reset for next method
   168  		}
   169  	}
   170  
   171  	if err := scanner.Err(); err != nil {
   172  		return nil, fmt.Errorf("error reading file: %w", err)
   173  	}
   174  
   175  	return results, nil
   176  }
   177  

View as plain text