From a mutation-testing pass: about sixty small, plausible changes to the compiler and runtime, each applied, run and restored. Nineteen of them left the whole suite green. The compiler was right in every case - what was missing was anything that looked. The two programs here close the severe cluster. cleanup.flan covers six claims: an early return runs the defers registered above it, and runs them innermost first; a defer that calls something, which is what puts a guard inside a defer on the transfer path; a transfer out of a handler-bind pops its frames; a two-clause handler-bind pops both; and a signal stops once a handler has answered it by transferring. The numbers differ per failure, so a wrong answer names its own cause rather than just being wrong. signedness.flan covers the ashr/lshr and slt/ult choices. Either could have been hardcoded to one arm and nothing would have noticed, because no program in the corpus shifted a negative integer right or compared an unsigned value above 2^31 - where a signed compare answers the other way on every operator. Each was verified able to fail, with the numbers the report predicted: hardcode lshr and -4 becomes 9223372036854775804; drop the defers from the return path and 21 becomes 0; reverse them and it becomes 12; let the signal walk continue past a handler that transferred and the outer handler runs too. The ones left open are recorded for the next pass: Reach's walk of index expressions, addr places and restart clause bodies; the dev registry's size-change guard; a local shadowing an imported name; and the 4K result cap, which has no coverage at all rather than a missing assertion.
22 lines
1.2 KiB
Plaintext
22 lines
1.2 KiB
Plaintext
;;;; Signedness, which nothing in the corpus was exercising.
|
|
;;;;
|
|
;;;; Found by mutation testing: emit.ml chooses ashr vs lshr and slt vs ult
|
|
;;;; from the operand's type, and both choices could be hardcoded to one arm
|
|
;;;; with the whole suite still green — because no program shifted a negative
|
|
;;;; integer right, and none compared an unsigned value above 2^31.
|
|
(defn main [] i32
|
|
;; An arithmetic shift keeps the sign. A logical one on -8 gives a number
|
|
;; near 2^63, which is the wrong answer that looks like a huge right one.
|
|
(print-i64 (>> (i64 -8) 1)) (newline) ; -4
|
|
(print-i64 (>> (i64 -1) 40)) (newline) ; -1, still, however far it goes
|
|
|
|
;; And unsigned stays unsigned: 3000000000 has its top bit set, so a signed
|
|
;; compare reads it as negative and answers the other way on every operator.
|
|
(let [big (bit-or (<< (u32 1) 31) (u32 1000))] ; 2^31 + 1000
|
|
(print-line (if (< big (u32 5)) "wrong: signed compare" "big is not small"))
|
|
(print-line (if (> big (u32 5)) "big is large" "wrong: signed compare"))
|
|
;; The same value through >>, which is logical on an unsigned type: a
|
|
;; signed shift here would keep the top bit and answer near 2^31 again.
|
|
(print-i64 (i64 (>> big 31))) (newline)) ; 1
|
|
0)
|