https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126014

            Bug ID: 126014
           Summary: DSA: RACW receiver stub dispatches interface primitive
                    via wrong DT slot (crashes before body)
           Product: gcc
           Version: 16.1.1
               URL: https://codeberg.org/charlie5/aDSA/src/branch/main/exa
                    mples/asyncfn5
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: ada
          Assignee: unassigned at gcc dot gnu.org
          Reporter: rodakay5 at gmail dot com
                CC: dkm at gcc dot gnu.org
  Target Milestone: ---
              Host: x86_64-pc-linux-gnu
            Target: x86_64-pc-linux-gnu
             Build: x86_64-pc-linux-gnu

Created attachment 64881
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=64881&action=edit
asyncfn5 DSA reproducer + exp_dist.adb fix + stock-compiler proof and -gnatD
evidence

GNAT bug report
===============

Title: DSA: RACW receiver stub (gnatzr) dispatches to wrong primary-DT slot
       when concrete type has non-interface own primitives before overriding
       declarations

Summary
-------
When a concrete type that implements a Remote_Types interface (RACW dispatch)
also has non-interface own primitive declarations that appear BEFORE the
interface-overriding declarations, the GNAT-generated receiver stub (produced
by  gcc -gnatzr  from the interface spec) dispatches to the wrong dispatch
table slot and crashes (STORAGE_ERROR / SIGSEGV) before the user body is
entered.

The slot index hardcoded in the receiver stub is computed from the interface
type's own-primitive count and is correct only when the concrete type's DT
has the interface's prims starting at exactly the same slot — which fails
whenever the concrete type has extra own prims beforehand.

Affected: reproduced and fixed on FSF GNAT 16.1.1 (gcc 16, trunk, 20260625).
The receiver-stub builder (exp_dist.adb GARLIC_Support.Build_Subprogram_
Receiving_Stubs) is structurally the same on the 14.x / 15.x branches, so they
are very likely affected too, but that was not verified here.  This report
concerns the GARLIC (stream-based) personality, which our ZMQ PCS uses; the
PolyORB "Any" personality was not investigated (the same application is
reported to run under PolyORB without this crash).

How to verify (please read before triaging as "cannot reproduce")
-----------------------------------------------------------------
This is a Distributed Systems Annex (Annex E) code-generation bug, so it only
manifests with a PCS in place.  Stock FSF GNAT ships
System.Partition_Interface.DSA_Implementation = No_DSA, under which exp_dist
generates no stubs at all -- a plain build of the reproducer silently behaves
non-distributed and shows nothing.  Generating the faulty stub requires a
runtime that selects a PCS personality (here, GARLIC_DSA); the attached
ZMQ-backed PCS provides one.

You do NOT need to run it to confirm the bug:

  * Static evidence (no PCS, no run) -- the attached -gnatD dumps.  In
    world_types.ads.dg the receiver for the interface primitive Sprites
    (SubpId 21) hardcodes  ...itemH!(tag).all(22);  in world_final.ads.dg the
    concrete type's primary dispatch table has register_mirror at slot 22 and
    Sprites at slot 26 -- so the receiver dispatches to the wrong primitive.

  * Stock-compiler demonstration -- prove_fix.adb (build_prove_fix.sh) builds
    and runs with a plain gcc/gnat, no PCS or libraries, and shows that
    dispatching the interface primitive through the primary view (what the stub
    does today) reaches the wrong primitive, while dispatching through the
    interface view (what the patch does, via Ada.Tags.Displace) reaches the
    real body.

Full end-to-end reproduction with the PCS is in the "To reproduce" section
below; the server raises STORAGE_ERROR before the user body runs.

Minimal reproducer
------------------
Location: examples/asyncfn5/   (self-contained; builds with build_asyncfn5.sh)

Type hierarchy (the relevant structure):

  -- Interfaces (Remote_Types):
  package Iface_Subject  -- 11 abstract prims
  package Iface_Observer -- 6  abstract prims
  package World_Types    -- 8  own abstract prims incl. Sprites

  -- Concrete chain (server-side only):
  World_Base.Object  new Iface_Subject.Item and Iface_Observer.Item
                       -- implements all 17 inherited prims (slots 1..17)
       |
  World_Mid.Object   abstract new World_Base.Object and World_Types.Item
       |                -- HERE: 4 extra non-interface own prims declared
BEFORE
       |                -- the 8 overriding declarations for World_Types
       v
  World_Final.Object new World_Mid.Object with null record
                       -- the actually-allocated type; inherits everything

  -- RCI (factory):
  World_Svc  Remote_Call_Interface
    function Make (Initial : Integer) return World_Types.View;
                       -- returns World_Types.View pointing at
World_Final.Object

World_Mid.ads (the critical ordering — see source):

  type Object is abstract new World_Base.Object and World_Types.Item
    with null record;

  --  4 NON-INTERFACE own prims (slots 18..21 in primary DT):
  function  World_Id      (Self : in     Object) return Integer;
  function  World_Age     (Self : in     Object) return Float;
  procedure World_Age_Is  (Self : in out Object; Age : in Float);
  procedure World_Destroy (Self : in out Object);

  --  World_Types overrides (slots 22..29 in primary DT, shifted by 4):
  overriding procedure Register_Mirror   (...);   -- slot 22
  overriding procedure Deregister_Mirror (...);   -- slot 23
  overriding function  Graphics_Models   (...);   -- slot 24
  overriding function  Physics_Models    (...);   -- slot 25
  overriding function  Sprites           (...);   -- slot 26  <-- ACTUALLY HERE
  overriding procedure Motion_Updates_Are(...);   -- slot 27
  overriding procedure Kick_Sprite       (...);   -- slot 28
  overriding function  Rich_Sprites      (...);   -- slot 29

The primary DT of World_Final.Object has 29 slots (confirmed by -gnatD dump).

What GNAT generates (gnatzr on World_Types.ads)
------------------------------------------------
The gnatzr-generated receiver for Sprites (SubpId 21, 0-indexed) calls:

  world_types__F268s__B1240s__T1243s!(
    world_types__itemH!(
      world_types__view!(system__address?(P265s)).all._tag).all(22))
    .all(world_types__view!(system__address?(P265s)).all)

Slot 22 is hardcoded.  It is derived from:
  11 (Subject) + 6 (Observer) + 4 (own prims of World_Types before Sprites)
  = 21 prims before Sprites, so prim #22 (1-indexed) => .all(22).

This is correct for asyncfn4 (no extra prims), where slot 22 = Sprites.
This is WRONG for asyncfn5, where slot 22 = Register_Mirror.

Calling Register_Mirror
  (procedure (Self : access Object; Mirror : Integer; Mirror_Obs : View))
with the Sprites ABI
  (function (Self : in out Object) return Sprite_Pairs)
corrupts the stack => SIGSEGV => STORAGE_ERROR.

Confirmed by -gnatD dump of World_Final.ads
------------------------------------------
  world_final__objectT.prims_ptr = (
    [1]  world_base__name_s,
    [2]  world_base__next_seq,
    ... (slots 1..17: Subject 11 + Observer 6 inherited from World_Base)
    [18] world_mid__world_id,
    [19] world_mid__world_age,
    [20] world_mid__world_age_is,
    [21] world_mid__world_destroy,
    [22] world_mid__register_mirror,   <- gnatzr calls .all(22) for Sprites
    [23] world_mid__deregister_mirror,
    [24] world_mid__graphics_models,
    [25] world_mid__physics_models,
    [26] world_mid__sprites,           <- actually here, never reached
    [27] world_mid__motion_updates_are,
    [28] world_mid__kick_sprite,
    [29] world_mid__rich_sprites);

Observed behavior
-----------------
Server raises STORAGE_ERROR before the Sprites body is ever entered.
Client re-raises the exception.  Confirmed: the diagnostic Put_Line at the
start of the Sprites body body ("THIS LINE SHOULD NOT APPEAR") never prints.

Expected behavior
-----------------
The receiver stub should call the correct function (slot 26,
world_mid__sprites)
rather than using a hardcoded slot number that assumes the concrete type has no
extra own primitives before its interface overrides.

The correct fix is for the receiver stub to dispatch through the World_Types
interface's secondary dispatch table (which contains thunks that map each
interface primitive to its actual slot in the concrete type's primary DT),
rather than computing and hardcoding a primary-DT slot offset from the
interface's own-prim position.

Regression test
---------------
asyncfn4 (same structure, but without the 4 extra non-interface prims) PASSES.
This proves the PCS runtime is correct; the bug is purely in gnatzr code-gen.

To reproduce
------------
1.  Install a DSA-capable PCS that uses the GARLIC (stream-based) personality;
    the ZMQ PCS from this repo works.  (See "How to verify" above for why a PCS
    is needed and how to confirm the bug without running it.)
2.  cd examples/asyncfn5
3.  bash build_asyncfn5.sh          # builds server_main5 + client_main5
4.  pcs_nameserver &
5.  ./obj-server5/server_main5 &
6.  ./obj-client5/client_main5
7.  Observe: client prints "[client] calling Sprites ..." then
    "raised STORAGE_ERROR : s-intman.adb:136 explicit raise".
    The server log is empty (body never entered).

Alternatively: run  bash build_asyncfn4.sh  and  obj-server4/server_main4 &
then  obj-client4/client_main4  to see the PASS case (asyncfn4 has no extra
own prims before overriding declarations).

Proposed fix
------------
See  0001-DSA-displace-RACW-controlling-actual-to-interface-view.patch
(against gcc/ada/exp_dist.adb).

Root cause: in Build_Subprogram_Receiving_Stubs (the GARLIC variant), the
controlling object for the dispatching call is rebuilt from the marshalled
address with a bare

    Unchecked_Convert_To (RACW_Type, OK_Convert_To (Address, Object))

The marshalled value is the object's BASE (primary) address, so this access
value is the primary view. When RACW_Type designates an interface, the
ensuing dispatching call reads the object's primary tag and indexes it with
the primitive's interface-relative position -- which is only valid when the
concrete type adds no primitives ahead of its interface overrides.

The fix wraps the address in Ada.Tags.Displace (addr, Item'Tag) when the
designated type is an interface, so the access value is the interface
(secondary) view and the dispatch goes through the interface's dispatch
table. This is the same RTL routine (RE_Displace) the front end already
inserts for an ordinary access conversion to an interface class-wide type.

Validation without rebuilding GCC: prove_fix.adb (build_prove_fix.sh)
reproduces both code paths by hand on a local World_Final.Object. It builds
with a STOCK gnat compiler -- no PCS, no custom RTS, no libzmq -- because the
discrepancy is pure Ada 2005 interface dispatch (primary view vs interface
view); the DSA is only how the un-displaced pointer arises in practice. The
displaced (patched) path enters the real Sprites body and returns 3 pairs;
the raw-address (current) path does not enter Sprites and returns garbage:

    [PATCHED path] interface view (compiler-displaced), dispatch Sprites:
    [server/World_Mid] Sprites body entered ...
       -> Sprites returned 3 pairs.  CORRECT: reached the real Sprites body.
    [BUGGY path] primary address, NO displace, dispatch Sprites:
       -> returned 996358900 pairs (unsound; any value here is luck).

Scope: this patch addresses the GARLIC (stream-based) receiving-stub builder
(GARLIC_Support.Build_Subprogram_Receiving_Stubs), the one the reproducer
exercises. The PolyORB receiving-stub builder
(PolyORB_Support.Build_Subprogram_Receiving_Stubs) uses a structurally similar
raw-address conversion (Get_Local_Address -> Unchecked_Convert_To (RACW_Type,
Addr)), so it may be susceptible to the same issue; however, the same
application is reported to run under a PolyORB-based PCS without this crash, so
PolyORB is left out of scope and not investigated here.

Status: the patch has been built into a full GCC and verified end-to-end -- it
fixes the crash both in the asyncfn5 reproducer and in the real application the
bug was found in. It is validated by prove_fix on a stock compiler as above.

GNAT version
------------
Output of  gcc --version:
  gcc (GCC) 16.1.1 20260625   (target x86_64-pc-linux-gnu)
  Verified on this version; 14.x/15.x likely affected by inspection, not
tested.

Related
-------
This bug was discovered while implementing a ZMQ-backed PCS for Ada's
Distributed Systems Annex (Annex E) as a replacement for PolyORB.  It
manifests in the charlie5/lace+gel ("arcana") application where gel.World.Item
declares many non-interface own primitives (Id, Age, Space, local_Observer,
local_Subject, etc.) before its gel.remote.World.item overrides, causing the
gel.remote.world.Sprites receiver to dispatch to an unrelated function.

Reply via email to