Просмотр исходного кода

tests: re-anchor three nonvacuity mutations that item 1a invalidated

run_all.sh's header says to run tests/nonvacuity.sh directly after touching
any of the checks.  Running it gave 75 ok, 3 failed -- and SUMMARY.md at the
previous tag already claimed "78 ok, 0 failed".  The number of cases (78) was
right; the "ok, 0 failed" was not.

All three failures are pre-existing: they fail at v-TP3-STRIDX-VAR too,
because item 1a (variable string subscripts) changed the code the mutations
match.  Each mutation had stopped applying to the code it names, which is the
one failure mode this file exists to catch:

  * check_framedisp / EmBpDisp matched its old body verbatim, but EmBpDisp
    gained a `regf` parameter (the DI forms need reg 7).  The python assertion
    fired, the file was left UNMUTATED, rebuild_compiler succeeded, and the
    check passed on unmutated code -- reported as "NOT NON-VACUOUS".  Both
    EmBpDisp mutations are re-anchored, and both keep `regf`: the mutation is
    about the disp8/disp16 choice, and breaking the DI forms at the same time
    would be two faults wearing one label.

  * SaveLeft matched its old kind-2 body, but an `ELSIF left.kind = 5` branch
    (a string element whose address is in DI) was added.  Reported as "BROKEN
    CASE".  It now disables BOTH guards, asserting each is unique -- `IF
    left.kind = 2 THEN` reads like it can only be SaveLeft's, but other
    routines branch on kind too.

  * Assignment's string arm matched `IF symtab [r.idx].cls = TString THEN'.
    That arm no longer says that (it grew `AND (r.cls = TString)` so the new
    `s[i] := c` element arm is not swallowed by the whole-string one), and
    ParseSub now says it for `s[i]`.  `s.index` therefore found ParseSub's,
    ~800 lines earlier, and the splice deleted everything between the two --
    "the compiler would not rebuild with the string assignment refused".  The
    whole guard is now matched, with an assert.

Verified by hand that each mutation again does what its label says: the
truncation case reports "no 8B access at [BP+128]" (offsets seen include
-128), the left-operand case makes t34_arith print FAIL, and the
string-assignment case makes t39 red.  tests/nonvacuity.sh: 78 ok, 0 failed.
Eric Streit 15 часов назад
Родитель
Сommit
cdb8db07ee
1 измененных файлов с 47 добавлено и 36 удалено
  1. 47 36
      shell/tests/nonvacuity.sh

+ 47 - 36
shell/tests/nonvacuity.sh

@@ -369,24 +369,26 @@ rebuild_compiler () {
     return 0
     return 0
 }
 }
 
 
-# 1. the original bug: `off MOD 100H`, always disp8.  Restores exactly the code
-#    that was there before EmBpDisp existed.  t28's [BP+128] read becomes
-#    [BP-128], which is the failure this whole check is named after.
+# 1. the original bug: `off MOD 100H`, always disp8.  Restores the code that
+#    was there before EmBpDisp existed.  t28's [BP+128] read becomes [BP-128],
+#    which is the failure this whole check is named after.
+#
+#    regf is KEPT.  It is the other half of EmBpDisp's job - the ModR/M reg
+#    field, which the DI forms (regf 7) need - and it was added after this
+#    case was written.  Hard-coding 46H here again would break the DI forms at
+#    the same time and turn one mutation into two faults; the check would
+#    still go red, but for a reason the label does not name.
 python3 - "$SAVED_C" <<'PYEOF'
 python3 - "$SAVED_C" <<'PYEOF'
 import sys
 import sys
 p = 'Compiler.mod'
 p = 'Compiler.mod'
 s = open(p).read()
 s = open(p).read()
-old = """BEGIN
-   IF off <= 127 THEN
-      Ebyte (46H) ; Ebyte (VAL (BYTE, off))
+old = """   IF off <= 127 THEN
+      Ebyte (VAL (BYTE, 40H + regf * 8 + 6H)) ; Ebyte (VAL (BYTE, off))
    ELSE
    ELSE
-      Ebyte (86H) ; Eword (off)
+      Ebyte (VAL (BYTE, 80H + regf * 8 + 6H)) ; Eword (off)
    END
    END
 END EmBpDisp ;"""
 END EmBpDisp ;"""
-new = """VAR disp : CARDINAL ;
-BEGIN
-   disp := off MOD 100H ;
-   Ebyte (46H) ; Ebyte (VAL (BYTE, disp))
+new = """   Ebyte (VAL (BYTE, 40H + regf * 8 + 6H)) ; Ebyte (VAL (BYTE, off MOD 100H))
 END EmBpDisp ;"""
 END EmBpDisp ;"""
 assert old in s, "EmBpDisp body not found -- update this mutation"
 assert old in s, "EmBpDisp body not found -- update this mutation"
 open(p, 'w').write(s.replace(old, new))
 open(p, 'w').write(s.replace(old, new))
@@ -409,11 +411,11 @@ python3 - <<'PYEOF'
 p = 'Compiler.mod'
 p = 'Compiler.mod'
 s = open(p).read()
 s = open(p).read()
 old = """   IF off <= 127 THEN
 old = """   IF off <= 127 THEN
-      Ebyte (46H) ; Ebyte (VAL (BYTE, off))
+      Ebyte (VAL (BYTE, 40H + regf * 8 + 6H)) ; Ebyte (VAL (BYTE, off))
    ELSE
    ELSE
-      Ebyte (86H) ; Eword (off)
+      Ebyte (VAL (BYTE, 80H + regf * 8 + 6H)) ; Eword (off)
    END"""
    END"""
-new = """   Ebyte (86H) ; Eword (off)"""
+new = """   Ebyte (VAL (BYTE, 80H + regf * 8 + 6H)) ; Eword (off)"""
 assert old in s, "EmBpDisp branch not found -- update this mutation"
 assert old in s, "EmBpDisp branch not found -- update this mutation"
 open(p, 'w').write(s.replace(old, new))
 open(p, 'w').write(s.replace(old, new))
 PYEOF
 PYEOF
@@ -785,21 +787,23 @@ cp "$SAVED_C" Compiler.mod
 if python3 - <<'PYX'
 if python3 - <<'PYX'
 p = 'Compiler.mod'
 p = 'Compiler.mod'
 s = open(p).read()
 s = open(p).read()
-old = """BEGIN
-   IF left.kind = 2 THEN
-      EmPushAx () ;
-      left.kind := 4                      (* 4 = on the top of the stack *)
-   END
-END SaveLeft ;"""
-new = """BEGIN
-   (* MUTATION: the park is unreachable, so nothing survives the parse *)
-   IF left.kind = 99 THEN
-      EmPushAx () ;
-      left.kind := 4
-   END
-END SaveLeft ;"""
-assert s.count(old) == 1, 'SaveLeft body found %d times -- update this mutation' % s.count(old)
-open(p, 'w').write(s.replace(old, new))
+# Both guards are matched, and each must be unique.  `IF left.kind = 2 THEN'
+# reads like it can only be SaveLeft's, but LoadAtom and friends also branch
+# on kind, so an unasserted replace could disable one of THEM instead and the
+# case would still go red - for a reason the label does not name.
+#
+# The kind-5 ("a string element whose address is in DI") guard did not exist
+# when this case was written; it arrived with `s[i]`.  Making only the kind-2
+# park unreachable would leave that one alive, so the mutation would no longer
+# be "SaveLeft parks nothing" - it would be a half-disabled SaveLeft, and the
+# case would be testing something narrower than it says.
+g2 = '   IF left.kind = 2 THEN\n'
+g5 = '   ELSIF left.kind = 5 THEN\n'
+assert s.count(g2) == 1, 'the kind-2 guard found %d times -- update this mutation' % s.count(g2)
+assert s.count(g5) == 1, 'the kind-5 guard found %d times -- update this mutation' % s.count(g5)
+s = s.replace(g2, '   IF left.kind = 99 THEN\n')
+s = s.replace(g5, '   ELSIF left.kind = 99 THEN\n')
+open(p, 'w').write(s)
 PYX
 PYX
 then
 then
     if rebuild_compiler; then
     if rebuild_compiler; then
@@ -1043,19 +1047,26 @@ cp "$SAVED_C" Compiler.mod
 #      and only rt_exec.py can see a wrstr that reads no length at all.
 #      and only rt_exec.py can see a wrstr that reads no length at all.
 
 
 # 1. Assignment's string arm is gone, so `s := 'hello'' trips the old
 # 1. Assignment's string arm is gone, so `s := 'hello'' trips the old
-#    `size > 2' refusal again.  The arm is spliced out by its boundaries, not
-#    by its text, so it cannot silently stop matching when the comment inside
-#    is touched.
+#    `size > 2' refusal again.  The arm is spliced between its guard and the
+#    `size > 2' check that follows it, so a comment inside cannot break the
+#    match - but the GUARD is matched by text, and it has to be the whole
+#    guard.  It grew an `AND (r.cls = TString)' when `s[i] := c' made a
+#    TString destination reach a second arm, and ParseSub grew a look-alike
+#    `IF symtab [r.idx].cls = TString THEN' for `s[i]'.  The shorter anchor
+#    therefore now finds ParseSub's, some 800 lines earlier, and the splice
+#    below deletes everything between the two - which does not test the
+#    refusal, it tests whether the compiler still compiles.  Hence the assert
+#    and the spelled-out guard.
 cp "$SAVED_C" Compiler.mod
 cp "$SAVED_C" Compiler.mod
 if python3 - <<'PYX'
 if python3 - <<'PYX'
 p = 'Compiler.mod'
 p = 'Compiler.mod'
 s = open(p).read()
 s = open(p).read()
-a = s.index('   IF symtab [r.idx].cls = TString THEN')
+g = '   IF (symtab [r.idx].cls = TString) AND (r.cls = TString) THEN\n'
+assert s.count(g) == 1, 'the string-assignment guard found %d times' % s.count(g)
+a = s.index(g)
 m = '      RETURN\n   END ;\n   IF symtab [r.idx].size > 2 THEN\n'
 m = '      RETURN\n   END ;\n   IF symtab [r.idx].size > 2 THEN\n'
 i = s.index(m, a)
 i = s.index(m, a)
-new = ('   IF symtab [r.idx].cls = TString THEN\n'
-       '      Err (ENoLib) ;              (* MUTATION: the arm is refused *)\n'
-       '      RETURN\n')
+new = g + '      Err (ENoLib) ;              (* MUTATION: the arm is refused *)\n      RETURN\n'
 open(p, 'w').write(s[:a] + new + s[i + len('      RETURN\n'):])
 open(p, 'w').write(s[:a] + new + s[i + len('      RETURN\n'):])
 PYX
 PYX
 then
 then