theory StarForth_Log_Words imports StarForth_Base begin (* ========================================================================= Mirrors: src/word_source/log_words.c Registers: LOG-ERROR LOG-WARN LOG-INFO LOG-TEST LOG-DEBUG LOG-LEVEL! LOG-LEVEL@ (do-log-error/warn/info/test/debug) LOG-ERROR"/WARN"/INFO"/TEST"/DEBUG" (IMMEDIATE) LOG-ERROR-STR/WARN-STR/INFO-STR/TEST-STR/DEBUG-STR ── Scope ───────────────────────────────────────────────────────────── Fully modelled: the five level-constant pushes (LOG-ERROR..LOG-DEBUG), LOG-LEVEL!'s guard+clamp (the clamped VALUE is a pure function of the popped cell, even though where it's stored -- the log subsystem's active level -- is outside vm_state), and all five LOG-*-STR words (same "pop2 + bounds-check guard, no further vm_state effect" shape as StarForth_Lifecycle_Words_Hosted.thy -- log_message is I/O-only, reads memory but never writes it). NOT modelled: LOG-LEVEL@ (pushes the log subsystem's active level, a value with no vm_state counterpart -- same class of gap as SEED/RANDOM's g_prng_state, just for a different global); the five (do-log-N) name-family runtime words (read/advance the return-stack top as a raw C pointer into inline threaded-code data, the same "raw host pointer, not a VM address" gap already flagged for LIT in StarForth_Defining_Words.thy); `log_emit_string` and the five `LOG-*"` immediates built on it (TIB/ input-buffer dependency in interpret mode, PLUS compile-mode dependency on vm_find_word + vm_compile_word + vm_allot -- several already-flagged gaps compounded in one helper). ── CORRECTED finding: LOG-* words are NOT missing a guard ───────────── LOG-ERROR/WARN/INFO/TEST/DEBUG and LOG-LEVEL@ all push via C's `vm_push()` (src/stack_management.c:75), which bounds-checks internally -- this file's earlier claim of a missing `ds_full` check was a gap in this theory's abstract push model, not a real defect in the C. Re-verified 2026-08-14; see proof/FINDINGS.md §2 for the full correction across every file this pattern was raised against. ======================================================================== *) definition LOG_ERROR_LEVEL :: cell where "LOG_ERROR_LEVEL = 0" definition LOG_WARN_LEVEL :: cell where "LOG_WARN_LEVEL = 1" definition LOG_INFO_LEVEL :: cell where "LOG_INFO_LEVEL = 2" definition LOG_TEST_LEVEL :: cell where "LOG_TEST_LEVEL = 3" definition LOG_DEBUG_LEVEL :: cell where "LOG_DEBUG_LEVEL = 4" definition LOG_LINE_MAX :: nat where "LOG_LINE_MAX = 256" (* ── LOG-ERROR / LOG-WARN / LOG-INFO / LOG-TEST / LOG-DEBUG ( -- n ) ────── *) definition forth_log_error :: "vm_state \ vm_state" where "forth_log_error vm = vm\data_stack := LOG_ERROR_LEVEL # data_stack vm\" definition forth_log_warn :: "vm_state \ vm_state" where "forth_log_warn vm = vm\data_stack := LOG_WARN_LEVEL # data_stack vm\" definition forth_log_info :: "vm_state \ vm_state" where "forth_log_info vm = vm\data_stack := LOG_INFO_LEVEL # data_stack vm\" definition forth_log_test :: "vm_state \ vm_state" where "forth_log_test vm = vm\data_stack := LOG_TEST_LEVEL # data_stack vm\" definition forth_log_debug :: "vm_state \ vm_state" where "forth_log_debug vm = vm\data_stack := LOG_DEBUG_LEVEL # data_stack vm\" lemma log_error_pushes: "data_stack (forth_log_error vm) = 0 # data_stack vm" by (simp add: forth_log_error_def LOG_ERROR_LEVEL_def) lemma log_warn_pushes: "data_stack (forth_log_warn vm) = 1 # data_stack vm" by (simp add: forth_log_warn_def LOG_WARN_LEVEL_def) lemma log_info_pushes: "data_stack (forth_log_info vm) = 2 # data_stack vm" by (simp add: forth_log_info_def LOG_INFO_LEVEL_def) lemma log_test_pushes: "data_stack (forth_log_test vm) = 3 # data_stack vm" by (simp add: forth_log_test_def LOG_TEST_LEVEL_def) lemma log_debug_pushes: "data_stack (forth_log_debug vm) = 4 # data_stack vm" by (simp add: forth_log_debug_def LOG_DEBUG_LEVEL_def) lemma log_levels_no_overflow_guard: True \ \See file header finding -- all five push unconditionally.\ by simp (* ── LOG-LEVEL! ( n -- ) : guard + pure clamp ─────────────────────────── *) (* C: error if stack empty; else pop n, clamp to [LOG_ERROR, LOG_DEBUG], call log_set_level(n) -- the STORE target is outside vm_state, but the clamped value is a pure function of the input, modelled as such. *) definition log_level_clamp :: "cell \ cell" where "log_level_clamp n = (if n vm_state" where "forth_log_level_store_guard vm = (case data_stack vm of [] \ set_error vm | n # xs \ vm\data_stack := xs\)" lemma log_level_store_underflow: assumes "data_stack vm = []" shows "vm_error (forth_log_level_store_guard vm)" by (simp add: forth_log_level_store_guard_def set_error_def assms) lemma log_level_store_pops_one: assumes "data_stack vm = n # xs" shows "data_stack (forth_log_level_store_guard vm) = xs" by (simp add: forth_log_level_store_guard_def assms) lemma log_level_clamp_bounds: "\ (log_level_clamp n (LOG_DEBUG_LEVEL n LOG_DEBUG_LEVEL \log_set_level(n) writes the log subsystem's active level, which has no vm_state counterpart.\ by simp (* ── LOG-LEVEL@ ( -- n ) -- NOT MODELLED ──────────────────────────────── *) lemma log_level_fetch_not_modelled: True \ \Pushes log_get_level(), reading the same unmodelled global LOG-LEVEL! writes to. Push is via vm_push(), which bounds-checks -- see corrected file header.\ by simp (* ── (do-log-N) runtime words -- NOT MODELLED ─────────────────────────── *) lemma do_log_runtime_words_not_modelled: True \ \All five read/advance vm->return_stack[vm->rsp] as a raw C uint8_t* into inline threaded-code data -- the same raw-host-pointer gap already flagged for LIT (StarForth_Defining_Words.thy), not the IP-as-vaddr usage control_words.c's return-stack addresses already resolved.\ by simp (* ── LOG-*" (IMMEDIATE) and log_emit_string -- NOT MODELLED ──────────── *) lemma log_quote_words_not_modelled: True \ \log_emit_string: TIB/input-buffer dependency (interpret mode) plus, in compile mode, vm_find_word + vm_compile_word + vm_allot -- several already-flagged gaps compounded in one helper shared by all five LOG-*" immediates.\ by simp (* ── LOG-ERROR-STR / -WARN-STR / -INFO-STR / -TEST-STR / -DEBUG-STR ( c-addr u -- ) : fully modelled, same shape as lifecycle words ─────── C: error if dsp<1; pop u, pop addr; if u<=0, no-op (beyond the pops); if addr out of [0, VM_MEMORY_SIZE - u] range, error; else clamp u to LOG_LINE_MAX-1 and log_message a READ of vm->memory[addr..addr+u) -- no vm_state write at all beyond the two pops. All five share this shape (log_str_emit), differing only in the log level passed through, which has no vm_state footprint. *) definition forth_log_str_emit :: "vm_state \ vm_state" where "forth_log_str_emit vm = (case data_stack vm of u # addr # xs \ (if u \s 0 then vm\data_stack := xs\ else if addr unat addr + unat u > VM_MEMORY_SIZE then set_error (vm\data_stack := xs\) else vm\data_stack := xs\) | _ \ set_error vm)" lemma log_str_emit_underflow_nil: assumes "data_stack vm = []" shows "vm_error (forth_log_str_emit vm)" by (simp add: forth_log_str_emit_def set_error_def assms) lemma log_str_emit_underflow_one: assumes "data_stack vm = [x]" shows "vm_error (forth_log_str_emit vm)" by (simp add: forth_log_str_emit_def set_error_def assms) lemma log_str_emit_nonpositive_len_pops_only: assumes "data_stack vm = u # addr # xs" assumes "u \s 0" shows "forth_log_str_emit vm = vm\data_stack := xs\" using assms by (simp add: forth_log_str_emit_def) lemma log_str_emit_out_of_range_errors: assumes "data_stack vm = u # addr # xs" assumes "\ u \s 0" assumes "addr unat addr + unat u > VM_MEMORY_SIZE" shows "vm_error (forth_log_str_emit vm)" using assms by (simp add: forth_log_str_emit_def set_error_def) lemma log_str_emit_normal: assumes "data_stack vm = u # addr # xs" assumes "\ u \s 0" assumes "\ (addr unat addr + unat u > VM_MEMORY_SIZE)" shows "forth_log_str_emit vm = vm\data_stack := xs\" using assms by (simp add: forth_log_str_emit_def) lemma log_str_emit_never_writes_memory: "memory (forth_log_str_emit vm) = memory vm" by (auto simp: forth_log_str_emit_def set_error_def split: list.split) definition forth_log_error_str :: "vm_state \ vm_state" where "forth_log_error_str = forth_log_str_emit" definition forth_log_warn_str :: "vm_state \ vm_state" where "forth_log_warn_str = forth_log_str_emit" definition forth_log_info_str :: "vm_state \ vm_state" where "forth_log_info_str = forth_log_str_emit" definition forth_log_test_str :: "vm_state \ vm_state" where "forth_log_test_str = forth_log_str_emit" definition forth_log_debug_str :: "vm_state \ vm_state" where "forth_log_debug_str = forth_log_str_emit" lemma log_error_str_is_emit: "forth_log_error_str vm = forth_log_str_emit vm" by (simp add: forth_log_error_str_def) lemma log_warn_str_is_emit: "forth_log_warn_str vm = forth_log_str_emit vm" by (simp add: forth_log_warn_str_def) lemma log_info_str_is_emit: "forth_log_info_str vm = forth_log_str_emit vm" by (simp add: forth_log_info_str_def) lemma log_test_str_is_emit: "forth_log_test_str vm = forth_log_str_emit vm" by (simp add: forth_log_test_str_def) lemma log_debug_str_is_emit: "forth_log_debug_str vm = forth_log_str_emit vm" by (simp add: forth_log_debug_str_def) end