fix(codegen): empty loop body no longer crashes the compiler (#150)
`for x := 0 to N do;` and `while C do;` have an empty statement as their
body, which the parser represents as a nil statement (its convention for
"no statement here" — every statement-list builder already filters nil).
A loop's single Body field is not filtered, so the nil reached EmitStmt:
* Native backend: EmitStmt fell through every `is` check to the
unsupported-statement fallback, which dereferenced AStmt.ClassName on
nil — segfaulting the compiler (issue #150 was reported against the
default native backend).
* QBE backend: EmitStmt raised "EmitStmt called with nil statement",
rejecting a legal empty body.
Both backends now treat a nil statement as a no-op, matching the parser's
nil-as-empty convention and the way AnalyseStmt already tolerates it. An
empty loop body compiles to a loop that does nothing.
E2E regression in cp.test.e2e.controlflow.pas asserts both `for ... do;`
and `while ... do;` compile + run as a no-op on every backend.
This commit is contained in:
parent
f517f5148a
commit
a205c22136
|
|
@ -10811,6 +10811,12 @@ var
|
|||
PCHTotal: Integer;
|
||||
AliasBuf: Integer;
|
||||
begin
|
||||
{ An empty statement (e.g. the body of `for x := 0 to N do;`) parses to a nil
|
||||
statement — the parser's convention for "no statement here". It is a valid,
|
||||
do-nothing body, so emit nothing. Without this guard the unsupported-
|
||||
statement fallback at the tail dereferences AStmt.ClassName and segfaults. }
|
||||
if AStmt = nil then
|
||||
Exit;
|
||||
Self.DbgStmtLabel(AStmt);
|
||||
if AStmt is TAsmStmt then
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -2572,8 +2572,11 @@ procedure TCodeGenQBE.EmitStmt(AStmt: TASTStmt);
|
|||
var
|
||||
DeadLbl: string;
|
||||
begin
|
||||
{ An empty statement (e.g. the body of `for x := 0 to N do;`) parses to a nil
|
||||
statement — the parser's convention for "no statement here". It is a valid,
|
||||
do-nothing body, so emit nothing rather than rejecting it. }
|
||||
if AStmt = nil then
|
||||
raise ECodeGenError.Create('EmitStmt called with nil statement');
|
||||
Exit;
|
||||
if AStmt is TAsmStmt then
|
||||
raise ECodeGenError.Create(
|
||||
'inline asm blocks require the native backend (--backend native); '
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ type
|
|||
procedure TestRun_For_Upward_PrintsRange;
|
||||
procedure TestRun_For_Downto_PrintsRange;
|
||||
procedure TestRun_While_PrintsRange;
|
||||
procedure TestRun_For_EmptyBody_NoCrash;
|
||||
procedure TestRun_While_EmptyBody_NoCrash;
|
||||
procedure TestRun_Repeat_PrintsRange;
|
||||
procedure TestRun_For_BreakExitsEarly;
|
||||
procedure TestRun_For_ContinueSkipsIteration;
|
||||
|
|
@ -105,6 +107,29 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
{ Empty loop body — `for ... do;` / `while ... do;` parse to a nil body.
|
||||
Regression for issue #150: the native backend segfaulted (nil.ClassName in
|
||||
the unsupported-statement fallback) and the QBE backend rejected it; both
|
||||
must now treat the empty body as a valid no-op. }
|
||||
SrcForEmptyBody = '''
|
||||
program P;
|
||||
var I: Integer;
|
||||
begin
|
||||
for I := 0 to 9 do;
|
||||
WriteLn('done')
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcWhileEmptyBody = '''
|
||||
program P;
|
||||
var I: Integer;
|
||||
begin
|
||||
I := 0;
|
||||
while I < 0 do;
|
||||
WriteLn('done')
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcRepeat = '''
|
||||
program P;
|
||||
var I: Integer;
|
||||
|
|
@ -202,6 +227,20 @@ begin
|
|||
AssertEquals('1 2 3', '1' + LE + '2' + LE + '3' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TE2EControlFlowTests.TestRun_For_EmptyBody_NoCrash;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
{ Issue #150: an empty `for` body must compile + run as a no-op on both
|
||||
backends (native previously segfaulted the compiler). }
|
||||
AssertRunsOnAll(SrcForEmptyBody, 'done' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EControlFlowTests.TestRun_While_EmptyBody_NoCrash;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcWhileEmptyBody, 'done' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EControlFlowTests.TestRun_Repeat_PrintsRange;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
|
|
|
|||
Loading…
Reference in a new issue