commit 17ffef6431c02dd6b487a62ef7d21d1365cceb3f
parent 41ffdc2a3fa3b9492c0a8d82497602187411babe
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 1 May 2026 17:37:35 -0700
cc/pp: track #else seen on cond-stack; reject #elif/#else after #else
The cond-stack frame was a (active? . taken?) pair, so the dispatcher
couldn't tell whether the current #if-group had already passed
through an #else. Sequences like #if 0 / #else / #elif 1 / #endif
silently fell through with no diagnostic — and worse, an #elif clause
in that position re-evaluated its expression and could turn back on
content that the spec says is dead.
Promote the frame to a triple (active? taken? else?). %pp-do-else
sets else? = #t; %pp-do-elif and %pp-do-else die when they see a
frame with else? = #t already.
Test: tests/cc-pp/54-elif-after-else.c expects exit 1 (was passing
through, exit 0).
Diffstat:
3 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -2056,32 +2056,42 @@
(else (cons (car al) (%pp-alist-drop key (cdr al))))))
;; --- #if / #ifdef / #ifndef / #elif / #else / #endif ---
+;; cond-stack frame: (active? taken? else?). active? gates the body
+;; until the next #elif/#else/#endif; taken? records whether ANY arm
+;; (the original #if branch or any #elif) has matched, so later arms
+;; stay inactive; else? records that we have already passed an #else
+;; in this frame, so a subsequent #elif/#else is rejected.
+(define (%pp-frame a? t? e?) (list a? t? e?))
+(define (%pp-frame-active? f) (car f))
+(define (%pp-frame-taken? f) (car (cdr f)))
+(define (%pp-frame-else? f) (car (cdr (cdr f))))
+
(define (%pp-do-if line state)
(cond
((not (%pp-active? state))
- (pps-cond-stack-set! state (cons (cons #f #f) (pps-cond-stack state))))
+ (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
(else
(let* ((v (pp-eval-cexpr line (pps-macros state)))
(a? (not (= v 0))))
- (pps-cond-stack-set! state (cons (cons a? a?) (pps-cond-stack state)))))))
+ (pps-cond-stack-set! state (cons (%pp-frame a? a? #f) (pps-cond-stack state)))))))
(define (%pp-do-ifdef line state)
(cond
((not (%pp-active? state))
- (pps-cond-stack-set! state (cons (cons #f #f) (pps-cond-stack state))))
+ (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
(else
(let ((d? (%pp-defined? (%pp-name-of-single line) state)))
(pps-cond-stack-set! state
- (cons (cons d? d?) (pps-cond-stack state)))))))
+ (cons (%pp-frame d? d? #f) (pps-cond-stack state)))))))
(define (%pp-do-ifndef line state)
(cond
((not (%pp-active? state))
- (pps-cond-stack-set! state (cons (cons #f #f) (pps-cond-stack state))))
+ (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
(else
(let ((a? (not (%pp-defined? (%pp-name-of-single line) state))))
(pps-cond-stack-set! state
- (cons (cons a? a?) (pps-cond-stack state)))))))
+ (cons (%pp-frame a? a? #f) (pps-cond-stack state)))))))
(define (%pp-name-of-single line)
(cond
@@ -2101,16 +2111,18 @@
((null? cs) (die #f "#elif outside #if"))
(else
(let* ((top (car cs)) (rest (cdr cs))
- (taken? (cdr top))
+ (taken? (%pp-frame-taken? top))
+ (else? (%pp-frame-else? top))
(par? (%pp-parent-active? state)))
(cond
+ (else? (die #f "#elif after #else"))
((or (not par?) taken?)
- (pps-cond-stack-set! state (cons (cons #f taken?) rest)))
+ (pps-cond-stack-set! state (cons (%pp-frame #f taken? #f) rest)))
(else
(let* ((v (pp-eval-cexpr line (pps-macros state)))
(a? (not (= v 0))))
(pps-cond-stack-set! state
- (cons (cons a? (or a? taken?)) rest))))))))))
+ (cons (%pp-frame a? (or a? taken?) #f) rest))))))))))
(define (%pp-do-else line state)
(let ((cs (pps-cond-stack state)))
@@ -2118,15 +2130,17 @@
((null? cs) (die #f "#else outside #if"))
(else
(let* ((top (car cs)) (rest (cdr cs))
- (taken? (cdr top))
+ (taken? (%pp-frame-taken? top))
+ (else? (%pp-frame-else? top))
(par? (%pp-parent-active? state)))
(cond
+ (else? (die #f "#else after #else"))
((not par?)
- (pps-cond-stack-set! state (cons (cons #f taken?) rest)))
+ (pps-cond-stack-set! state (cons (%pp-frame #f taken? #t) rest)))
(taken?
- (pps-cond-stack-set! state (cons (cons #f #t) rest)))
+ (pps-cond-stack-set! state (cons (%pp-frame #f #t #t) rest)))
(else
- (pps-cond-stack-set! state (cons (cons #t #t) rest)))))))))
+ (pps-cond-stack-set! state (cons (%pp-frame #t #t #t) rest)))))))))
(define (%pp-do-endif line state)
(let ((cs (pps-cond-stack state)))
diff --git a/tests/cc-pp/54-elif-after-else.c b/tests/cc-pp/54-elif-after-else.c
@@ -0,0 +1,7 @@
+#if 0
+1
+#else
+2
+#elif 1
+3
+#endif
diff --git a/tests/cc-pp/54-elif-after-else.expected-exit b/tests/cc-pp/54-elif-after-else.expected-exit
@@ -0,0 +1 @@
+1