mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-22 20:01:31 -05:00
Stops an ICE from occuring when path attribute is empty Fixes Rust-GCC#3607. gcc/rust/ChangeLog: * parse/rust-parse.cc (Rust::extract_module_path): Handle empty or whitespace-only path attributes. gcc/testsuite/ChangeLog: * rust/compile/torture/extern_mod2.rs: New test to ensure an error is emitted for empty path attributes. Signed-off-by: Vishruth Thimmaiah <vishruththimmaiah@gmail.com>
378 lines
10 KiB
C++
378 lines
10 KiB
C++
/* This file is part of GCC.
|
|
|
|
GCC 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.
|
|
|
|
GCC 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 GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "rust-parse.h"
|
|
#include "rust-linemap.h"
|
|
#include "rust-diagnostics.h"
|
|
#include "rust-token.h"
|
|
#include "rust-attribute-values.h"
|
|
|
|
namespace Rust {
|
|
|
|
std::string
|
|
extract_module_path (const AST::AttrVec &inner_attrs,
|
|
const AST::AttrVec &outer_attrs, const std::string &name)
|
|
{
|
|
AST::Attribute path_attr = AST::Attribute::create_empty ();
|
|
for (const auto &attr : inner_attrs)
|
|
{
|
|
if (attr.get_path ().as_string () == Values::Attributes::PATH)
|
|
{
|
|
path_attr = attr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Here, we found a path attribute, but it has no associated string. This is
|
|
// invalid
|
|
if (!path_attr.is_empty () && !path_attr.has_attr_input ())
|
|
{
|
|
rust_error_at (
|
|
path_attr.get_locus (),
|
|
"path attributes must contain a filename: %<#[path = \"file\"]%>");
|
|
return name;
|
|
}
|
|
|
|
for (const auto &attr : outer_attrs)
|
|
{
|
|
if (attr.get_path ().as_string () == Values::Attributes::PATH)
|
|
{
|
|
path_attr = attr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We didn't find a path attribute. This is not an error, there simply isn't
|
|
// one present
|
|
if (path_attr.is_empty ())
|
|
return name;
|
|
|
|
// Here, we found a path attribute, but it has no associated string. This is
|
|
// invalid
|
|
if (!path_attr.has_attr_input ())
|
|
{
|
|
rust_error_at (
|
|
path_attr.get_locus (),
|
|
"path attributes must contain a filename: %<#[path = \"file\"]%>");
|
|
return name;
|
|
}
|
|
|
|
auto path_value = path_attr.get_attr_input ().as_string ();
|
|
|
|
// At this point, the 'path' is of the following format: '= "<file.rs>"'
|
|
// We need to remove the equal sign and only keep the actual filename.
|
|
// In order to do this, we can simply go through the string until we find
|
|
// a character that is not an equal sign or whitespace
|
|
auto filename_begin = path_value.find_first_not_of ("=\t ");
|
|
|
|
// If the path consists of only whitespace, then we have an error
|
|
if (filename_begin == std::string::npos)
|
|
{
|
|
rust_error_at (
|
|
path_attr.get_locus (),
|
|
"path attributes must contain a filename: %<#[path = \"file\"]%>");
|
|
return name;
|
|
}
|
|
|
|
auto path = path_value.substr (filename_begin);
|
|
|
|
// On windows, the path might mix '/' and '\' separators. Replace the
|
|
// UNIX-like separators by MSDOS separators to make sure the path will resolve
|
|
// properly.
|
|
//
|
|
// Source: rustc compiler
|
|
// (https://github.com/rust-lang/rust/blob/9863bf51a52b8e61bcad312f81b5193d53099f9f/compiler/rustc_expand/src/module.rs#L174)
|
|
#if defined(HAVE_DOS_BASED_FILE_SYSTEM)
|
|
std::replace (path.begin (), path.end (), '/', '\\');
|
|
#endif /* HAVE_DOS_BASED_FILE_SYSTEM */
|
|
|
|
return path;
|
|
}
|
|
|
|
template <typename T>
|
|
static bool
|
|
contains (std::vector<T> &vec, T elm)
|
|
{
|
|
return std::find (vec.begin (), vec.end (), elm) != vec.end ();
|
|
}
|
|
|
|
/**
|
|
* Avoid UB by calling .front() and .back() on empty containers...
|
|
*/
|
|
|
|
template <typename T>
|
|
static const T *
|
|
get_back_ptr (const std::vector<std::unique_ptr<T>> &values)
|
|
{
|
|
if (values.empty ())
|
|
return nullptr;
|
|
|
|
return values.back ().get ();
|
|
}
|
|
|
|
template <typename T>
|
|
static const T *
|
|
get_front_ptr (const std::vector<std::unique_ptr<T>> &values)
|
|
{
|
|
if (values.empty ())
|
|
return nullptr;
|
|
|
|
return values.front ().get ();
|
|
}
|
|
|
|
static bool
|
|
peculiar_fragment_match_compatible_fragment (
|
|
const AST::MacroFragSpec &last_spec, const AST::MacroFragSpec &spec,
|
|
location_t match_locus)
|
|
{
|
|
static std::unordered_map<AST::MacroFragSpec::Kind,
|
|
std::vector<AST::MacroFragSpec::Kind>>
|
|
fragment_follow_set
|
|
= {{AST::MacroFragSpec::PATH, {AST::MacroFragSpec::BLOCK}},
|
|
{AST::MacroFragSpec::TY, {AST::MacroFragSpec::BLOCK}},
|
|
{AST::MacroFragSpec::VIS,
|
|
{AST::MacroFragSpec::IDENT, AST::MacroFragSpec::TY,
|
|
AST::MacroFragSpec::PATH}}};
|
|
|
|
auto is_valid
|
|
= contains (fragment_follow_set[last_spec.get_kind ()], spec.get_kind ());
|
|
|
|
if (!is_valid)
|
|
rust_error_at (match_locus,
|
|
"fragment specifier %qs is not allowed after %qs fragments",
|
|
spec.as_string ().c_str (), last_spec.as_string ().c_str ());
|
|
|
|
return is_valid;
|
|
}
|
|
|
|
static bool
|
|
peculiar_fragment_match_compatible (const AST::MacroMatchFragment &last_match,
|
|
const AST::MacroMatch &match)
|
|
{
|
|
static std::unordered_map<AST::MacroFragSpec::Kind, std::vector<TokenId>>
|
|
follow_set
|
|
= {{AST::MacroFragSpec::EXPR, {MATCH_ARROW, COMMA, SEMICOLON}},
|
|
{AST::MacroFragSpec::STMT, {MATCH_ARROW, COMMA, SEMICOLON}},
|
|
{AST::MacroFragSpec::PAT, {MATCH_ARROW, COMMA, EQUAL, PIPE, IF, IN}},
|
|
{AST::MacroFragSpec::PATH,
|
|
{MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE,
|
|
RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}},
|
|
{AST::MacroFragSpec::TY,
|
|
{MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE,
|
|
RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}},
|
|
{AST::MacroFragSpec::VIS,
|
|
{COMMA,
|
|
IDENTIFIER,
|
|
LEFT_PAREN,
|
|
LEFT_SQUARE,
|
|
EXCLAM,
|
|
ASTERISK,
|
|
AMP,
|
|
LOGICAL_AND,
|
|
QUESTION_MARK,
|
|
LIFETIME,
|
|
LEFT_ANGLE,
|
|
LEFT_SHIFT,
|
|
UNDERSCORE,
|
|
ABSTRACT,
|
|
AS,
|
|
ASYNC,
|
|
AUTO,
|
|
BECOME,
|
|
BOX,
|
|
BREAK,
|
|
CONST,
|
|
CONTINUE,
|
|
CRATE,
|
|
DO,
|
|
DYN,
|
|
ELSE,
|
|
ENUM_KW,
|
|
EXTERN_KW,
|
|
FALSE_LITERAL,
|
|
FINAL_KW,
|
|
FN_KW,
|
|
FOR,
|
|
IF,
|
|
IMPL,
|
|
IN,
|
|
LET,
|
|
LOOP,
|
|
MACRO,
|
|
MATCH_KW,
|
|
MOD,
|
|
MOVE,
|
|
MUT,
|
|
OVERRIDE_KW,
|
|
PUB,
|
|
REF,
|
|
RETURN_KW,
|
|
SELF_ALIAS,
|
|
SELF,
|
|
STATIC_KW,
|
|
STRUCT_KW,
|
|
SUPER,
|
|
TRAIT,
|
|
TRUE_LITERAL,
|
|
TRY,
|
|
TYPE,
|
|
TYPEOF,
|
|
UNSAFE,
|
|
UNSIZED,
|
|
USE,
|
|
VIRTUAL,
|
|
WHERE,
|
|
WHILE,
|
|
YIELD}}};
|
|
|
|
location_t error_locus = match.get_match_locus ();
|
|
std::string kind_str = "fragment";
|
|
auto &allowed_toks = follow_set[last_match.get_frag_spec ().get_kind ()];
|
|
|
|
// There are two behaviors to handle here: If the follow-up match is a token,
|
|
// we want to check if it is allowed.
|
|
// If it is a fragment, repetition or matcher then we know that it will be
|
|
// an error.
|
|
// For repetitions and matchers we want to extract a proper location to report
|
|
// the error.
|
|
switch (match.get_macro_match_type ())
|
|
{
|
|
case AST::MacroMatch::Tok:
|
|
{
|
|
auto tok = static_cast<const AST::Token *> (&match);
|
|
if (contains (allowed_toks, tok->get_id ()))
|
|
return true;
|
|
kind_str = "token `"
|
|
+ std::string (get_token_description (tok->get_id ())) + "`";
|
|
error_locus = tok->get_match_locus ();
|
|
break;
|
|
}
|
|
break;
|
|
case AST::MacroMatch::Repetition:
|
|
{
|
|
auto repetition
|
|
= static_cast<const AST::MacroMatchRepetition *> (&match);
|
|
auto &matches = repetition->get_matches ();
|
|
auto first_frag = get_front_ptr (matches);
|
|
if (first_frag)
|
|
return peculiar_fragment_match_compatible (last_match, *first_frag);
|
|
break;
|
|
}
|
|
case AST::MacroMatch::Matcher:
|
|
{
|
|
auto matcher = static_cast<const AST::MacroMatcher *> (&match);
|
|
auto first_token = matcher->get_delim_type ();
|
|
TokenId delim_id;
|
|
switch (first_token)
|
|
{
|
|
case AST::PARENS:
|
|
delim_id = LEFT_PAREN;
|
|
break;
|
|
case AST::SQUARE:
|
|
delim_id = LEFT_SQUARE;
|
|
break;
|
|
case AST::CURLY:
|
|
delim_id = LEFT_CURLY;
|
|
break;
|
|
default:
|
|
rust_unreachable ();
|
|
break;
|
|
}
|
|
if (contains (allowed_toks, delim_id))
|
|
return true;
|
|
kind_str = "token `" + std::string (get_token_description (delim_id))
|
|
+ "` at start of matcher";
|
|
error_locus = matcher->get_match_locus ();
|
|
break;
|
|
}
|
|
case AST::MacroMatch::Fragment:
|
|
{
|
|
auto last_spec = last_match.get_frag_spec ();
|
|
auto fragment = static_cast<const AST::MacroMatchFragment *> (&match);
|
|
if (last_spec.has_follow_set_fragment_restrictions ())
|
|
return peculiar_fragment_match_compatible_fragment (
|
|
last_spec, fragment->get_frag_spec (), match.get_match_locus ());
|
|
}
|
|
break;
|
|
}
|
|
|
|
rust_error_at (error_locus, "%s is not allowed after %qs fragment",
|
|
kind_str.c_str (),
|
|
last_match.get_frag_spec ().as_string ().c_str ());
|
|
auto allowed_toks_str
|
|
= "`" + std::string (get_token_description (allowed_toks[0])) + "`";
|
|
for (size_t i = 1; i < allowed_toks.size (); i++)
|
|
allowed_toks_str
|
|
+= ", `" + std::string (get_token_description (allowed_toks[i])) + "`";
|
|
|
|
rust_inform (error_locus, "allowed tokens are %s", allowed_toks_str.c_str ());
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
is_match_compatible (const AST::MacroMatch &last_match,
|
|
const AST::MacroMatch &match)
|
|
{
|
|
const AST::MacroMatch *new_last = nullptr;
|
|
|
|
// We want to "extract" the concerning matches. In cases such as matchers and
|
|
// repetitions, we actually store multiple matchers, but are only concerned
|
|
// about the follow-set ambiguities of certain elements.
|
|
// There are some cases where we can short-circuit the algorithm: There will
|
|
// never be restrictions on token literals, or on certain fragments which do
|
|
// not have a set of follow-restrictions.
|
|
|
|
switch (last_match.get_macro_match_type ())
|
|
{
|
|
// This is our main stop condition: When we are finally looking at the
|
|
// last match (or its actual last component), and it is a fragment, it
|
|
// may contain some follow up restrictions.
|
|
case AST::MacroMatch::Fragment:
|
|
{
|
|
auto fragment
|
|
= static_cast<const AST::MacroMatchFragment *> (&last_match);
|
|
if (fragment->get_frag_spec ().has_follow_set_restrictions ())
|
|
return peculiar_fragment_match_compatible (*fragment, match);
|
|
else
|
|
return true;
|
|
}
|
|
case AST::MacroMatch::Repetition:
|
|
{
|
|
// A repetition on the left hand side means we want to make sure the
|
|
// last match of the repetition is compatible with the new match
|
|
auto repetition
|
|
= static_cast<const AST::MacroMatchRepetition *> (&last_match);
|
|
new_last = get_back_ptr (repetition->get_matches ());
|
|
// If there are no matches in the matcher, then it can be followed by
|
|
// anything
|
|
if (!new_last)
|
|
return true;
|
|
break;
|
|
}
|
|
case AST::MacroMatch::Matcher:
|
|
case AST::MacroMatch::Tok:
|
|
return true;
|
|
}
|
|
|
|
rust_assert (new_last);
|
|
|
|
// We check recursively until we find a terminating condition
|
|
// FIXME: Does expansion depth/limit matter here?
|
|
return is_match_compatible (*new_last, match);
|
|
}
|
|
} // namespace Rust
|