From 26505a87bfc6391a354566064ead7e16a67fa70d Mon Sep 17 00:00:00 2001 From: Andrei Kurushin Date: Fri, 18 Sep 2026 16:12:50 +0300 Subject: [PATCH 1/2] guard optional clientInfo in ShowConfigStatus clientInfo is optional in the LSP spec and TClientInfo.version is not auto-created; accessing them without a check crashed the server on initialize from minimal clients that omit clientInfo. --- src/serverprotocol/PasLS.General.pas | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/serverprotocol/PasLS.General.pas b/src/serverprotocol/PasLS.General.pas index 606f1d5..daa3dd8 100644 --- a/src/serverprotocol/PasLS.General.pas +++ b/src/serverprotocol/PasLS.General.pas @@ -252,13 +252,19 @@ procedure TInitialize.ShowConfigStatus(Params: TInitializeParams; CodeToolsOptio I: Integer; FPCOptions: TStringArray; begin - if Params.clientInfo.version.HasValue then + // clientInfo is optional in the LSP spec and TClientInfo.version is + // not auto-created, so both must be checked before use + if (Params.clientInfo <> nil) and (Params.clientInfo.version <> nil) + and Params.clientInfo.version.HasValue then clientInfoVersion := ' ' + Params.clientInfo.version.Value else clientInfoVersion := ''; DoLog(kStatusPrefix+'Server: ' + {$INCLUDE %DATE%}); - DoLog(kStatusPrefix+'Client: ' + Params.clientInfo.name + clientInfoVersion); + if Params.clientInfo <> nil then + DoLog(kStatusPrefix+'Client: ' + Params.clientInfo.name + clientInfoVersion) + else + DoLog(kStatusPrefix+'Client: [unspecified]'); DoLog(kStatusPrefix+'FPCPath: ' + CodeToolsOptions.FPCPath); DoLog(kStatusPrefix+'FPCSrcDir: ' + CodeToolsOptions.FPCSrcDir); From 68026218851a641f1301f5106b1fd8fa4e0a2a71 Mon Sep 17 00:00:00 2001 From: Andrei Kurushin Date: Fri, 18 Sep 2026 16:12:50 +0300 Subject: [PATCH 2/2] fix empty filename in diagnostics for scanner errors In ParseSource the except branch passed the filename through "with FParser.CurSourcePos do", whose FileName field is empty for scanner errors (e.g. include file not found, fired before the parser advanced). The empty record field shadowed the filled local Filename, so the diagnostic was published with a broken "file:" URI and clients dropped it - the file looked like it never got any diagnostics. - ParseSource except: drop the with-statement, pass the local Filename - DoError: fall back to the buffer filename when CurSourcePos.FileName is empty (same scanner-error case) --- src/serverprotocol/PasLS.Parser.pas | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/serverprotocol/PasLS.Parser.pas b/src/serverprotocol/PasLS.Parser.pas index 9a51365..bbb8e8b 100644 --- a/src/serverprotocol/PasLS.Parser.pas +++ b/src/serverprotocol/PasLS.Parser.pas @@ -125,7 +125,15 @@ procedure TSourceParser.DoError(Sender: TObject; else aCode:=-1; With FParser.CurSourcePos do - FOnError(Self,aContext.Error.Message,FileName,aCode, Row,Column); + begin + // scanner errors (e.g. include file not found) fire before the parser + // advanced, CurSourcePos is still empty (Line=0, Column=0, FileName='') - + // fall back to the buffer filename so the diagnostic keeps a valid URI + if FileName='' then + FOnError(Self,aContext.Error.Message,FCode.FileName,aCode, Row,Column) + else + FOnError(Self,aContext.Error.Message,FileName,aCode, Row,Column); + end; end; end; {$ENDIF} @@ -274,15 +282,18 @@ function TSourceParser.ParseSource: TPasModule; begin FreeAndNil(Result); if Assigned(FOnError) then - With FParser.CurSourcePos do - begin - aCode:=-1; - {$IFDEF MULTIERROR} - if E is EParserError then - aCode:=EParserError(E).ErrNo; - {$ENDIF} - FOnError(Self,E.Message,FileName,aCode,Row,Column); - end; + begin + aCode:=-1; + {$IFDEF MULTIERROR} + if E is EParserError then + aCode:=EParserError(E).ErrNo; + {$ENDIF} + // note: no "with FParser.CurSourcePos do" here - its FileName field is + // empty for scanner errors (e.g. include file not found) and would + // shadow the local Filename, producing diagnostics with a broken URI + FOnError(Self,E.Message,Filename,aCode, + FParser.CurSourcePos.Row,FParser.CurSourcePos.Column); + end; end; end; finally