Skip to content

Commit edc1ec7

Browse files
committed
feat(scan-log): Phase-Tracking + Crash-Trail im Outer-Handler
Problem: Crash AUSSERHALB des Detector-Try-Loops (z.B. Pre-Index, gAstFileCache.Evict im finally, gFileTextCache.Clear) erzeugte in der UI nur generisches "Analyseabbruch: <message>" ohne Hinweis welche Phase / welches File betroffen war. Wir mussten das heute Morgen per Hand mit DIAG-LogLines lokalisieren und hinterher revertieren. Fix: Zwei Lightweight-Variablen LastPhase + LastFile werden an Schluesselstellen aktualisiert (kostenlos im Happy-Path, kein Logging). Neuer Outer-Try in ParseLeaks faengt jede Non-EAbort- Exception, schreibt vor dem Re-Raise: === ABBRUCH: <ExceptionClass>: <Message> === === letzte Phase: <PhaseString> === === aktuelles File: <FileName> === und re-raised, damit der Caller-Outer-Handler sein SARIF-Finding wie bisher anlegt. EAbort (User-Cancel) wird durchgereicht. Getrackte Phasen: - init / Pre-Index: DfmRepoIndex.Build / Pre-Index: SymbolRefIndex.Build - Parser.Create + Main-Loop start - File N/M: pre-check / Parse/Acquire / AutoDiscovery / CustomRules / RunAllDetectors / finally Root.Free/Evict / finally gFileTextCache.Clear - Main-Loop fertig, post-process
1 parent ba12cf5 commit edc1ec7

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

SCA.Engine/sources/Infrastructure/uStaticAnalyzer2.pas

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,11 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
669669
LogStream : TStreamWriter;
670670
Watch : TStopwatch;
671671
ElapsedMs : Int64;
672+
// Phase-Tracking fuer Crash-Diagnose. Wird an Schluesselstellen aktualisiert
673+
// und im Outer-Handler bei "Analyseabbruch: ..." mit-geloggt. Variable-
674+
// Assignment ist quasi-kostenlos; kein Logging im Happy-Path.
675+
LastPhase : string;
676+
LastFile : string;
672677

673678
procedure LogLine(const S: string);
674679
begin
@@ -692,6 +697,8 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
692697
// Best-effort: kein ConfigDir = kein Log, der Scan laeuft trotzdem.
693698
end;
694699
Parser := nil;
700+
LastPhase := 'init';
701+
LastFile := '';
695702
// Eine gemeinsame try-finally klammer fuer LogStream UND Parser - so leakt
696703
// weder bei Parser-Create-OOM der LogStream noch umgekehrt.
697704
try
@@ -704,6 +711,12 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
704711
LogStream := nil;
705712
end;
706713

714+
// Outer-Diagnose-Try: bei Exception VOR re-raise die letzte Phase + das
715+
// aktuelle File ins Log schreiben. Sonst sieht der Caller-Outer-Handler
716+
// nur "Analyseabbruch: <Message>" ohne Kontext.
717+
try
718+
719+
707720
// AST-File-Cache: pro .pas einmal parsen, von beiden Pre-Indizes UND
708721
// dem Main-Loop wiederverwendet. Spart 2 von 3 Parser-Durchlaeufen pro
709722
// File (perf_analyse.md Hot-Spot 🅐).
@@ -731,6 +744,7 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
731744
var IndexFiles: TStringList := IndexFileList;
732745
if IndexFiles = nil then IndexFiles := FileList;
733746

747+
LastPhase := 'Pre-Index: DfmRepoIndex.Build';
734748
gDfmRepoIndex := TDfmRepoIndex.Create;
735749
try
736750
gDfmRepoIndex.Build(IndexFiles);
@@ -744,20 +758,24 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
744758
// hier dennoch aufgebaut weil andere Konsumenten (Tests, mORMot-Cross-
745759
// Check) ihn lesen; kann perspektivisch entfallen wenn keiner mehr
746760
// referenziert.
761+
LastPhase := 'Pre-Index: SymbolRefIndex.Build';
747762
gSymbolRefIndex := TSymbolReferenceIndex.Create;
748763
try
749764
gSymbolRefIndex.Build(IndexFiles);
750765
except
751766
FreeAndNil(gSymbolRefIndex);
752767
end;
753768

769+
LastPhase := 'Parser.Create + Main-Loop start';
754770
Parser := TParser2.Create;
755771
Total := FileList.Count;
756772
for i := 0 to Total - 1 do
757773
begin
758774
SafeProgress(i + 1, Total);
759775

760776
FileName := FileList[i];
777+
LastFile := FileName;
778+
LastPhase := Format('File %d/%d: pre-check', [i + 1, Total]);
761779

762780
// Leerer Dateiname → ignorieren (defensiv)
763781
if Trim(FileName) = '' then
@@ -811,6 +829,7 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
811829
// OwnsRoot=False wenn der Cache das Root besitzt (er freet es bei
812830
// Evict). True wenn lokal geparst -> nach AST-Verarbeitung selbst free.
813831
var OwnsRoot := False;
832+
LastPhase := Format('File %d/%d: Parse/Acquire', [i + 1, Total]);
814833
try
815834
try
816835
// Cache-Pfad bevorzugen: Pre-Indizes haben das Root schon erzeugt.
@@ -891,6 +910,7 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
891910
// Beide Gruppen respektieren LeakyClassExcludes. Die INI bleibt
892911
// unangetastet; der User entscheidet handisch welche Klasse er in
893912
// [Detectors] LeakyClasses uebernimmt.
913+
LastPhase := Format('File %d/%d: AutoDiscovery', [i + 1, Total]);
894914
if AutoDiscoverCustomClasses then
895915
begin
896916
var Instantiable : TArray<string>;
@@ -922,9 +942,11 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
922942
// Detektoren - so liegen sie im Output sortierbar zusammen.
923943
// No-op wenn TCustomRuleDetector.LoadFromYaml nicht aufgerufen
924944
// wurde (HasRules = False).
945+
LastPhase := Format('File %d/%d: CustomRules', [i + 1, Total]);
925946
if TCustomRuleDetector.HasRules then
926947
TCustomRuleDetector.AnalyzeFile(FileName, Results);
927948

949+
LastPhase := Format('File %d/%d: RunAllDetectors', [i + 1, Total]);
928950
RunAllDetectors(Root, FileName, Results, AIncludeUsesCheck,
929951
procedure(const Name: string; ElapsedMs: Int64)
930952
var
@@ -968,16 +990,42 @@ class procedure TStaticAnalyzer2.ParseLeaks(FileList: TStringList;
968990
CaptResults.Add(F);
969991
end);
970992
finally
993+
LastPhase := Format('File %d/%d: finally Root.Free/Evict', [i + 1, Total]);
971994
if OwnsRoot then
972995
Root.Free
973996
else if Assigned(gAstFileCache) then
974997
gAstFileCache.Evict(FileName); // Memory-Peak bremsen
998+
LastPhase := Format('File %d/%d: finally gFileTextCache.Clear', [i + 1, Total]);
975999
// Text-Cache fuer die abgearbeitete Datei freigeben - die File-Scan-
9761000
// Detektoren haben sie konsumiert, niemand brauchts mehr.
9771001
if Assigned(gFileTextCache) then
9781002
gFileTextCache.Clear;
9791003
end;
9801004
end;
1005+
LastPhase := 'Main-Loop fertig, post-process';
1006+
1007+
// Outer-Diagnose-Try Schliessung: bei Exception VOR re-raise die letzte
1008+
// Phase + das aktuelle File ins Log schreiben. So sieht der Caller-Outer-
1009+
// Handler (AnalyzeLeaksRecursive etc.) statt nur "Analyseabbruch: <msg>"
1010+
// im scan.log einen klaren Trail "letzte Phase: ... aktuelles File: ...".
1011+
except
1012+
on EAbort do raise;
1013+
on E: Exception do
1014+
begin
1015+
LogLine('');
1016+
LogLine(Format('=== ABBRUCH: %s: %s ===',
1017+
[E.ClassName, E.Message]));
1018+
LogLine(Format('=== letzte Phase: %s ===', [LastPhase]));
1019+
if LastFile <> '' then
1020+
LogLine(Format('=== aktuelles File: %s ===', [LastFile]))
1021+
else
1022+
LogLine('=== aktuelles File: (n/a - Crash vor Main-Loop) ===');
1023+
// Re-raise damit der Caller-Outer-Handler sein "Analyseabbruch: ..."
1024+
// SARIF/UI-Finding wie bisher anlegt. Im scan.log steht jetzt der
1025+
// Kontext direkt vor dieser Meldung.
1026+
raise;
1027+
end;
1028+
end;
9811029
finally
9821030
// LogStream auch bei EAbort sauber schliessen, danach Parser.
9831031
// Eine gemeinsame Klammer verhindert dass Parser-Create-OOM den LogStream

0 commit comments

Comments
 (0)