test: add resource limit regression tests

ober

e37c4e764a5a2e3d9db066d9ff04408240c9398c

diff --git a/Makefile b/Makefile
index 9d4f5da..67bc571 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ SBOM_DIR ?= dist/sbom
 REPRO_DIR ?= dist/reproducibility
 TARGET_EVIDENCE_DIR ?= dist/target-evidence
 
-.PHONY: all build binary test test-parser parser-corpus test-cli-policy import-check clean-generated security audit verify sbom reproducibility-report target-evidence release-evidence clean install lint
+.PHONY: all build binary test test-parser test-limits parser-corpus test-cli-policy import-check clean-generated security audit verify sbom reproducibility-report target-evidence release-evidence clean install lint
 
 all: binary
 
@@ -42,6 +42,9 @@ install: binary
 test-parser:
 	@$(JEXEC) tests/test-parser.ss
 
+test-limits:
+	@$(JEXEC) tests/test-limits.ss
+
 parser-corpus:
 	@$(JEXEC) support/parser-corpus-evidence.ss
 
@@ -52,10 +55,12 @@ test-cli-policy: binary
 	@echo "--- system opt-in ---"; JAWK_ALLOW_SYSTEM=1 ./$(JAWK_BIN) 'BEGIN{print system("true")}' | grep -qx 0
 	@echo "--- pipe opt-in ---"; JAWK_ALLOW_SYSTEM=1 ./$(JAWK_BIN) 'BEGIN{print "ok" | "cat"}'
 	@echo "--- file I/O opt-in ---"; rm -f .jawk-test-out; JAWK_ALLOW_FILE_IO=1 ./$(JAWK_BIN) 'BEGIN{print "ok" > ".jawk-test-out"}'; grep -qx ok .jawk-test-out; rm -f .jawk-test-out
+	@echo "--- absolute path rejected ---"; if JAWK_ALLOW_FILE_IO=1 ./$(JAWK_BIN) 'BEGIN{print "PWNED" > "/tmp/jawk-abs-test"}' 2>/dev/null; then echo "FAIL: absolute path write succeeded"; exit 1; fi
+	@echo "--- dotdot path rejected ---"; if JAWK_ALLOW_FILE_IO=1 ./$(JAWK_BIN) 'BEGIN{print "PWNED" > "sub/../jawk-dotdot-test"}' 2>/dev/null; then echo "FAIL: dotdot path write succeeded"; exit 1; fi
 	@echo "--- ENVIRON hidden by default ---"; ./$(JAWK_BIN) 'BEGIN{print ("PATH" in ENVIRON)}' | grep -qx 0
 	@echo "--- ENVIRON allowlist opt-in ---"; JAWK_EXPOSE_ENVIRON=1 ./$(JAWK_BIN) 'BEGIN{print ("PATH" in ENVIRON)}' | grep -qx 1
 
-test: test-parser binary
+test: test-parser test-limits binary
 	@echo "--- print field ---";  echo "hello world" | ./$(JAWK_BIN) '{print $$1}'
 	@echo "--- -F, ---";          echo "a,b,c" | ./$(JAWK_BIN) -F, '{print $$2}'
 	@echo "--- BEGIN/END ---";    echo "" | ./$(JAWK_BIN) 'BEGIN{print "start"} END{print "end"}'
diff --git a/tests/test-limits.ss b/tests/test-limits.ss
new file mode 100644
index 0000000..e65775d
--- /dev/null
+++ b/tests/test-limits.ss
@@ -0,0 +1,88 @@
+#!chezscheme
+
+(import (scheme)
+        (jerboa-awk parser)
+        (jerboa-awk ast)
+        (jerboa-awk main)
+        (jerboa-awk value)
+        (jerboa-awk runtime)
+        (jerboa-awk builtins string))
+
+(define pass 0)
+(define fail 0)
+
+(define-syntax test
+  (syntax-rules ()
+    ((_ name expr expected)
+     (guard (exn (else
+                  (set! fail (+ fail 1))
+                  (printf "FAIL ~a: ~a~%" name
+                          (if (message-condition? exn) (condition-message exn) exn))))
+       (let ((got expr))
+         (if (equal? got expected)
+           (begin
+             (set! pass (+ pass 1))
+             (printf "  ok ~a~%" name))
+           (begin
+             (set! fail (+ fail 1))
+             (printf "FAIL ~a: got ~s expected ~s~%" name got expected))))))))
+
+(define-syntax test-raises
+  (syntax-rules ()
+    ((_ name body ...)
+     (let ((raised? #f))
+       (guard (exn (else (set! raised? #t)))
+         body ...)
+       (if raised?
+         (begin
+           (set! pass (+ pass 1))
+           (printf "  ok ~a~%" name))
+         (begin
+           (set! fail (+ fail 1))
+           (printf "FAIL ~a: expected exception~%" name)))))))
+
+(printf "--- jawk resource limit tests ---~%")
+
+(test-raises "NF=300000000 raises limit error"
+  (let ((env (make-initial-env)))
+    (env-set! env 'NF (make-awk-number 300000000))))
+
+(test-raises "$1000000000 assignment raises limit error"
+  (let ((env (make-initial-env)))
+    (env-set-field! env 1000000000 (make-awk-string "x"))))
+
+(test-raises "infinite user-function recursion raises depth error"
+  (run-awk '("function f(){return f()} BEGIN{f()}")))
+
+(test-raises "printf width 20000000 raises limit error"
+  (awk-sprintf "%20000000d" (list (make-awk-number 5))))
+
+(test-raises "printf precision 20000000 raises limit error"
+  (awk-sprintf "%.20000000f" (list (make-awk-number 5))))
+
+(test-raises "absolute path rejected without JAWK_FILE_ROOT"
+  (let ((env (make-initial-env)))
+    (env-get-output-port env "/tmp/jawk-pwned" #f)))
+
+(test-raises "dotdot path rejected without JAWK_FILE_ROOT"
+  (let ((env (make-initial-env)))
+    (env-get-output-port env "dir/sub/../x" #f)))
+
+(test "NF within limit succeeds"
+  (let ((env (make-initial-env)))
+    (env-set! env 'NF (make-awk-number 10))
+    (awk-env-nf env))
+  10)
+
+(test "printf within limit succeeds"
+  (awk-sprintf "%10d" (list (make-awk-number 5)))
+  "         5")
+
+(test "array within limit succeeds"
+  (let ((env (make-initial-env)))
+    (env-array-set! env 'a "k1" (make-awk-number 1))
+    (= (awk->number (env-array-ref env 'a "k1")) 1))
+  #t)
+
+(printf "~%pass: ~a fail: ~a~%" pass fail)
+(when (> fail 0) (exit 1))