mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-12-03 21:07:35 +01:00
FPDebug: starting error msgs
git-svn-id: trunk@44196 -
This commit is contained in:
parent
7e70fbf99b
commit
da31678f3b
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1270,6 +1270,7 @@ components/fpdebug/fpdbgwinextra.pp svneol=native#text/pascal
|
|||||||
components/fpdebug/fpdebug.lpk svneol=native#text/pascal
|
components/fpdebug/fpdebug.lpk svneol=native#text/pascal
|
||||||
components/fpdebug/fpdebug.pas svneol=native#text/pascal
|
components/fpdebug/fpdebug.pas svneol=native#text/pascal
|
||||||
components/fpdebug/fpdmemorytools.pas svneol=native#text/pascal
|
components/fpdebug/fpdmemorytools.pas svneol=native#text/pascal
|
||||||
|
components/fpdebug/fperrormessages.pas svneol=native#text/pascal
|
||||||
components/fpdebug/fpimgreaderbase.pas svneol=native#text/pascal
|
components/fpdebug/fpimgreaderbase.pas svneol=native#text/pascal
|
||||||
components/fpdebug/fpimgreaderelf.pas svneol=native#text/pascal
|
components/fpdebug/fpimgreaderelf.pas svneol=native#text/pascal
|
||||||
components/fpdebug/fpimgreaderelftypes.pas svneol=native#text/pascal
|
components/fpdebug/fpimgreaderelftypes.pas svneol=native#text/pascal
|
||||||
|
|||||||
@ -34,7 +34,7 @@ File(s) with other licenses (see also header in file(s):
|
|||||||
|
|
||||||
(Any modifications/translations of this file are from duby)
|
(Any modifications/translations of this file are from duby)
|
||||||
"/>
|
"/>
|
||||||
<Files Count="21">
|
<Files Count="22">
|
||||||
<Item1>
|
<Item1>
|
||||||
<Filename Value="fpdbgclasses.pp"/>
|
<Filename Value="fpdbgclasses.pp"/>
|
||||||
<UnitName Value="FpDbgClasses"/>
|
<UnitName Value="FpDbgClasses"/>
|
||||||
@ -120,6 +120,10 @@ File(s) with other licenses (see also header in file(s):
|
|||||||
<Filename Value="fpdmemorytools.pas"/>
|
<Filename Value="fpdmemorytools.pas"/>
|
||||||
<UnitName Value="FpdMemoryTools"/>
|
<UnitName Value="FpdMemoryTools"/>
|
||||||
</Item21>
|
</Item21>
|
||||||
|
<Item22>
|
||||||
|
<Filename Value="fperrormessages.pas"/>
|
||||||
|
<UnitName Value="fperrormessages"/>
|
||||||
|
</Item22>
|
||||||
</Files>
|
</Files>
|
||||||
<Type Value="RunAndDesignTime"/>
|
<Type Value="RunAndDesignTime"/>
|
||||||
<RequiredPkgs Count="3">
|
<RequiredPkgs Count="3">
|
||||||
|
|||||||
@ -10,7 +10,8 @@ uses
|
|||||||
FpDbgClasses, FpDbgDisasX86, FpDbgDwarf, FpDbgDwarfConst, FpDbgLoader, FpDbgPETypes,
|
FpDbgClasses, FpDbgDisasX86, FpDbgDwarf, FpDbgDwarfConst, FpDbgLoader, FpDbgPETypes,
|
||||||
FpDbgSymbols, FpDbgUtil, FpDbgWinExtra, FpImgReaderWinPE, FpImgReaderElf,
|
FpDbgSymbols, FpDbgUtil, FpDbgWinExtra, FpImgReaderWinPE, FpImgReaderElf,
|
||||||
FpImgReaderElfTypes, FpImgReaderBase, FpPascalParser, macho, FpImgReaderMachoFile,
|
FpImgReaderElfTypes, FpImgReaderBase, FpPascalParser, macho, FpImgReaderMachoFile,
|
||||||
FpImgReaderMacho, FpPascalBuilder, FpDbgInfo, FpdMemoryTools, LazarusPackageIntf;
|
FpImgReaderMacho, FpPascalBuilder, FpDbgInfo, FpdMemoryTools, FpErrorMessages,
|
||||||
|
LazarusPackageIntf;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
|||||||
162
components/fpdebug/fperrormessages.pas
Normal file
162
components/fpdebug/fperrormessages.pas
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
unit FpErrorMessages;
|
||||||
|
|
||||||
|
{$mode objfpc}{$H+}
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
SysUtils, LazLoggerBase;
|
||||||
|
|
||||||
|
type
|
||||||
|
TFpErrorCode = Integer;
|
||||||
|
|
||||||
|
resourcestring
|
||||||
|
// %0:s is always linebreak
|
||||||
|
MsgfpErrAnyError = '%1:s';
|
||||||
|
MsgfpErrSymbolNotFound = 'Identifier not found: "%1:s"';
|
||||||
|
MsgfpErrNoMemberWithName = 'Member not found: %1:s';
|
||||||
|
|
||||||
|
const
|
||||||
|
fpErrAnyError = TFpErrorCode(1);
|
||||||
|
fpErrSymbolNotFound = TFpErrorCode(2);
|
||||||
|
fpErrNoMemberWithName = TFpErrorCode(3);
|
||||||
|
|
||||||
|
type
|
||||||
|
|
||||||
|
TFpError = record
|
||||||
|
ErrorCode: TFpErrorCode;
|
||||||
|
ErrorData: Array of TVarRec;
|
||||||
|
ErrorData2: Array of String;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TErrorHandler }
|
||||||
|
|
||||||
|
{ TFpErrorHandler }
|
||||||
|
|
||||||
|
TFpErrorHandler = class
|
||||||
|
protected
|
||||||
|
function GetErrorRawString(AnErrorCode: TFpErrorCode): string;
|
||||||
|
public
|
||||||
|
function CreateError(AnErrorCode: TFpErrorCode; AData: array of const): TFpError;
|
||||||
|
function CreateError(AnErrorCode: TFpErrorCode; AnError: TFpError; AData: array of const): TFpError;
|
||||||
|
function ErrorAsString(AnError: TFpError): string;
|
||||||
|
function ErrorAsString(AnErrorCode: TFpErrorCode; AData: array of const): string;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetFpErrorHandler: TFpErrorHandler;
|
||||||
|
procedure SetFpErrorHandler(AHandler: TFpErrorHandler);
|
||||||
|
|
||||||
|
property FpErrorHandler: TFpErrorHandler read GetFpErrorHandler write SetFpErrorHandler;
|
||||||
|
|
||||||
|
function IsFpError(AnError: TFpError): Boolean;
|
||||||
|
function FpErrorNone: TFpError;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
var TheErrorHandler: TFpErrorHandler = nil;
|
||||||
|
|
||||||
|
function GetFpErrorHandler: TFpErrorHandler;
|
||||||
|
begin
|
||||||
|
if TheErrorHandler = nil then
|
||||||
|
TheErrorHandler := TFpErrorHandler.Create;
|
||||||
|
Result := TheErrorHandler;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure SetFpErrorHandler(AHandler: TFpErrorHandler);
|
||||||
|
begin
|
||||||
|
FreeAndNil(TheErrorHandler);
|
||||||
|
TheErrorHandler := AHandler;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function IsFpError(AnError: TFpError): Boolean;
|
||||||
|
begin
|
||||||
|
Result := AnError.ErrorCode <> 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function FpErrorNone: TFpError;
|
||||||
|
begin
|
||||||
|
Result.ErrorCode := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ TFpErrorHandler }
|
||||||
|
|
||||||
|
function TFpErrorHandler.GetErrorRawString(AnErrorCode: TFpErrorCode): string;
|
||||||
|
begin
|
||||||
|
case AnErrorCode of
|
||||||
|
fpErrAnyError: Result := MsgfpErrAnyError;
|
||||||
|
fpErrSymbolNotFound: Result := MsgfpErrSymbolNotFound;
|
||||||
|
fpErrNoMemberWithName: Result := MsgfpErrNoMemberWithName;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TFpErrorHandler.CreateError(AnErrorCode: TFpErrorCode;
|
||||||
|
AData: array of const): TFpError;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Result.ErrorCode := AnErrorCode;
|
||||||
|
SetLength(Result.ErrorData, Length(AData));
|
||||||
|
SetLength(Result.ErrorData2, Length(AData));
|
||||||
|
for i := low(AData) to high(AData) do begin
|
||||||
|
Result.ErrorData[i] := AData[i];
|
||||||
|
if AData[i].VType = vtAnsiString then begin
|
||||||
|
Result.ErrorData2[i] := AnsiString(AData[i].VAnsiString);
|
||||||
|
Result.ErrorData[i].VAnsiString := Pointer(Result.ErrorData2[i]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TFpErrorHandler.CreateError(AnErrorCode: TFpErrorCode; AnError: TFpError;
|
||||||
|
AData: array of const): TFpError;
|
||||||
|
var
|
||||||
|
i, j: Integer;
|
||||||
|
begin
|
||||||
|
Result.ErrorCode := AnErrorCode;
|
||||||
|
j := Length(AnError.ErrorData);
|
||||||
|
SetLength(Result.ErrorData, Length(AData) + j);
|
||||||
|
SetLength(Result.ErrorData2, Length(AData) + j);
|
||||||
|
for i := 0 to j - 1 do begin
|
||||||
|
Result.ErrorData2[i] := AnError.ErrorData2[i];
|
||||||
|
Result.ErrorData[i] := AnError.ErrorData[i];
|
||||||
|
end;
|
||||||
|
for i := low(AData) to high(AData) do begin
|
||||||
|
Result.ErrorData[j+i] := AData[i];
|
||||||
|
if AData[i].VType = vtAnsiString then begin
|
||||||
|
Result.ErrorData2[j+i] := AnsiString(AData[i].VAnsiString);
|
||||||
|
Result.ErrorData[j+i].VAnsiString := Pointer(Result.ErrorData2[j+i]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TFpErrorHandler.ErrorAsString(AnError: TFpError): string;
|
||||||
|
var
|
||||||
|
RealData: Array of TVarRec;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Result := ErrorAsString(AnError.ErrorCode, AnError.ErrorData);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TFpErrorHandler.ErrorAsString(AnErrorCode: TFpErrorCode;
|
||||||
|
AData: array of const): string;
|
||||||
|
var
|
||||||
|
RealData: Array of TVarRec;
|
||||||
|
i: Integer;
|
||||||
|
s: String;
|
||||||
|
begin
|
||||||
|
Result := '';
|
||||||
|
if AnErrorCode = 0 then exit;
|
||||||
|
SetLength(RealData, Length(AData) + 1);
|
||||||
|
s := LineEnding;
|
||||||
|
RealData[0].VAnsiString := Pointer(s); // first arg is always line end
|
||||||
|
for i := 0 to Length(AData) - 1 do
|
||||||
|
RealData[i + 1] := AData[i];
|
||||||
|
s := GetErrorRawString(AnErrorCode);
|
||||||
|
if s = '' then s := 'Internal Error: ' + IntToStr(AnErrorCode);
|
||||||
|
Result := Format(s, RealData);
|
||||||
|
end;
|
||||||
|
|
||||||
|
finalization
|
||||||
|
FreeAndNil(TheErrorHandler);
|
||||||
|
|
||||||
|
end.
|
||||||
|
|
||||||
@ -29,7 +29,8 @@ unit FpPascalParser;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, sysutils, math, DbgIntfBaseTypes, FpDbgInfo, FpdMemoryTools, LazLoggerBase, LazClasses;
|
Classes, sysutils, math, DbgIntfBaseTypes, FpDbgInfo, FpdMemoryTools, FpErrorMessages,
|
||||||
|
LazLoggerBase, LazClasses;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
@ -46,14 +47,15 @@ type
|
|||||||
|
|
||||||
TFpPascalExpression = class
|
TFpPascalExpression = class
|
||||||
private
|
private
|
||||||
FError: String;
|
FError: TFpError;
|
||||||
FContext: TDbgInfoAddressContext;
|
FContext: TDbgInfoAddressContext;
|
||||||
FTextExpression: String;
|
FTextExpression: String;
|
||||||
FExpressionPart: TFpPascalExpressionPart;
|
FExpressionPart: TFpPascalExpressionPart;
|
||||||
FValid: Boolean;
|
FValid: Boolean;
|
||||||
function GetResultValue: TDbgSymbolValue;
|
function GetResultValue: TDbgSymbolValue;
|
||||||
procedure Parse;
|
procedure Parse;
|
||||||
procedure SetError(AMsg: String);
|
procedure SetError(AMsg: String); // deprecated;
|
||||||
|
procedure SetError(AnErrorCode: TFpErrorCode; AData: array of const);
|
||||||
function PosFromPChar(APChar: PChar): Integer;
|
function PosFromPChar(APChar: PChar): Integer;
|
||||||
protected
|
protected
|
||||||
function GetDbgSymbolForIdentifier({%H-}AnIdent: String): TDbgSymbol;
|
function GetDbgSymbolForIdentifier({%H-}AnIdent: String): TDbgSymbol;
|
||||||
@ -63,7 +65,7 @@ type
|
|||||||
constructor Create(ATextExpression: String; AContext: TDbgInfoAddressContext);
|
constructor Create(ATextExpression: String; AContext: TDbgInfoAddressContext);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
function DebugDump(AWithResults: Boolean = False): String;
|
function DebugDump(AWithResults: Boolean = False): String;
|
||||||
property Error: String read FError;
|
property Error: TFpError read FError;
|
||||||
property Valid: Boolean read FValid;
|
property Valid: Boolean read FValid;
|
||||||
// ResultValue
|
// ResultValue
|
||||||
// - May be a type, if expression is a type
|
// - May be a type, if expression is a type
|
||||||
@ -88,8 +90,9 @@ type
|
|||||||
procedure SetEndChar(AValue: PChar);
|
procedure SetEndChar(AValue: PChar);
|
||||||
procedure SetParent(AValue: TFpPascalExpressionPartContainer);
|
procedure SetParent(AValue: TFpPascalExpressionPartContainer);
|
||||||
procedure SetStartChar(AValue: PChar);
|
procedure SetStartChar(AValue: PChar);
|
||||||
procedure SetError(AMsg: String = '');
|
procedure SetError(AMsg: String = ''); // deprecated;
|
||||||
procedure SetError(APart: TFpPascalExpressionPart; AMsg: String = '');
|
procedure SetError(APart: TFpPascalExpressionPart; AMsg: String = ''); // deprecated;
|
||||||
|
procedure SetError(AnErrorCode: TFpErrorCode; AData: array of const);
|
||||||
protected
|
protected
|
||||||
function DebugText(AIndent: String; {%H-}AWithResults: Boolean): String; virtual; // Self desc only
|
function DebugText(AIndent: String; {%H-}AWithResults: Boolean): String; virtual; // Self desc only
|
||||||
function DebugDump(AIndent: String; AWithResults: Boolean): String; virtual;
|
function DebugDump(AIndent: String; AWithResults: Boolean): String; virtual;
|
||||||
@ -1149,8 +1152,10 @@ var
|
|||||||
begin
|
begin
|
||||||
Result := nil;
|
Result := nil;
|
||||||
DbgSymbol := FExpression.GetDbgSymbolForIdentifier(GetText);
|
DbgSymbol := FExpression.GetDbgSymbolForIdentifier(GetText);
|
||||||
if DbgSymbol = nil then
|
if DbgSymbol = nil then begin
|
||||||
|
SetError(fpErrSymbolNotFound, [GetText]);
|
||||||
exit;
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
Result := DbgSymbol.Value;
|
Result := DbgSymbol.Value;
|
||||||
if Result = nil then begin
|
if Result = nil then begin
|
||||||
@ -1352,11 +1357,21 @@ end;
|
|||||||
|
|
||||||
procedure TFpPascalExpression.SetError(AMsg: String);
|
procedure TFpPascalExpression.SetError(AMsg: String);
|
||||||
begin
|
begin
|
||||||
|
if FError.ErrorCode <> 0 then begin
|
||||||
|
DebugLn(['Skipping error ', AMsg]);
|
||||||
FValid := False;
|
FValid := False;
|
||||||
FError := AMsg;
|
exit;
|
||||||
|
end;
|
||||||
|
SetError(fpErrAnyError, [AMsg]);
|
||||||
DebugLn(['PARSER ERROR ', AMsg]);
|
DebugLn(['PARSER ERROR ', AMsg]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TFpPascalExpression.SetError(AnErrorCode: TFpErrorCode; AData: array of const);
|
||||||
|
begin
|
||||||
|
FValid := False;
|
||||||
|
FError := FpErrorHandler.CreateError(AnErrorCode, AData);
|
||||||
|
end;
|
||||||
|
|
||||||
function TFpPascalExpression.PosFromPChar(APChar: PChar): Integer;
|
function TFpPascalExpression.PosFromPChar(APChar: PChar): Integer;
|
||||||
begin
|
begin
|
||||||
Result := APChar - @FTextExpression[1] + 1;
|
Result := APChar - @FTextExpression[1] + 1;
|
||||||
@ -1375,6 +1390,7 @@ constructor TFpPascalExpression.Create(ATextExpression: String;
|
|||||||
begin
|
begin
|
||||||
FContext := AContext;
|
FContext := AContext;
|
||||||
FTextExpression := ATextExpression;
|
FTextExpression := ATextExpression;
|
||||||
|
FError := FpErrorNone;
|
||||||
FValid := True;
|
FValid := True;
|
||||||
Parse;
|
Parse;
|
||||||
end;
|
end;
|
||||||
@ -1388,7 +1404,7 @@ end;
|
|||||||
function TFpPascalExpression.DebugDump(AWithResults: Boolean): String;
|
function TFpPascalExpression.DebugDump(AWithResults: Boolean): String;
|
||||||
begin
|
begin
|
||||||
Result := 'TFpPascalExpression: ' + FTextExpression + LineEnding +
|
Result := 'TFpPascalExpression: ' + FTextExpression + LineEnding +
|
||||||
'Valid: ' + dbgs(FValid) + ' Error: "' + FError + '"'+ LineEnding
|
'Valid: ' + dbgs(FValid) + ' Error: "' + dbgs(FError.ErrorCode) + '"'+ LineEnding
|
||||||
;
|
;
|
||||||
if FExpressionPart <> nil then
|
if FExpressionPart <> nil then
|
||||||
Result := Result + FExpressionPart.DebugDump(' ', AWithResults);
|
Result := Result + FExpressionPart.DebugDump(' ', AWithResults);
|
||||||
@ -1476,6 +1492,11 @@ begin
|
|||||||
else Self.SetError(AMsg);
|
else Self.SetError(AMsg);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TFpPascalExpressionPart.SetError(AnErrorCode: TFpErrorCode; AData: array of const);
|
||||||
|
begin
|
||||||
|
FExpression.SetError(AnErrorCode, AData);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TFpPascalExpressionPart.Init;
|
procedure TFpPascalExpressionPart.Init;
|
||||||
begin
|
begin
|
||||||
//
|
//
|
||||||
@ -2051,9 +2072,12 @@ begin
|
|||||||
// Todo unit
|
// Todo unit
|
||||||
if (tmp.Kind in [skClass, skRecord, skObject]) then begin
|
if (tmp.Kind in [skClass, skRecord, skObject]) then begin
|
||||||
Result := tmp.MemberByName[Items[1].GetText];
|
Result := tmp.MemberByName[Items[1].GetText];
|
||||||
if Result <> nil then
|
if Result = nil then begin
|
||||||
|
SetError(fpErrNoMemberWithName, [Items[1].GetText]);
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
Result.AddReference{$IFDEF WITH_REFCOUNT_DEBUG}(nil, 'DoGetResultValue'){$ENDIF};
|
Result.AddReference{$IFDEF WITH_REFCOUNT_DEBUG}(nil, 'DoGetResultValue'){$ENDIF};
|
||||||
Assert((Result=nil) or (Result.DbgSymbol=nil)or(Result.DbgSymbol.SymbolType=stValue), 'member is value');
|
Assert((Result.DbgSymbol=nil)or(Result.DbgSymbol.SymbolType=stValue), 'member is value');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ uses
|
|||||||
Classes, sysutils, math, FpdMemoryTools, FpDbgInfo, FpDbgClasses, GDBMIDebugger,
|
Classes, sysutils, math, FpdMemoryTools, FpDbgInfo, FpDbgClasses, GDBMIDebugger,
|
||||||
DbgIntfBaseTypes, DbgIntfDebuggerBase, GDBMIMiscClasses,
|
DbgIntfBaseTypes, DbgIntfDebuggerBase, GDBMIMiscClasses,
|
||||||
GDBTypeInfo, maps, LCLProc, Forms, FpDbgLoader, FpDbgDwarf, FpDbgDwarfConst, LazLoggerBase,
|
GDBTypeInfo, maps, LCLProc, Forms, FpDbgLoader, FpDbgDwarf, FpDbgDwarfConst, LazLoggerBase,
|
||||||
LazLoggerProfiling, LazClasses, FpPascalParser, FpPascalBuilder;
|
LazLoggerProfiling, LazClasses, FpPascalParser, FpPascalBuilder, FpErrorMessages;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
@ -1454,6 +1454,21 @@ begin
|
|||||||
PasExpr := TFpPascalExpression.Create(AExpression, Ctx);
|
PasExpr := TFpPascalExpression.Create(AExpression, Ctx);
|
||||||
try
|
try
|
||||||
if not IsWatchValueAlive then exit;
|
if not IsWatchValueAlive then exit;
|
||||||
|
PasExpr.ResultValue; // trigger evaluate // and check errors
|
||||||
|
if not IsWatchValueAlive then exit;
|
||||||
|
|
||||||
|
if not PasExpr.Valid then begin
|
||||||
|
DebugLn(FpErrorHandler.ErrorAsString(PasExpr.Error));
|
||||||
|
if PasExpr.Error.ErrorCode <> fpErrAnyError then begin
|
||||||
|
Result := True;
|
||||||
|
AResText := FpErrorHandler.ErrorAsString(PasExpr.Error);;
|
||||||
|
if AWatchValue <> nil then begin;
|
||||||
|
AWatchValue.Value := AResText;
|
||||||
|
AWatchValue.Validity := ddsError;
|
||||||
|
end;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
if not (PasExpr.Valid and (PasExpr.ResultValue <> nil)) then
|
if not (PasExpr.Valid and (PasExpr.ResultValue <> nil)) then
|
||||||
exit; // TODO handle error
|
exit; // TODO handle error
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user