chore: import upstream snapshot with attribution
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// cValueRefCandidates returns the function-as-value candidates the C extractor
|
||||
// captured for src — the placeholder edges (via=callback_candidate) awaiting the
|
||||
// resolver gate — as a name → source-line map. It is the capture-side view a
|
||||
// generated command table exercises before any resolution runs.
|
||||
func cValueRefCandidates(t *testing.T, path, src string) map[string]int {
|
||||
t.Helper()
|
||||
r, err := NewCExtractor().Extract(path, []byte(src))
|
||||
require.NoError(t, err)
|
||||
out := map[string]int{}
|
||||
for _, e := range r.Edges {
|
||||
if e.Meta == nil {
|
||||
continue
|
||||
}
|
||||
if v, _ := e.Meta["via"].(string); v != "callback_candidate" {
|
||||
continue
|
||||
}
|
||||
if name, _ := e.Meta["fn_value_name"].(string); name != "" {
|
||||
out[name] = e.Line
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestCExtractor_CommandTableMacroCandidates pins the redis command-table shape:
|
||||
// a `MAKE_CMD(..., handler, ...)` macro row in a generated `.def` fragment
|
||||
// captures the handler as a function-value candidate, while the string summary,
|
||||
// the ALL_CAPS flag macro, the short arity counts, and the MAKE_CMD callee
|
||||
// itself do not become references.
|
||||
func TestCExtractor_CommandTableMacroCandidates(t *testing.T) {
|
||||
src := "" +
|
||||
"struct redisCommand redisCommandTable[] = {\n" + // line 1
|
||||
"{MAKE_CMD(\"get\", \"Get the value\", -2, CMD_READONLY, getCommand, 1, 2)},\n" + // line 2
|
||||
"{MAKE_CMD(\"strlen\", \"String length\", 2, CMD_READONLY, strlenCommand, 1, 2)},\n" + // line 3
|
||||
"};\n"
|
||||
cands := cValueRefCandidates(t, "commands.def", src)
|
||||
|
||||
assert.Equal(t, 2, cands["getCommand"], "handler in a macro arg list is captured")
|
||||
assert.Equal(t, 3, cands["strlenCommand"], "handler in a macro arg list is captured")
|
||||
|
||||
assert.NotContains(t, cands, "CMD_READONLY", "an ALL_CAPS flag macro is not a function reference")
|
||||
assert.NotContains(t, cands, "MAKE_CMD", "the macro callee is a call, not a value reference")
|
||||
assert.NotContains(t, cands, "get", "a short string-literal table key is not an identifier reference")
|
||||
assert.NotContains(t, cands, "Get the value", "a string literal is never captured")
|
||||
}
|
||||
|
||||
// TestCExtractor_DispatchTableInitListCandidates covers the classic positional
|
||||
// initializer-list dispatch table `{ "name", fnPtr, arity }` — the handler in
|
||||
// the second slot is captured as a function-value candidate.
|
||||
func TestCExtractor_DispatchTableInitListCandidates(t *testing.T) {
|
||||
src := "" +
|
||||
"struct cmd table[] = {\n" + // line 1
|
||||
"{ \"ping\", pingCommand, 2 },\n" + // line 2
|
||||
"{ \"echo\", echoCommand, 2 },\n" + // line 3
|
||||
"};\n"
|
||||
cands := cValueRefCandidates(t, "table.c", src)
|
||||
|
||||
assert.Equal(t, 2, cands["pingCommand"], "handler in an initializer-list slot is captured")
|
||||
assert.Equal(t, 3, cands["echoCommand"], "handler in an initializer-list slot is captured")
|
||||
assert.NotContains(t, cands, "ping", "a string key is not an identifier reference")
|
||||
}
|
||||
|
||||
// TestCExtractor_CommandTableNoiseNotCaptured is the negative precision pin: the
|
||||
// three noise classes the guard exists for — ALL_CAPS macros / enum constants,
|
||||
// sub-4-character identifiers, and string literals — never become candidates,
|
||||
// while the one mixed-case, long-enough handler in the same row does.
|
||||
func TestCExtractor_CommandTableNoiseNotCaptured(t *testing.T) {
|
||||
src := "" +
|
||||
"struct redisCommand t[] = {\n" + // line 1
|
||||
"{MAKE_CMD(\"noise\", ARG_TYPE_KEY, CMD_WRITE, run, xy, realCommand)},\n" + // line 2
|
||||
"};\n"
|
||||
cands := cValueRefCandidates(t, "n.def", src)
|
||||
|
||||
assert.Contains(t, cands, "realCommand", "the mixed-case, long-enough handler is captured")
|
||||
assert.NotContains(t, cands, "ARG_TYPE_KEY", "an ALL_CAPS enum constant is filtered")
|
||||
assert.NotContains(t, cands, "CMD_WRITE", "an ALL_CAPS flag macro is filtered")
|
||||
assert.NotContains(t, cands, "run", "a sub-4-character identifier is filtered")
|
||||
assert.NotContains(t, cands, "xy", "a two-character identifier is filtered")
|
||||
}
|
||||
|
||||
// TestCExtractor_CommandTableErrorRecovery pins that a malformed generated
|
||||
// fragment (a missing close paren plus a line of garbage — the shape a `.def`
|
||||
// degrades to when its surrounding translation-unit context is absent) does not
|
||||
// suppress extraction: the extractor recovers the handlers whose argument lists
|
||||
// still parse rather than emitting nothing.
|
||||
func TestCExtractor_CommandTableErrorRecovery(t *testing.T) {
|
||||
src := "" +
|
||||
"MAKE_CMD(\"get\", getCommand, 2)\n" + // line 1: clean
|
||||
"MAKE_CMD(\"strlen\", strlenCommand, 2\n" + // line 2: missing close paren
|
||||
"%%% garbage !!! {{{ nonsense\n" + // line 3: junk
|
||||
"MAKE_CMD(\"append\", appendCommand, 3)\n" // line 4
|
||||
|
||||
r, err := NewCExtractor().Extract("broken.def", []byte(src))
|
||||
require.NoError(t, err, "extraction must not fail on an ERROR-recovered tree")
|
||||
|
||||
cands := map[string]bool{}
|
||||
for _, e := range r.Edges {
|
||||
if e.Meta == nil {
|
||||
continue
|
||||
}
|
||||
if v, _ := e.Meta["via"].(string); v != "callback_candidate" {
|
||||
continue
|
||||
}
|
||||
if name, _ := e.Meta["fn_value_name"].(string); name != "" {
|
||||
cands[name] = true
|
||||
}
|
||||
}
|
||||
assert.True(t, cands["getCommand"], "the cleanly-parsed row before the error survives")
|
||||
assert.True(t, cands["strlenCommand"], "the handler still in an arg list after the missing paren survives recovery")
|
||||
}
|
||||
|
||||
// TestCExtractor_LexerLookaheadFilteredAndDeduped pins the generated-lexer flood
|
||||
// shape. An uninitialised local (`int32_t lookahead;`) compared on ==/!= across
|
||||
// many lines is a local, never a function address, so it yields no candidate; a
|
||||
// genuinely-undeclared free name compared the same way collapses to exactly one
|
||||
// candidate regardless of how many lines mention it (the per-name dedup).
|
||||
func TestCExtractor_LexerLookaheadFilteredAndDeduped(t *testing.T) {
|
||||
src := "" +
|
||||
"void lexScan(void) {\n" + // line 1
|
||||
" int32_t lookahead;\n" + // line 2: uninitialised local
|
||||
" if (lookahead == 65) return;\n" + // 3
|
||||
" if (lookahead != 66) return;\n" + // 4
|
||||
" if (lookahead == 67) return;\n" + // 5
|
||||
" if (externScanHook == 68) return;\n" + // 6: undeclared free name
|
||||
" if (externScanHook != 69) return;\n" + // 7
|
||||
" if (externScanHook == 70) return;\n" + // 8
|
||||
"}\n"
|
||||
r, err := NewCExtractor().Extract("lexer.c", []byte(src))
|
||||
require.NoError(t, err)
|
||||
|
||||
counts := map[string]int{}
|
||||
for _, e := range r.Edges {
|
||||
if e.Meta == nil {
|
||||
continue
|
||||
}
|
||||
if v, _ := e.Meta["via"].(string); v != "callback_candidate" {
|
||||
continue
|
||||
}
|
||||
if name, _ := e.Meta["fn_value_name"].(string); name != "" {
|
||||
counts[name]++
|
||||
}
|
||||
}
|
||||
assert.Zero(t, counts["lookahead"], "an uninitialised local compared on ==/!= is never a function-address candidate")
|
||||
assert.Equal(t, 1, counts["externScanHook"], "an undeclared free name on N comparison lines collapses to one candidate")
|
||||
}
|
||||
|
||||
// TestCExtractor_EnumMembersNotCaptured pins the generated-parser enum flood: an
|
||||
// in-file anonymous enum's members, referenced from a designated-initializer
|
||||
// action table, are compile-time constants — never function addresses — so none
|
||||
// becomes a candidate, while a genuine cross-TU handler in the same table does.
|
||||
func TestCExtractor_EnumMembersNotCaptured(t *testing.T) {
|
||||
src := "" +
|
||||
"enum { sym_alpha, sym_beta, sym_gamma };\n" + // line 1
|
||||
"void *actionTable[] = {\n" + // line 2
|
||||
" [0] = sym_alpha,\n" + // 3
|
||||
" [1] = sym_beta,\n" + // 4
|
||||
" [2] = sym_gamma,\n" + // 5
|
||||
" [3] = reduceAction,\n" + // 6: a real cross-TU handler
|
||||
"};\n"
|
||||
cands := cValueRefCandidates(t, "parser.c", src)
|
||||
|
||||
assert.NotContains(t, cands, "sym_alpha", "an in-file enum member is not a function reference")
|
||||
assert.NotContains(t, cands, "sym_beta", "an in-file enum member is not a function reference")
|
||||
assert.NotContains(t, cands, "sym_gamma", "an in-file enum member is not a function reference")
|
||||
assert.Contains(t, cands, "reduceAction", "a genuine handler in the same table is still captured")
|
||||
}
|
||||
|
||||
// TestCExtractor_PerFileCandidateFuse pins the per-file fuse: a pathological
|
||||
// generated file with more distinct value-position handlers than the cap emits
|
||||
// exactly the cap — the bound that stops a generated dispatch table from
|
||||
// exploding the placeholder set.
|
||||
func TestCExtractor_PerFileCandidateFuse(t *testing.T) {
|
||||
var b strings.Builder
|
||||
b.WriteString("void *bigDispatch[] = {\n")
|
||||
for i := 0; i < cFnAddressMaxPerFile+200; i++ {
|
||||
fmt.Fprintf(&b, " handlerFn%05d,\n", i)
|
||||
}
|
||||
b.WriteString("};\n")
|
||||
|
||||
r, err := NewCExtractor().Extract("generated.c", []byte(b.String()))
|
||||
require.NoError(t, err)
|
||||
|
||||
n := 0
|
||||
for _, e := range r.Edges {
|
||||
if e.Meta == nil {
|
||||
continue
|
||||
}
|
||||
if v, _ := e.Meta["via"].(string); v == "callback_candidate" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, cFnAddressMaxPerFile, n, "the per-file fuse caps emitted candidates at the const bound")
|
||||
}
|
||||
|
||||
// TestCExtractor_DispatchTableCandidateFields pins the candidate metadata a
|
||||
// realistic cross-TU dispatch table produces: each handler is captured exactly
|
||||
// once (dedup), marked ungated so the resolver binds it cross-translation-unit,
|
||||
// tagged with the capturing grammar, and carries no address-of form (a plain
|
||||
// value slot).
|
||||
func TestCExtractor_DispatchTableCandidateFields(t *testing.T) {
|
||||
src := "" +
|
||||
"struct cmd table[] = {\n" + // line 1
|
||||
"{ \"get\", getCommand, 2 },\n" + // 2
|
||||
"{ \"set\", setCommand, 3 },\n" + // 3
|
||||
"};\n"
|
||||
r, err := NewCExtractor().Extract("dispatch.c", []byte(src))
|
||||
require.NoError(t, err)
|
||||
|
||||
type capture struct {
|
||||
count int
|
||||
ungated bool
|
||||
form string
|
||||
lang string
|
||||
}
|
||||
got := map[string]capture{}
|
||||
for _, e := range r.Edges {
|
||||
if e.Meta == nil {
|
||||
continue
|
||||
}
|
||||
if v, _ := e.Meta["via"].(string); v != "callback_candidate" {
|
||||
continue
|
||||
}
|
||||
name, _ := e.Meta["fn_value_name"].(string)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
c := got[name]
|
||||
c.count++
|
||||
if u, _ := e.Meta["fn_value_ungated"].(bool); u {
|
||||
c.ungated = true
|
||||
}
|
||||
c.form, _ = e.Meta["fn_ref_form"].(string)
|
||||
c.lang, _ = e.Meta["fn_ref_lang"].(string)
|
||||
got[name] = c
|
||||
}
|
||||
|
||||
for _, name := range []string{"getCommand", "setCommand"} {
|
||||
c := got[name]
|
||||
assert.Equal(t, 1, c.count, "%s is captured exactly once", name)
|
||||
assert.True(t, c.ungated, "%s is ungated for cross-TU binding", name)
|
||||
assert.Equal(t, "", c.form, "%s sits in a plain value position (no address-of form)", name)
|
||||
assert.Equal(t, "c", c.lang, "%s is tagged with the capturing grammar", name)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user