| /* Bundles of location information used when printing diagnostics. |
| Copyright (C) 2015-2024 Free Software Foundation, Inc. |
| |
| This program is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by the |
| Free Software Foundation; either version 3, or (at your option) any |
| later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT 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 |
| along with this program; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. |
| |
| In other words, you are welcome to use, share and improve this program. |
| You are forbidden to forbid anyone else to use, share and improve |
| what you give them. Help stamp out software-hoarding! */ |
| |
| #ifndef LIBCPP_RICH_LOCATION_H |
| #define LIBCPP_RICH_LOCATION_H |
| |
| class range_label; |
| |
| /* A hint to diagnostic_show_locus on how to print a source range within a |
| rich_location. |
| |
| Typically this is SHOW_RANGE_WITH_CARET for the 0th range, and |
| SHOW_RANGE_WITHOUT_CARET for subsequent ranges, |
| but the Fortran frontend uses SHOW_RANGE_WITH_CARET repeatedly for |
| printing things like: |
| |
| x = x + y |
| 1 2 |
| Error: Shapes for operands at (1) and (2) are not conformable |
| |
| where "1" and "2" are notionally carets. */ |
| |
| enum range_display_kind |
| { |
| /* Show the pertinent source line(s), the caret, and underline(s). */ |
| SHOW_RANGE_WITH_CARET, |
| |
| /* Show the pertinent source line(s) and underline(s), but don't |
| show the caret (just an underline). */ |
| SHOW_RANGE_WITHOUT_CARET, |
| |
| /* Just show the source lines; don't show the range itself. |
| This is for use when displaying some line-insertion fix-it hints (for |
| showing the user context on the change, for when it doesn't make sense |
| to highlight the first column on the next line). */ |
| SHOW_LINES_WITHOUT_RANGE |
| }; |
| |
| /* A location within a rich_location: a caret&range, with |
| the caret potentially flagged for display, and an optional |
| label. */ |
| |
| struct location_range |
| { |
| location_t m_loc; |
| |
| enum range_display_kind m_range_display_kind; |
| |
| /* If non-NULL, the label for this range. */ |
| const range_label *m_label; |
| }; |
| |
| /* A partially-embedded vec for use within rich_location for storing |
| ranges and fix-it hints. |
| |
| Elements [0..NUM_EMBEDDED) are allocated within m_embed, after |
| that they are within the dynamically-allocated m_extra. |
| |
| This allows for static allocation in the common case, whilst |
| supporting the rarer case of an arbitrary number of elements. |
| |
| Dynamic allocation is not performed unless it's needed. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| class semi_embedded_vec |
| { |
| public: |
| semi_embedded_vec (); |
| ~semi_embedded_vec (); |
| |
| unsigned int count () const { return m_num; } |
| T& operator[] (int idx); |
| const T& operator[] (int idx) const; |
| |
| void push (const T&); |
| void truncate (int len); |
| |
| private: |
| int m_num; |
| T m_embedded[NUM_EMBEDDED]; |
| int m_alloc; |
| T *m_extra; |
| }; |
| |
| /* Constructor for semi_embedded_vec. In particular, no dynamic allocation |
| is done. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| semi_embedded_vec<T, NUM_EMBEDDED>::semi_embedded_vec () |
| : m_num (0), m_alloc (0), m_extra (NULL) |
| { |
| } |
| |
| /* semi_embedded_vec's dtor. Release any dynamically-allocated memory. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| semi_embedded_vec<T, NUM_EMBEDDED>::~semi_embedded_vec () |
| { |
| XDELETEVEC (m_extra); |
| } |
| |
| /* Look up element IDX, mutably. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| T& |
| semi_embedded_vec<T, NUM_EMBEDDED>::operator[] (int idx) |
| { |
| linemap_assert (idx < m_num); |
| if (idx < NUM_EMBEDDED) |
| return m_embedded[idx]; |
| else |
| { |
| linemap_assert (m_extra != NULL); |
| return m_extra[idx - NUM_EMBEDDED]; |
| } |
| } |
| |
| /* Look up element IDX (const). */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| const T& |
| semi_embedded_vec<T, NUM_EMBEDDED>::operator[] (int idx) const |
| { |
| linemap_assert (idx < m_num); |
| if (idx < NUM_EMBEDDED) |
| return m_embedded[idx]; |
| else |
| { |
| linemap_assert (m_extra != NULL); |
| return m_extra[idx - NUM_EMBEDDED]; |
| } |
| } |
| |
| /* Append VALUE to the end of the semi_embedded_vec. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| void |
| semi_embedded_vec<T, NUM_EMBEDDED>::push (const T& value) |
| { |
| int idx = m_num++; |
| if (idx < NUM_EMBEDDED) |
| m_embedded[idx] = value; |
| else |
| { |
| /* Offset "idx" to be an index within m_extra. */ |
| idx -= NUM_EMBEDDED; |
| if (NULL == m_extra) |
| { |
| linemap_assert (m_alloc == 0); |
| m_alloc = 16; |
| m_extra = XNEWVEC (T, m_alloc); |
| } |
| else if (idx >= m_alloc) |
| { |
| linemap_assert (m_alloc > 0); |
| m_alloc *= 2; |
| m_extra = XRESIZEVEC (T, m_extra, m_alloc); |
| } |
| linemap_assert (m_extra); |
| linemap_assert (idx < m_alloc); |
| m_extra[idx] = value; |
| } |
| } |
| |
| /* Truncate to length LEN. No deallocation is performed. */ |
| |
| template <typename T, int NUM_EMBEDDED> |
| void |
| semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len) |
| { |
| linemap_assert (len <= m_num); |
| m_num = len; |
| } |
| |
| class fixit_hint; |
| class diagnostic_path; |
| |
| /* A "rich" source code location, for use when printing diagnostics. |
| A rich_location has one or more carets&ranges, where the carets |
| are optional. These are referred to as "ranges" from here. |
| Typically the zeroth range has a caret; other ranges sometimes |
| have carets. |
| |
| The "primary" location of a rich_location is the caret of range 0, |
| used for determining the line/column when printing diagnostic |
| text, such as: |
| |
| some-file.c:3:1: error: ...etc... |
| |
| Additional ranges may be added to help the user identify other |
| pertinent clauses in a diagnostic. |
| |
| Ranges can (optionally) be given labels via class range_label. |
| |
| rich_location instances are intended to be allocated on the stack |
| when generating diagnostics, and to be short-lived. |
| |
| Examples of rich locations |
| -------------------------- |
| |
| Example A |
| ********* |
| int i = "foo"; |
| ^ |
| This "rich" location is simply a single range (range 0), with |
| caret = start = finish at the given point. |
| |
| Example B |
| ********* |
| a = (foo && bar) |
| ~~~~~^~~~~~~ |
| This rich location has a single range (range 0), with the caret |
| at the first "&", and the start/finish at the parentheses. |
| Compare with example C below. |
| |
| Example C |
| ********* |
| a = (foo && bar) |
| ~~~ ^~ ~~~ |
| This rich location has three ranges: |
| - Range 0 has its caret and start location at the first "&" and |
| end at the second "&. |
| - Range 1 has its start and finish at the "f" and "o" of "foo"; |
| the caret is not flagged for display, but is perhaps at the "f" |
| of "foo". |
| - Similarly, range 2 has its start and finish at the "b" and "r" of |
| "bar"; the caret is not flagged for display, but is perhaps at the |
| "b" of "bar". |
| Compare with example B above. |
| |
| Example D (Fortran frontend) |
| **************************** |
| x = x + y |
| 1 2 |
| This rich location has range 0 at "1", and range 1 at "2". |
| Both are flagged for caret display. Both ranges have start/finish |
| equal to their caret point. The frontend overrides the diagnostic |
| context's default caret character for these ranges. |
| |
| Example E (range labels) |
| ************************ |
| printf ("arg0: %i arg1: %s arg2: %i", |
| ^~ |
| | |
| const char * |
| 100, 101, 102); |
| ~~~ |
| | |
| int |
| This rich location has two ranges: |
| - range 0 is at the "%s" with start = caret = "%" and finish at |
| the "s". It has a range_label ("const char *"). |
| - range 1 has start/finish covering the "101" and is not flagged for |
| caret printing. The caret is at the start of "101", where its |
| range_label is printed ("int"). |
| |
| Fix-it hints |
| ------------ |
| |
| Rich locations can also contain "fix-it hints", giving suggestions |
| for the user on how to edit their code to fix a problem. These |
| can be expressed as insertions, replacements, and removals of text. |
| The edits by default are relative to the zeroth range within the |
| rich_location, but optionally they can be expressed relative to |
| other locations (using various overloaded methods of the form |
| rich_location::add_fixit_*). |
| |
| For example: |
| |
| Example F: fix-it hint: insert_before |
| ************************************* |
| ptr = arr[0]; |
| ^~~~~~ |
| & |
| This rich location has a single range (range 0) covering "arr[0]", |
| with the caret at the start. The rich location has a single |
| insertion fix-it hint, inserted before range 0, added via |
| richloc.add_fixit_insert_before ("&"); |
| |
| Example G: multiple fix-it hints: insert_before and insert_after |
| **************************************************************** |
| #define FN(ARG0, ARG1, ARG2) fn(ARG0, ARG1, ARG2) |
| ^~~~ ^~~~ ^~~~ |
| ( ) ( ) ( ) |
| This rich location has three ranges, covering "arg0", "arg1", |
| and "arg2", all with caret-printing enabled. |
| The rich location has 6 insertion fix-it hints: each arg |
| has a pair of insertion fix-it hints, suggesting wrapping |
| them with parentheses: one a '(' inserted before, |
| the other a ')' inserted after, added via |
| richloc.add_fixit_insert_before (LOC, "("); |
| and |
| richloc.add_fixit_insert_after (LOC, ")"); |
| |
| Example H: fix-it hint: removal |
| ******************************* |
| struct s {int i};; |
| ^ |
| - |
| This rich location has a single range at the stray trailing |
| semicolon, along with a single removal fix-it hint, covering |
| the same range, added via: |
| richloc.add_fixit_remove (); |
| |
| Example I: fix-it hint: replace |
| ******************************* |
| c = s.colour; |
| ^~~~~~ |
| color |
| This rich location has a single range (range 0) covering "colour", |
| and a single "replace" fix-it hint, covering the same range, |
| added via |
| richloc.add_fixit_replace ("color"); |
| |
| Example J: fix-it hint: line insertion |
| ************************************** |
| |
| 3 | #include <stddef.h> |
| + |+#include <stdio.h> |
| 4 | int the_next_line; |
| |
| This rich location has a single range at line 4 column 1, marked |
| with SHOW_LINES_WITHOUT_RANGE (to avoid printing a meaningless caret |
| on the "i" of int). It has a insertion fix-it hint of the string |
| "#include <stdio.h>\n". |
| |
| Adding a fix-it hint can fail: for example, attempts to insert content |
| at the transition between two line maps may fail due to there being no |
| location_t value to express the new location. |
| |
| Attempts to add a fix-it hint within a macro expansion will fail. |
| |
| There is only limited support for newline characters in fix-it hints: |
| only hints with newlines which insert an entire new line are permitted, |
| inserting at the start of a line, and finishing with a newline |
| (with no interior newline characters). Other attempts to add |
| fix-it hints containing newline characters will fail. |
| Similarly, attempts to delete or replace a range *affecting* multiple |
| lines will fail. |
| |
| The rich_location API handles these failures gracefully, so that |
| diagnostics can attempt to add fix-it hints without each needing |
| extensive checking. |
| |
| Fix-it hints within a rich_location are "atomic": if any hints can't |
| be applied, none of them will be (tracked by the m_seen_impossible_fixit |
| flag), and no fix-its hints will be displayed for that rich_location. |
| This implies that diagnostic messages need to be worded in such a way |
| that they make sense whether or not the fix-it hints are displayed, |
| or that richloc.seen_impossible_fixit_p () should be checked before |
| issuing the diagnostics. */ |
| |
| class rich_location |
| { |
| public: |
| /* Constructors. */ |
| |
| /* Constructing from a location. */ |
| rich_location (line_maps *set, location_t loc, |
| const range_label *label = NULL); |
| |
| /* Destructor. */ |
| ~rich_location (); |
| |
| /* The class manages the memory pointed to by the elements of |
| the M_FIXIT_HINTS vector and is not meant to be copied or |
| assigned. */ |
| rich_location (const rich_location &) = delete; |
| void operator= (const rich_location &) = delete; |
| |
| /* Accessors. */ |
| location_t get_loc () const { return get_loc (0); } |
| location_t get_loc (unsigned int idx) const; |
| |
| void |
| add_range (location_t loc, |
| enum range_display_kind range_display_kind |
| = SHOW_RANGE_WITHOUT_CARET, |
| const range_label *label = NULL); |
| |
| void |
| set_range (unsigned int idx, location_t loc, |
| enum range_display_kind range_display_kind); |
| |
| unsigned int get_num_locations () const { return m_ranges.count (); } |
| |
| const location_range *get_range (unsigned int idx) const; |
| location_range *get_range (unsigned int idx); |
| |
| expanded_location get_expanded_location (unsigned int idx) const; |
| |
| void |
| override_column (int column); |
| |
| /* Fix-it hints. */ |
| |
| /* Methods for adding insertion fix-it hints. */ |
| |
| /* Suggest inserting NEW_CONTENT immediately before the primary |
| range's start. */ |
| void |
| add_fixit_insert_before (const char *new_content); |
| |
| /* Suggest inserting NEW_CONTENT immediately before the start of WHERE. */ |
| void |
| add_fixit_insert_before (location_t where, |
| const char *new_content); |
| |
| /* Suggest inserting NEW_CONTENT immediately after the end of the primary |
| range. */ |
| void |
| add_fixit_insert_after (const char *new_content); |
| |
| /* Suggest inserting NEW_CONTENT immediately after the end of WHERE. */ |
| void |
| add_fixit_insert_after (location_t where, |
| const char *new_content); |
| |
| /* Methods for adding removal fix-it hints. */ |
| |
| /* Suggest removing the content covered by range 0. */ |
| void |
| add_fixit_remove (); |
| |
| /* Suggest removing the content covered between the start and finish |
| of WHERE. */ |
| void |
| add_fixit_remove (location_t where); |
| |
| /* Suggest removing the content covered by SRC_RANGE. */ |
| void |
| add_fixit_remove (source_range src_range); |
| |
| /* Methods for adding "replace" fix-it hints. */ |
| |
| /* Suggest replacing the content covered by range 0 with NEW_CONTENT. */ |
| void |
| add_fixit_replace (const char *new_content); |
| |
| /* Suggest replacing the content between the start and finish of |
| WHERE with NEW_CONTENT. */ |
| void |
| add_fixit_replace (location_t where, |
| const char *new_content); |
| |
| /* Suggest replacing the content covered by SRC_RANGE with |
| NEW_CONTENT. */ |
| void |
| add_fixit_replace (source_range src_range, |
| const char *new_content); |
| |
| unsigned int get_num_fixit_hints () const { return m_fixit_hints.count (); } |
| fixit_hint *get_fixit_hint (int idx) const { return m_fixit_hints[idx]; } |
| fixit_hint *get_last_fixit_hint () const; |
| bool seen_impossible_fixit_p () const { return m_seen_impossible_fixit; } |
| |
| /* Set this if the fix-it hints are not suitable to be |
| automatically applied. |
| |
| For example, if you are suggesting more than one |
| mutually exclusive solution to a problem, then |
| it doesn't make sense to apply all of the solutions; |
| manual intervention is required. |
| |
| If set, then the fix-it hints in the rich_location will |
| be printed, but will not be added to generated patches, |
| or affect the modified version of the file. */ |
| void fixits_cannot_be_auto_applied () |
| { |
| m_fixits_cannot_be_auto_applied = true; |
| } |
| |
| bool fixits_can_be_auto_applied_p () const |
| { |
| return !m_fixits_cannot_be_auto_applied; |
| } |
| |
| /* An optional path through the code. */ |
| const diagnostic_path *get_path () const { return m_path; } |
| void set_path (const diagnostic_path *path) { m_path = path; } |
| |
| /* A flag for hinting that the diagnostic involves character encoding |
| issues, and thus that it will be helpful to the user if we show some |
| representation of how the characters in the pertinent source lines |
| are encoded. |
| The default is false (i.e. do not escape). |
| When set to true, non-ASCII bytes in the pertinent source lines will |
| be escaped in a manner controlled by the user-supplied option |
| -fdiagnostics-escape-format=, so that the user can better understand |
| what's going on with the encoding in their source file. */ |
| bool escape_on_output_p () const { return m_escape_on_output; } |
| void set_escape_on_output (bool flag) { m_escape_on_output = flag; } |
| |
| const line_maps *get_line_table () const { return m_line_table; } |
| |
| private: |
| bool reject_impossible_fixit (location_t where); |
| void stop_supporting_fixits (); |
| void maybe_add_fixit (location_t start, |
| location_t next_loc, |
| const char *new_content); |
| |
| public: |
| static const int STATICALLY_ALLOCATED_RANGES = 3; |
| |
| protected: |
| line_maps * const m_line_table; |
| semi_embedded_vec <location_range, STATICALLY_ALLOCATED_RANGES> m_ranges; |
| |
| int m_column_override; |
| |
| mutable bool m_have_expanded_location; |
| bool m_seen_impossible_fixit; |
| bool m_fixits_cannot_be_auto_applied; |
| bool m_escape_on_output; |
| |
| mutable expanded_location m_expanded_location; |
| |
| static const int MAX_STATIC_FIXIT_HINTS = 2; |
| semi_embedded_vec <fixit_hint *, MAX_STATIC_FIXIT_HINTS> m_fixit_hints; |
| |
| const diagnostic_path *m_path; |
| }; |
| |
| /* A struct for the result of range_label::get_text: a NUL-terminated buffer |
| of localized text, and a flag to determine if the caller should "free" the |
| buffer. */ |
| |
| class label_text |
| { |
| public: |
| label_text () |
| : m_buffer (NULL), m_owned (false) |
| {} |
| |
| ~label_text () |
| { |
| if (m_owned) |
| free (m_buffer); |
| } |
| |
| /* Move ctor. */ |
| label_text (label_text &&other) |
| : m_buffer (other.m_buffer), m_owned (other.m_owned) |
| { |
| other.release (); |
| } |
| |
| /* Move assignment. */ |
| label_text & operator= (label_text &&other) |
| { |
| if (m_owned) |
| free (m_buffer); |
| m_buffer = other.m_buffer; |
| m_owned = other.m_owned; |
| other.release (); |
| return *this; |
| } |
| |
| /* Delete the copy ctor and copy-assignment operator. */ |
| label_text (const label_text &) = delete; |
| label_text & operator= (const label_text &) = delete; |
| |
| /* Create a label_text instance that borrows BUFFER from a |
| longer-lived owner. */ |
| static label_text borrow (const char *buffer) |
| { |
| return label_text (const_cast <char *> (buffer), false); |
| } |
| |
| /* Create a label_text instance that takes ownership of BUFFER. */ |
| static label_text take (char *buffer) |
| { |
| return label_text (buffer, true); |
| } |
| |
| void release () |
| { |
| m_buffer = NULL; |
| m_owned = false; |
| } |
| |
| const char *get () const |
| { |
| return m_buffer; |
| } |
| |
| bool is_owner () const |
| { |
| return m_owned; |
| } |
| |
| private: |
| char *m_buffer; |
| bool m_owned; |
| |
| label_text (char *buffer, bool owned) |
| : m_buffer (buffer), m_owned (owned) |
| {} |
| }; |
| |
| /* Abstract base class for labelling a range within a rich_location |
| (e.g. for labelling expressions with their type). |
| |
| Generating the text could require non-trivial work, so this work |
| is delayed (via the "get_text" virtual function) until the diagnostic |
| printing code "knows" it needs it, thus avoiding doing it e.g. for |
| warnings that are filtered by command-line flags. This virtual |
| function also isolates libcpp and the diagnostics subsystem from |
| the front-end and middle-end-specific code for generating the text |
| for the labels. |
| |
| Like the rich_location instances they annotate, range_label instances |
| are intended to be allocated on the stack when generating diagnostics, |
| and to be short-lived. */ |
| |
| class range_label |
| { |
| public: |
| virtual ~range_label () {} |
| |
| /* Get localized text for the label. |
| The RANGE_IDX is provided, allowing for range_label instances to be |
| shared by multiple ranges if need be (the "flyweight" design pattern). */ |
| virtual label_text get_text (unsigned range_idx) const = 0; |
| }; |
| |
| /* A fix-it hint: a suggested insertion, replacement, or deletion of text. |
| We handle these three types of edit with one class, by representing |
| them as replacement of a half-open range: |
| [start, next_loc) |
| Insertions have start == next_loc: "replace" the empty string at the |
| start location with the new string. |
| Deletions are replacement with the empty string. |
| |
| There is only limited support for newline characters in fix-it hints |
| as noted above in the comment for class rich_location. |
| A fixit_hint instance can have at most one newline character; if |
| present, the newline character must be the final character of |
| the content (preventing e.g. fix-its that split a pre-existing line). */ |
| |
| class fixit_hint |
| { |
| public: |
| fixit_hint (location_t start, |
| location_t next_loc, |
| const char *new_content); |
| ~fixit_hint () { free (m_bytes); } |
| |
| bool affects_line_p (const line_maps *set, |
| const char *file, |
| int line) const; |
| location_t get_start_loc () const { return m_start; } |
| location_t get_next_loc () const { return m_next_loc; } |
| bool maybe_append (location_t start, |
| location_t next_loc, |
| const char *new_content); |
| |
| const char *get_string () const { return m_bytes; } |
| size_t get_length () const { return m_len; } |
| |
| bool insertion_p () const { return m_start == m_next_loc; } |
| |
| bool ends_with_newline_p () const; |
| |
| private: |
| /* We don't use source_range here since, unlike most places, |
| this is a half-open/half-closed range: |
| [start, next_loc) |
| so that we can support insertion via start == next_loc. */ |
| location_t m_start; |
| location_t m_next_loc; |
| char *m_bytes; |
| size_t m_len; |
| }; |
| |
| #endif /* !LIBCPP_RICH_LOCATION_H */ |