commit bbea27bd4c29996f1dc1ebc94d3ab798e4f9e731
parent c506d1cf9a1cfb2cffd0944f4a216b878d5c7940
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 29 Apr 2026 12:39:00 -0700
tests/cc: extended vararg test
Diffstat:
2 files changed, 116 insertions(+), 0 deletions(-)
diff --git a/tests/cc/131-vararg-mixed.c b/tests/cc/131-vararg-mixed.c
@@ -0,0 +1,115 @@
+/* Variadic recv across mixed types and printf-style forwarding.
+ *
+ * Existing vararg coverage (015/067/076/079/097/116) only pulls `int`
+ * via __builtin_va_arg from the same function that called va_start.
+ * Real callers (printf-shaped) interleave pointers and 64-bit ints,
+ * AND forward `va_list` across function boundaries
+ * (printf -> vprintf -> vfprintf). cg-va-arg always loads 8 bytes
+ * and lets the caller cast, and doesn't require the enclosing
+ * function to be variadic — but that path was untested.
+ *
+ * This test pins down:
+ * - va_arg(ap, long) — full 64-bit value survives (past INT_MAX)
+ * - va_arg(ap, char *) — pointer round-trips intact
+ * - alternating types in one call
+ * - va_list passed as a parameter to a non-variadic helper which
+ * then calls va_arg (the vfprintf shape)
+ * - two-level forwarding: variadic wrapper -> v-helper -> reader
+ * (the printf -> vprintf -> vfprintf shape)
+ */
+
+typedef char *va_list;
+
+static int streq(char *a, char *b) {
+ while (*a && *b) {
+ if (*a != *b) return 0;
+ a = a + 1;
+ b = b + 1;
+ }
+ return *a == *b;
+}
+
+/* Each "record" is (long, char *, int). */
+static long check(int n, ...) {
+ va_list ap;
+ long sum = 0;
+ int i = 0;
+ long lv;
+ char *sv;
+ int iv;
+ __builtin_va_start(ap, n);
+ while (i < n) {
+ lv = __builtin_va_arg(ap, long);
+ sv = __builtin_va_arg(ap, char *);
+ iv = __builtin_va_arg(ap, int);
+ if (!streq(sv, "ok")) return -1;
+ sum = sum + lv + iv;
+ i = i + 1;
+ }
+ __builtin_va_end(ap);
+ return sum;
+}
+
+/* Non-variadic reader. Receives an already-initialized va_list and a
+ * format-shaped char* + count. Mirrors vfprintf: ap was set up in a
+ * caller's frame, this function only consumes it. */
+static long vsum(char *tag, int n, va_list ap) {
+ long total = 0;
+ int i = 0;
+ if (!streq(tag, "go")) return -1;
+ while (i < n) {
+ total = total + __builtin_va_arg(ap, int);
+ i = i + 1;
+ }
+ return total;
+}
+
+/* Mid-layer forwarder. Non-variadic; takes ap by value and hands it
+ * down. Mirrors vprintf -> vfprintf. */
+static long vsum_fwd(char *tag, int n, va_list ap) {
+ return vsum(tag, n, ap);
+}
+
+/* Top-level variadic wrapper: va_start then forward through two
+ * non-variadic frames before any va_arg runs. Mirrors printf. */
+static long psum(char *tag, int n, ...) {
+ va_list ap;
+ long r;
+ __builtin_va_start(ap, n);
+ r = vsum_fwd(tag, n, ap);
+ __builtin_va_end(ap);
+ return r;
+}
+
+int main(int argc, char **argv) {
+ /* --- Mixed-type direct va_arg (long / char* / int) ----------- */
+ /* Two records. Longs chosen so the sum exceeds INT_MAX
+ * (2^31 - 1 = 2147483647); a 32-bit truncation in va_arg would
+ * corrupt the total. */
+ long r = check(2,
+ 1000000000L, "ok", 5,
+ 2000000000L, "ok", 7);
+ if (r == -1) return 1; /* pointer arg lost or corrupted */
+ if (r != 3000000012L) return 2; /* long arg truncated */
+
+ /* Single record, distinct pointer to confirm we don't latch the
+ * literal across calls. */
+ long r2 = check(1, 4000000000L, "ok", 1);
+ if (r2 == -1) return 3;
+ if (r2 != 4000000001L) return 4;
+
+ /* --- printf-style two-level va_list forwarding --------------- */
+ /* psum -> vsum_fwd -> vsum. va_arg only runs in vsum, on an ap
+ * that was initialized two frames up. The single-int case
+ * matches printf("got %d\n", 42) at the ABI level. */
+ long s1 = psum("go", 1, 42);
+ if (s1 != 42) return 5;
+
+ /* Multi-arg forwarding to confirm ap advances correctly across
+ * frames (each va_arg increments the local copy; the originator's
+ * frame is irrelevant once values are read). */
+ long s2 = psum("go", 4, 10, 20, 30, 40);
+ if (s2 != 100) return 6;
+
+ return 0;
+}
diff --git a/tests/cc/131-vararg-mixed.expected-exit b/tests/cc/131-vararg-mixed.expected-exit
@@ -0,0 +1 @@
+0