- edited description
Destroy method
In the SMS ver 3.1.0.75, the compiler emits this:
var TObject={
$ClassName: "TObject",
$Parent: null,
ClassName: function (s) { return s.$ClassName },
ClassType: function (s) { return s },
ClassParent: function (s) { return s.$Parent },
$Init: function (s) {},
Create: function (s) { return s },
Destroy: function (s) { for (var prop in s) if (s.hasOwnProperty(prop)) delete s[prop] },
Destroy$: function(s) { return s.ClassType.Destroy(s) },
Free: function (s) { if (s!==null) s.ClassType.Destroy(s) }
}
Note:
delete s[prop]
In previous versions, SMS 1.2 / 2.0 / 2.1 / 2.2 it was
delete s.prop
I think SMS 3.1x is the TObject.Destroy method is correct. --> delete s[prop] It will remove all properties from JavaScript object myObject. ---> this is the expected! The delete operator is more about freeing memory than deleting an element.
delete s[prop] will delete all properties of the myObject object, for instance:
var myObject = {
"ircEvent": "PRIVMSG",
"method": "newURI",
"regex": "^http://.*"
};
function destruir(s) { for (var prop in s) if (s.hasOwnProperty(prop)) delete s[prop] };
destruir(myObject);
I have different results testing Destroy
type
TMyObj = class
Field : Integer;
end;
var o : TMyObj;
begin
console.log('Free');
o:=TMyObj.Create;
o.Free;
try
o.Free;
except
on E: Exception do console.log(E.Message);
end;
try
console.log(o.Field);
except
on E: Exception do console.log(E.Message);
end;
try
console.log(o.ClassName);
except
on E: Exception do console.log(E.Message);
end;
console.log('Destroy');
o:=TMyObj.Create;
o.Destroy;
try
o.Destroy;
except
on E: Exception do console.log(E.Message);
end;
try
console.log(o.Field);
except
on E: Exception do console.log(E.Message);
end;
try
console.log(o.ClassName);
except
on E: Exception do console.log(E.Message);
end;
console.log('nil');
o:=TMyObj.Create;
o:=nil;
o.Free;
try
console.log(o.Field);
except
on E: Exception do console.log(E.Message);
end;
try
console.log(o.ClassName);
except
on E: Exception do console.log(E.Message);
end;
in SMS 2.2.2, the output is:
Free
0
TMyObj
Destroy
0
TMyObj
nil
TypeError, Cannot read property 'Field' of null
TypeError, Cannot read property 'ClassType' of null
in SMS 3x the output is:
Free
TypeError, Cannot read property 'Destroy' of undefined
undefined
TypeError, Cannot read property '$ClassName' of undefined
Destroy
TypeError, Cannot read property 'Destroy' of undefined
undefined
TypeError, Cannot read property '$ClassName' of undefined
nil
TypeError, Cannot read property 'Field' of null
TypeError, Cannot read property 'ClassType' of null
I spent hours debugging an issue with mORMot classes with SMS 3.1x I couldn't detect why it couldn't work like in the previous version 2.2x. I think the error comes from the /destroy method.
Comments (7)
-
reporter -
repo owner Hi, I am not sure to understand what the issue is with the 3.x output ?
The older code would just delete the property named 'prop' rather than the actually properties, which in turns meant references in a "freed" object could not be garbage collected until the freed object got out of the scope. This should only apply to long-running sequences of code that would free a collection of objects without releasing the collection (allowing the GC to work its magic during the lifetime of those sequences), or for legacy/Delphi-compatible code using free. In all other cases, explicit use of Free is not required for memory management purposes.
In the snippet of code you gave, use of an object after free/destroy will have an undefined behavior in Delphi (memory overwrites, or access violations if you are lucky), in the SMS output you provided, there are undefined exceptions that happen after the first "free", which looks correct to me, or did I miss something?
-
reporter Thanks for the feedback!
"...what the issue is with the 3.x output?"
The TObject.Destroy method - SMS ver 3.1x - is correct. I think the issue, the method destroy, was on the previous SMS ver 1.2 | 2.0 | 2.1 | 2.2.2.I've tested the snippet of code in both SMS 2.2.2.4694 and SMS 3.1.0.75 based on the https://bitbucket.org/egrange/dwscript/src/master/Test/SimpleScripts/destroy.pas
In SMS versions 1.2,| 2.0 | 2.1 and 2.2.2 the Destroy method would just delete the property named "prop" rather than the actually the properties of the object itself.
// JS CODE EMITTED IN OLD SMS 2.2.2 var TObject={ (...) Destroy: function (s) { for (var prop in s) if (s.hasOwnProperty(prop)) delete s.prop }, Destroy$: function(s) { return s.ClassType.Destroy(s) }, (...) }
... the result of the tests done at SMS 2.2.2, the above output is very similar to https://bitbucket.org/egrange/dwscript/src/master/Test/SimpleScripts/destroy.jstxt
In my mind, the overall tests looks correct to me, like the ver 3.1x
// Tests Free | Destroy | nil type TMyObj = class Field : Integer; end; var o : TMyObj; begin // Your code here console.log('Free'); o:=TMyObj.Create; o.Free; try o.Free; except on E: Exception do console.log(E.Message); // I think it shouldn't fire exception here end; try console.log(o.Field); except on E: Exception do console.log(E.Message); // Cannot read property because 'Field' of null / Field is unassigned end; try console.log(o.ClassName); except on E: Exception do console.log(E.Message); // Cannot read property '$classname' of null end; //------------------------------------------------- console.log('Destroy'); o:=TMyObj.Create; o.Destroy; try o.Destroy; except on E: Exception do console.log(E.Message); // I think it shouldn't fire an exception here end; try console.log(o.Field); except on E: Exception do console.log(E.Message); // 0 it should displays '0' end; try console.log(o.ClassName); except on E: Exception do console.log(E.Message); // it should displays 'TMyObj' end; //------------------------------------------------------------------- console.log('nil'); // nil o:=TMyObj.Create; o:=nil; o.Free; try console.log(o.Field); except on E: Exception do console.log(E.Message); // Uncaught TypeError: Cannot read property 'Field' of null end; try console.log(o.ClassName); except on E: Exception do console.log(E.Message); // Uncaught TypeError: Cannot read property '$classname' of null end;
-
repo owner I am sorry, I still do not understand the issue you are reporting :/ Is it that the fix on 3.x branch should be reported on 1.x and 2.x ?
-
reporter I really thank you for your time and sorry for the inconvenience.
In fact, Arnauld Bouchez has created interesting classes, generated by mORMot server, interface-based service can be accessed via a SmartPascal, just to prove the power the smart pascal. You can invoke a webservice created with MORMot to perform the authentication and call some services in smartmobilestudio. It was tested using SMS 2.1.1.
I spent hours debugging an mORMot with SMS 3.1x client, and realized something was a bit different from the previous SMS 2.1.1 but the mORMot classes couldn't work like in the previous version 2.x. It could be TObject/Destroy method issue.
Is it that the fix on 3.x branch should be reported on 1.x and 2.x? It could be reported on 1.x and 2.x branch, but I think it's not worthy. Chin up! Better days will come.
-
repo owner Do you have a minimal snippet of that code that worked, but not longer works, and is supposed to be correct ? (ie. without access to a freed object)
-
reporter No. It works as expected on 3.x branch.
Anyway, look at this piece of code, it was created by Arnauld Bouchez, it does work at the previous SMS 1.2 | 2.0x | 2.2.x versions. A closer look, there is "client.Free" method. Actually, the Free method does nothing there, TObject::Free automatically calls the destructor, but the TObject::Destroy method is buggy in those SMS versions.
On newer SMS 3.0x/3.1x, which the TObject::Destroy method is correct. When running the same project, it produces unexpected results. Take a closer look at the code, ...the code is also buggy :) he using "client.Free" onSuccess scope!
I deliberately changed client.Free to client:=nil. We need explicity to set object client to nil on the Success connection. This changes worked as expected at both SMS 3.x and 2.x versions.
var model: TSQLModel; people: TSQLRecordPeople; client: TSQLRestClientHTTP; begin model := GetModel; model.GetTableIndexExisting(TSQLRecordPeople); people := new TSQLRecordPeople; client := TSQLRestClientHTTP.Create('127.0.0.1',888,model,false); client.Connect( lambda if client.ServerTimeStamp=0 then ShowMessage('Impossible to retrieve server time stamp') else writeln('ServerTimeStamp='+IntToStr(client.ServerTimeStamp)); if not client.SetUser(TSQLRestServerAuthenticationDefault,'User','synopse') then ShowMessage('Authentication Error'); writeln('Safely connected with SessionID='+IntToStr(client.Authentication.SessionID)); people := TSQLRecordPeople.Create(client,1); // blocking request assert(people.ID=1); writeln('Disconnect from server'); client.Free; console.log(client); end, lambda ShowMessage('Impossible to connect to the server'); end); ( . . . ) procedure TSQLRestClientURI.Connect(onSuccess, onError: TSQLRestEvent); var Call: TSQLRestURIParams; begin SetAsynch(Call,onSuccess,onError, lambda result := (Call.OutStatus=HTTP_SUCCESS) and SetServerTimeStamp(Call.OutBody); end); CallBackGet('TimeStamp',[],Call,nil); // asynchronous call end;
- Log in to comment