Removed a potential memory leak when a JSONRPC with dinamically allocated parameters method raises exception.

This commit is contained in:
Daniele Teti 2023-05-23 11:45:58 +02:00
parent ba537b2ad9
commit bb30db152d
10 changed files with 229 additions and 194 deletions

View File

@ -7,7 +7,6 @@ uses
MVCFramework.Logger,
MVCFramework.Commons,
MVCFramework.Console,
MVCFramework.REPLCommandsHandlerU,
Web.ReqMulti,
Web.WebReq,
Web.WebBroker,

View File

@ -183,17 +183,17 @@
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="4">
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule"/>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libPCRE.dylib" Class="DependencyModule"/>
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule"/>
<DeployFile LocalName="$(BDS)\Redist\osx32\libcgsqlite3.dylib" Class="DependencyModule"/>
<DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule"/>
<DeployFile LocalName="Win32\Debug\jsonrpcserverwithobjects.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="bin\jsonrpcserverwithobjects.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>jsonrpcserverwithobjects.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win32\Debug\jsonrpcserverwithobjects.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="OSX32">
<Operation>1</Operation>
@ -585,6 +585,127 @@
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug"/>
<DeployClass Name="ProjectOSXEntitlements"/>
<DeployClass Name="ProjectOSXInfoPList"/>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements"/>
<DeployClass Name="ProjectiOSInfoPList"/>
<DeployClass Name="ProjectiOSLaunchScreen"/>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
@ -785,127 +906,6 @@
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements"/>
<DeployClass Name="ProjectiOSInfoPList"/>
<DeployClass Name="ProjectiOSLaunchScreen"/>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug"/>
<DeployClass Name="ProjectOSXEntitlements"/>
<DeployClass Name="ProjectOSXInfoPList"/>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>

View File

@ -1813,67 +1813,67 @@ begin
SetLength(lParamsIsRecord, lParamsCount);
SetLength(lRecordsPointer, lParamsCount);
SetLength(lParamArrayLength, lParamsCount);
// scroll json params and rttimethod params and find the best match
if Assigned(lJSONParams) then
begin
// positional params
for I := 0 to lJSONParams.Count - 1 do
begin
JSONDataValueToTValueParamEx(
fSerializer,
lJSONParams[I],
lRTTIMethodParams[I],
lParamsArray[I],
lParamsIsRecord[I],
lRecordsPointer[I],
lParamArrayLength[i]
);
end;
end
else if Assigned(lJSONNamedParams) then
begin
// named params
for I := 0 to lJSONNamedParams.Count - 1 do
begin
JSONDataValueToTValueParamEx(
fSerializer,
GetJsonDataValueHelper(lJSONNamedParams, lRTTIMethodParams[I].Name.ToLower),
lRTTIMethodParams[I],
lParamsArray[I],
lParamsIsRecord[I],
lRecordsPointer[I],
lParamArrayLength[i]);
end;
end;
TryToCallMethod(RTTIType, JSONRPC_HOOKS_ON_BEFORE_CALL, JSON);
BeforeCallHookHasBeenInvoked := True;
try
LogD('[JSON-RPC][CALL][' + CALL_TYPE[RTTIMethod.MethodKind] + '][' + fRPCInstance.ClassName + '.' +
RTTIMethod.Name + ']');
Result := RTTIMethod.Invoke(fRPCInstance, lParamsArray);
except
on E: EInvalidCast do
// scroll json params and rttimethod params and find the best match
if Assigned(lJSONParams) then
begin
raise EMVCJSONRPCInvalidParams.Create('Check your input parameters types');
end;
on Ex: EMVCJSONRPCInvalidRequest do
begin
raise EMVCJSONRPCInvalidParams.Create(Ex.Message);
end;
end;
for I := 0 to lParamsCount - 1 do
begin
if lParamsArray[I].IsObject then
begin
lParamsArray[I].AsObject.Free;
// positional params
for I := 0 to lJSONParams.Count - 1 do
begin
JSONDataValueToTValueParamEx(
fSerializer,
lJSONParams[I],
lRTTIMethodParams[I],
lParamsArray[I],
lParamsIsRecord[I],
lRecordsPointer[I],
lParamArrayLength[i]
);
end;
end
else if lParamsIsRecord[I] then
else if Assigned(lJSONNamedParams) then
begin
//FinalizeRecord(lRecordsPointer[I], lRTTIMethodParams[I].ParamType.Handle);
FreeMem(lRecordsPointer[I], lRTTIMethodParams[I].ParamType.TypeSize);
// named params
for I := 0 to lJSONNamedParams.Count - 1 do
begin
JSONDataValueToTValueParamEx(
fSerializer,
GetJsonDataValueHelper(lJSONNamedParams, lRTTIMethodParams[I].Name.ToLower),
lRTTIMethodParams[I],
lParamsArray[I],
lParamsIsRecord[I],
lRecordsPointer[I],
lParamArrayLength[i]);
end;
end;
TryToCallMethod(RTTIType, JSONRPC_HOOKS_ON_BEFORE_CALL, JSON);
BeforeCallHookHasBeenInvoked := True;
try
LogD('[JSON-RPC][CALL][' + CALL_TYPE[RTTIMethod.MethodKind] + '][' + fRPCInstance.ClassName + '.' +
RTTIMethod.Name + ']');
Result := RTTIMethod.Invoke(fRPCInstance, lParamsArray);
except
on E: EInvalidCast do
begin
raise EMVCJSONRPCInvalidParams.Create('Check your input parameters types');
end;
on Ex: EMVCJSONRPCInvalidRequest do
begin
raise EMVCJSONRPCInvalidParams.Create(Ex.Message);
end;
end;
finally
for I := 0 to lParamsCount - 1 do
begin
if lParamsArray[I].IsObject then
begin
lParamsArray[I].AsObject.Free;
end
else if lParamsIsRecord[I] then
begin
FreeMem(lRecordsPointer[I], lRTTIMethodParams[I].ParamType.TypeSize);
end;
end;
end;
end;

View File

@ -481,7 +481,7 @@ type
function Headers: TStrings;
function HeaderValue(const aName: string): string;
function Cookies: TCookies;
function CookieByName(const aName: string): TCookie;
function CookieByName(const aName: string; const RaiseExceptionIfNotFound: Boolean = False): TCookie;
function Server: string;
function ContentType: string;
function ContentEncoding: string;

View File

@ -538,7 +538,7 @@ type
function Headers: TStrings;
function HeaderValue(const aName: string): string;
function Cookies: TCookies;
function CookieByName(const aName: string): TCookie;
function CookieByName(const aName: string; const RaiseExceptionIfNotFound: Boolean = False): TCookie;
function Server: string;
function ContentType: string;
function ContentEncoding: string;
@ -1928,7 +1928,7 @@ begin
Result := fContentType;
end;
function TMVCRESTResponse.CookieByName(const aName: string): TCookie;
function TMVCRESTResponse.CookieByName(const aName: string; const RaiseExceptionIfNotFound: Boolean): TCookie;
var
lCookie: TCookie;
begin
@ -1938,6 +1938,11 @@ begin
if SameText(lCookie.Name, aName) then
Exit(lCookie);
end;
if RaiseExceptionIfNotFound then
begin
raise EMVCRESTClientException.CreateFmt('Cookie "%s" not found', [aName]);
end;
end;
function TMVCRESTResponse.Cookies: TCookies;

View File

@ -232,16 +232,12 @@ begin
inherited Destroy;
end;
// class procedure TMVCSessionFactory.DestroyInstance;
// begin
// if Assigned(cInstance) then
// cInstance.Free;
// end;
class function TMVCSessionFactory.GetInstance: TMVCSessionFactory;
begin
if not Assigned(cInstance) then
begin
cInstance := TMVCSessionFactory.Create;
end;
Result := cInstance;
end;

View File

@ -1,2 +1,2 @@
const
DMVCFRAMEWORK_VERSION = '3.3.0-fluorine';
DMVCFRAMEWORK_VERSION = '3.4.0-neon-beta';

View File

@ -348,6 +348,9 @@ type
// objects tests
[Test]
procedure TestRequestWithObjectParameters;
// exception tests
[Test]
procedure TestRequestWithException;
// hooks tests
[Test]
procedure TestHooks;
@ -2490,13 +2493,17 @@ var
c1: IMVCRESTClient;
res: IMVCRESTResponse;
S: string;
lCookie: TCookie;
begin
c1 := TMVCRESTClient.New.BaseURL(TEST_SERVER_ADDRESS, 9999);
c1.Accept(TMVCMediaType.APPLICATION_JSON);
res := c1.Post('/session/daniele teti'); // imposto un valore in sessione
S := res.HeaderValue('Set-Cookie');
Assert.IsFalse(S.Contains('Expires'), 'Session cookie contains "expires" attribute');
res := c1.Get('/session'); // rileggo il valore dalla sessione
Assert.IsTrue(res.Cookies.Count > 0);
lCookie := res.CookieByName('dtsessionid', True);
Assert.AreEqual('dtsessionid', lCookie.Name);
// Assert.IsFalse(S.Contains('Expires'), 'Session cookie contains "expires" attribute');
res := c1.AddCookie('dtsessionid', lCookie.Value).Get('/session'); // rileggo il valore dalla sessione
S := res.Content;
Assert.areEqual('daniele teti', res.Content);
c1.Accept(TMVCMediaType.TEXT_PLAIN);
res := c1.Get('/session');
@ -3077,6 +3084,22 @@ begin
Assert.Contains(lRPCResp.Error.ErrMessage, 'cannot find parameter', true);
end;
procedure TJSONRPCServerTest.TestRequestWithException;
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lPersSrc: TPerson;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'DoError';
lPersSrc := TPerson.GetNew('Daniele','Teti', EncodeDate(1979,12,1), True);
lReq.Params.AddByName('MyObj', lPersSrc);
lReq.RequestID := 1;
lResp := FExecutor2.ExecuteRequest(lReq);
Assert.IsTrue(lResp.IsError);
Assert.AreEqual('BOOOM!! (TTestJSONRPCClass.DoError)', lResp.Error.ErrMessage);
end;
procedure TJSONRPCServerTest.TestRequestWithNamedParams_I_I_I_ret_O;
var
lReq: IJSONRPCRequest;

View File

@ -119,6 +119,7 @@
<Icon_MainIcon>TestServer_Icon.ico</Icon_MainIcon>
<Manifest_File>(None)</Manifest_File>
<AppDPIAwarenessMode>none</AppDPIAwarenessMode>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
@ -157,6 +158,8 @@
<Source Name="MainSource">TestServer.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k280.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp280.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k280.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp280.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>

View File

@ -44,6 +44,10 @@ type
[MVCInheritable]
function AddTimeToDateTime(aDateTime: TDateTime; aTime: TTime): TDateTime;
//exceptions
[MVCInheritable]
function DoError(MyObj: TPerson): TPerson;
//objects support
[MVCInheritable]
function HandlingObjects(MyObj: TPerson): TPerson;
@ -204,6 +208,11 @@ begin
Result := aDateTime + aTime;
end;
function TTestJSONRPCClass.DoError(MyObj: TPerson): TPerson;
begin
raise Exception.Create('BOOOM!! (TTestJSONRPCClass.DoError)');
end;
function TTestJSONRPCClass.EchoArrayOfRecords(
const ComplexRecordArray: TComplexRecordArray): TComplexRecordArray;
begin