From c8454f32bd966fb5f3083d4721d77afa6f80ae17 Mon Sep 17 00:00:00 2001 From: TetzkatLipHoka Date: Sat, 11 Jul 2026 11:13:13 +0200 Subject: [PATCH] Fix dynamic array parameters and results on the Rtti.Invoke call path Imported (host) functions called through the Rtti.Invoke path (the default since XE2; PS_USECLASSICINVOKE was not affected) mishandled dynamic arrays in two ways: 1. By-value dynamic array parameters passed the address of the script's variable slot instead of the array pointer stored in it. The callee then interpreted the slot address as an array reference and crashed with an access violation (reproducible on Win32 and Win64). 2. Dynamic array results were only retrieved when a host RTTI type could be located whose name ends with the script type's exported name. Script types are usually not name-exported, so the lookup found nothing - and because the Invoke call sat inside the search loop, the imported function was then never called at all: the script silently received an empty array (and none of the function's side effects). The empty-result path additionally corrupted the result IFC record by setting res.dta to nil. The parameter case now passes the dereferenced array pointer. The result case selects a type info matching the managed kind of the element type (Invoke only needs it for the - universally identical - dynamic array result ABI and for releasing the TValue's reference; the element-wise transfer into the script slot goes through the script's own type record). The host-type name lookup remains as the path for nested container elements, and a failed lookup now fails the call instead of faking an empty result. Empty results clear the script slot properly via CopyArrayContents. Related: #198 (the FPC report covers the classic x86 path, but the same symptom class exists here on the default path). Co-Authored-By: Claude Fable 5 --- Source/InvokeCall.inc | 52 +++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/Source/InvokeCall.inc b/Source/InvokeCall.inc index 5251d075..0f45464c 100644 --- a/Source/InvokeCall.inc +++ b/Source/InvokeCall.inc @@ -290,8 +290,9 @@ begin exit; end; end - else //dynarray = just push pointer - aValues := aValues + [TValue.From(aValue^.dta)]; + else //dynamic array by value: pass the array pointer itself, not + //the address of the slot holding it (that crashed the callee) + aValues := aValues + [TValue.From(PPointer(aValue^.dta)^)]; end; //13 btPointer: aValues := aValues + [TValue.From(aValue^.dta)]; @@ -386,6 +387,7 @@ var SysCalConv : TCallConv; ctx: TRTTIContext; RttiType : TRttiType; ResValue : TValue; + ArrTypeInfo: PTypeInfo; begin Result := False; IsStatic := _Self = nil; @@ -478,16 +480,42 @@ begin //12 btArray: //need to check with open arrays begin - for RttiType in ctx.GetTypes do - if (RttiType.Name.ToUpper.EndsWith(String(res.aType.FExportName))) and (RttiType.TypeKind = tkDynArray) then - begin - ResValue := Invoke(Address,Args,SysCalConv,RttiType.Handle,IsStatic); - if ResValue.GetArrayLength > 0 then - CopyArrayContents(res.dta, ResValue.GetReferenceToRawData, 1, res.aType) - else - res.dta := nil; - Break; - end; + // Invoke needs a dynamic array type info only for the result ABI + // (identical for all dynamic arrays) and to release the TValue's + // reference afterwards. The element-wise handling below uses the + // script's own type record, so for the TValue any type info with the + // right managed kind of element suffices. + ArrTypeInfo := nil; + case TPSTypeRec_Array(res.aType).ArrayType.BaseType of + btString: ArrTypeInfo := TypeInfo(TArray); + {$IFNDEF PS_NOWIDESTRING} + btWideString: ArrTypeInfo := TypeInfo(TArray); + btUnicodeString: ArrTypeInfo := TypeInfo(TArray); + {$ENDIF} + btVariant: ArrTypeInfo := TypeInfo(TArray); + btInterface: ArrTypeInfo := TypeInfo(TArray); + btArray, btRecord, btStaticArray: + // nested containers: prefer the host's own array type (exact + // element layout), located by the exported type name + for RttiType in ctx.GetTypes do + if (RttiType.TypeKind = tkDynArray) and (res.aType.FExportName <> '') and + (RttiType.Name.ToUpper.EndsWith(String(res.aType.FExportName))) then + begin + ArrTypeInfo := RttiType.Handle; + Break; + end; + else + // unmanaged elements: releasing the reference is a plain block + // free, any element type will do + ArrTypeInfo := TypeInfo(TArray); + end; + if ArrTypeInfo = nil then + Exit; // fail the call instead of silently returning an empty array + ResValue := Invoke(Address, Args, SysCalConv, ArrTypeInfo, IsStatic); + // reference-assign into the script slot using the script's own type + // record; this also clears the slot correctly for empty results (the + // old code corrupted the IFC record by setting res.dta to nil) + CopyArrayContents(res.dta, ResValue.GetReferenceToRawData, 1, res.aType); end; //13 btPointer: res.dta := Pointer(Invoke(Address,Args,SysCalConv,TypeInfo(Pointer),IsStatic).AsType);