| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT COMPILER COMPONENTS -- |
| -- -- |
| -- E R R O U T C . S A R I F _ E M I T T E R -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 1992-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 Ada Core Technologies Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| with JSON_Utils; use JSON_Utils; |
| with GNAT.Lists; use GNAT.Lists; |
| with Gnatvsn; use Gnatvsn; |
| with Lib; use Lib; |
| with Namet; use Namet; |
| with Output; use Output; |
| with Sinput; use Sinput; |
| with System.OS_Lib; |
| |
| package body Erroutc.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 := "startLine"; |
| 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"; |
| pragma Style_Checks ("M100"); |
| SARIF_Schema : constant String := |
| "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. |
| |
| procedure Destroy (Elem : in out Error_Msg_Object) is null; |
| pragma Inline (Destroy); |
| package Error_Msg_Lists is new Doubly_Linked_Lists |
| (Element_Type => Error_Msg_Object, |
| "=" => "=", |
| Destroy_Element => Destroy, |
| Check_Tampering => False); |
| |
| subtype Error_Msg_List is Error_Msg_Lists.Doubly_Linked_List; |
| |
| procedure Destroy (Elem : in out Edit_Type); |
| |
| procedure Destroy (Elem : in out Edit_Type) is |
| begin |
| -- Diagnostic elements will be freed when all the diagnostics have been |
| -- emitted. |
| null; |
| end Destroy; |
| |
| pragma Inline (Destroy); |
| |
| package Edit_Lists is new Doubly_Linked_Lists |
| (Element_Type => Edit_Type, |
| "=" => "=", |
| Destroy_Element => Destroy, |
| Check_Tampering => False); |
| |
| subtype Edit_List is Edit_Lists.Doubly_Linked_List; |
| |
| type Artifact_Change is record |
| 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); |
| pragma Inline (Destroy); |
| |
| function Equals (L, R : Artifact_Change) return Boolean is |
| (L.File_Index = R.File_Index); |
| |
| package Artifact_Change_Lists is new Doubly_Linked_Lists |
| (Element_Type => Artifact_Change, |
| "=" => Equals, |
| Destroy_Element => Destroy, |
| Check_Tampering => False); |
| |
| subtype Artifact_Change_List is Artifact_Change_Lists.Doubly_Linked_List; |
| |
| function Get_Artifact_Changes (Fix : Fix_Type) return Artifact_Change_List; |
| -- Group edits of a Fix into Artifact_Changes that organize the edits by |
| -- file name. |
| |
| function Get_Unique_Rules return Error_Msg_List; |
| -- Get a list of diagnostics that have unique Diagnostic Id-s. |
| |
| procedure Print_Replacement (Replacement : Edit_Type); |
| -- Print a replacement node |
| -- |
| -- { |
| -- deletedRegion: {<Region>}, |
| -- insertedContent: {<Message>} |
| -- } |
| |
| procedure Print_Fix (Fix : Fix_Type); |
| -- Print the fix node |
| -- |
| -- { |
| -- description: {<Message>}, |
| -- artifactChanges: [<ArtifactChange>] |
| -- } |
| |
| procedure Print_Fixes (E_Msg : Error_Msg_Object); |
| -- Print the fixes node |
| -- |
| -- "fixes": [ |
| -- <Fix>, |
| -- ... |
| -- ] |
| |
| procedure Print_Invocations; |
| -- Print an invocations node that consists of |
| -- * a single invocation node that consists of: |
| -- * commandLine |
| -- * executionSuccessful |
| -- |
| -- "invocations": [ |
| -- { |
| -- "commandLine": <command line arguments provided to the GNAT FE>, |
| -- "executionSuccessful": ["true"|"false"], |
| -- } |
| -- ] |
| |
| procedure Print_Artifact_Change (A : Artifact_Change); |
| -- Print an ArtifactChange node |
| -- |
| -- { |
| -- artifactLocation: {<ArtifactLocation>}, |
| -- replacements: [<Replacements>] |
| -- } |
| |
| procedure Print_Artifact_Location (Sfile : Source_File_Index); |
| -- Print an artifactLocation node |
| -- |
| -- "artifactLocation": { |
| -- "uri": <File_Name>, |
| -- "uriBaseId": "PWD" |
| -- } |
| |
| procedure Print_Location (Loc : Labeled_Span_Type; Msg : String_Ptr); |
| -- Print a location node that consists of |
| -- * an optional message node |
| -- * a physicalLocation node |
| -- * ArtifactLocation node that consists of the file name |
| -- * Region node that consists of the start and end positions of the span |
| -- |
| -- { |
| -- "message": { |
| -- "text": <Msg> |
| -- }, |
| -- "physicalLocation": { |
| -- "artifactLocation": { |
| -- "uri": <File_Name (Loc)> |
| -- }, |
| -- "region": { |
| -- "startLine": <Line(Loc.Fst)>, |
| -- "startColumn": <Col(Loc.Fst)>, |
| -- "endLine": <Line(Loc.Lst)>, |
| -- "endColumn": Col(Loc.Lst)> |
| -- } |
| -- } |
| -- } |
| |
| procedure Print_Locations (E_Msg : Error_Msg_Object); |
| -- Print a locations node that consists of multiple location nodes. However |
| -- typically just one location for the primary span of the diagnostic. |
| -- |
| -- "locations": [ |
| -- <Location (Primary_Span (Diag))> |
| -- ], |
| |
| 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>. |
| -- |
| -- <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 (E_Msg : Error_Msg_Object); |
| -- Print a relatedLocations node that consists of multiple location nodes. |
| -- Related locations are the non-primary spans of the diagnostic and the |
| -- primary locations of sub-diagnostics. |
| -- |
| -- "relatedLocations": [ |
| -- <Location (Diag.Loc)> |
| -- ], |
| |
| procedure Print_Region |
| (Start_Line : Int; |
| Start_Col : Int; |
| End_Line : Int; |
| End_Col : Int; |
| Name : String := N_REGION); |
| -- Print a region node. |
| -- |
| -- More specifically a text region node that specifies the textual |
| -- location of the region. Note that in SARIF there are also binary |
| -- regions. |
| -- |
| -- "<Name>": { |
| -- "startLine": Start_Line, |
| -- "startColumn": Start_Col, |
| -- "endLine": End_Line, |
| -- "endColumn": End_Col + 1 |
| -- } |
| -- |
| -- Note that there are many types of nodes that can have a region type, |
| -- but have a different node name. |
| -- |
| -- The end column is defined differently in the SARIF report than it is |
| -- for the spans within GNAT. Internally we consider the end column of a |
| -- span to be the last character of the span. |
| -- |
| -- However in SARIF the end column is defined as: |
| -- "The column number of the character following the end of the region" |
| -- |
| -- This method assumes that the End_Col passed to this procedure is using |
| -- the GNAT span definition and we amend the endColumn value so that it |
| -- matches the SARIF definition. |
| |
| procedure Print_Result (E_Msg : Error_Msg_Object); |
| -- { |
| -- "ruleId": <Diag.Id>, |
| -- "level": <Diag.Kind>, |
| -- "message": { |
| -- "text": <Diag.Message> |
| -- }, |
| -- "locations": [<Primary_Location>], |
| -- "relatedLocations": [<Secondary_Locations>] |
| -- }, |
| |
| procedure Print_Results; |
| -- Print a results node that consists of multiple result nodes for each |
| -- diagnostic instance. |
| -- |
| -- "results": [ |
| -- <Result (Diag)> |
| -- ] |
| |
| procedure Print_Rule (E : Error_Msg_Object); |
| -- Print a rule node that consists of the following attributes: |
| -- * ruleId |
| -- * name |
| -- |
| -- { |
| -- "id": <Diag.Id>, |
| -- "name": <Human_Id(Diag)> |
| -- }, |
| |
| procedure Print_Rules; |
| -- Print a rules node that consists of multiple rule nodes. |
| -- Rules are considered to be a set of unique diagnostics with the unique |
| -- id-s. |
| -- |
| -- "rules": [ |
| -- <Rule (Diag)> |
| -- ] |
| |
| procedure Print_Runs; |
| -- Print a runs node that can consist of multiple run nodes. |
| -- However for our report it consists of a single run that consists of |
| -- * a tool node |
| -- * a results node |
| -- |
| -- { |
| -- "tool": { <Tool (Diags)> }, |
| -- "results": [<Results (Diags)>] |
| -- } |
| |
| procedure Print_Tool; |
| -- Print a tool node that consists of |
| -- * a driver node that consists of: |
| -- * name |
| -- * version |
| -- * rules |
| -- |
| -- "tool": { |
| -- "driver": { |
| -- "name": "GNAT", |
| -- "version": <GNAT_Version>, |
| -- "rules": [<Rules (Diags)>] |
| -- } |
| -- } |
| |
| ------------- |
| -- Destroy -- |
| ------------- |
| |
| procedure Destroy (Elem : in out Artifact_Change) is |
| begin |
| Edit_Lists.Destroy (Elem.Replacements); |
| end Destroy; |
| |
| -------------------------- |
| -- Get_Artifact_Changes -- |
| -------------------------- |
| |
| function Get_Artifact_Changes (Fix : Fix_Type) return Artifact_Change_List |
| is |
| procedure Insert (Changes : Artifact_Change_List; E : Edit_Type); |
| |
| ------------ |
| -- Insert -- |
| ------------ |
| |
| procedure Insert (Changes : Artifact_Change_List; E : Edit_Type) is |
| A : Artifact_Change; |
| |
| It : Artifact_Change_Lists.Iterator := |
| Artifact_Change_Lists.Iterate (Changes); |
| begin |
| while Artifact_Change_Lists.Has_Next (It) loop |
| Artifact_Change_Lists.Next (It, A); |
| |
| if A.File_Index = Get_Source_File_Index (E.Span.Ptr) then |
| Edit_Lists.Append (A.Replacements, E); |
| return; |
| end if; |
| end loop; |
| |
| declare |
| Replacements : constant Edit_List := Edit_Lists.Create; |
| begin |
| Edit_Lists.Append (Replacements, E); |
| Artifact_Change_Lists.Append |
| (Changes, |
| (File_Index => Get_Source_File_Index (E.Span.Ptr), |
| Replacements => Replacements)); |
| end; |
| end Insert; |
| |
| Changes : constant Artifact_Change_List := Artifact_Change_Lists.Create; |
| |
| E : Edit_Type; |
| |
| It : Edit_Id; |
| |
| -- Start of processing for Get_Artifact_Changes |
| |
| begin |
| It := Fix.Edits; |
| |
| while It /= No_Edit loop |
| E := Edits.Table (It); |
| |
| Insert (Changes, E); |
| |
| It := E.Next; |
| end loop; |
| |
| return Changes; |
| end Get_Artifact_Changes; |
| |
| ---------------------- |
| -- Get_Unique_Rules -- |
| ---------------------- |
| |
| function Get_Unique_Rules return Error_Msg_List is |
| use Error_Msg_Lists; |
| |
| procedure Insert (Rules : Error_Msg_List; E : Error_Msg_Object); |
| |
| ------------ |
| -- Insert -- |
| ------------ |
| |
| procedure Insert (Rules : Error_Msg_List; E : Error_Msg_Object) is |
| It : Iterator := Iterate (Rules); |
| R : Error_Msg_Object; |
| begin |
| while Has_Next (It) loop |
| Next (It, R); |
| |
| if R.Id = E.Id then |
| return; |
| elsif R.Id > E.Id then |
| Insert_Before (Rules, R, E); |
| return; |
| end if; |
| end loop; |
| |
| Append (Rules, E); |
| end Insert; |
| |
| Unique_Rules : constant Error_Msg_List := Create; |
| |
| E : Error_Msg_Id; |
| |
| -- Start of processing for Get_Unique_Rules |
| |
| begin |
| E := First_Error_Msg; |
| while E /= No_Error_Msg loop |
| Insert (Unique_Rules, Errors.Table (E)); |
| |
| Next_Error_Msg (E); |
| end loop; |
| |
| return Unique_Rules; |
| end Get_Unique_Rules; |
| |
| --------------------------- |
| -- Print_Artifact_Change -- |
| --------------------------- |
| |
| procedure Print_Artifact_Change (A : Artifact_Change) is |
| use Edit_Lists; |
| E : Edit_Type; |
| E_It : Iterator; |
| |
| First : Boolean := True; |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print artifactLocation |
| |
| Print_Artifact_Location (A.File_Index); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Write_Str ("""" & N_REPLACEMENTS & """" & ": " & "["); |
| Begin_Block; |
| NL_And_Indent; |
| |
| E_It := Iterate (A.Replacements); |
| |
| while Has_Next (E_It) loop |
| Next (E_It, E); |
| |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| Print_Replacement (E); |
| end loop; |
| |
| -- End replacements |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| |
| -- End artifactChange |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Artifact_Change; |
| |
| ----------------------------- |
| -- Print_Artifact_Location -- |
| ----------------------------- |
| |
| procedure Print_Artifact_Location (Sfile : Source_File_Index) is |
| Full_Name : constant String := Get_Name_String (Full_Ref_Name (Sfile)); |
| begin |
| Write_Str ("""" & N_ARTIFACT_LOCATION & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| |
| 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; |
| Write_Char ('}'); |
| end Print_Artifact_Location; |
| |
| ----------------------- |
| -- Print_Replacement -- |
| ----------------------- |
| |
| procedure Print_Replacement (Replacement : Edit_Type) is |
| -- Span start positions |
| Fst : constant Source_Ptr := Replacement.Span.First; |
| Line_Fst : constant Int := Int (Get_Physical_Line_Number (Fst)); |
| Col_Fst : constant Int := Int (Get_Column_Number (Fst)); |
| |
| -- Span end positions |
| Lst : constant Source_Ptr := Replacement.Span.Last; |
| Line_Lst : constant Int := Int (Get_Physical_Line_Number (Lst)); |
| Col_Lst : constant Int := Int (Get_Column_Number (Lst)); |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print deletedRegion |
| |
| Print_Region |
| (Start_Line => Line_Fst, |
| Start_Col => Col_Fst, |
| End_Line => Line_Lst, |
| End_Col => Col_Lst, |
| Name => N_DELETED_REGION); |
| |
| if Replacement.Text /= null then |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Print_Message (Replacement.Text.all, N_INSERTED_CONTENT); |
| end if; |
| |
| -- End replacement |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Replacement; |
| |
| --------------- |
| -- Print_Fix -- |
| --------------- |
| |
| procedure Print_Fix (Fix : Fix_Type) is |
| First : Boolean := True; |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print the message if the location has one |
| |
| if Fix.Description /= null then |
| Print_Message (Fix.Description.all, N_DESCRIPTION); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| end if; |
| |
| declare |
| use Artifact_Change_Lists; |
| Changes : Artifact_Change_List := Get_Artifact_Changes (Fix); |
| A : Artifact_Change; |
| A_It : Iterator := Iterate (Changes); |
| begin |
| Write_Str ("""" & N_ARTIFACT_CHANGES & """" & ": " & "["); |
| Begin_Block; |
| |
| while Has_Next (A_It) loop |
| Next (A_It, A); |
| |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| |
| Print_Artifact_Change (A); |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| |
| Destroy (Changes); |
| end; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Fix; |
| |
| ----------------- |
| -- Print_Fixes -- |
| ----------------- |
| |
| procedure Print_Fixes (E_Msg : Error_Msg_Object) is |
| F : Fix_Type; |
| F_It : Fix_Id; |
| |
| First : Boolean := True; |
| begin |
| Write_Str ("""" & N_FIXES & """" & ": " & "["); |
| Begin_Block; |
| |
| F_It := E_Msg.Fixes; |
| while F_It /= No_Fix loop |
| F := Fixes.Table (F_It); |
| |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| Print_Fix (F); |
| |
| F_It := F.Next; |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| end Print_Fixes; |
| |
| ----------------------- |
| -- Print_Invocations -- |
| ----------------------- |
| |
| procedure Print_Invocations is |
| |
| function Compose_Command_Line return String; |
| -- Composes the original command line from the parsed main file name and |
| -- relevant compilation switches |
| |
| function Compose_Command_Line return String is |
| Buffer : Bounded_String; |
| begin |
| Find_Program_Name; |
| Append (Buffer, Name_Buffer (1 .. Name_Len)); |
| Append (Buffer, ' '); |
| Append (Buffer, Get_First_Main_File_Name); |
| for I in 1 .. Compilation_Switches_Last loop |
| declare |
| Switch : constant String := Get_Compilation_Switch (I).all; |
| begin |
| if Buffer.Length + Switch'Length + 1 <= Buffer.Max_Length then |
| Append (Buffer, ' ' & Switch); |
| end if; |
| end; |
| end loop; |
| |
| return +Buffer; |
| end Compose_Command_Line; |
| |
| begin |
| Write_Str ("""" & N_INVOCATIONS & """" & ": " & "["); |
| Begin_Block; |
| NL_And_Indent; |
| |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print commandLine |
| |
| Write_String_Attribute (N_COMMAND_LINE, Compose_Command_Line); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print executionSuccessful |
| |
| Write_Boolean_Attribute (N_EXECUTION_SUCCESSFUL, Exit_Code = E_Success); |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| end Print_Invocations; |
| |
| ------------------ |
| -- Print_Region -- |
| ------------------ |
| |
| procedure Print_Region |
| (Start_Line : Int; |
| Start_Col : Int; |
| End_Line : Int; |
| End_Col : Int; |
| Name : String := N_REGION) |
| is |
| |
| begin |
| Write_Str ("""" & Name & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| |
| Write_Int_Attribute (N_START_LINE, Start_Line); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Write_Int_Attribute (N_START_COLUMN, Start_Col); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| 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 (N_END_COLUMN, End_Col + 1); |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Region; |
| |
| -------------------- |
| -- Print_Location -- |
| -------------------- |
| |
| procedure Print_Location (Loc : Labeled_Span_Type; Msg : String_Ptr) is |
| |
| -- Span start positions |
| Fst : constant Source_Ptr := Loc.Span.First; |
| Line_Fst : constant Int := Int (Get_Physical_Line_Number (Fst)); |
| Col_Fst : constant Int := Int (Get_Column_Number (Fst)); |
| |
| -- Span end positions |
| Lst : constant Source_Ptr := Loc.Span.Last; |
| Line_Lst : constant Int := Int (Get_Physical_Line_Number (Lst)); |
| Col_Lst : constant Int := Int (Get_Column_Number (Lst)); |
| |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print the message if the location has one |
| |
| if Msg /= null then |
| Print_Message (Msg.all); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| end if; |
| |
| Write_Str ("""" & N_PHYSICAL_LOCATION & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print artifactLocation |
| |
| Print_Artifact_Location (Get_Source_File_Index (Loc.Span.Ptr)); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print region |
| |
| Print_Region |
| (Start_Line => Line_Fst, |
| Start_Col => Col_Fst, |
| End_Line => Line_Lst, |
| End_Col => Col_Lst); |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Location; |
| |
| --------------------- |
| -- Print_Locations -- |
| --------------------- |
| |
| procedure Print_Locations (E_Msg : Error_Msg_Object) is |
| Loc : Labeled_Span_Type; |
| It : Labeled_Span_Id; |
| |
| First : Boolean := True; |
| begin |
| Write_Str ("""" & N_LOCATIONS & """" & ": " & "["); |
| Begin_Block; |
| |
| It := E_Msg.Locations; |
| while It /= No_Labeled_Span loop |
| Loc := Locations.Table (It); |
| |
| -- Only the primary span is considered as the main location other |
| -- spans are considered related locations |
| |
| if Loc.Is_Primary then |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| Print_Location (Loc, Loc.Label); |
| end if; |
| |
| It := Loc.Next; |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| end Print_Locations; |
| |
| ------------------- |
| -- Print_Message -- |
| ------------------- |
| |
| procedure Print_Message (Text : String; Name : String := N_MESSAGE) is |
| |
| begin |
| Write_Str ("""" & Name & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| 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 -- |
| ----------------------------- |
| |
| procedure Print_Related_Locations (E_Msg : Error_Msg_Object) is |
| Loc : Labeled_Span_Type; |
| Loc_It : Labeled_Span_Id; |
| |
| Sub : Error_Msg_Object; |
| Sub_It : Error_Msg_Id; |
| |
| First : Boolean := True; |
| begin |
| Write_Str ("""" & N_RELATED_LOCATIONS & """" & ": " & "["); |
| Begin_Block; |
| |
| -- Related locations are the non-primary spans of the diagnostic |
| |
| Loc_It := E_Msg.Locations; |
| while Loc_It /= No_Labeled_Span loop |
| Loc := Locations.Table (Loc_It); |
| |
| -- Non-primary spans are considered related locations |
| |
| if not Loc.Is_Primary then |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| Print_Location (Loc, Loc.Label); |
| end if; |
| Loc_It := Loc.Next; |
| end loop; |
| |
| -- And the sub-diagnostic locations |
| |
| Sub_It := E_Msg.Next; |
| while Sub_It /= No_Error_Msg and then Errors.Table (Sub_It).Msg_Cont loop |
| Sub := Errors.Table (Sub_It); |
| |
| declare |
| Found : Boolean := False; |
| |
| Prim_Loc_Id : Labeled_Span_Id; |
| begin |
| Prim_Loc_Id := Primary_Location (Sub); |
| |
| if Prim_Loc_Id /= No_Labeled_Span then |
| Found := True; |
| else |
| Prim_Loc_Id := Primary_Location (E_Msg); |
| Found := True; |
| end if; |
| |
| -- For mapping sub-diagnostics to related locations we have to |
| -- make some compromises in details. |
| -- |
| -- Firstly we only make one entry that is for the primary span |
| -- of the sub-diagnostic. |
| -- |
| -- Secondly this span can also have a label. However this |
| -- pattern is not advised and by default we include the message |
| -- of the sub-diagnostic as the message in location node since |
| -- it should have more information. |
| |
| if Found then |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| NL_And_Indent; |
| Print_Location (Locations.Table (Prim_Loc_Id), Sub.Text); |
| end if; |
| end; |
| |
| Next_Continuation_Msg (Sub_It); |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| end Print_Related_Locations; |
| |
| ------------------ |
| -- Print_Result -- |
| ------------------ |
| |
| procedure Print_Result (E_Msg : Error_Msg_Object) is |
| |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Print ruleId |
| |
| Write_String_Attribute (N_RULE_ID, "[" & To_String (E_Msg.Id) & "]"); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print level |
| |
| Write_String_Attribute (N_LEVEL, Kind_To_String (E_Msg)); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print message |
| |
| Print_Message (E_Msg.Text.all); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print locations |
| |
| Print_Locations (E_Msg); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print related locations |
| |
| Print_Related_Locations (E_Msg); |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- Print fixes |
| |
| Print_Fixes (E_Msg); |
| |
| End_Block; |
| NL_And_Indent; |
| |
| Write_Char ('}'); |
| end Print_Result; |
| |
| ------------------- |
| -- Print_Results -- |
| ------------------- |
| |
| procedure Print_Results is |
| E : Error_Msg_Id; |
| |
| First : Boolean := True; |
| begin |
| Write_Str ("""" & N_RESULTS & """" & ": " & "["); |
| Begin_Block; |
| |
| E := First_Error_Msg; |
| while E /= No_Error_Msg loop |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| |
| Print_Result (Errors.Table (E)); |
| |
| Next_Error_Msg (E); |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| end Print_Results; |
| |
| ---------------- |
| -- Print_Rule -- |
| ---------------- |
| |
| procedure Print_Rule (E : Error_Msg_Object) is |
| Human_Id : constant String_Ptr := Get_Human_Id (E); |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| Write_String_Attribute (N_ID, "[" & To_String (E.Id) & "]"); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| if Human_Id = null then |
| Write_String_Attribute (N_NAME, "Uncategorized_Diagnostic"); |
| else |
| Write_String_Attribute (N_NAME, Human_Id.all); |
| end if; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| end Print_Rule; |
| |
| ----------------- |
| -- Print_Rules -- |
| ----------------- |
| |
| procedure Print_Rules is |
| use Error_Msg_Lists; |
| R : Error_Msg_Object; |
| Rules : Error_Msg_List := Get_Unique_Rules; |
| It : Iterator := Iterate (Rules); |
| |
| First : Boolean := True; |
| begin |
| Write_Str ("""" & N_RULES & """" & ": " & "["); |
| Begin_Block; |
| |
| while Has_Next (It) loop |
| Next (It, R); |
| |
| if First then |
| First := False; |
| else |
| Write_Char (','); |
| end if; |
| |
| NL_And_Indent; |
| Print_Rule (R); |
| end loop; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char (']'); |
| |
| Error_Msg_Lists.Destroy (Rules); |
| end Print_Rules; |
| |
| ---------------- |
| -- Print_Tool -- |
| ---------------- |
| |
| procedure Print_Tool is |
| |
| begin |
| Write_Str ("""" & N_TOOL & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- -- Attributes of tool |
| |
| Write_Str ("""" & N_DRIVER & """" & ": " & "{"); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Attributes of tool.driver |
| |
| Write_String_Attribute (N_NAME, "GNAT"); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Write_String_Attribute (N_VERSION, Gnat_Version_String); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Print_Rules; |
| |
| -- End of tool.driver |
| |
| End_Block; |
| NL_And_Indent; |
| |
| Write_Char ('}'); |
| |
| -- End of tool |
| |
| End_Block; |
| NL_And_Indent; |
| |
| Write_Char ('}'); |
| end Print_Tool; |
| |
| ---------------- |
| -- Print_Runs -- |
| ---------------- |
| |
| procedure Print_Runs is |
| |
| begin |
| Write_Str ("""" & N_RUNS & """" & ": " & "["); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- Runs can consist of multiple "run"-s. However the GNAT SARIF report |
| -- only has one. |
| |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| -- A run consists of a tool |
| |
| Print_Tool; |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- A run consists of an invocation |
| Print_Invocations; |
| |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Print_Original_Uri_Base_Ids; |
| Write_Char (','); |
| NL_And_Indent; |
| |
| -- A run consists of results |
| |
| Print_Results; |
| |
| -- End of run |
| |
| End_Block; |
| NL_And_Indent; |
| |
| Write_Char ('}'); |
| |
| End_Block; |
| NL_And_Indent; |
| |
| -- End of runs |
| |
| Write_Char (']'); |
| end Print_Runs; |
| |
| ------------------------ |
| -- Print_SARIF_Report -- |
| ------------------------ |
| |
| procedure Print_SARIF_Report is |
| begin |
| Write_Char ('{'); |
| Begin_Block; |
| NL_And_Indent; |
| |
| Write_String_Attribute (N_SCHEMA, SARIF_Schema); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Write_String_Attribute (N_VERSION, SARIF_Version); |
| Write_Char (','); |
| NL_And_Indent; |
| |
| Print_Runs; |
| |
| End_Block; |
| NL_And_Indent; |
| Write_Char ('}'); |
| |
| Write_Eol; |
| end Print_SARIF_Report; |
| |
| end Erroutc.SARIF_Emitter; |