-- --
------------------------------------------------------------------------------
-with Diagnostics.Utils; use Diagnostics.Utils;
-with Diagnostics.JSON_Utils; use Diagnostics.JSON_Utils;
-with Gnatvsn; use Gnatvsn;
-with Output; use Output;
-with Sinput; use Sinput;
-with Lib; use Lib;
-with Namet; use Namet;
-with Osint; use Osint;
-with Errout; use Errout;
+with Errout; use Errout;
+with Diagnostics.JSON_Utils; use Diagnostics.JSON_Utils;
+with Diagnostics.Utils; use Diagnostics.Utils;
+with Gnatvsn; use Gnatvsn;
+with Lib; use Lib;
+with Namet; use Namet;
+with Osint; use Osint;
+with Output; use Output;
+with Sinput; use Sinput;
+with System.OS_Lib;
package body Diagnostics.SARIF_Emitter is
+ -- SARIF attribute names
+
+ N_ARTIFACT_CHANGES : constant String := "artifactChanges";
+ N_ARTIFACT_LOCATION : constant String := "artifactLocation";
+ N_COMMAND_LINE : constant String := "commandLine";
+ N_DELETED_REGION : constant String := "deletedRegion";
+ N_DESCRIPTION : constant String := "description";
+ N_DRIVER : constant String := "driver";
+ N_END_COLUMN : constant String := "endColumn";
+ N_END_LINE : constant String := "endLine";
+ N_EXECUTION_SUCCESSFUL : constant String := "executionSuccessful";
+ N_FIXES : constant String := "fixes";
+ N_ID : constant String := "id";
+ N_INSERTED_CONTENT : constant String := "insertedContent";
+ N_INVOCATIONS : constant String := "invocations";
+ N_LOCATIONS : constant String := "locations";
+ N_LEVEL : constant String := "level";
+ N_MESSAGE : constant String := "message";
+ N_NAME : constant String := "name";
+ N_ORIGINAL_URI_BASE_IDS : constant String := "originalUriBaseIds";
+ N_PHYSICAL_LOCATION : constant String := "physicalLocation";
+ N_REGION : constant String := "region";
+ N_RELATED_LOCATIONS : constant String := "relatedLocations";
+ N_REPLACEMENTS : constant String := "replacements";
+ N_RESULTS : constant String := "results";
+ N_RULES : constant String := "rules";
+ N_RULE_ID : constant String := "ruleId";
+ N_RUNS : constant String := "runs";
+ N_SCHEMA : constant String := "$schema";
+ N_START_COLUMN : constant String := "startColumn";
+ N_START_LINE : constant String := "strartLine";
+ N_TEXT : constant String := "text";
+ N_TOOL : constant String := "tool";
+ N_URI : constant String := "uri";
+ N_URI_BASE_ID : constant String := "uriBaseId";
+ N_VERSION : constant String := "version";
+
-- We are currently using SARIF 2.1.0
SARIF_Version : constant String := "2.1.0";
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
pragma Style_Checks ("M79");
+ URI_Base_Id_Name : constant String := "PWD";
+ -- We use the pwd as the originalUriBaseIds when providing absolute paths
+ -- in locations.
+
+ Current_Dir : constant String := Get_Current_Dir;
+ -- Cached value of the current directory that is used in the URI_Base_Id
+ -- and it is also the path that all other Uri attributes will be created
+ -- relative to.
+
type Artifact_Change is record
- File : String_Ptr;
- -- Name of the file
+ File_Index : Source_File_Index;
+ -- Index for the source file
Replacements : Edit_List;
-- Regions of texts to be edited
end record;
- procedure Destroy (Elem : in out Artifact_Change);
+ procedure Destroy (Elem : in out Artifact_Change) is null;
pragma Inline (Destroy);
function Equals (L, R : Artifact_Change) return Boolean is
- (L.File /= null
- and then R.File /= null
- and then L.File.all = R.File.all);
+ (L.File_Index = R.File_Index);
package Artifact_Change_Lists is new Doubly_Linked_Lists
(Element_Type => Artifact_Change,
-- replacements: [<Replacements>]
-- }
- procedure Print_Artifact_Location (File_Name : String);
+ procedure Print_Artifact_Location (Sfile : Source_File_Index);
-- Print an artifactLocation node
--
-- "artifactLocation": {
- -- "URI": <File_Name>
+ -- "uri": <File_Name>,
+ -- "uriBaseId": "PWD"
-- }
procedure Print_Location (Loc : Labeled_Span_Type;
-- },
-- "physicalLocation": {
-- "artifactLocation": {
- -- "URI": <File_Name (Loc)>
+ -- "uri": <File_Name (Loc)>
-- },
-- "region": {
-- "startLine": <Line(Loc.Fst)>,
-- <Location (Primary_Span (Diag))>
-- ],
- procedure Print_Message (Text : String; Name : String := "message");
- -- Print a SARIF message node
+ procedure Print_Message (Text : String; Name : String := N_MESSAGE);
+ -- Print a SARIF message node.
+ --
+ -- There are many message type nodes in the SARIF report however they can
+ -- have a different node <Name>.
--
- -- "message": {
+ -- <Name>: {
-- "text": <text>
-- },
+ procedure Print_Original_Uri_Base_Ids;
+ -- Print the originalUriBaseIds that holds the PWD value
+ --
+ -- "originalUriBaseIds": {
+ -- "PWD": {
+ -- "uri": "<current_working_directory>"
+ -- }
+ -- },
+
procedure Print_Related_Locations (Diag : Diagnostic_Type);
-- Print a relatedLocations node that consists of multiple location nodes.
-- Related locations are the non-primary spans of the diagnostic and the
Start_Col : Int;
End_Line : Int;
End_Col : Int;
- Name : String := "region");
+ Name : String := N_REGION);
-- Print a region node.
--
-- More specifically a text region node that specifies the textual
-- }
-- }
- -------------
- -- Destroy --
- -------------
-
- procedure Destroy (Elem : in out Artifact_Change)
- is
-
- begin
- Free (Elem.File);
- end Destroy;
-
--------------------------
-- Get_Artifact_Changes --
--------------------------
while Artifact_Change_Lists.Has_Next (It) loop
Artifact_Change_Lists.Next (It, A);
- if A.File.all = To_File_Name (E.Span.Ptr) then
+ if A.File_Index = Get_Source_File_Index (E.Span.Ptr) then
Edit_Lists.Append (A.Replacements, E);
return;
end if;
Edit_Lists.Append (Replacements, E);
Artifact_Change_Lists.Append
(Changes,
- (File => new String'(To_File_Name (E.Span.Ptr)),
+ (File_Index => Get_Source_File_Index (E.Span.Ptr),
Replacements => Replacements));
end;
end Insert;
-- Print artifactLocation
- Print_Artifact_Location (A.File.all);
+ Print_Artifact_Location (A.File_Index);
Write_Char (',');
NL_And_Indent;
- Write_Str ("""" & "replacements" & """" & ": " & "[");
+ Write_Str ("""" & N_REPLACEMENTS & """" & ": " & "[");
Begin_Block;
NL_And_Indent;
-- Print_Artifact_Location --
-----------------------------
- procedure Print_Artifact_Location (File_Name : String) is
-
+ procedure Print_Artifact_Location (Sfile : Source_File_Index) is
+ Full_Name : constant String := Get_Name_String (Full_Ref_Name (Sfile));
begin
- Write_Str ("""" & "artifactLocation" & """" & ": " & "{");
+ Write_Str ("""" & N_ARTIFACT_LOCATION & """" & ": " & "{");
Begin_Block;
NL_And_Indent;
- Write_String_Attribute ("uri", File_Name);
+ if System.OS_Lib.Is_Absolute_Path (Full_Name) then
+ declare
+ Abs_Name : constant String :=
+ System.OS_Lib.Normalize_Pathname
+ (Name => Full_Name, Resolve_Links => False);
+ begin
+ -- We cannot create relative paths between different drives on
+ -- Windows. If the path is on a different drive than the PWD print
+ -- the absolute path in the URI and omit the baseUriId attribute.
+
+ if Osint.On_Windows
+ and then Abs_Name (Abs_Name'First) =
+ Current_Dir (Current_Dir'First)
+ then
+ Write_String_Attribute
+ (N_URI, To_File_Uri (Abs_Name));
+ else
+ Write_String_Attribute
+ (N_URI,
+ To_File_Uri
+ (Relative_Path (Abs_Name, Current_Dir)));
+
+ Write_Char (',');
+ NL_And_Indent;
+
+ Write_String_Attribute
+ (N_URI_BASE_ID, URI_Base_Id_Name);
+ end if;
+ end;
+ else
+ -- If the path was not absolute it was given relative to the
+ -- uriBaseId.
+
+ Write_String_Attribute (N_URI, To_File_Uri (Full_Name));
+
+ Write_Char (',');
+ NL_And_Indent;
+
+ Write_String_Attribute (N_URI_BASE_ID, URI_Base_Id_Name);
+ end if;
End_Block;
NL_And_Indent;
Start_Col => Col_Fst,
End_Line => Line_Lst,
End_Col => Col_Lst,
- Name => "deletedRegion");
+ Name => N_DELETED_REGION);
if Replacement.Text /= null then
Write_Char (',');
NL_And_Indent;
- Print_Message (Replacement.Text.all, "insertedContent");
+ Print_Message (Replacement.Text.all, N_INSERTED_CONTENT);
end if;
-- End replacement
-- Print the message if the location has one
if Fix.Description /= null then
- Print_Message (Fix.Description.all, "description");
+ Print_Message (Fix.Description.all, N_DESCRIPTION);
Write_Char (',');
NL_And_Indent;
A : Artifact_Change;
A_It : Iterator := Iterate (Changes);
begin
- Write_Str ("""" & "artifactChanges" & """" & ": " & "[");
+ Write_Str ("""" & N_ARTIFACT_CHANGES & """" & ": " & "[");
Begin_Block;
while Has_Next (A_It) loop
First : Boolean := True;
begin
- Write_Str ("""" & "fixes" & """" & ": " & "[");
+ Write_Str ("""" & N_FIXES & """" & ": " & "[");
Begin_Block;
if Present (Diag.Fixes) then
end Compose_Command_Line;
begin
- Write_Str ("""" & "invocations" & """" & ": " & "[");
+ Write_Str ("""" & N_INVOCATIONS & """" & ": " & "[");
Begin_Block;
NL_And_Indent;
-- Print commandLine
- Write_String_Attribute ("commandLine", Compose_Command_Line);
+ Write_String_Attribute (N_COMMAND_LINE, Compose_Command_Line);
Write_Char (',');
NL_And_Indent;
-- Print executionSuccessful
- Write_Boolean_Attribute ("executionSuccessful", Compilation_Errors);
+ Write_Boolean_Attribute (N_EXECUTION_SUCCESSFUL, Compilation_Errors);
End_Block;
NL_And_Indent;
Start_Col : Int;
End_Line : Int;
End_Col : Int;
- Name : String := "region")
+ Name : String := N_REGION)
is
begin
Begin_Block;
NL_And_Indent;
- Write_Int_Attribute ("startLine", Start_Line);
+ Write_Int_Attribute (N_START_LINE, Start_Line);
Write_Char (',');
NL_And_Indent;
- Write_Int_Attribute ("startColumn", Start_Col);
+ Write_Int_Attribute (N_START_COLUMN, Start_Col);
Write_Char (',');
NL_And_Indent;
- Write_Int_Attribute ("endLine", End_Line);
+ Write_Int_Attribute (N_END_LINE, End_Line);
Write_Char (',');
NL_And_Indent;
-- Convert the end of the span to the definition of the endColumn
-- for a SARIF region.
- Write_Int_Attribute ("endColumn", End_Col + 1);
+ Write_Int_Attribute (N_END_COLUMN, End_Col + 1);
End_Block;
NL_And_Indent;
NL_And_Indent;
end if;
- Write_Str ("""" & "physicalLocation" & """" & ": " & "{");
+ Write_Str ("""" & N_PHYSICAL_LOCATION & """" & ": " & "{");
Begin_Block;
NL_And_Indent;
-- Print artifactLocation
- Print_Artifact_Location (To_File_Name (Loc.Span.Ptr));
+ Print_Artifact_Location (Get_Source_File_Index (Loc.Span.Ptr));
Write_Char (',');
NL_And_Indent;
First : Boolean := True;
begin
- Write_Str ("""" & "locations" & """" & ": " & "[");
+ Write_Str ("""" & N_LOCATIONS & """" & ": " & "[");
Begin_Block;
while Has_Next (It) loop
-- Print_Message --
-------------------
- procedure Print_Message (Text : String; Name : String := "message") is
+ procedure Print_Message (Text : String; Name : String := N_MESSAGE) is
begin
Write_Str ("""" & Name & """" & ": " & "{");
Begin_Block;
NL_And_Indent;
- Write_String_Attribute ("text", Text);
+ Write_String_Attribute (N_TEXT, Text);
End_Block;
NL_And_Indent;
Write_Char ('}');
end Print_Message;
+ ---------------------------------
+ -- Print_Original_Uri_Base_Ids --
+ ---------------------------------
+
+ procedure Print_Original_Uri_Base_Ids is
+ begin
+ Write_Str ("""" & N_ORIGINAL_URI_BASE_IDS & """" & ": " & "{");
+ Begin_Block;
+ NL_And_Indent;
+
+ Write_Str ("""" & URI_Base_Id_Name & """" & ": " & "{");
+ Begin_Block;
+ NL_And_Indent;
+
+ Write_String_Attribute (N_URI, To_File_Uri (Current_Dir));
+
+ End_Block;
+ NL_And_Indent;
+ Write_Char ('}');
+
+ End_Block;
+ NL_And_Indent;
+ Write_Char ('}');
+ end Print_Original_Uri_Base_Ids;
+
-----------------------------
-- Print_Related_Locations --
-----------------------------
First : Boolean := True;
begin
- Write_Str ("""" & "relatedLocations" & """" & ": " & "[");
+ Write_Str ("""" & N_RELATED_LOCATIONS & """" & ": " & "[");
Begin_Block;
-- Related locations are the non-primary spans of the diagnostic
-- Print ruleId
- Write_String_Attribute ("ruleId", "[" & To_String (Diag.Id) & "]");
+ Write_String_Attribute (N_RULE_ID, "[" & To_String (Diag.Id) & "]");
Write_Char (',');
NL_And_Indent;
-- Print level
- Write_String_Attribute ("level", Kind_To_String (Diag));
+ Write_String_Attribute (N_LEVEL, Kind_To_String (Diag));
Write_Char (',');
NL_And_Indent;
First : Boolean := True;
begin
- Write_Str ("""" & "results" & """" & ": " & "[");
+ Write_Str ("""" & N_RESULTS & """" & ": " & "[");
Begin_Block;
if Present (Diags) then
Begin_Block;
NL_And_Indent;
- Write_String_Attribute ("id", "[" & To_String (Diag.Id) & "]");
+ Write_String_Attribute (N_ID, "[" & To_String (Diag.Id) & "]");
Write_Char (',');
NL_And_Indent;
if Human_Id = null then
- Write_String_Attribute ("name", "Uncategorized_Diagnostic");
+ Write_String_Attribute (N_NAME, "Uncategorized_Diagnostic");
else
- Write_String_Attribute ("name", Human_Id.all);
+ Write_String_Attribute (N_NAME, Human_Id.all);
end if;
End_Block;
First : Boolean := True;
begin
- Write_Str ("""" & "rules" & """" & ": " & "[");
+ Write_Str ("""" & N_RULES & """" & ": " & "[");
Begin_Block;
while Has_Next (It) loop
procedure Print_Tool (Diags : Diagnostic_List) is
begin
- Write_Str ("""" & "tool" & """" & ": " & "{");
+ Write_Str ("""" & N_TOOL & """" & ": " & "{");
Begin_Block;
NL_And_Indent;
-- -- Attributes of tool
- Write_Str ("""" & "driver" & """" & ": " & "{");
+ Write_Str ("""" & N_DRIVER & """" & ": " & "{");
Begin_Block;
NL_And_Indent;
-- Attributes of tool.driver
- Write_String_Attribute ("name", "GNAT");
+ Write_String_Attribute (N_NAME, "GNAT");
Write_Char (',');
NL_And_Indent;
- Write_String_Attribute ("version", Gnat_Version_String);
+ Write_String_Attribute (N_VERSION, Gnat_Version_String);
Write_Char (',');
NL_And_Indent;
procedure Print_Runs (Diags : Diagnostic_List) is
begin
- Write_Str ("""" & "runs" & """" & ": " & "[");
+ Write_Str ("""" & N_RUNS & """" & ": " & "[");
Begin_Block;
NL_And_Indent;
Write_Char (',');
NL_And_Indent;
+ Print_Original_Uri_Base_Ids;
+ Write_Char (',');
+ NL_And_Indent;
+
-- A run consists of results
Print_Results (Diags);
Begin_Block;
NL_And_Indent;
- Write_String_Attribute ("$schema", SARIF_Schema);
+ Write_String_Attribute (N_SCHEMA, SARIF_Schema);
Write_Char (',');
NL_And_Indent;
- Write_String_Attribute ("version", SARIF_Version);
+ Write_String_Attribute (N_VERSION, SARIF_Version);
Write_Char (',');
NL_And_Indent;
-- Used in Locate_File as a fake directory when Name is already an
-- absolute path.
+ procedure Get_Current_Dir
+ (Dir : System.Address; Length : System.Address);
+ pragma Import (C, Get_Current_Dir, "__gnat_get_current_dir");
+
+ Max_Path : Integer;
+ pragma Import (C, Max_Path, "__gnat_max_path_len");
+ -- Maximum length of a path name
+
-------------------------------------
-- Use of Name_Find and Name_Enter --
-------------------------------------
Smart_Find_File (N, Source, Full_File, Attr.all);
end Full_Source_Name;
+ ---------------------
+ -- Get_Current_Dir --
+ ---------------------
+
+ function Get_Current_Dir return String is
+ Current_Dir : String (1 .. Max_Path + 1);
+ Last : Natural;
+ begin
+ Get_Current_Dir (Current_Dir'Address, Last'Address);
+ return Current_Dir (1 .. Last);
+ end Get_Current_Dir;
+
-------------------
-- Get_Directory --
-------------------
(Search_Dir : String;
File_Type : Search_File_Type) return String_Ptr
is
- procedure Get_Current_Dir
- (Dir : System.Address;
- Length : System.Address);
- pragma Import (C, Get_Current_Dir, "__gnat_get_current_dir");
-
- Max_Path : Integer;
- pragma Import (C, Max_Path, "__gnat_max_path_len");
- -- Maximum length of a path name
-
Current_Dir : String_Ptr;
Default_Search_Dir : String_Access;
Default_Suffix_Dir : String_Access;
pragma Assert (Hi = Src'Last);
end Read_Source_File;
+ -------------------
+ -- Relative_Path --
+ -------------------
+
+ function Relative_Path (Path : String; Ref : String) return String is
+ Norm_Path : constant String :=
+ Normalize_Pathname (Name => Path, Resolve_Links => False);
+ Norm_Ref : constant String :=
+ Normalize_Pathname (Name => Ref, Resolve_Links => False);
+ Rel_Path : Bounded_String;
+ Last : Natural := Norm_Ref'Last;
+ Old : Natural;
+ Depth : Natural := 0;
+
+ begin
+ pragma Assert (System.OS_Lib.Is_Absolute_Path (Norm_Path));
+ pragma Assert (System.OS_Lib.Is_Absolute_Path (Norm_Ref));
+ pragma Assert (System.OS_Lib.Is_Directory (Norm_Ref));
+
+ -- If the root drives are different on Windows then we cannot create a
+ -- relative path.
+
+ if Root (Norm_Path) /= Root (Norm_Ref) then
+ return Norm_Path;
+ end if;
+
+ if Norm_Path = Norm_Ref then
+ return ".";
+ end if;
+
+ loop
+ exit when Last - Norm_Ref'First + 1 <= Norm_Path'Length
+ and then
+ Norm_Path
+ (Norm_Path'First ..
+ Norm_Path'First + Last - Norm_Ref'First) =
+ Norm_Ref (Norm_Ref'First .. Last);
+
+ Old := Last;
+ for J in reverse Norm_Ref'First .. Last - 1 loop
+ if Is_Directory_Separator (Norm_Ref (J)) then
+ Depth := Depth + 1;
+ Last := J;
+ exit;
+ end if;
+ end loop;
+
+ if Old = Last then
+ -- No Dir_Separator in Ref... Let's return Path
+ return Norm_Path;
+ end if;
+ end loop;
+
+ -- Move up the directory chain to the common point
+
+ for I in 1 .. Depth loop
+ Append (Rel_Path, ".." & System.OS_Lib.Directory_Separator);
+ end loop;
+
+ -- Add the rest of the path from the common point
+
+ Append
+ (Rel_Path,
+ Norm_Path
+ (Norm_Path'First + Last - Norm_Ref'First + 1 ..
+ Norm_Path'Last));
+
+ return To_String (Rel_Path);
+ end Relative_Path;
+
-------------------
-- Relocate_Path --
-------------------
return new String'(Path);
end Relocate_Path;
+ ----------
+ -- Root --
+ ----------
+
+ function Root (Path : String) return String is
+ Last : Natural := Path'First;
+ begin
+ pragma Assert (System.OS_Lib.Is_Absolute_Path (Path));
+
+ for I in Path'Range loop
+ if Is_Directory_Separator (Path (I)) then
+ Last := I;
+ exit;
+ end if;
+ end loop;
+
+ return Path (Path'First .. Last);
+ end Root;
+
-----------------
-- Set_Program --
-----------------