fix(native): nil l-value Free slots; sret record calls into array elements
Two native miscompiles, found chasing the generic-template in-memory bif round-trip crash (TIfaceIOTests.TestRoundTrip_Generic*_TemplateParams): 1. .Free() on an l-value receiver expression (Def.ClassDef.Free()) only released — the slot was not nilled (the QBE lowering stores 0). The stale pointer aliased the next same-size allocation, so the following ARC field store double-released the NEW object; its owned lists died while still referenced (ReadGenericClassPayload's Free+Create+assign dance left ClassDef.Fields freed → nil deref in the test). Field and ident receivers now release AND nil via EmitLValueSlotAddr; other expression receivers keep the release-only behaviour. 2. A record-returning call assigned into an array element (t[i] := FLexer.Next()) evaluated the RHS via EmitExprToEax, which cannot pass the hidden sret pointer — the callee wrote its Result record through garbage (TLexerTests crashed; the suite had never run natively because the round-trip crash aborted earlier). Both subscript-assign branches now sret record-returning calls directly into the element after releasing the old element's managed fields. The FULL natively-compiled TestRunner passes for the first time: 2944 tests, 0 failures (previously aborted at TIfaceIOTests). Tests: TNativeConstArgTests.TestFree_FieldReceiver_NilsSlot (asm-level) + TE2ERecordsTests.TestRun_RecordCallResult_IntoArrayElement (both backends). Suite: 2945 OK; FIXPOINT_OK; NATIVE_FIXPOINT_OK.
This commit is contained in:
parent
c62ce5b8b1
commit
d6b706ffd3
|
|
@ -353,6 +353,10 @@ type
|
|||
procedure EmitSretCall(const AFuncSym: string; ADecl: TMethodDecl;
|
||||
AArgs: TObjectList; const ASretAddr: string;
|
||||
ASretIsIndirect: Boolean);
|
||||
{ True when AExpr is a record-returning function or method call. }
|
||||
function IsNativeRecordCall(AExpr: TASTExpr): Boolean;
|
||||
{ Sret a record-returning call (function or method) into ADest. }
|
||||
procedure EmitRecordCallSretAt(AExpr: TASTExpr; const ADest: string);
|
||||
procedure EmitMethodSretCall(ACall: TMethodCallExpr; const ASretAddr: string;
|
||||
ASretIsIndirect: Boolean);
|
||||
{ Compute the address of a record/class field into %rcx. Handles
|
||||
|
|
@ -3182,6 +3186,31 @@ begin
|
|||
'native backend: unsupported L-value form');
|
||||
end;
|
||||
|
||||
function TX86_64Backend.IsNativeRecordCall(AExpr: TASTExpr): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (AExpr is TMethodCallExpr) and
|
||||
(TMethodCallExpr(AExpr).ResolvedMethod <> nil) and
|
||||
(TMethodDecl(TMethodCallExpr(AExpr).ResolvedMethod).ResolvedReturnType <> nil) and
|
||||
(TMethodDecl(TMethodCallExpr(AExpr).ResolvedMethod).ResolvedReturnType.Kind = tyRecord) then
|
||||
Exit(True);
|
||||
if (AExpr is TFuncCallExpr) and
|
||||
(TFuncCallExpr(AExpr).ResolvedDecl <> nil) and
|
||||
(TMethodDecl(TFuncCallExpr(AExpr).ResolvedDecl).ResolvedReturnType <> nil) and
|
||||
(TMethodDecl(TFuncCallExpr(AExpr).ResolvedDecl).ResolvedReturnType.Kind = tyRecord) then
|
||||
Exit(True);
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.EmitRecordCallSretAt(AExpr: TASTExpr; const ADest: string);
|
||||
begin
|
||||
if AExpr is TMethodCallExpr then
|
||||
Self.EmitMethodSretCall(TMethodCallExpr(AExpr), ADest, False)
|
||||
else
|
||||
Self.EmitSretCall(FuncSymbolOf(TFuncCallExpr(AExpr)),
|
||||
TMethodDecl(TFuncCallExpr(AExpr).ResolvedDecl),
|
||||
TFuncCallExpr(AExpr).Args, ADest, False);
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.EmitIncDec(ACall: TProcCall);
|
||||
var
|
||||
Arg0: TASTExpr;
|
||||
|
|
@ -6493,9 +6522,25 @@ begin
|
|||
end
|
||||
else if ACall.ObjExpr <> nil then
|
||||
begin
|
||||
Self.EmitExprToEax(ACall.ObjExpr);
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
if (ACall.ObjExpr is TFieldAccessExpr) or (ACall.ObjExpr is TIdentExpr) then
|
||||
begin
|
||||
{ L-value receiver (Def.ClassDef.Free()): release AND nil the slot.
|
||||
A stale pointer left here aliases the next allocation of the same
|
||||
size class, and the following ARC field store double-releases it —
|
||||
the QBE lowering nils the slot, so must we. }
|
||||
Self.EmitLValueSlotAddr(ACall.ObjExpr);
|
||||
Self.Emit(#9'pushq %rdx');
|
||||
Self.Emit(#9'movq (%rdx), %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
Self.Emit(#9'popq %rdx');
|
||||
Self.Emit(#9'movq $0, (%rdx)');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.EmitExprToEax(ACall.ObjExpr);
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
end;
|
||||
end
|
||||
else if ACall.IsVarParam then
|
||||
begin
|
||||
|
|
@ -9574,6 +9619,32 @@ begin
|
|||
(SSA.ResolvedArrayType.Kind = tyDynArray) then
|
||||
begin
|
||||
DAElemType := TDynArrayTypeDesc(SSA.ResolvedArrayType).ElementType;
|
||||
{ Record-returning call as the RHS: sret directly into the element —
|
||||
EmitExprToEax cannot evaluate a record call (the callee writes its
|
||||
Result through the hidden sret pointer, which would be garbage). }
|
||||
if (DAElemType.Kind = tyRecord) and Self.IsNativeRecordCall(SSA.ValueExpr) then
|
||||
begin
|
||||
Self.Emit(#9'pushq %rbx');
|
||||
Self.EmitExprToEax(SSA.IndexExpr);
|
||||
Self.Emit(Format(#9'imulq $%d, %%rax', [DAElemType.RawSize()]));
|
||||
if SSA.IsImplicitSelf then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand('Self')]));
|
||||
if SSA.ImplicitFieldInfo.Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
|
||||
Self.Emit(#9'movq (%rcx), %rcx');
|
||||
end
|
||||
else if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
else
|
||||
Self.Emit(Format(#9'movq %s(%%rip), %%rcx', [SSA.ArrayName]));
|
||||
Self.Emit(#9'addq %rcx, %rax');
|
||||
Self.Emit(#9'movq %rax, %rbx');
|
||||
Self.EmitRecordFieldReleases(TRecordTypeDesc(DAElemType), '%rbx');
|
||||
Self.EmitRecordCallSretAt(SSA.ValueExpr, '(%rbx)');
|
||||
Self.Emit(#9'popq %rbx');
|
||||
Exit;
|
||||
end;
|
||||
Self.EmitExprToEax(SSA.ValueExpr);
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.EmitExprToEax(SSA.IndexExpr);
|
||||
|
|
@ -9646,6 +9717,33 @@ begin
|
|||
raise ENativeCodeGenError.Create(
|
||||
'native backend: static subscript assign on non-static-array');
|
||||
DAElemType := TStaticArrayTypeDesc(SSA.ResolvedArrayType).ElementType;
|
||||
{ Record-returning call RHS: sret directly into the element (see the
|
||||
dyn-array branch above). }
|
||||
if (DAElemType.Kind = tyRecord) and Self.IsNativeRecordCall(SSA.ValueExpr) then
|
||||
begin
|
||||
Self.Emit(#9'pushq %rbx');
|
||||
Self.EmitExprToEax(SSA.IndexExpr);
|
||||
if TStaticArrayTypeDesc(SSA.ResolvedArrayType).LowBound <> 0 then
|
||||
Self.Emit(Format(#9'subq $%d, %%rax',
|
||||
[TStaticArrayTypeDesc(SSA.ResolvedArrayType).LowBound]));
|
||||
Self.Emit(Format(#9'imulq $%d, %%rax', [DAElemType.RawSize()]));
|
||||
if SSA.IsImplicitSelf then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand('Self')]));
|
||||
if SSA.ImplicitFieldInfo.Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rcx', [SSA.ImplicitFieldInfo.Offset]));
|
||||
end
|
||||
else if Self.IsLocal(SSA.ArrayName) then
|
||||
Self.Emit(Format(#9'leaq %s, %%rcx', [Self.VarOperand(SSA.ArrayName)]))
|
||||
else
|
||||
Self.Emit(Format(#9'leaq %s(%%rip), %%rcx', [SSA.ArrayName]));
|
||||
Self.Emit(#9'addq %rcx, %rax');
|
||||
Self.Emit(#9'movq %rax, %rbx');
|
||||
Self.EmitRecordFieldReleases(TRecordTypeDesc(DAElemType), '%rbx');
|
||||
Self.EmitRecordCallSretAt(SSA.ValueExpr, '(%rbx)');
|
||||
Self.Emit(#9'popq %rbx');
|
||||
Exit;
|
||||
end;
|
||||
Self.EmitExprToEax(SSA.ValueExpr);
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.EmitExprToEax(SSA.IndexExpr);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ type
|
|||
procedure TestRun_SubscriptChain_FieldElemWrite;
|
||||
procedure TestRun_RecordField_StaticArrayElemWrite;
|
||||
procedure TestRun_RecordField_DynArrayOfString_ElemWrite_ARC;
|
||||
procedure TestRun_RecordCallResult_IntoArrayElement;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1031,6 +1032,48 @@ begin
|
|||
'bye' + LE + 'world' + LE, 0);
|
||||
end;
|
||||
|
||||
const
|
||||
SrcRecordCallIntoElem = '''
|
||||
program P;
|
||||
type
|
||||
TTok = record
|
||||
Kind: Integer;
|
||||
Text: String;
|
||||
end;
|
||||
TLex = class
|
||||
FN: Integer;
|
||||
function Next(): TTok;
|
||||
end;
|
||||
function TLex.Next(): TTok;
|
||||
begin
|
||||
FN := FN + 1;
|
||||
Result.Kind := FN;
|
||||
Result.Text := 'tok' + IntToStr(FN);
|
||||
end;
|
||||
var
|
||||
L: TLex;
|
||||
A: array[0..2] of TTok;
|
||||
D: array of TTok;
|
||||
I: Integer;
|
||||
begin
|
||||
L := TLex.Create();
|
||||
for I := 0 to 2 do
|
||||
A[I] := L.Next();
|
||||
writeln(A[0].Kind, ' ', A[0].Text);
|
||||
writeln(A[2].Kind, ' ', A[2].Text);
|
||||
SetLength(D, 2);
|
||||
D[1] := L.Next();
|
||||
writeln(D[1].Kind, ' ', D[1].Text);
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2ERecordsTests.TestRun_RecordCallResult_IntoArrayElement;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(SrcRecordCallIntoElem,
|
||||
'1 tok1' + LE + '3 tok3' + LE + '4 tok4' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2ERecordsTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ type
|
|||
procedure TestConstArg_AddrTakenLocal_Pins;
|
||||
procedure TestConstArg_CapturedLocal_Pins;
|
||||
procedure TestConstArg_InterfaceDispatch_ConcatPins;
|
||||
{ Free on a chained field receiver must nil the slot after the release —
|
||||
a stale pointer aliases the next allocation (UAF regression). }
|
||||
procedure TestFree_FieldReceiver_NilsSlot;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -461,6 +464,32 @@ begin
|
|||
Pos('_StringRelease', Region) >= 0);
|
||||
end;
|
||||
|
||||
procedure TNativeConstArgTests.TestFree_FieldReceiver_NilsSlot;
|
||||
var
|
||||
Region: string;
|
||||
begin
|
||||
Region := FuncRegion(GenAsm('''
|
||||
program P;
|
||||
type
|
||||
TInner = class
|
||||
N: Integer;
|
||||
end;
|
||||
TOuter = class
|
||||
Inner: TInner;
|
||||
end;
|
||||
procedure Drop(O: TOuter);
|
||||
begin
|
||||
O.Inner.Free();
|
||||
end;
|
||||
begin
|
||||
end.
|
||||
'''), 'Drop');
|
||||
AssertTrue('releases the field value',
|
||||
CountOccurrences('callq _ClassRelease', Region) >= 1);
|
||||
AssertTrue('nils the field slot after the release',
|
||||
CountOccurrences('movq $0, (%rdx)', Region) >= 1);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TNativeConstArgTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue