Is this a gnat bug? I think so possibly

I shortened this down to a small useless example and tested against 12.2 on godbolt. Unless I am missing something I think GNAT has a bug and wanted to check and make sure I wasn’t missing something obvious. There’s not any unchecked operations that I can tell. The example is obtuse, but I was whittling it down from someone’s code that I was reviewing and had to do a lot of reorg/renaming to avoid showing anything proprietary. At the end, I think the person ends up with a pointer to a temporary object that no longer exists.

EDIT: the potential bug is that it assigns an access to an out of scope object:

p : Instance_Access := Bug.Make.Bad;

Full code:

function Square(num : Integer) return Integer is

    package Bug is
        type Instance is tagged limited private;
        function Make return Instance;
        function Bad(Self : aliased Instance) return not null access constant Instance;
    private
        type Inner(Source : not null access Instance) is limited null record;
        type Instance is tagged limited record
            Core : Inner(Instance'Access);
        end record;
    end Bug;
    
    package body Bug is
        function Make return  Instance is 
        begin
            return Result : Instance;
        end Make;
        
        function Bad(Self : aliased Instance) return not null access constant Instance is
        begin
            return Self.Core.Source;
        end Bad;
    end Bug;
    
    type Instance_Access is access constant Bug.Instance;
    
    p : Instance_Access := Bug.Make.Bad;
    
begin
      
    return 0;
end Square;

What is the bug? The compilation succeeds on godbolt: Compiler Explorer

There’s certainly a bug, but I think it’s in this code, not the compiler? unless it’s illegal to create a limited object in the scope of an expression. Could just be erroneous.

The problem as I see it is that in the line

      p : Instance_Access := Bug.Make.Bad;

Bug.Make returns an anonymous object, .Bad returns a pointer to that object, and after the assignment the anonymous object has been finalized but the pointer to it remains.

Extended demo:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Finalization;

procedure Jere is

   function Square (num : Integer) return Integer is

      package Bug is
         type Instance is new Ada.Finalization.Limited_Controlled with private;
         function Make return Instance;
         function Bad
           (Self : aliased Instance) return not null access constant Instance;
         function Instance_Number (Self : Instance) return Natural;
      private
         type Inner (Source : not null access Instance) is limited null record;
         type Instance is new Ada.Finalization.Limited_Controlled with record
            Instance_Number : Natural := 0;
            Finalized : Boolean := False;
            Core : Inner (Instance'Access);
         end record;
         overriding procedure Initialize (Obj : in out Instance);
         overriding procedure Finalize (Obj : in out Instance);
      end Bug;

      package body Bug is
         function Make return Instance is
         begin
            return Result : Instance;
         end Make;

         function Bad
           (Self : aliased Instance) return not null access constant Instance
         is
         begin
            Put_Line ("in Bad");
            return Self.Core.Source;
         end Bad;

         function Instance_Number (Self : Instance) return Natural
           is (Self.Instance_Number);

         Count : Natural := 0;

         procedure Initialize (Obj : in out Instance) is
         begin
            Count := Count + 1;
            Obj.Instance_Number := Count;
            Put_Line ("initializing object" & Obj.Instance_Number'Image);
         end Initialize;

         procedure Finalize (Obj : in out Instance) is
         begin
            if Obj.Finalized then
               Put ("re-");
            end if;
            Obj.Finalized := True;
            Put_Line ("finalizing object" & Obj.Instance_Number'Image);
            Obj.Instance_Number := Natural'Last;
         end Finalize;
      end Bug;

      type Instance_Access is access constant Bug.Instance;

      p : Instance_Access := Bug.Make.Bad;

   begin
      Put_Line ("in Square; instance number" & P.Instance_Number'Image);
      return 0;
   end Square;

begin
   Put_Line ("about to call Square ...");
   declare
      Dummy : Integer;
   begin
      Dummy := Square (42);
   end;
   Put_Line ("... done.");
end Jere;

I thought that the compiler should prevent (either compile time or runtime) the assignment of that access object, since it points to temporary memory that is no longer in scope. I don’t think I am doing anything unchecked in that code?

If I am doing something unchecked,then I agree it’s a bug in the code. But if I am not, then I think the compiler is supposed to catch it and it would be a compiler bug? To be fair, I don’t know the heart of darkness section very well, so it could be a language bug, but that’s less likely than the first two scenarios.

Simon hit up on it, but I think the compiler should prevent that access object assignment, either at runtime or compile time (not sure which). I was just making sure the code I presented didn’t have some sort of unchecked programming that I am missing.

I’m reviewing code similar to this, so I wanted to make sure I address it in the review correctly.