Files
LithosAnanake/src/word_source/acl_words.c
T

356 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* acl_words.c — C primitive words for the word-level ACL system
*
* All policy logic lives in capsules/ACL.4th. These primitives expose
* DictEntry ACL fields to FORTH: ACL-MODE@, ACL-MODE!, ACL-PINNED?,
* ACL-PIN, ACL-TTL@, ACL-TTL!, ACL-ALLOW@, ACL-ALLOW!, ACL-HEAT@,
* ACL-INHERIT, ACL-INIT-PRIMITIVES, ACL-WORD-ID.
*
* All words that take an XT expect a DictEntry* cast to cell_t on the
* stack — exactly what ' (tick) produces in StarForth.
*/
#include "include/acl_words.h"
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/**
* @brief Pop an execution token (XT) from the data stack and return the
* corresponding DictEntry pointer.
*
* Sets @c vm->error and returns NULL on stack underflow or if the XT is zero.
* All ACL word implementations call this before accessing DictEntry fields.
*
* @param vm Pointer to the VM structure
* @return Pointer to the DictEntry, or NULL on error
*/
static DictEntry *pop_xt(VM *vm)
{
if (!vm || vm->dsp < 0) {
log_message(LOG_ERROR, "ACL word: stack underflow");
if (vm) vm->error = 1;
return NULL;
}
cell_t xt = vm_pop(vm);
DictEntry *entry = (DictEntry *)(uintptr_t)xt;
if (!entry) {
log_message(LOG_ERROR, "ACL word: NULL XT");
vm->error = 1;
return NULL;
}
return entry;
}
/**
* @brief Implements FORTH word @c ACL-MODE@ — fetch ACL enforcement mode
*
* Stack effect: ( xt -- mode )
* Pushes the enforcement mode of the word identified by @c xt:
* 0 = STRICT (allow/deny is permanent), 1 = TTL (expires after countdown).
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_mode_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->acl_mode);
}
/**
* @brief Implements FORTH word @c ACL-MODE! — store ACL enforcement mode
*
* Stack effect: ( mode xt -- )
* Sets the enforcement mode of the word identified by @c xt.
* No-op if the word is pinned — pinned entries are immutable.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_mode_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-MODE!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t mode = vm_pop(vm);
if (e->acl_pinned) return; /* one-way ratchet: pinned is immutable */
e->acl_mode = (uint8_t)(mode & 0xFF);
}
/**
* @brief Implements FORTH word @c ACL-PINNED? — query pin status
*
* Stack effect: ( xt -- flag )
* Pushes -1 (true) if the word is pinned, 0 (false) otherwise.
* A pinned word's ACL fields cannot be modified by any FORTH operation.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_pinned_query(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, e->acl_pinned ? -1 : 0);
}
/**
* @brief Implements FORTH word @c ACL-PIN — pin a word's ACL entry
*
* Stack effect: ( xt -- )
* Sets @c acl_pinned = 1 on the word identified by @c xt.
* This is a one-way ratchet: once set it can never be cleared, even by
* ACL-INHERIT. Use with @c ' WORD ACL-PIN in FORTH, never from C policy code.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_pin(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
e->acl_pinned = 1;
}
/**
* @brief Implements FORTH word @c ACL-TTL@ — fetch TTL countdown value
*
* Stack effect: ( xt -- n )
* Pushes the current TTL (time-to-live) countdown of the word's ACL entry.
* When TTL reaches zero in TTL mode the interpreter calls @c acl_recheck().
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_ttl_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->acl_ttl);
}
/**
* @brief Implements FORTH word @c ACL-TTL! — store TTL countdown value
*
* Stack effect: ( n xt -- )
* Sets the TTL countdown on the word's ACL entry. Negative values are
* clamped to 0; values above UINT32_MAX are clamped to UINT32_MAX.
* No-op if the word is pinned.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_ttl_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-TTL!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t n = vm_pop(vm);
if (e->acl_pinned) return;
e->acl_ttl = (uint32_t)(n < 0 ? 0 : (uint64_t)n > UINT32_MAX ? UINT32_MAX : (uint32_t)n);
}
/**
* @brief Implements FORTH word @c ACL-ALLOW@ — fetch cached allow/deny flag
*
* Stack effect: ( xt -- flag )
* Pushes the cached ACL decision for the word: -1 = allowed, 0 = denied.
* This cached value is checked on every execution in TTL mode; the cache is
* refreshed by @c acl_recheck() when TTL expires.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_allow_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, e->acl_allow ? -1 : 0);
}
/**
* @brief Implements FORTH word @c ACL-ALLOW! — store cached allow/deny flag
*
* Stack effect: ( flag xt -- )
* Sets the cached ACL decision: any non-zero @c flag = allowed, 0 = denied.
* No-op if the word is pinned.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_allow_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-ALLOW!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t flag = vm_pop(vm);
if (e->acl_pinned) return;
e->acl_allow = (flag != 0) ? 1 : 0;
}
/**
* @brief Implements FORTH word @c ACL-HEAT@ — fetch execution heat
*
* Stack effect: ( xt -- heat )
* Pushes the @c execution_heat counter for the word identified by @c xt.
* Used by @c ACL-TTL-COMPUTE in @c ACL.4th to calibrate the TTL value based
* on how frequently the word is executed.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_heat_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->execution_heat);
}
/**
* @brief Implements FORTH word @c ACL-WORD-ID — fetch stable word identifier
*
* Stack effect: ( xt -- n )
* Pushes the stable @c word_id for the entry identified by @c xt. Unlike
* dictionary positions, @c word_id is assigned at registration and never
* changes, making it safe to use as a persistent table index in @c ACL.4th.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_word_id(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->word_id);
}
/**
* @brief Implements FORTH word @c ACL-INHERIT — inherit ACL policy from parent
*
* Stack effect: ( src dst -- )
* Copies @c acl_mode from @c src to @c dst, then clears @c dst->acl_pinned,
* resets @c acl_ttl to 0, and sets @c acl_allow to 1 (optimistic default).
* This is a C primitive because FORTH code cannot clear @c acl_pinned — pin
* is a one-way ratchet that only C can reset during inheritance.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_inherit(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-INHERIT: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *dst = pop_xt(vm);
if (!dst) return;
DictEntry *src = pop_xt(vm);
if (!src) return;
acl_inherit_entry(src, dst);
}
/**
* @brief Implements FORTH word @c ACL-INIT-PRIMITIVES — reset ACL fields on all
* unpinned dictionary entries
*
* Stack effect: ( -- )
* Walks the entire dictionary and resets ACL fields on every entry that is not
* pinned: sets @c acl_ttl = 0 (force recheck on first execution), @c acl_allow
* = 1 (optimistic default), @c acl_mode = ACL_MODE_TTL. Called by @c ACL-BOOT
* in @c ACL.4th immediately after the ACL capsule finishes loading.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_init_primitives(VM *vm)
{
if (!vm) return;
for (DictEntry *e = vm->latest; e; e = e->link) {
if (!e->acl_pinned) {
/* Re-assert permissive defaults; ACL-RECHECK will calibrate on first exec */
e->acl_ttl = 0; /* force recheck on first execution */
e->acl_allow = 1;
e->acl_mode = ACL_MODE_TTL;
}
}
}
/**
* @brief Direct C entry point for ACL policy inheritance on the capsule birth path.
*
* Copies @c acl_mode from @c src to @c dst, clears @c acl_pinned, resets
* @c acl_ttl to 0, and sets @c acl_allow to 1. Called from capsule birth code
* (not from FORTH) — no stack manipulation is performed.
*
* @param src Source DictEntry whose @c acl_mode is inherited
* @param dst Destination DictEntry whose ACL fields are updated
*/
void acl_inherit_entry(DictEntry *src, DictEntry *dst)
{
if (!src || !dst) return;
dst->acl_mode = src->acl_mode;
dst->acl_pinned = 0; /* pin is contextual, not viral */
dst->acl_ttl = 0; /* child has no history; force recheck */
dst->acl_allow = 1; /* optimistic default */
}
/**
* @brief Register all ACL C-primitive words with the VM dictionary.
*
* Registers: field accessors (@c ACL-MODE@ / @c ACL-MODE! / @c ACL-PINNED? /
* @c ACL-TTL@ / @c ACL-TTL! / @c ACL-ALLOW@ / @c ACL-ALLOW! / @c ACL-HEAT@ /
* @c ACL-WORD-ID), and mutation words (@c ACL-PIN / @c ACL-INHERIT /
* @c ACL-INIT-PRIMITIVES). Called from @c vm_bootstrap.c during VM
* initialisation.
*
* @param vm Pointer to the VM structure
*/
void register_acl_words(VM *vm)
{
/* Field accessors — read */
register_word(vm, "ACL-MODE@", forth_acl_mode_fetch);
register_word(vm, "ACL-PINNED?", forth_acl_pinned_query);
register_word(vm, "ACL-TTL@", forth_acl_ttl_fetch);
register_word(vm, "ACL-ALLOW@", forth_acl_allow_fetch);
register_word(vm, "ACL-HEAT@", forth_acl_heat_fetch);
register_word(vm, "ACL-WORD-ID", forth_acl_word_id);
/* Field accessors — write (all no-op if pinned) */
register_word(vm, "ACL-MODE!", forth_acl_mode_store);
register_word(vm, "ACL-TTL!", forth_acl_ttl_store);
register_word(vm, "ACL-ALLOW!", forth_acl_allow_store);
/* Pin ratchet — one-way set */
register_word(vm, "ACL-PIN", forth_acl_pin);
/* Higher-level operations */
register_word(vm, "ACL-INHERIT", forth_acl_inherit);
register_word(vm, "ACL-INIT-PRIMITIVES", forth_acl_init_primitives);
}