_Intf codegen emits access to ancestor's private fields when a struct descends from a struct in a used RODL (E2361)

Product / versions

  • Remoting SDK + Data Abstract for Delphi 10.0.0.1627 — RAD Studio 13.1 (Win32/Win64) → fails
  • Same project with Remoting SDK + Data Abstract for Delphi 10.0.0.1553 — RAD Studio 11.2 → compiled fine

Summary

When a RODL struct declares an Ancestor that comes from a used RODL marked DontCodeGen=“1” (the classic MyUserInfo : UserInfo case for SimpleLoginService), the generated _Intf.pas assigns directly to the backing fields of the ancestor. Those fields are declared private in another unit (DataAbstract4_Intf.pas), so the unit cannot compile.

RODL (minimal repro)

<Structs>
  <Struct Name="LeContManagementUserInfo" UID="{576C3CF8-...}" AutoCreateParams="1" Ancestor="UserInfo">
    <Elements>
      <Element Name="IDRole"   DataType="Integer" />
      <Element Name="RoleName" DataType="WideString" />
      <Element Name="Name"     DataType="WideString" />
      <Element Name="Surname"  DataType="WideString" />
    </Elements>
  </Struct>
</Structs>
<Uses>
  <Use Name="DataAbstract" UID="{EBEAC9B9-...}"
       Rodl="$(Data Abstract for Delphi)\Source\DataAbstract4.RODL"
       DontCodeGen="1" UsedRodlUID="{DC8B7BE2-...}">
    <Includes Delphi="DataAbstract4" ... />
  </Use>
</Uses>

Generated code (broken) — LeContManagementDA_Intf.pas, non-RecordStrictOrder branch of ReadComplex / WriteComplex:

LeContManagementUserInfo = class(DataAbstract4_Intf.UserInfo)   // ancestor lives in ANOTHER unit
...
procedure LeContManagementUserInfo.ReadComplex(aSerializer: TObject);
begin
  ...
  else begin
    __Serializer.ReadArrayWithErrorHandling('Attributes', DataAbstract4_Intf.VariantArray, l_Attributes);
    if Self.int_Attributes <> l_Attributes then FreeOrDisposeOf(Self.int_Attributes);
    Self.fAttributes := l_Attributes;    // <-- E2361
    ...
    Self.fPrivileges := l_Privileges;    // <-- E2361
    Self.fSessionID  := l_SessionID;     // <-- E2361
    Self.fUserData   := l_UserData;      // <-- E2361
    Self.fUserID     := l_UserID;        // <-- E2361
  end;
end;

procedure LeContManagementUserInfo.WriteComplex(aSerializer: TObject);
begin
  ...
  else begin
    ...
    l_SessionID := Self.fSessionID;      // <-- E2361
    l_UserID    := Self.fUserID;         // <-- E2361
  end;
end;

Ancestor declaration — DataAbstract4_Intf.pas (shipped, unmodified):

UserInfo = class(TROComplexType)
private
  fSessionID: ROUTF8String;
  fUserID: ROUTF8String;
  fPrivileges: StringArray;
  fAttributes: VariantArray;
  fUserData: Binary;
  ...
protected
  property int_Privileges: StringArray read fPrivileges;
  property int_Attributes: VariantArray read fAttributes;
  property int_UserData: Binary read fUserData;
published
  property SessionID: ROUTF8String read fSessionID write fSessionID;
  property UserID: ROUTF8String read fUserID write fUserID;
  property Privileges: StringArray read GetPrivileges write fPrivileges;
  property Attributes: VariantArray read GetAttributes write fAttributes;
  property UserData: Binary read GetUserData write fUserData;
end;

Compiler output

[dcc32 Error] LeContManagementDA_Intf.pas(353): E2361 Cannot access private symbol UserInfo.fAttributes
[dcc32 Error] LeContManagementDA_Intf.pas(362): E2361 Cannot access private symbol UserInfo.fPrivileges
[dcc32 Error] LeContManagementDA_Intf.pas(366): E2361 Cannot access private symbol UserInfo.fSessionID
[dcc32 Error] LeContManagementDA_Intf.pas(373): E2361 Cannot access private symbol UserInfo.fUserData
[dcc32 Error] LeContManagementDA_Intf.pas(375): E2361 Cannot access private symbol UserInfo.fUserID
[dcc32 Error] LeContManagementDA_Intf.pas(416): E2361 Cannot access private symbol UserInfo.fSessionID
[dcc32 Error] LeContManagementDA_Intf.pas(422): E2361 Cannot access private symbol UserInfo.fUserID

Analysis

The generator is clearly aware that the ancestor is external — for arrays and binary it correctly reads through the protected accessors (Self.int_Attributes, Self.int_Privileges, Self.int_UserData). It is only the assignments (and the two scalar reads of SessionID/UserID) that go through Self.f, which is legal only when the ancestor is generated into the same unit. The RecordStrictOrder branch is fine, since it delegates inherited members to inherited ReadComplex / inherited WriteComplex; only the flattened (non-strict-order) branch is affected.

An older generated version of the very same unit, still present in our repository and used by the client application, emitted the published properties instead and compiles without changes:

Self.Attributes := l_Attributes;
Self.SessionID := l_SessionID;
l_UserID := Self.UserID;

This looks like the Delphi counterpart of bug #77561 (“C++ compiling error when FreeAndNil of private properties in an inherited structure”), fixed for the C++Builder generator by switching this->f… to this->int_…

Workaround currently in use

Hand-editing the 7 lines in the generated _Intf.pas (Self.f → Self.) and disabling the {#ROGEN:…} directive in the .dpr, so the IDE compile notifier does not regenerate — and thus re-break — the unit on every build.

Hi,

thanks for report. fixed.

I’ve attached archive with updated codegen and rodl2code.exe program.

  • copy content of archive to C:\Program Files (x86)\RemObjects Software\RemObjects SDK (Common)\Bin\update\ (new folder)
  • launch a.cmd from that folder with admin rights.

note: you can extract archive to other folder, but you need to adjust path inside a.cmd.
codegen.zip (1.1 MB)

Thank you very much @EvgenyK

That solved the issue!

Best regards