blob: 5a5ae16193e5efb61cb9c7c040ccfc1353e8a4a1 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT COMPILER COMPONENTS --
-- --
-- G E N E R A T E _ M I N I M A L _ R E P R O D U C E R --
-- --
-- B o d y --
-- --
-- Copyright (C) 2024-2025, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 3, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
-- for more details. You should have received a copy of the GNU General --
-- Public License distributed with GNAT; see file COPYING3. If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by AdaCore. --
-- --
------------------------------------------------------------------------------
with Atree;
with Fmap;
with Fname.UF;
with Lib;
with Namet; use Namet;
with Osint; use Osint;
with Output; use Output;
with Sinfo.Nodes; use Sinfo.Nodes;
with System.CRTL;
with System.OS_Lib; use System.OS_Lib;
with Types; use Types;
with Uname;
procedure Generate_Minimal_Reproducer is
Reproducer_Generation_Failed : exception;
function Create_Reproducer_Directory return String;
-- Create a directory that will be used to run adareducer, and will
-- eventually contain the reduced set of sources to be collected by the
-- user. The name of the directory makes its purpose clear, and it has a
-- numeric suffix to avoid clashes with other compiler invocations that
-- might have generated reproducers already.
---------------------------------
-- Create_Reproducer_Directory --
---------------------------------
function Create_Reproducer_Directory return String is
Max_Id : constant Positive := 1000;
Prefix : constant String := "reduce-crash-reproducer";
Result : System.CRTL.int;
begin
for Id in 1 .. Max_Id loop
declare
Candidate_Path : String := Prefix & Positive'Image (Id);
begin
Candidate_Path (Prefix'Length + 1) := '-';
Result := System.CRTL.mkdir (Candidate_Path & ASCII.NUL);
-- If mkdir fails, we assume that it's because the directory
-- already exists. We should check for EEXIST instead???
if Result = 0 then
return Candidate_Path;
end if;
end;
end loop;
Write_Line ("failed to create reproducer directory");
raise Reproducer_Generation_Failed;
end Create_Reproducer_Directory;
Dirname : constant String := Create_Reproducer_Directory;
Gpr_File_Path : constant String :=
Dirname & Directory_Separator & "reduce_crash_reproducer.gpr";
Src_Dir_Path : constant String := Dirname & Directory_Separator & "src";
Oracle_Path : constant String :=
Dirname & Directory_Separator & Executable_Name ("oracle");
Main_Library_Item : constant Node_Id := Unit (Lib.Cunit (Main_Unit));
-- There is a special case that we need to detect: when the main library
-- item is the instantiation of a generic that has a body, and the
-- instantiation of generic bodies has started. We start by binding whether
-- the main library item is an instantiation to the following constant.
Main_Is_Instantiation : constant Boolean :=
Nkind (Atree.Original_Node (Main_Library_Item))
in N_Generic_Instantiation;
-- If the main library item is an instantiation and its unit name is a body
-- name, it means that Make_Instance_Unit has been called. We need to use
-- the corresponding spec name to reconstruct the on-disk form of the
-- semantic closure.
Main_Unit_Name : constant Unit_Name_Type :=
(if Main_Is_Instantiation
and then Uname.Is_Body_Name (Lib.Unit_Name (Main_Unit))
then Uname.Get_Spec_Name (Lib.Unit_Name (Main_Unit))
else Lib.Unit_Name (Main_Unit));
Result : Integer;
begin
Create_Semantic_Closure_Project :
declare
Gpr_File : File_Descriptor;
B : constant Saved_Output_Buffer := Save_Output_Buffer;
begin
Gpr_File := Create_File (Gpr_File_Path, Text);
if Gpr_File = Invalid_FD then
Write_Line ("failed to create GPR file");
raise Reproducer_Generation_Failed;
end if;
Push_Output;
Set_Output (Gpr_File);
Write_Line ("project Reduce_Crash_Reproducer is");
Write_Line (" for Source_Dirs use (""src"");");
Write_Line ("end Reduce_Crash_Reproducer;");
Close (Gpr_File);
Pop_Output;
Restore_Output_Buffer (B);
Result := System.CRTL.mkdir (Src_Dir_Path & ASCII.NUL);
if Result /= 0 then
Write_Line ("failed to create reproducer directory");
raise Reproducer_Generation_Failed;
end if;
for J in Main_Unit .. Lib.Last_Unit loop
-- We skip library units that fall under one of the following cases:
-- - Internal library units.
-- - Units that were synthesized for child subprograms without spec
-- files.
-- - Dummy entries that Add_Preprocessing_Dependency puts in
-- Lib.Units.
-- Those cases correspond to the conjuncts in the condition below.
if not Lib.Is_Internal_Unit (J)
and then Comes_From_Source (Lib.Cunit (J))
and then Lib.Unit_Name (J) /= No_Unit_Name
then
declare
Path : File_Name_Type :=
Fmap.Mapped_Path_Name (Lib.Unit_File_Name (J));
Unit_Name : constant Unit_Name_Type :=
(if J = Main_Unit then Main_Unit_Name else Lib.Unit_Name (J));
Default_File_Name : constant String :=
Fname.UF.Get_Default_File_Name (Unit_Name);
File_Copy_Path : constant String :=
Src_Dir_Path & Directory_Separator & Default_File_Name;
begin
-- Mapped_Path_Name might have returned No_File. This has been
-- observed for files with a Source_File_Name pragma.
if Path = No_File then
Path := Find_File (Lib.Unit_File_Name (J), Osint.Source);
pragma Assert (Path /= No_File);
end if;
declare
File_Path : constant String := Get_Name_String (Path);
Success : Boolean;
begin
System.OS_Lib.Copy_File
(File_Path, File_Copy_Path, Success, Overwrite);
pragma Assert (Success);
end;
end;
end if;
end loop;
end Create_Semantic_Closure_Project;
Create_Oracle :
declare
Gnatmake_Path : String_Access := Locate_Exec_On_Path ("gnatmake");
Oracle_Dir_Path : constant String :=
Dirname & Directory_Separator & "oracle-src";
Source_File_Path : constant String :=
Oracle_Dir_Path & Directory_Separator & "oracle.adb";
Source_File : File_Descriptor;
Result : System.CRTL.int;
begin
if Gnatmake_Path = null then
Write_Line ("-gnatd_m was specified but gnatmake is not available");
raise Reproducer_Generation_Failed;
end if;
Result := System.CRTL.mkdir (Oracle_Dir_Path & ASCII.NUL);
if Result /= 0 then
Write_Line ("failed to create directory");
raise Reproducer_Generation_Failed;
end if;
Source_File := Create_File (Source_File_Path, Text);
if Source_File = Invalid_FD then
Write_Line ("failed to create oracle source file");
raise Reproducer_Generation_Failed;
end if;
Write_Oracle_Code :
declare
Old_Main_Path : constant String :=
Get_Name_String
(Fmap.Mapped_Path_Name (Lib.Unit_File_Name (Main_Unit)));
Default_Main_Name : constant String :=
Fname.UF.Get_Default_File_Name (Main_Unit_Name);
New_Main_Path : constant String :=
Src_Dir_Path & Directory_Separator & Default_Main_Name;
Gnat1_Path : String (1 .. Len_Arg (0));
B : constant Saved_Output_Buffer := Save_Output_Buffer;
begin
Fill_Arg (Gnat1_Path'Address, 0);
Push_Output;
Set_Output (Source_File);
Write_Line ("with Ada.Command_Line;");
Write_Line ("use Ada.Command_Line;");
Write_Line ("with GNAT.Expect;");
Write_Line ("with GNAT.OS_Lib;");
Write_Eol;
Write_Line ("procedure Oracle is");
Write_Line (" Child_Code : aliased Integer;");
Write_Eol;
Write_Line (" Gnat1_Path : constant String := ");
Write_Str (" """);
Write_Str (Gnat1_Path);
Write_Line (""";");
Write_Eol;
Write_Line (" Args : constant GNAT.OS_Lib.Argument_List :=");
Write_Str
(" (new String'(""-quiet""), new String'(""-gnatd_M"")");
-- The following way of iterating through the command line arguments
-- was copied from Set_Targ. TODO factorize???
declare
type Arg_Array is array (Nat) of Big_String_Ptr;
type Arg_Array_Ptr is access Arg_Array;
-- Types to access compiler arguments
save_argc : Nat;
pragma Import (C, save_argc);
-- Saved value of argc (number of arguments), imported from
-- misc.cc
save_argv : Arg_Array_Ptr;
pragma Import (C, save_argv);
-- Saved value of argv (argument pointers), imported from misc.cc
gnat_argc : Nat;
gnat_argv : Arg_Array_Ptr;
pragma Import (C, gnat_argc);
pragma Import (C, gnat_argv);
-- If save_argv is not set, default to gnat_argc/argv
argc : Nat;
argv : Arg_Array_Ptr;
function Len_Arg (Arg : Big_String_Ptr) return Nat;
-- Determine length of argument Arg (a nul terminated C string).
-------------
-- Len_Arg --
-------------
function Len_Arg (Arg : Big_String_Ptr) return Nat is
begin
for J in 1 .. Nat'Last loop
if Arg (Natural (J)) = ASCII.NUL then
return J - 1;
end if;
end loop;
raise Program_Error;
end Len_Arg;
begin
if save_argv /= null then
argv := save_argv;
argc := save_argc;
else
-- Case of a non-GCC compiler, e.g. gnat2why or gnat2scil
argv := gnat_argv;
argc := gnat_argc;
end if;
for Arg in 1 .. argc - 1 loop
declare
Argv_Ptr : constant Big_String_Ptr := argv (Arg);
Argv_Len : constant Nat := Len_Arg (Argv_Ptr);
Arg : constant String := Argv_Ptr (1 .. Natural (Argv_Len));
begin
-- We filter out mapping file arguments because we want to
-- use the copies of source files we made.
if Argv_Len > 8 and then Arg (1 .. 8) = "-gnatem=" then
null;
-- We must not have the oracle run the compiler in
-- reduce-on-crash mode, that would result in recursive
-- invocations.
elsif Arg = "-gnatd_m" then
null;
else
Write_Line (",");
Write_Str (" new String'(""");
-- We replace references to the main source file with
-- references to the copy we made.
if Old_Main_Path = Arg then
Write_Str (New_Main_Path);
-- We copy the other command line arguments unmodified
else
Write_Str (Arg);
end if;
Write_Str (""")");
end if;
end;
end loop;
end;
Write_Line (");");
Write_Eol;
Write_Line (" Output : constant String :=");
Write_Line (" GNAT.Expect.Get_Command_Output");
Write_Str (" (Gnat1_Path, Args, """", Child_Code'Access, ");
Write_Line ("Err_To_Out => True);");
Write_Eol;
Write_Line (" Crash_Marker : constant String :=");
Write_Line (" ""+===========================GNAT BUG DETECTE"";");
Write_Eol;
Write_Line (" Crashed : constant Boolean :=");
Write_Line (" Crash_Marker'Length <= Output'Length");
Write_Str (" and then Output (Output'First .. Output'First ");
Write_Line ("+ Crash_Marker'Length - 1)");
Write_Line (" = Crash_Marker;");
Write_Eol;
Write_Str (" Status_Code : Exit_Status := ");
Write_Line ("(if Crashed then 0 else 1);");
Write_Line ("begin");
Write_Line (" Set_Exit_Status (Status_Code);");
Write_Line ("end Oracle;");
Pop_Output;
Restore_Output_Buffer (B);
end Write_Oracle_Code;
Close (Source_File);
declare
Args : constant Argument_List :=
(new String'(Source_File_Path),
new String'("-o"),
new String'(Oracle_Path),
new String'("-D"),
new String'(Oracle_Dir_Path));
Success : Boolean;
begin
Spawn (Gnatmake_Path.all, Args, Success);
pragma Assert (Success);
end;
Free (Gnatmake_Path);
end Create_Oracle;
Run_Adareducer :
declare
-- See section 12.8.3 of the GNAT Studio user's guide for documentation
-- about how to invoke adareducer.
Gnatstudio_Cli_Path : String_Access :=
Locate_Exec_On_Path ("gnatstudio_cli");
begin
if Gnatstudio_Cli_Path = null then
Write_Line ("-gnatd_m was specified but adareducer is not available");
return;
end if;
declare
Args : constant Argument_List :=
(new String'("adareducer"),
new String'("-P"),
new String'(Gpr_File_Path),
new String'("-s"),
new String'(Oracle_Path));
Success : Boolean;
begin
Spawn (Gnatstudio_Cli_Path.all, Args, Success);
pragma Assert (Success);
end;
Free (Gnatstudio_Cli_Path);
end Run_Adareducer;
Clean_Up_Reproducer_Source :
declare
use type System.Address;
Directory_Stream : System.CRTL.DIRs;
function opendir (file_name : String) return System.CRTL.DIRs with
Import, Convention => C, External_Name => "__gnat_opendir";
Conservative_Name_Max : constant Positive := 4096;
Buffer : String (1 .. Conservative_Name_Max);
Length : aliased Integer;
Addr : System.Address;
Dummy : Integer;
Dummy_Success : Boolean;
function readdir
(Directory : System.CRTL.DIRs;
Buffer : System.Address;
Length : access Integer) return System.Address
with Import, Convention => C, External_Name => "__gnat_readdir";
function closedir (directory : System.CRTL.DIRs) return Integer with
Import, Convention => C, External_Name => "__gnat_closedir";
begin
Directory_Stream := opendir (Src_Dir_Path & ASCII.NUL);
if Directory_Stream = System.Null_Address then
return;
end if;
loop
Addr := readdir (Directory_Stream, Buffer'Address, Length'Access);
if Addr = System.Null_Address then
exit;
end if;
declare
S : constant String := Buffer (1 .. Length);
begin
if (5 <= S'Length and then S (S'Last - 4 .. S'Last) = ".orig")
or else (2 <= S'Length and then S (S'Last - 1 .. S'Last) = ".s")
then
System.OS_Lib.Delete_File
(Src_Dir_Path & Directory_Separator & S, Dummy_Success);
end if;
end;
end loop;
Dummy := closedir (Directory_Stream);
end Clean_Up_Reproducer_Source;
end Generate_Minimal_Reproducer;