Zum Inhalt

Status: Archiv/Informativ Prioritaet bei Konflikt: Es gelten immer 00 bis 08. Hinweis: Diese Datei ist historischer oder ergaenzender Kontext und nicht normativer Kern.

Anhang X: Laufzeitumgebung, Handles und Speichersicherheit

Status: Informativ (nicht normativ), inhaltlich konsistent mit Abschnitten 3 und 7.

Dieser Anhang beschreibt das konzeptuelle Modell hinter drei eng verwandten Themen:

  • der Laufzeitumgebung als Effektkontext (Abschnitt 7),
  • dem HandleId + Generation-Design und dem ABA-Schutz (Abschnitt 3.4),
  • dem allgemeinen Sicherheitsprofil der Jade Memory Engine,
  • und den bewusst vorgesehenen Ausstiegspfaden für Low-Level- und Hochleistungscode.

Er dient als erzählerisches Bindeglied zwischen den formalen Abschnitten.


X.1 Ziele und Abgrenzung

Jade soll folgendes bieten:

  • Speichersicherheit für JDL-Code per Konstruktion:
  • Keine rohen Zeiger im Anwendungscode.
  • Keine unverfolgten Lebenszeiten.
  • Kein impliziter globaler Zustand als primärer Weg zum Zugriff auf Ressourcen.
  • Kontrollierte Ausstiegspfade für:
  • FFI (Anbindung an fremden Code),
  • Hochleistungs-Plugins,
  • und Low-Level-Laufzeitintegration.

Das Ziel ist nicht, mit Rust als vollständig linear-typisierter, borrow-geprüfter Sprache zu konkurrieren. Stattdessen bietet Jade:

  • eine sichere Standardumgebung für JDL-Code,
  • mit explizit markierten „unsicheren Rändern" und starker Tooling-Unterstützung (LSP, Diagnosen),
  • und eine klare Trennung zwischen Type-Engine-Semantik und Memory-Engine-Speicher.

X.2 Laufzeitumgebung als Effektkontext

X.2.1 Capabilities und deps

Das Effektsystem stellt Capabilities als Protokolle bereit, zum Beispiel:

protocol Db {
    def query(sql: str, params: [SqlParam]) -> Result[Rows, DbError]
    def execute(sql: str, params: [SqlParam]) -> Result[u64, DbError]
}

protocol Logger {
    def debug(msg: str) -> ()
    def info(msg: str) -> ()
    def warn(msg: str) -> ()
    def error(msg: str) -> ()
}

Funktionen deklarieren die Capabilities die sie benötigen über deps:

// TODO: deps-Syntax veraltet — konsolidieren zu imperativem oder Effect-Stil
def<{ deps: [Db, Logger] }>
processOrder(input: OrderInput) -> Result[Order, OrderError] { ... }

Auf Typebene bedeutet das:

"processOrder ist nur sinnvoll in einer Umgebung, in der Db- und Logger-Instanzen verfügbar sind."

Die Type Engine behandelt deps als Teil des Funktionstyps — sie sind keine Kommentare, sondern Einschränkungen darüber, wo die Funktion ausgeführt werden darf.

X.2.2 Laufzeitumgebung als Capability-Kontext

Die Laufzeitumgebung (Runtime) ist der Effektkontext, der konkrete Handler für abstrakte Capabilities bereitstellt.

Konzeptuell:

type Runtime: struct { ... }

def Runtime.new() -> Runtime

def<{Cap}>
Runtime.registerHandler(self, handler: Cap) -> ()

// Gibt den aktuell aktiven Handler für eine Capability zurück
def<{Cap}>
Runtime.getHandler(self) -> Cap

Die konkrete Darstellung wird von der VM definiert. Logisch gesehen verwaltet die Laufzeitumgebung eine Zuordnung:

Capability-Typ → konkreter Handler-Wert
Db             → PostgresHandler { ... }
Logger         → FileLoggerHandler { ... }
Payment        → StripePayment { ... }
Email          → SmtpEmail { ... }

X.2.3 Programmatische Handler-Registrierung

Die programmatische Registrierung bindet abstrakte Capabilities an konkrete Implementierungen:

def main() {
    val rt = Runtime.new()

    rt.registerHandler[Logger](
        FileLoggerHandler {
            path: "/var/log/app.log"
            level: LogLevel.Info
        }
    )

    rt.registerHandler[Db](
        PostgresHandler {
            pool: ConnectionPool.new(config.dbUrl)
        }
    )

    // Anwendungslogik in diesem Effektkontext ausführen
    runApp(rt)
}

Ab diesem Punkt erhält jede Funktion mit deps: [Db, Logger], die in dieser Laufzeitumgebung ausgeführt wird, diese konkreten Handler.

X.2.4 Integration mit CallGraph und env

Deklarative Workflows nutzen requires und env um dieselbe Idee auf höherer Ebene auszudrücken:

CallGraph processOrderGraph(input: OrderInput) -> Result[Order, OrderError] {
    requires: [Db, Payment, Email, Logger]

    env: {
        Db:      PostgresHandler { pool: connectionPool }
        Payment: StripePayment { key: config.stripeKey }
        Email:   SmtpEmail { host: config.smtpHost }
        Logger:  FileLoggerHandler { path: "/var/log/app.log", level: Info }
    }

    Node validate {
        call: validateOrder(input)
    }

    Node create {
        call: Db.insert("orders", validate.result)
    }

    Node charge {
        call: Payment.charge(create.result)
    }

    Node sendConfirmation {
        call: Email.sendConfirmation(create.result)
    }
}

Semantisch:

  1. requires: [Db, Payment, Email, Logger] definiert die Capability-Menge die der Graph benötigt.
  2. env: { ... } beschreibt, wie eine Laufzeitumgebung aufgebaut wird, die diese Capabilities erfüllt.
  3. Die CallGraph-Engine führt den Graph innerhalb dieser Laufzeitumgebung aus.

Damit ergibt sich folgende Schichtung:

  • Die Logikschicht drückt aus, was eine Berechnung benötigt (deps).
  • Die deklarative Schicht (CallGraph, env) drückt aus, wie diese Abhängigkeiten verdrahtet werden.
  • Die Laufzeitumgebung ist zur Ausführungszeit die Brücke zwischen beiden Schichten.

X.2.5 Bereichsbegrenzte Überschreibungen

Die Laufzeitumgebung unterstützt auch bereichsbegrenzte Überschreibungen — lokale Änderungen ohne Anpassung von Typsignaturen:

// TODO: deps-Syntax veraltet — konsolidieren zu imperativem oder Effect-Stil
def<{ deps: [Logger] }>
withDebugLogging[T](f: () -> T) -> T {
    val original = Runtime.getHandler[Logger]()
    val debugLogger = FileLoggerHandler {
        path: "/tmp/debug.log"
        level: LogLevel.Debug
    }

    Runtime.withHandler[Logger](debugLogger) {
        val result = f()
        result
    }

    // Nach dem Block ist Logger wieder auf 'original' zurückgesetzt
}

Effekte bleiben explizit:

  • Der Funktionstyp ist weiterhin deps: [Logger].
  • Nur das Verhalten der Capability ändert sich lokal.
  • Die VM kann denselben CallGraph in verschiedenen Laufzeitumgebungen (Test, Entwicklung, Produktion) ausführen, indem unterschiedliche Handler-Sätze gewählt werden.

X.3 Handle-IDs und ABA-Schutz

X.3.1 Das Handle-Table-Modell

Die Memory Engine verwendet Handles statt roher Zeiger im JDL- und Plugin-Code.

Ein Handle ist dabei ein indirekter Verweis auf ein Objekt im Speicher — ähnlich wie eine Ausweisnummer statt einer Adresse. Das Objekt kann intern verschoben werden, die Ausweisnummer bleibt gleich.

Abstrakt:

HandleId = { index: u32, generation: u32 }

HandleTable[index] = {
    ptr:        *void,
    type:       TypeId,
    generation: u32,
    refCount:   u32,
    policies:   PolicySet,
    ...
}
  • index wählt einen Slot in der globalen Handle-Tabelle.
  • generation unterscheidet verschiedene Belegungen desselben Slots.

Dieses Design verhindert, dass veraltete Handles still auf neu allokierte Objekte zeigen, wenn Slots wiederverwendet werden.

X.3.2 Das ABA-Problem und Generation-Counter

Ohne Generation-Counter wäre ein Handle nur:

HandleId = { index: u32 }

Wenn Slot-Wiederverwendung auftritt, entsteht folgendes Problem:

  1. Slot 42 enthält Objekt A.
  2. A wird freigegeben; Slot 42 wird verfügbar.
  3. Slot 42 wird für Objekt B wiederverwendet.
  4. Der alte Handle { index: 42 } zeigt jetzt auf B, obwohl sein ursprüngliches Ziel A nicht mehr existiert.

Das ist das klassische ABA-Problem: Der „Indexwert" erscheint unverändert, aber das darunterliegende Objekt hat sich geändert — ein stiller, schwer zu findender Fehler.

Mit Generation-Countern:

  • Bei jedem Freigeben und anschließenden Wiederverwenden eines Slots wird generation erhöht.
  • Die Memory Engine prüft bei jedem Zugriff:
slot = handleTable[id.index]
if slot.generation != id.generation:
    FEHLER_UNGÜLTIGER_HANDLE  // veralteter / „Zombie"-Handle

Damit gilt:

  • HandleId {42, 10} und HandleId {42, 11} sind unterschiedliche Identitäten.
  • Ein Handle kann nicht still auf ein anderes Objekt umgebunden werden, nur weil Indizes wiederverwendet werden.

X.3.3 Sicherheitsgarantien

Dieses Design bietet:

  • Starken Schutz gegen Use-After-Free in Kombination mit Slot-Wiederverwendung.
  • Eine klare Trennung zwischen:
  • Logischer Identität (index, generation) und physischem Speicher (ptr).
  • Erkennbare Fehler statt stiller Speicherkorruption:
  • Veraltete Handles lösen einen Fehler aus oder geben FEHLER_UNGÜLTIGER_HANDLE zurück — sie „funktionieren" nie versehentlich auf neuen Objekten.

Das Handle-System ist zentral für Jades Sicherheitsgarantien: JDL-Code und der Großteil des Plugin-Codes manipulieren niemals rohe Adressen, sondern ausschließlich typisierte Handles.


X.4 Sicherheitsprofil: Korruption vs. Leaks

X.4.1 Was die Memory Engine verhindert

Die Kombination aus:

  • opaken Handles,
  • Generation-Countern,
  • typgesteuerten Policies (z.B. share, memory, borrow),
  • und dem Ring-Modell (JME in Ring 0, VM in Ring 1, Plugins in Ring 2),

bietet starken Schutz gegen:

  • Use-After-Free (veraltete Handles werden abgelehnt).
  • Versehentlichen Missbrauch roher Zeiger in JDL und Plugin-Code.
  • Viele Klassen von Data Races auf Refcounts (share: local vs. share: sync).
  • FFI-Missbrauch, wenn er durch geeignete Policies abgesichert ist (ffi, ipc, zero_copy-Semantik).

Insbesondere kann JDL-Code nicht:

  • free direkt aufrufen,
  • in beliebigen Speicher indizieren,
  • oder gültige Handles „aus dem Nichts" konstruieren.

Alle solchen Operationen müssen über die Memory-Engine-APIs laufen und werden gegen die Handle-Tabelle und die Policies geprüft.

X.4.2 Wo Leaks dennoch auftreten können

Das Design unterscheidet bewusst zwischen Sicherheit und Lebendigkeit:

  • Speicherkorruption wird per Konstruktion verhindert.
  • Speicherleaks sind weiterhin möglich, wenn das logische Ownership-Modell falsch angewendet wird.

Typische Ursachen für Leaks:

Refcount-Zyklen:

  • Zwei Objekte halten starke Referenzen aufeinander (oder größere Zyklen aus starken Referenzen).
  • Refcounts erreichen nie null; die Memory Engine nimmt korrekt an, dass sie noch benötigt werden.

Arenen die nie zurückgesetzt werden:

  • Arena-Allokation bedeutet „alle Objekte leben bis zum reset()".
  • Wenn eine langlebige Arena nie zurückgesetzt oder freigegeben wird, bleiben alle enthaltenen Objekte logisch am Leben.

Globale Caches / Registries:

  • Langlebige Maps von Schlüsseln zu Handles, die Einträge nie verwerfen.
  • Aus Sicht der Memory Engine sind alle Einträge noch erreichbar.

FFI / Plugin-Missbrauch:

  • Fehlende retain/release-Aufrufe rund um Handles,
  • versteckte Handles in nicht verwalteten Datenstrukturen,
  • nicht übereinstimmende Lebenszeiten an FFI-Grenzen.

X.4.3 Speicher-Policies und Allokationsstrategien

Jades Sicherheitsmodell ist orthogonal zur Allokationsstrategie eines Wertes. Die Type Engine stellt dies über Speicher-Policies bereit:

  • memory: ref – Heap-allokiertes, refgezähltes Objekt, vollständig von der JME verwaltet.
  • memory: arena[ArenaTag] – Objekt wird aus einer semantischen Arena allokiert (z.B. pro Request, pro Task).
  • memory: pool[Profile] – Objekt wird aus einem typisierten Pool für Festgrößen-Allokationen bereitgestellt.
  • memory: mapped – Extern besessener Speicher, z.B. OS-gesichertes mmap.

Jede Policy entspricht einer Backend-Strategie innerhalb der JME, aber alle teilen dieselbe Sicherheitshülle:

  • Werte werden immer über opake Handles mit Generation-Countern angesprochen.
  • drop- und share-Policies werden einheitlich durchgesetzt (Arena vs. Heap ändert die Aliasing-Regeln nicht).
  • Eigene Backends (z.B. speichergemappte Buffer, Slab-Allokator) müssen weiterhin durch die Handle-Tabelle gehen.

Typische Zuordnungen:

  • memory: ref → normaler Heap-Allokator (ggf. vom Host-Sprachen-Allokator unterlegt).
  • memory: arena[A] → wachsender Region-Allokator, an eine semantische Lebenszeit gebunden; Reset/Ende gibt alle Objekte frei.
  • memory: pool[Profile] → Pool/Slab-Allokator, optimiert für Festgrößen-Objekte.
  • memory: mapped → OS-gesichertes Mapping mit eigener drop-Implementierung, die unmappt und aufräumt.

JDL-Code manipuliert diese Backends nicht direkt. Er:

  • deklariert oder komponiert Policies (über :> <{ memory: ... }>-Refinements wie InArena(A)),
  • nutzt optional Capabilities (z.B. Arena[RequestArena], Mmap), die Fehler als normale Werte zurückgeben (Result[_, ArenaError]),
  • und verlässt sich auf die Laufzeitumgebung / den CallGraph, der passende Profile und Backends für jede Policy wählt.

Das hält Sicherheitsgarantien stabil, erlaubt aber verschiedenen Deployments, Allokationsstrategien zu optimieren ohne JDL-Quellcode zu ändern.

Diese Situationen sind primär logische Fehler, keine Memory-Engine-Bugs. Sie können abgemildert, aber nicht vollständig eliminiert werden ohne schwerere Mechanismen (Zykluserkennung, GC, lineare Typen) — die Jade bewusst nicht per Default erzwingt.


X.5 Bewusste Ausstiegspfade

Jade enthält bewusst Ausstiegspfade für Fälle, in denen absolute Sicherheit gegen Performance, Interoperabilität oder Ausdruckskraft abgewogen wird. Sie sind eng begrenzt und explizit markiert.

Häufige Ausstiegspfade:

  • FFI-Grenzen

  • Typen und Funktionen mit Policies wie ffi: unsafe.

  • Handles und Buffer, die Sprachgrenzen überschreiten.
  • Die Verantwortung für korrektes Retain/Release und Lebenszeit-Management wird an die FFI-Schicht delegiert.

  • Low-Level-Plugins

  • Native Plugins in Ring 2 können Memory-Engine-APIs mit weniger Prüfungen aufrufen.

  • Sie dürfen eigene Caches und interne Handle-Graphen verwalten.
  • Missbrauch kann zu Leaks oder Logikfehlern führen, aber nicht zu stiller prozessübergreifender Korruption.

  • Zero-Copy / Buffer-Protokolle

  • Buffer- und ByteStream-Abstraktionen können Sichten auf den darunterliegenden Speicher freigeben.

  • Falscher Einsatz von Zero-Copy-Semantik (z.B. einen Buffer nach dem Reset seiner Arena verwenden) ist eine bewusste „scharfe Kante".
  • Policies (zero_copy, borrow) machen solche Bereiche explizit sichtbar.

  • Arenen und Pools

  • Arena-Allokator sind mächtig, aber nicht trivial zu verstehen.

  • Langlebige Arenen als globaler Speicher sind ein bewusster Ausstiegspfad:
    • Sie tauschen Vorhersagbarkeit gegen Performance und geringen Allokations-Overhead,
    • erfordern aber Disziplin um unkontrolliertes Wachstum zu vermeiden.

Diese Ausstiegspfade sind keine Unfälle — sie sind explizite Designentscheidungen. Sie erkennen an, dass Jade in realen Umgebungen eingesetzt wird (FFI, bestehende C/D-Codebasen, hochoptimierte Plugins), in denen perfekte statische Sicherheit nicht immer erreichbar oder wünschenswert ist.


X.6 Schwache Referenzen und Zyklen

Die Memory Engine von Jade basiert auf Referenzzählung. Um zyklische Strukturen ohne vollständigen GC zu handhaben, sieht das Design schwache Referenzen vor.

Zur Erinnerung: Ein Referenzzähler zählt, wie viele Stellen im Code auf ein Objekt zeigen. Wenn der Zähler auf null sinkt, wird das Objekt freigegeben. Das Problem: Wenn A auf B zeigt und B auf A, wird der Zähler nie null — obwohl keiner mehr von außen erreichbar ist. Schwache Referenzen lösen das, indem sie den Zähler nicht erhöhen.

Konzeptuell:

// Starke Referenz: erhöht den Refcount
type Strong[T]: struct {
    handle: HandleId
}

// Schwache Referenz: hält das Objekt nicht am Leben
type Weak[T]: struct {
    handle: HandleId
}

Die Memory Engine stellt folgende Operation bereit:

  • Weak[T].upgrade() -> Option[Strong[T]]

  • Gibt Some(Strong[T]) zurück, wenn das Objekt noch existiert.

  • Gibt None zurück, wenn das Objekt bereits freigegeben wurde.

Empfohlene Muster:

  • In Eltern-Kind-Graphen:
  • Eltern → Kinder über starke Referenzen,
  • Kinder → Eltern über schwache Referenzen.

  • In Caches:

  • Schwache Referenzen speichern und nur bei Bedarf zu starken machen.

Damit werden permanente Refcount-Zyklen vermieden, ohne die Komplexität eines vollständigen GC. Der Kompromiss ist explizites Modellieren von Ownership und klare Muster, wo schwache Referenzen angemessen sind.


X.7 Tooling und LSP-Diagnosen

Die Laufzeitumgebung implementiert keinen vollständigen GC, kann aber dem Tooling reichhaltige Informationen bereitstellen:

  • Die LSP / IDE-Integration kann abfragen:
  • welche Typen häufig Zyklen bilden (z.B. selbstreferenzielle Graphen),
  • welche Arenen langlebig sind und nie zurückgesetzt werden,
  • welche Module große globale Caches pflegen,
  • wo FFI-gebundene Typen mit ffi: unsafe eingesetzt werden.

Mögliche Diagnosen:

  • Warnungen wenn:
  • ein Typ mit memory: ref ein Handle auf sich selbst oder einen Elternteil hält (möglicher starker Zyklus),
  • eine Arena allokiert, aber innerhalb der beobachteten Lebenszeit nie zurückgesetzt wird (möglicher Arena-Leak),
  • eine deps-annotierte Funktion Ressourcen allokiert, sie aber auf keinem Kontrollfluss-Pfad freigibt.
  • Unterstützung bei:
  • der Identifikation von Kandidaten für Weak[T] statt starker Referenzen,
  • der Empfehlung lebenszeit-begrenzter Arenen (z.B. pro Request, pro Task),
  • der Lokalisierung von „Hotspots", an denen Plugins allokieren ohne freizugeben.

Diese Werkzeuge unterstützen einen „sicher per Default, verifiziert durch Tooling"-Ansatz:

  • JDL und die Memory Engine verhindern katastrophale Korruption.
  • Tooling schließt die Lücke für Leak-Erkennung und Best-Practice-Durchsetzung, ohne die Sprache zu einem vollständig linearen oder borrow-geprüften System zu machen.

X.8 Designphilosophie im Vergleich zu Rust

Jades Design unterscheidet sich bewusst von Rust:

  • Jade:
  • Sicher per Default: Handles, Policies, Laufzeit-Ringe und Effektkontexte erzwingen starke Invarianten.
  • Erlaubt kontrollierte Low-Level-Ausstiegspfade, wo Performance oder Interoperabilität es verlangen.
  • Betont Tooling-Unterstützung (LSP-Diagnosen, Laufzeit-Inspektion) zur Erkennung von Leaks und Missbrauchsmustern.
  • Hält das Typsystem mächtig, aber nicht so syntaktisch schwer wie ein vollständiger Borrow-Checker.

  • Rust:

  • Erzwingt lineare/affine Semantik und striktes Borrowing zur Compile-Zeit.
  • Eliminiert ganze Klassen von Leaks und Lebenszeit-Bugs durch aggressive Compile-Zeit-Prüfungen (auf Kosten von Komplexität und Ergonomie).

Jade versucht nicht, Rusts Garantien zu replizieren. Stattdessen soll es sein:

Eine pragmatische, capability-orientierte VM und Sprache mit starken Sicherheitsgarantien für wohlverhalteneden Code, expliziten unsicheren Rändern, und mächtigem Tooling — damit Entwickler dort Disziplin halten können, wo absolute statische Garantien nicht praktikabel sind.


X.9 LSP- und Tooling-Empfehlungen

Status: Informativ — für Tool-Autoren und IDE-Integrationen.

Jade setzt bewusst auf eine Kombination aus:

  • starken Laufzeit-Invarianten (Handles, Generationen, Policies, Ring-Modell),
  • und externem Tooling (LSP, IDEs, Analysatoren),

um Speicherlecks und Missbrauchsmuster zu erkennen und zu verhindern, die nicht vollständig im Kern-Typsystem abbildbar sind.


X.9.1 Statische Analysen

Das LSP soll statische Prüfungen bereitstellen, die JDLs reiche Typ- und Policy-Informationen nutzen:

  1. Refcount-Zykluserkennung (Heuristisch)

  2. Typen identifizieren, die möglicherweise starke Referenzzyklen bilden, z.B.:

    type Node: struct {
        value:  T
        next:   Node        // einfach verkettet
        parent: Node        // oder Rückzeiger
    }
    
  3. Warnen wenn:

    • ein Typ mit memory: ref ein Feld desselben Typs hält, oder
    • ein Handle auf einen Typ hält, der transitiv zurückverweist.
  4. Empfehlen, Weak[T] (oder eine schwache Policy) für Rückzeiger einzuführen.

  5. Arena-Lebenszeit-Analyse

  6. Muster wie folgendes verfolgen:

    val arena = Arena.new()
    // Allokationen in arena
    // ...
    // kein Aufruf von arena.reset() oder arena.drop()
    
  7. Unterscheiden zwischen:

    • kurzlebigen, klar begrenzten Arenen (pro Request, pro Task),
    • langlebigen Arenen, die nie zurückgesetzt werden.
  8. Warnen wenn:

    • eine Arena ihren beabsichtigten Scope verlässt,
    • oder ein Modul wiederholt in eine langlebige Arena allokiert ohne je zurückzusetzen.
  9. Globale Cache / Registry-Hotspots

  10. Globale oder langlebige Maps/Mengen identifizieren, die Handles speichern:

    var GLOBAL_CACHE: Map[Key, Handle[Value]]
    
  11. Warnen wenn:

    • Einträge hinzugefügt, aber nie entfernt werden,
    • oder der Cache auf heißen Aufrufpfaden unbegrenzt wächst.
  12. FFI-Ownership-Diskrepanzen

  13. Wenn ein Typ oder eine Funktion mit ffi: unsafe oder verwandten Policies annotiert ist:

    • verfolgen, wo Handles die FFI-Grenze überschreiten,
    • häufige Anti-Muster erkennen (z.B. Handle in JDL erzeugt, in FFI gehalten, nie freigegeben).
  14. Gezielte Diagnosen bereitstellen:

    • „Handle an FFI übergeben ohne passendes Release"
    • „FFI-Callback speichert Handle in statischem/globalem Kontext"
  15. Effekt- / Ressourcen-Nutzungsmuster

  16. Für Funktionen mit deps: [Db, Logger, FileSystem, ...]:

    • Kontrollfluss auf fehlende „close/release/drop"-artige Operationen analysieren,
    • Codepfade hervorheben, an denen Ressourcen erworben aber nicht offensichtlich freigegeben werden.
  17. Kann konservativ sein (Best-Effort, nicht vollständig), ist aber in der Praxis sehr wertvoll.


X.9.2 Laufzeit-Introspektions-APIs für Tooling

Um Live-Diagnosen und Leak-Erkennung zu unterstützen, soll die Jade Memory Engine nur in Debug-Builds verfügbare APIs bereitstellen:

  1. Aktiver Handle-Snapshot
   def JME.debug_listActiveHandles() -> [DebugHandleInfo]
````

`DebugHandleInfo` enthält:

```jdl
type DebugHandleInfo: struct {
    id:          HandleId
    type:        TypeId
    refCount:    u32
    policies:    PolicySet
    approxSize:  u64
    origin:      Option[DebugOrigin]
}

type DebugOrigin: struct {
    module:   str
    function: str
    // optional: Quelldatei, Allokationsstelle oder kompakter Backtrace
}

Das LSP kann damit:

  • eine „Aktive Handles"-Ansicht in der IDE anzeigen,
  • nach Typ, Modul oder Herkunft filtern,
  • verdächtige Cluster hervorheben (z.B. viele DbConnection-Handles, die nie verschwinden).

  • Arena-Statistiken

def JME.debug_arenaStats() -> [ArenaDebugInfo]

Enthält:

type ArenaDebugInfo: struct {
    id:                 ArenaId
    ownerModule:        str
    allocatedBytes:     u64
    allocatedObjects:   u64
    lastResetTimestamp: Option[Timestamp]
}

Tooling kann damit:

  • visualisieren, welche Arenen „heiß" sind,
  • warnen, wenn eine Arena lange nicht zurückgesetzt wurde,
  • Arenen mit spezifischen Modulen / Aufrufpfaden korrelieren.

  • Pro-Plugin / Pro-Modul-Accounting

def JME.debug_memoryByModule() -> [ModuleMemoryInfo]

Beispiel:

type ModuleMemoryInfo: struct {
    module:          str
    totalBytes:      u64
    handleCount:     u64
    arenaBytes:      u64
    ffiManagedBytes: u64
}

Die IDE kann damit:

  • Speicherverbrauch spezifischen Modulen oder Plugins zuordnen,
  • Module markieren, die über die Zeit unbegrenzt wachsen,
  • Refactoring oder strengere Policies vorschlagen.

X.9.3 LSP-Nutzererfahrung

Das Ziel ist, gute Speicherhygiene sichtbar und bequem zu machen — ohne eine Rust-artige Typdisziplin zu erzwingen.

Empfohlene UX-Muster:

  1. Inline-Warnungen und Hinweise

  2. Verdächtige Muster (Zyklen, langlebige Arenen, Caches) unterstreichen mit:

    • „Diese Struktur könnte einen Referenzzyklus bilden. Erwäge Weak[T] für Rückzeiger."
    • „Arena RequestArena wird in diesem Scope nie zurückgesetzt."
    • „Globaler Cache SessionCache wächst unbegrenzt. Erwäge Eviction oder TTL."
  3. Speicher-Health-Panel

  4. Eine dedizierte Ansicht mit:

    • aktiven Handles nach Typ und Modul,
    • Arena-Statistiken,
    • Speicherverbrauch pro Plugin,
    • aktuellen Allokationstrends.
  5. Aktualisierbar:

    • auf Abruf,
    • oder periodisch während Debug-Sitzungen.
  6. Geführte Refactoring-Vorschläge

  7. Für jede Diagnose bereitstellen:

    • Quick-Fixes (wo möglich),
    • Links zu relevanten Spec-Abschnitten (z.B. dieser Anhang, der WeakRef-Abschnitt, Arena-Nutzungsrichtlinien),
    • Beispiele für „gute Muster" (z.B. Elternteil stark / Kind schwach, Request-Arenen).
  8. Debug- vs. Release-Modus

  9. Schwere Introspektionen optional machen:

    • in Debug-Builds aktiviert,
    • in Release-Builds deaktiviert oder minimiert.
  10. Sicherstellen, dass das Aktivieren des Debug-Modus die Programmsemantik nicht ändert, nur die Beobachtbarkeit.


X.9.4 Philosophie: Tooling als Teil der Sicherheit

Jades Philosophie:

  • Kernsicherheit:
  • Von der Laufzeitumgebung erzwungen: Handles, Generationen, Policies, Ringe.
  • Verhindert Speicherkorruption und viele Klassen undefiniertem Verhaltens.

  • Leak-Prävention und Hygiene:

  • Vom Tooling unterstützt, nicht in das Kern-Typsystem eingebettet.
  • Betont:
    • Sichtbarkeit (was wird allokiert, wo, von wem),
    • Führung (welche Muster sind riskant, welche Alternativen existieren),
    • und Ergonomie (Entwickler sehen Probleme früh in ihrer IDE).

Das LSP und die zugehörigen Werkzeuge sind damit ein erstklassiger Teil von Jades Sicherheitsgeschichte — sie ergänzen Memory Engine und Effektsystem, ohne die Sprache zu einem vollständig linearen oder borrow-geprüften System zu machen.