test(parser): cover self-includes, deep chains and diamonds
The include guard's scope is now pinned from three sides: a file including itself is a cycle diagnostic, a chain of two thousand distinct headers completes with the deepest content spliced, and a header reached again through a separate branch splices a second time and is refused as a redefinition. Assisted-by: GLM 5.3
This commit is contained in:
1 parent
32c453d66e
commit
21b6348dbe
1 file changed
+74
@@ -6,6 +6,7 @@ package parser
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -480,6 +481,79 @@ func TestIncludeCycleIsDetected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfIncludeIsDetected covers the shortest include cycle there is: a
|
||||
// file that includes itself. The contract is the two-file cycle's, a
|
||||
// diagnostic instead of unbounded recursion.
|
||||
func TestSelfIncludeIsDetected(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "self.s")
|
||||
if err := os.WriteFile(path, []byte("#include \"self.s\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, errs := ParseWithOptions(path, "#include \"self.s\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n", Options{Expand: true})
|
||||
if len(errs) == 0 || !strings.Contains(errs[0].Error(), "include cycle") {
|
||||
t.Fatalf("self-include: got %v, want a cycle diagnostic, not a hang", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeepIncludeChainCompletes pins the depth contract of include splicing:
|
||||
// a chain of distinct files costs one recursion level each, and a long chain
|
||||
// completes with the deepest file's content spliced in, rather than
|
||||
// overflowing or revisiting files the cycle guard already closed.
|
||||
func TestDeepIncludeChainCompletes(t *testing.T) {
|
||||
const depth = 2000
|
||||
dir := t.TempDir()
|
||||
for i := 0; i < depth; i++ {
|
||||
var content string
|
||||
if i == depth-1 {
|
||||
content = "#define DEEPEST $7\n"
|
||||
} else {
|
||||
content = "#include \"h" + strconv.Itoa(i+1) + ".h\"\n"
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "h"+strconv.Itoa(i)+".h"), []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
f, errs := ParseWithOptions(filepath.Join(dir, "h0.h"), "#include \"h1.h\"\n", Options{Expand: true})
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("deep include chain: %v", errs)
|
||||
}
|
||||
if !definesMacro(f, "DEEPEST") {
|
||||
t.Fatal("the deepest header's definition did not survive the chain")
|
||||
}
|
||||
}
|
||||
|
||||
// definesMacro reports whether the parsed file carries a define of name.
|
||||
func definesMacro(f *ast.File, name string) bool {
|
||||
for _, d := range f.Decls {
|
||||
if pp, ok := d.(*ast.Preproc); ok {
|
||||
if strings.HasPrefix(pp.Raw, "define "+name+" ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestDiamondIncludeSplicesTwice pins the guard's scope: the cycle detector
|
||||
// tracks the files currently being read, not the files ever read, so a header
|
||||
// reached again through a separate branch splices a second time and its
|
||||
// redefinition is refused, exactly as the toolchain's input stack behaves.
|
||||
func TestDiamondIncludeSplicesTwice(t *testing.T) {
|
||||
src := "#include \"left.h\"\n#include \"right.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n"
|
||||
dir := includeTree(t, map[string]string{
|
||||
"main.s": src,
|
||||
"left.h": "#include \"shared.h\"\n",
|
||||
"right.h": "#include \"shared.h\"\n",
|
||||
"shared.h": "#define FROM left\n",
|
||||
})
|
||||
_, errs := ParseWithOptions(filepath.Join(dir, "main.s"), src, Options{Expand: true})
|
||||
if len(errs) == 0 || !strings.Contains(errs[0].Error(), "redefinition of macro FROM") {
|
||||
// Silence would mean the shared header was spliced once and quietly
|
||||
// skipped the second time, which loses its content on that path.
|
||||
t.Fatalf("diamond include: got %v, want both splices to land", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnresolvableIncludeIsAnError(t *testing.T) {
|
||||
_, errs := ParseWithOptions("t_amd64.s", "#include \"nothere.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n",
|
||||
Options{Expand: true, IncludeDirs: []string{t.TempDir()}})
|
||||
|
||||
Reference in new issue
Block a user