M ROOT => ROOT +1 -0
@@ 13,6 13,7 @@ session Remy in src = Remybase +
Compiler
Native
Encoding
+ Checker
Frontend
Examples
Export
M cli/driver.ML => cli/driver.ML +2 -1
@@ 36,9 36,10 @@ fun main () =
| ["eval", source, x, y, t] => output (Remy.reference (read source) (chars x) (chars y) (chars t))
| ["limits", depth, nodes, stack, source, destination] =>
emit destination (Remy.configured (chars depth) (chars nodes) (chars stack) (read source))
+ | ["check", binary] => output (Remy.validate (read binary))
| ["disassemble", binary] => output (Remy.disassemble (read binary))
| [source, destination] => emit destination (Remy.artifact (read source))
- | _ => raise Fail "usage: remy disassemble binary | remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t")
+ | _ => raise Fail "usage: remy check binary | remy disassemble binary | remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t")
handle exn =>
(TextIO.output (TextIO.stdErr, "remy: " ^ General.exnMessage exn ^ "\n");
OS.Process.exit OS.Process.failure);
A src/Checker.thy => src/Checker.thy +66 -0
@@ 0,0 1,66 @@
+theory Checker
+ imports Encoding
+begin
+
+fun effect :: "opcode \<Rightarrow> nat \<Rightarrow> nat option" where
+ "effect (Push r) n = Some (Suc n)"
+| "effect (Pop r) n = (if n = 0 then None else Some (n - 1))"
+| "effect (Peek i) n = (if i < n then Some n else None)"
+| "effect _ n = Some n"
+
+fun audit :: "opcode list \<Rightarrow> nat \<Rightarrow> nat option" where
+ "audit [] n = Some n"
+| "audit (op # code) n = (if writes op \<subseteq> {Rax,Rcx,Rbx} then
+ Option.bind (effect op n) (\<lambda>m. if m \<le> 512 then audit code m else None) else None)"
+
+lemma effect_sound:
+ "effect op (length (stack s)) = Some n \<Longrightarrow>
+ \<exists>t. transition op s = Some t \<and> length (stack t) = n"
+ by (cases op; cases "stack s") (auto split: if_splits)
+
+theorem audit_sound:
+ "audit code (length (stack s)) = Some n \<Longrightarrow>
+ \<exists>t. run code s = Some t \<and> length (stack t) = n"
+proof (induction code arbitrary: s)
+ case Nil
+ then show ?case by simp
+next
+ case (Cons op code)
+ then show ?case
+ by (auto dest!: effect_sound split: if_splits bind_splits; fastforce)
+qed
+
+lemma audit_scratch:
+ "audit code n = Some m \<Longrightarrow> list_all (\<lambda>op. writes op \<subseteq> {Rax,Rcx,Rbx}) code"
+ by (induction code arbitrary: n) (auto split: if_splits bind_splits)
+
+theorem audit_inputs:
+ "audit code n = Some m \<Longrightarrow> run code s = Some t \<Longrightarrow> inputs t = inputs s"
+proof -
+ assume audited: "audit code n = Some m" and ran: "run code s = Some t"
+ have scratch: "list_all (\<lambda>op. writes op \<subseteq> {Rax,Rcx,Rbx}) code"
+ using audit_scratch[OF audited] .
+ show ?thesis
+ proof (rule ext)
+ fix i
+ have keep: "list_all (\<lambda>op. input_register i \<notin> writes op) code"
+ unfolding list_all_iff
+ proof (rule ballI)
+ fix op
+ assume member: "op \<in> set code"
+ have bound: "writes op \<subseteq> {Rax,Rcx,Rbx}"
+ using scratch member by (auto simp: list_all_iff)
+ show "input_register i \<notin> writes op"
+ proof
+ assume written: "input_register i \<in> writes op"
+ have "input_register i \<in> {Rax,Rcx,Rbx}"
+ using subsetD[OF bound written] .
+ then show False by simp
+ qed
+ qed
+ show "inputs t i = inputs s i"
+ unfolding inputs_def using run_preserves[OF ran keep] .
+ qed
+qed
+
+end
M src/Export.thy => src/Export.thy +1 -1
@@ 6,6 6,6 @@ definition ascii :: "integer \<Rightarrow> char" where "ascii = char_of"
definition ordinal :: "char \<Rightarrow> integer" where "ordinal = of_char"
-export_code disassemble configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
+export_code validate disassemble configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
end
M src/Frontend.thy => src/Frontend.thy +8 -1
@@ 1,5 1,5 @@
theory Frontend
- imports Encoding
+ imports Checker
begin
fun finish :: "string \<Rightarrow> string list \<Rightarrow> string list" where
@@ 208,6 208,13 @@ definition disassemble :: "string \<Rightarrow> bool \<times> string" where
None \<Rightarrow> (False,''invalid native block stream'' @ newline)
| Some code \<Rightarrow> (True,listing 0 code))"
+definition validate :: "string \<Rightarrow> bool \<times> string" where
+ "validate bytes = (case blocks (map nat_of_char bytes) of
+ None \<Rightarrow> (False,''invalid native block stream'' @ newline)
+ | Some code \<Rightarrow> if audit (map snd code) 0 = Some 0
+ then (True,''native stack and register effects verified'' @ newline)
+ else (False,''invalid native stack or register effects'' @ newline))"
+
datatype problem = Unexpected nat | Exhausted nat
datatype 'a outcome = Failed problem | Parsed 'a
M tests/cli.py => tests/cli.py +8 -0
@@ 55,6 55,14 @@ def main():
assert 'invalid native' in invoke('disassemble',binary,ok=False)
binary.write_bytes(bytes([72,184,42,0,0,0,0,0,0,0,195]))
assert '10 ret' in invoke('disassemble',binary)
+ source.write_text('(+ x y)')
+ invoke(source,binary)
+ assert 'verified' in invoke('check',binary)
+ for code in [[88,195],[80,195],[65,88,195],[73,137,192,195],[72,139,68,36,0,195]]:
+ binary.write_bytes(bytes(code))
+ invoke('check',binary,ok=False)
+ binary.write_bytes(bytes([72,199,192,42,0,0,0,195]))
+ invoke('check',binary)
source.write_text('é')
assert 'ASCII' in invoke(source,binary,ok=False)
for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']:
M tests/native.py => tests/native.py +2 -0
@@ 80,6 80,8 @@ def main():
path.write_text(source(case))
result = subprocess.run([ROOT / ".build/remy", path, binary], capture_output=True, text=True, timeout=30)
assert result.returncode == 0, (case, result.stderr)
+ checked = subprocess.run([ROOT / ".build/remy", "check", binary],capture_output=True,text=True,timeout=30)
+ assert checked.returncode == 0, (case,checked.stderr)
function, image, thunk = native(binary.read_bytes())
vectors = [(a, b, a ^ b) for a in (0, 1, MASK, 1 << 63) for b in (0, 1, 63, 64, 65, MASK)]
vectors += [tuple(rng.getrandbits(64) for _ in range(3)) for _ in range(24)]