diff --git a/Source/InvokeCall.inc b/Source/InvokeCall.inc index 5251d075..34ac60d0 100644 --- a/Source/InvokeCall.inc +++ b/Source/InvokeCall.inc @@ -386,6 +386,7 @@ var SysCalConv : TCallConv; ctx: TRTTIContext; RttiType : TRttiType; ResValue : TValue; + BorrowList: TPSPCharBorrowList; begin Result := False; IsStatic := _Self = nil; @@ -422,7 +423,12 @@ begin btU8, btS8, btU16, btS16, btU32, btS32, btSingle, btDouble, btExtended, btString, btPChar, btChar, btCurrency, btUnicodeString {$IFNDEF PS_NOINT64}, bts64, btU64{$ENDIF}: - Arg := TValue.From( Pointer(fvar.dta) ); + begin + // the callee may overwrite P-Char slots (directly or inside + // records/static arrays); borrow their ownership around the call + PSBorrowPCharOwnership(fvar.aType, fvar.dta, BorrowList); + Arg := TValue.From( Pointer(fvar.dta) ); + end; else begin Exit; @@ -443,6 +449,7 @@ begin {$ENDIF CPUX86} IsConstr := (Integer(CallingConv) and 64) <> 0; + try if not assigned(res) then begin Invoke(Address, Args, SysCalConv, nil); { ignore return } @@ -492,11 +499,11 @@ begin //13 btPointer: res.dta := Pointer(Invoke(Address,Args,SysCalConv,TypeInfo(Pointer),IsStatic).AsType); //14 - btPChar: + btPChar: // the slot owns its content: copy the returned data {$IFDEF FPC} - ptbtPChar(res.dta)^ := tbtPChar(Invoke(Address,Args,SysCalConv,TypeInfo(tbtPChar),IsStatic).AsOrdinal); + tbtstring(res.dta^) := tbtstring(tbtPChar(Invoke(Address,Args,SysCalConv,TypeInfo(tbtPChar),IsStatic).AsOrdinal)); {$ELSE} - ptbtPChar(res.dta)^ := tbtPChar(Invoke(Address,Args,SysCalConv,TypeInfo(tbtPChar),IsStatic).AsType()); + tbtstring(res.dta^) := tbtstring(Invoke(Address,Args,SysCalConv,TypeInfo(tbtPChar),IsStatic).AsType()); {$ENDIF} //15 //btResourcePointer @@ -606,6 +613,9 @@ begin Exit; end; { case } end; //assigned(res) + finally + PSReownPCharOwnership(BorrowList); + end; SetLength(Args, 0); SetLength(old_Args, 0); SetLength(old_Args2, 0); diff --git a/Source/uPSRuntime.pas b/Source/uPSRuntime.pas index d22c6182..f1e4cdc2 100644 --- a/Source/uPSRuntime.pas +++ b/Source/uPSRuntime.pas @@ -893,6 +893,19 @@ function ResultAsRegister(b: TPSTypeRec): Boolean; function PSGetRecField(const avar: TPSVariantIFC; Fieldno: Longint): TPSVariantIFC; function PSGetArrayField(const avar: TPSVariantIFC; Fieldno: Longint): TPSVariantIFC; +type + { btPChar slots own their content as a managed string; before an external + call may write through or replace a var-param p-char, its reference is + borrowed and re-owned afterwards } + TPSPCharBorrowEntry = record + Slot: Pointer; // address of the P-Char slot + Holder: Pointer; // borrowed string reference (owned while borrowed) + end; + TPSPCharBorrowList = array of TPSPCharBorrowEntry; + +procedure PSBorrowPCharOwnership(aType: TPSTypeRec; Dta: Pointer; var List: TPSPCharBorrowList); +procedure PSReownPCharOwnership(var List: TPSPCharBorrowList); + function NewTPSVariantRecordIFC(avar: PPSVariant; Fieldno: Longint): TPSVariantIFC; function NewTPSVariantIFC(avar: PPSVariant; varparam: boolean): TPSVariantIFC; @@ -1878,7 +1891,7 @@ procedure InitializeVariant(p: Pointer; aType: TPSTypeRec); procedure DestroyHeapVariant2(v: Pointer; aType: TPSTypeRec); forward; const - NeedFinalization = [btStaticArray, btRecord, btArray, btPointer, btVariant {$IFNDEF PS_NOINTERFACES}, btInterface{$ENDIF}, btString {$IFNDEF PS_NOWIDESTRING}, btUnicodestring,btWideString{$ENDIF}]; + NeedFinalization = [btStaticArray, btRecord, btArray, btPointer, btVariant {$IFNDEF PS_NOINTERFACES}, btInterface{$ENDIF}, btString, btPChar {$IFNDEF PS_NOWIDESTRING}, btUnicodestring,btWideString{$ENDIF}]; type TDynArrayRecHeader = packed record @@ -1944,7 +1957,10 @@ procedure FinalizeVariant(p: Pointer; aType: TPSTypeRec); darr: PDynArrayRec; begin case aType.BaseType of - btString: tbtString(p^) := ''; + { btPChar slots own their content as a string: the payload pointer IS the + p-char value (readers like the comparison operators already treat the + slot as tbtString) } + btString, btPChar: tbtString(p^) := ''; {$IFNDEF PS_NOWIDESTRING} btWideString: tbtwidestring(p^) := ''; btUnicodeString: tbtunicodestring(p^) := ''; @@ -2035,6 +2051,55 @@ function CreateHeapVariant2(aType: TPSTypeRec): Pointer; InitializeVariant(Result, aType); end; +procedure PSBorrowPCharOwnership(aType: TPSTypeRec; Dta: Pointer; var List: TPSPCharBorrowList); +var + i, n: Longint; +begin + if (aType = nil) or (Dta = nil) then + Exit; + case aType.BaseType of + btPChar: + begin + n := Length(List); + SetLength(List, n + 1); + List[n].Slot := Dta; + List[n].Holder := Pointer(Dta^); // steal the reference; the slot keeps the raw pointer + end; + btRecord: + for i := 0 to TPSTypeRec_Record(aType).FieldTypes.Count - 1 do + PSBorrowPCharOwnership(TPSTypeRec_Record(aType).FieldTypes[i], + Pointer(IPointer(Dta) + IPointer(TPSTypeRec_Record(aType).RealFieldOffsets[i])), List); + btStaticArray: + for i := 0 to TPSTypeRec_StaticArray(aType).Size - 1 do + PSBorrowPCharOwnership(TPSTypeRec_StaticArray(aType).ArrayType, + Pointer(IPointer(Dta) + IPointer(Cardinal(i) * TPSTypeRec_StaticArray(aType).ArrayType.RealSize)), List); + end; +end; + +procedure PSReownPCharOwnership(var List: TPSPCharBorrowList); +var + i: Longint; + s: Pointer; +begin + for i := 0 to Length(List) - 1 do + with List[i] do + begin + if Pointer(Slot^) = Holder then + // untouched by the callee: the slot silently keeps its reference + Holder := nil + else + begin + // the callee stored a foreign pointer: copy its data into an owned + // string and release the original (still owned via the holder) + s := nil; + tbtstring(s) := tbtstring(pansichar(Slot^)); + tbtstring(Holder) := ''; + Pointer(Slot^) := s; // transfer ownership of the copy into the slot + end; + end; + SetLength(List, 0); +end; + procedure DestroyHeapVariant2(v: Pointer; aType: TPSTypeRec); begin if v = nil then exit; @@ -4122,13 +4187,20 @@ function CopyArrayContents(dest, src: Pointer; Len: Longint; aType: TPSTypeRec): Dest := Pointer(IPointer(Dest) + PointerSize); Src := Pointer(IPointer(Src) + PointerSize); end; - btClass, btpchar: + btClass: for i := 0 to Len -1 do begin Pointer(Dest^) := Pointer(Src^); Dest := Pointer(IPointer(Dest) + PointerSize); Src := Pointer(IPointer(Src) + PointerSize); end; + btpchar: + for i := 0 to Len -1 do + begin + tbtstring(Dest^) := tbtstring(Src^); // owned: reference copy + Dest := Pointer(IPointer(Dest) + PointerSize); + Src := Pointer(IPointer(Src) + PointerSize); + end; btU32, btS32, btSingle: for i := 0 to Len -1 do begin @@ -4757,7 +4829,10 @@ function TPSExec.SetVariantValue(dest, Src: Pointer; desttype, srctype: TPSTypeR end; end; btCurrency: tbtcurrency(Dest^) := PSGetCurrency(Src, srctype); - btPChar: pansichar(dest^) := pansichar(PSGetAnsiString(Src, srctype)); + { do NOT take the payload pointer of a temporary: store a managed copy. + The old code left dest pointing into a string that was freed right + after this statement. } + btPChar: tbtstring(dest^) := PSGetAnsiString(Src, srctype); btString: tbtstring(dest^) := PSGetAnsiString(Src, srctype); btChar: tbtchar(dest^) := PSGetAnsiChar(Src, srctype); @@ -11801,6 +11876,9 @@ function ClassCallProcEventPropertyHelper(Caller: TPSExec; p: TPSExternalProcRec Pointer(Pointer((IPointer(n.dta)+PointerSize))^) := data.Data; Pointer(Pointer((IPointer(n.dta)+PointerSize2))^) := data.Code; end; + // n2 is a btPChar variant abused as a raw pointer carrier (@data); + // clear it before destroy - finalization would treat it as a string + PPSVariantDynamicArray(n2)^.Data := nil; DestroyHeapVariant(n2); DisposePPSVariantIFCList(Params); end; diff --git a/Source/x64.inc b/Source/x64.inc index d06ba115..985ab10f 100644 --- a/Source/x64.inc +++ b/Source/x64.inc @@ -519,6 +519,7 @@ _XMM0: Double; {$ENDIF} RegUsage: Byte; CallData: TPSList; + BorrowList: TPSPCharBorrowList; I: Integer; pp: ^Byte; IsSafeCall: Boolean; @@ -735,6 +736,9 @@ _XMM0: Double; btS16, btU32, btS32, btSingle, btDouble, btExtended, btString, btPChar, btChar, btCurrency {$IFNDEF PS_NOINT64}, bts64, btU64{$ENDIF}: begin + // the callee may overwrite P-Char slots (directly or inside + // records/static arrays); borrow their ownership around the call + PSBorrowPCharOwnership(fvar.aType, fvar.Dta, BorrowList); Varptr := fvar.Dta; end; else begin @@ -1027,7 +1031,7 @@ begin {$IFNDEF PS_NOWIDESTRING} btWideChar, {$ENDIF} btu16, bts16: tbtu16(res.dta^) := _RAX; btClass : IPointer(res.dta^) := _RAX; btu32,bts32: tbtu32(res.dta^) := _RAX; - btPChar: pansichar(res.dta^) := Pansichar(_RAX); + btPChar: tbtstring(res.dta^) := tbtstring(Pansichar(_RAX)); // owned: copy {$IFNDEF PS_NOINT64} bts64: tbts64(res.dta^) := Int64(_RAX); btU64: tbtu64(res.dta^) := UInt64(_RAX); @@ -1064,6 +1068,7 @@ begin end; Result := True; finally + PSReownPCharOwnership(BorrowList); if CallData <> nil then begin for i := CallData.Count -1 downto 0 do begin diff --git a/Source/x86.inc b/Source/x86.inc index 8982d82c..af1401ce 100644 --- a/Source/x86.inc +++ b/Source/x86.inc @@ -270,6 +270,7 @@ var {$ENDIF} EAX, EDX, ECX: Longint; + BorrowList: TPSPCharBorrowList; function rp(p: PPSVariantIFC): PPSVariantIFC; begin @@ -367,6 +368,9 @@ var btS16, btU32, btS32, btSingle, btDouble, btExtended, btString, btPChar, btChar, btCurrency {$IFNDEF PS_NOINT64}, bts64, btU64{$ENDIF}: begin + // the callee may overwrite P-Char slots (directly or inside + // records/static arrays); borrow their ownership around the call + PSBorrowPCharOwnership(fvar.aType, fvar.Dta, BorrowList); Varptr := fvar.Dta; end; else begin @@ -726,7 +730,7 @@ begin end; btu32,bts32{$IFDEF FPC},btArray{$ENDIF}: tbtu32(res.dta^) := RealCall_Register(Address, EAX, EDX, ECX, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil); - btPChar: pansichar(res.dta^) := Pansichar(RealCall_Register(Address, EAX, EDX, ECX, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil)); + btPChar: tbtstring(res.dta^) := tbtstring(Pansichar(RealCall_Register(Address, EAX, EDX, ECX, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil))); // owned: copy {$IFNDEF PS_NOINT64}bts64, btU64: begin EAX := RealCall_Register(Address, EAX, EDX, ECX, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, @EDX); @@ -809,7 +813,7 @@ begin btChar, btU8, btS8: tbtu8(res^.Dta^) := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 1, nil); {$IFNDEF PS_NOWIDESTRING}btWideChar, {$ENDIF}btu16, bts16: tbtu16(res^.Dta^) := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 2, nil); btClass, btu32, bts32: tbtu32(res^.Dta^):= RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil); - btPChar: pansichar(res^.dta^) := Pansichar(RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil)); + btPChar: tbtstring(res^.dta^) := tbtstring(Pansichar(RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil))); // owned: copy {$IFNDEF PS_NOINT64}bts64, btU64: begin EAX := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, @EDX); @@ -899,7 +903,7 @@ begin btCHar, btU8, btS8: tbtu8(res^.dta^) := RealCall_Cdecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 1, nil); {$IFNDEF PS_NOWIDESTRING}btWideChar, {$ENDIF}btu16, bts16: tbtu16(res^.dta^) := RealCall_Cdecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 2, nil); btClass, btu32, bts32: tbtu32(res^.dta^) := RealCall_Cdecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil); - btPChar: pansichar(res^.dta^) := Pansichar(RealCall_Cdecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil)); + btPChar: tbtstring(res^.dta^) := tbtstring(Pansichar(RealCall_Cdecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil))); // owned: copy {$IFNDEF PS_NOINT64}bts64, btU64: begin EAX := RealCall_CDecl(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, @EDX); @@ -970,7 +974,7 @@ begin btChar, btU8, btS8: tbtu8(res^.dta^) := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 1, nil); {$IFNDEF PS_NOWIDESTRING}btWideChar, {$ENDIF}btu16, bts16: tbtu16(res^.dta^) := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 2, nil); btclass, btu32, bts32: tbtu32(res^.dta^) := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil); - btPChar: pansichar(res^.dta^) := Pansichar(RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil)); + btPChar: tbtstring(res^.dta^) := tbtstring(Pansichar(RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, nil))); // owned: copy {$IFNDEF PS_NOINT64}bts64, btU64: begin EAX := RealCall_Other(Address, @Stack[Length(Stack)-3], Length(Stack) div 4, 4, @EDX); @@ -1025,6 +1029,7 @@ begin end; end; finally + PSReownPCharOwnership(BorrowList); for i := CallData.Count -1 downto 0 do begin pp := CallData[i];