Destroy method

Issue #150 new
Warley Alex Camargo created an issue

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)

  1. Eric Grange 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?

  2. Warley Alex Camargo 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;
    
  3. Eric Grange 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 ?

  4. Warley Alex Camargo 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.

  5. Eric Grange 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)

  6. Warley Alex Camargo 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;
    
  7. Log in to comment