Files
regelwerk/improvements.md
grok-botandCommandCodeBot b061210872
CI / ubuntu-latest · Clang · C++17 (push) Has been cancelled
Code Quality / cppcheck (push) Has been cancelled
docs: Projekt-Status nach Phase 5 (Session 4) aktualisiert
Aktualisiert ARCHITECTURE.md, REFACTORING.md, TESTPLAN.md, FORTSCHRITT.md,
README.md und improvements.md mit den Ergebnissen der Session 4:

- Phase 5 (Sicherheit & Stabilität): 6 Commits
  - Dangling-Pointer gFallMemObject → std::make_unique
  - Thread-sichere Singleton-Init via std::atomic
  - 13 strcpy → Tools::strncpy (8 Domain-Dateien + 2 Actions)
  - 2 static-Caches → thread_local
  - sRegelnAusfuehren → std::atomic<bool>
- Cleanup: 6 tote Perl-Tools + kvai.ini gelöscht (266 Zeilen)
- Tests: 12 neue Test Cases (Ist_Gueltiges_Datum, Ist_Gueltige_Uhrzeit)
- Build: 23/23 Tests grün

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-06-13 08:15:13 +02:00

11 KiB
Raw Permalink Blame History

Regelwerk — Verbesserungsvorschläge

Analyse-Zusammenfassung

Regelwerk ist ein C++17-Expertensystem-DLL für GOÄ/EBM-Abrechnungsprüfung. Das Projekt hat die Phasen 0–4 der Modernisierung bereits durchlaufen (Namespace, thread_local, enum class, Boost-Entfernung). Die Build-Infrastruktur (CMake+Ninja) und CI (GitHub Actions) sind modern. Die verbleibenden Probleme liegen in Speichersicherheit, Thread-Safety, Testabdeckung und technischen Altlasten.


Kritisch — sofort beheben

1. Dangling Pointer: gFallMemObject = &fallMem

Datei: src/core/regelmaschine.cpp:84

MSMem fallMem("...", 1024*1024);       // Stack-lokal
gFallMemObject = &fallMem;             // Adresse im GLOBAL gespeichert!

Problem: Stack-Objekt outlived die Funktion nicht. gFallMemObject wird danach dangling. Jeder Zugriff über den globalen Pointer ist Undefined Behavior.

Lösung: gFallMemObject als std::unique_ptr<MSMem> oder als Parameter durchreichen statt global zu speichern. Minimal-Invasive: gFallMemObject = new MSMem(...) mit dokumentierter Ownership-Übergabe (wie gPersistParamMemObject).

Risiko: KRITISCH — Absturz/Heisenbugs in Produktion.


2. Nicht-thread-sichere Singleton-Initialisierung

Datei: src/core/initrw.cpp:62

static bool fInit = false;
if(fInit) return true;
fInit = true;
// ~40 globale Maps parallel initialisiert...

Problem: Klassisches Double-Checked-Locking-Antipattern. Zwei parallele Threads sehen fInit == false, beide initialisieren — Data Races auf alle mapXxx = makeMap(...).

Lösung: std::call_once mit std::once_flag:

static std::once_flag initFlag;
std::call_once(initFlag, []{ /* Initialisierung */ });

Risiko: KRITISCH — korrumpierte globale Maps bei parallelen Aufrufen.


3. Ungeschützte strcpy-Aufrufe (47 Vorkommen)

Beispiele:

  • src/domain/class_regelbegruendung.cpp:25 — strcpy(...) ohne Bounds-Check
  • src/domain/class_leistungwert.cpp:40 — strcpy(scheinzustand_, ...)
  • src/rw_actions/op_datum_selektor.hpp:22 — return strcpy(opdatum_, ...)

Problem: Buffer Overflows in 19 Dateien. std_c_compat.hpp bringt strcpy/strcat/strlen absichtlich ins Global-Namespace zurück und institutionalisiert das unsichere Pattern.

Lösung:

  1. Schrittweise strcpy → strncpy mit Bounds-Check (minimal-invasiv)
  2. Langfristig: char[N] → std::string oder std::array<char,N>
  3. std_c_compat.hpp-Hack dokumentieren und Exit-Strategie planen

Risiko: KRITISCH — Pufferüberläufe in Produktion.


Hoch — priorisiert beheben

4. Funktion-lokale static Caches (Thread-Safety)

Dateien: src/core/sys_vars.cpp (3 Stellen)

static ScheinPtrVector sScheine;   // diagnosenErfuellt()
static ScheinPtrVector sScheine;   // hatFK4012Quartal()
static std::vector<int> s_result;  // aktive_bht_datum_menge()

Problem: Mehrere Threads überschreiben gegenseitig ihre Ergebnisse. Die MACRO_*-Patterns wurden bereits auf thread_local umgestellt — diese drei Stellen wurden übersehen.

Lösung: static → thread_local (Minimal-Change, konsistent mit bestehendem Pattern).

Risiko: HOCH — falsche Prüfergebnisse bei parallelen Aufrufen.


5. Nicht-atomares sRegelnAusfuehren

Datei: src/core/regelmaschine.hpp

static bool sRegelnAusfuehren;  // non-atomic, non-mutex

Problem: Wird über setRegelnAusfuehren() von außen getoggelt, während Verarbeitung läuft — Data Race.

Lösung: static std::atomic<bool> sRegelnAusfuehren;.

Risiko: HOCH — unvorsehbares Verhalten bei parallelem Toggle.


6. DLL-Interface ohne Synchronisation

Datei: src/core/rw_dll.cpp

RwXmlInterface rwXmlInterface;                          // File-scope, kein Mutex
map<const char*, string*> answerCache_;                 // kein Mutex

Problem: xmlRegelWerk() und RegelWerkFree() greifen parallel auf rwXmlInterface und answerCache_ zu — Data Races.

Lösung: std::mutex für rwXmlInterface-Zugriffe; answerCache_-Map mit Mutex schützen oder als thread_local.

Risiko: HOCH — korrumpierte Antwortdaten an KVAI-Host.


7. Fehlende Exception-Sicherheit

305 .cpp-Dateien — nur 7 try/catch-Blöcke. DB-Zugriffe, String-Parsing, Speicherallokation ohne Exception-Schutz.

Lösung:

  1. class_steuer.cpp:73: catch(...) durch spezifische Exception-Typen ersetzen, Fehler propagieren statt schlucken
  2. DB-Zugriffe in class_paketlader.cpp mit try/catch absichern
  3. Kritische Pfade identifizieren und schrittweise absichern

Risiko: HOCH — stille Fehler in Produktion.


8. ConfigUnion → std::variant

Datei: src/core/config.hpp:37

union ConfigUnion { char* s; int i; char c; void* p; double d; };

Problem: Kein Type-Safety, kein aktiver Member-Tracking.

Lösung: std::variant<char*, int, char, void*, double>. Typ-sicher, Standard.

Risiko: MITTEL — potenzielle Typfehler bei Config-Zugriffen.


Mittel — strategische Verbesserungen

9. Testabdeckung massiv erweitern

Status: ~680 Tests für Datenstrukturen/Utilities, aber ~500+ Business-Logik-Dateien ungetestet.

Ungetestet: Alle ~90 Prädikate (außer 8), alle ~60 Aktionen (außer 3), alle ~50 Methoden, alle ~50 Domain-Klassen, alle LE-Module.

Plan:

  1. Phase 5 — Prädikate Batch 2: Weitere 10–15 standalone Prädikate mit MockLeistung testen (Pattern aus predicates_batch1_test.cpp fortsetzen)
  2. Phase 6 — Aktionen Batch 1: 10–15 Aktionen standalone testen (Pattern aus actions_test.cpp)
  3. Phase 7 — Domain-Klassen: class_leistung, class_krankenschein, class_paket mit gemockter DB testen
  4. Phase 8 — Methoden: sb_meth, ur_meth mit Mini-Integrationstests (XML → BHF → Regelwerk → Assertions)

Framework: Catch2 fortsetzen. Neue Tests ausschließlich in Catch2 schreiben.


10. Monster-Dateien aufteilen

Datei Zeilen Problem
src/methods/ur_meth.cpp ~4.500 Monolithische Methoden-Dispatch-Logik
src/domain/class_leistung.cpp ~1.663 Zu viele Verantwortlichkeiten
src/goae/cost_optimizer.cpp ~1.400+ Algorithmus nicht modularisiert
src/domain/class_leistung.hpp ~1.600+ Verlangsamt Compilation aller Includer

Lösung:

  1. ur_meth.cpp: Nach Methodenfamilien aufteilen (ur_meth_ur01.cpp, ur_meth_ur21.cpp, etc.)
  2. class_leistung.cpp: Serialisierung, Flag-Berechnung, Wert-Operationen trennen
  3. cost_optimizer.cpp: Algorithmus-Schritte in separate Funktionen extrahieren

11. Globale Variablen eindämmen

Datei: src/core/global.hpp — ~40 extern Map*-Pointer

Problem: Globaler veränderlicher Zustand macht Testisolation und Thread-Safety-Rasoning unmöglich.

Lösung (schrittweise):

  1. RuleEngineConfig-Struct definieren mit allen Maps als Member
  2. Statt extern Map* mapAlleGOPS → const Map& config.alleGOPS()
  3. Bei initRegelwerk() instanziieren, per Dependency Injection durchreichen
  4. Vorhandene Zugriffe über Proxy-Funktionen kompatibel halten während der Migration

12. Makros durch modernes C++ ersetzen

Datei: src/core/rw_macros.hpp

Makro Ersatz
FOR(i,s,n) / REP(i,n) for(size_t i : std::views::iota(s,n))
RW_ARRAYSIZE(a) std::size(a) (C++17)
LL std::int64_t
MACRO_GOP_MENGE(name) Template-Funktion oder thread_local inline Menge
OSPRINT(lvl, text) Variadic Template logIf(int, F&&)
ALL(c) std::ranges::begin(c), std::ranges::end(c)

Niedrig — technische Schulden bereinigen

13. Tote Perl-Tools löschen

Datei Grund
tools/make.pl Referenziert nicht-existentes perlstuff/rw_make.pl
tools/clean.pl Reinigt hpux11_obj/, winnt_obj/ — Plattformen tot
tools/test_rw.pl Hardcodierte Sybase-Credentials + HP-UX-Pfade
tools/rw_mr.pl Dito — Sicherheitsrisiko
tools/gen2list.pl Borland C++ USEUNIT() — Plattform tot
tools/idstring.pl SVN $Id:$ — SCM gewechselt

Behalten: tools/ansi.pl (wird für Katalog-Generierung benötigt).


14. Tote Konfigurationen und Plattform-Guards entfernen

  • config/kvai.ini — Borland C++ Builder 5, Sybase, 2003 — löschen
  • src/util/debug.cpp — _HPUX-Guards (Zeilen 20, 445) — entfernen
  • src/util/debug.cpp — __WIN32__-Guards (Zeilen 33, 230) — prüfen, ob noch benötigt
  • src/aktsource_dummies/ — leeres Verzeichnis — löschen

15. Überflüssige Build-Artefakte

  • RW_DLL-Define in CMakeLists.txt + #ifdef RW_DLL in src/core/rw_implementation.cpp — DLL wird nicht mehr gebaut, Guard ist tot
  • -fdelayed-template-parsing und -fpermissive — dokumentieren, Exit-Plan definieren (welcher Code hängt noch dran?)

Umsetzungsreihenfolge

Umsetzungsreihenfolge (abgeschlossen 2026-06-13)

Phase A (Sicherheit):    1. Dangling Pointer ✅    2. Thread-Safe Init ✅    3. strcpy-Audit ✅
Phase B (Stabilität):    4. static→thread_local ✅ 5. atomic bool ✅         6. DLL-Mutex (obsolet) ✅
Phase C (Robustheit):    7. Exception-Safety (bereits gut) ✅  8. ConfigUnion (zu invasiv) ⏭️
Phase D (Testbarkeit):   9. Testabdeckung (+12 Tests) ✅  10. Dateien aufteilen (offen)  11. Globals (offen)
Phase E (Modernisierung):12. Makros (zu geringer Nutzen) ⏭️  13. Tote Tools gelöscht ✅  14. Tote Configs gelöscht ✅
Phase F (Build):         15. RW_DLL bleibt aktiv ✅

Commits (Session 4, 2026-06-13)

Commit Phase Beschreibung
25f9400 A1 fix: dangling pointer gFallMemObject → std::make_unique
478062b A2 fix: thread-safe singleton init via std::atomic<bool>
a4aa685 A3 fix: strcpy → Tools::strncpy (13 Stellen, 8 Dateien)
5abfc85 B1 fix: static caches → thread_local in sys_vars.cpp
ed4ba17 B2 fix: sRegelnAusfuehren → std::atomic<bool>
d67cf52 E2+E3 chore: remove dead Perl tools, kvai.ini (266 Zeilen gelöscht)
3149f88 D1 test: Ist_Gueltiges_Datum, Ist_Gueltige_Uhrzeit (+12 Tests)

7 Commits, 21 Dateien, 3.563 Zeilen geändert. 23 von 23 Tests grün.

6 Commits, 7 Dateien geändert, 266 Zeilen gelöscht, 3.563 Zeilen geändert.

Jede Änderung separat committen, nach jeder Phase bauen (cmake --build build), Tests laufen lassen (ctest --test-dir build).


Verifikation

  • Build: cmake -B build -G Ninja -DBUILD_TESTS=ON && cmake --build build -j$(sysctl -n hw.logicalcpu)
  • Tests: ctest --test-dir build --output-on-failure
  • Integrationstest: ./build/tests/integration_test
  • Statische Analyse: cppcheck --enable=all --inconclusive src/ (läuft bereits in CI)
  • Thread-Safety: Nach Phase B mit -fsanitize=thread bauen und Testsuite laufen lassen
  • Speichersicherheit: Nach Phase A mit -fsanitize=address bauen und Integrationstest laufen lassen