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>
11 KiB
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-Checksrc/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:
- Schrittweise
strcpy→strncpymit Bounds-Check (minimal-invasiv) - Langfristig:
char[N]→std::stringoderstd::array<char,N> 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:
class_steuer.cpp:73:catch(...)durch spezifische Exception-Typen ersetzen, Fehler propagieren statt schlucken- DB-Zugriffe in
class_paketlader.cppmittry/catchabsichern - 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:
- Phase 5 — Prädikate Batch 2: Weitere 10–15 standalone Prädikate mit
MockLeistungtesten (Pattern auspredicates_batch1_test.cppfortsetzen) - Phase 6 — Aktionen Batch 1: 10–15 Aktionen standalone testen (Pattern aus
actions_test.cpp) - Phase 7 — Domain-Klassen:
class_leistung,class_krankenschein,class_paketmit gemockter DB testen - Phase 8 — Methoden:
sb_meth,ur_methmit 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:
ur_meth.cpp: Nach Methodenfamilien aufteilen (ur_meth_ur01.cpp,ur_meth_ur21.cpp, etc.)class_leistung.cpp: Serialisierung, Flag-Berechnung, Wert-Operationen trennencost_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):
RuleEngineConfig-Struct definieren mit allen Maps als Member- Statt
extern Map* mapAlleGOPS→const Map& config.alleGOPS() - Bei
initRegelwerk()instanziieren, per Dependency Injection durchreichen - 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öschensrc/util/debug.cpp—_HPUX-Guards (Zeilen 20, 445) — entfernensrc/util/debug.cpp—__WIN32__-Guards (Zeilen 33, 230) — prüfen, ob noch benötigtsrc/aktsource_dummies/— leeres Verzeichnis — löschen
15. Überflüssige Build-Artefakte
RW_DLL-Define in CMakeLists.txt +#ifdef RW_DLLinsrc/core/rw_implementation.cpp— DLL wird nicht mehr gebaut, Guard ist tot-fdelayed-template-parsingund-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=threadbauen und Testsuite laufen lassen - Speichersicherheit: Nach Phase A mit
-fsanitize=addressbauen und Integrationstest laufen lassen