blob: 9078a10164a2891a5449cce902ea59f8d2d97528 [file]
// Copyright (C) 2026 Free Software Foundation, Inc.
// 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-builtin-attribute-checker.h"
#include "optional.h"
#include "rust-attributes.h"
#include "rust-attribute-values.h"
#include "rust-session-manager.h"
namespace Rust {
namespace Analysis {
using Attrs = Values::Attributes;
void
check_inner_attribute (const AST::Attribute &attribute)
{
auto result_opt = lookup_builtin (attribute);
if (!result_opt.has_value ())
return;
auto result = result_opt.value ();
if (Attributes::valid_outer_attribute (result.name))
rust_error_at (attribute.get_locus (),
"attribute cannot be used at crate level");
}
/**
* Check that the string given to #[doc(alias = ...)] or #[doc(alias(...))] is
* valid.
*
* This means no whitespace characters other than spaces and no quoting
* characters.
*/
static void
check_doc_alias (const std::string &alias_input, const location_t locus)
{
// FIXME: The locus here is for the whole attribute. Can we get the locus
// of the alias input instead?
for (auto c : alias_input)
if ((ISSPACE (c) && c != ' ') || c == '\'' || c == '\"')
{
auto to_print = std::string (1, c);
switch (c)
{
case '\n':
to_print = "\\n";
break;
case '\t':
to_print = "\\t";
break;
default:
break;
}
rust_error_at (locus,
"invalid character used in %<#[doc(alias)]%> input: %qs",
to_print.c_str ());
}
if (alias_input.empty ())
return;
if (alias_input.front () == ' ' || alias_input.back () == ' ')
rust_error_at (locus,
"%<#[doc(alias)]%> input cannot start or end with a space");
}
// This namespace contains handlers for the builtin attribute checker,
// those handlers must verify the attribute internal structure and emit the
// appropriate error message if the structure is incorrect.
//
// They DO NOT check the attribute validity on the parent item.
namespace handlers {
void
doc (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
rust_error_at (
attribute.get_locus (),
"valid forms for the attribute are "
"%<#[doc(hidden|inline|...)]%> and %<#[doc = \" string \"]%>");
return;
}
switch (attribute.get_attr_input ().get_attr_input_type ())
{
case AST::AttrInput::LITERAL:
case AST::AttrInput::META_ITEM:
case AST::AttrInput::EXPR:
break;
// FIXME: Handle them as well
case AST::AttrInput::TOKEN_TREE:
{
// FIXME: This doesn't check for #[doc(alias(...))]
const auto &option = static_cast<const AST::DelimTokenTree &> (
attribute.get_attr_input ());
auto *meta_item = option.parse_to_meta_item ();
for (auto &item : meta_item->get_items ())
{
if (item->is_key_value_pair ())
{
auto name_value
= static_cast<AST::MetaNameValueStr *> (item.get ())
->get_name_value_pair ();
// FIXME: Check for other stuff than #[doc(alias = ...)]
if (name_value.first.as_string () == "alias")
check_doc_alias (name_value.second, attribute.get_locus ());
}
}
break;
}
}
}
void
deprecated (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
return;
const auto &input = attribute.get_attr_input ();
if (input.get_attr_input_type () != AST::AttrInput::META_ITEM)
return;
auto &meta = static_cast<const AST::AttrInputMetaItemContainer &> (input);
for (auto &current : meta.get_items ())
{
switch (current->get_kind ())
{
case AST::MetaItemInner::Kind::MetaItem:
{
auto *meta_item = static_cast<AST::MetaItem *> (current.get ());
switch (meta_item->get_item_kind ())
{
case AST::MetaItem::ItemKind::NameValueStr:
{
auto *nv = static_cast<AST::MetaNameValueStr *> (meta_item);
const std::string key = nv->get_name ().as_string ();
if (key != "since" && key != "note")
{
rust_error_at (nv->get_locus (), "unknown meta item %qs",
key.c_str ());
rust_inform (nv->get_locus (),
"expected one of %<since%>, %<note%>");
}
}
break;
case AST::MetaItem::ItemKind::Path:
{
// #[deprecated(a,a)]
auto *p = static_cast<AST::MetaItemPath *> (meta_item);
std::string ident = p->get_path ().as_string ();
rust_error_at (p->get_locus (), "unknown meta item %qs",
ident.c_str ());
rust_inform (p->get_locus (),
"expected one of %<since%>, %<note%>");
}
break;
case AST::MetaItem::ItemKind::Word:
{
// #[deprecated("a")]
auto *w = static_cast<AST::MetaWord *> (meta_item);
rust_error_at (
w->get_locus (),
"item in %<deprecated%> must be a key/value pair");
}
break;
case AST::MetaItem::ItemKind::PathExpr:
{
// #[deprecated(since=a)]
auto *px = static_cast<AST::MetaItemPathExpr *> (meta_item);
rust_error_at (
px->get_locus (),
"expected unsuffixed literal or identifier, found %qs",
px->get_expr ().as_string ().c_str ());
}
break;
case AST::MetaItem::ItemKind::Seq:
case AST::MetaItem::ItemKind::ListPaths:
case AST::MetaItem::ItemKind::ListNameValueStr:
default:
gcc_unreachable ();
break;
}
}
break;
case AST::MetaItemInner::Kind::LitExpr:
default:
gcc_unreachable ();
break;
}
}
}
void
link_section (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
rust_error_at (attribute.get_locus (),
"malformed %<link_section%> attribute input");
rust_inform (attribute.get_locus (),
"must be of the form: %<#[link_section = \"name\"]%>");
}
}
void
export_name (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
rust_error_at (attribute.get_locus (),
"malformed %<export_name%> attribute input");
rust_inform (attribute.get_locus (),
"must be of the form: %<#[export_name = \"name\"]%>");
return;
}
auto &attr_input = attribute.get_attr_input ();
if (attr_input.get_attr_input_type ()
== AST::AttrInput::AttrInputType::LITERAL)
{
auto &literal_expr
= static_cast<AST::AttrInputLiteral &> (attr_input).get_literal ();
auto lit_type = literal_expr.get_lit_type ();
switch (lit_type)
{
case AST::Literal::LitType::STRING:
case AST::Literal::LitType::RAW_STRING:
case AST::Literal::LitType::BYTE_STRING:
return;
default:
break;
}
}
rust_error_at (attribute.get_locus (), "attribute must be a string literal");
}
void
lint (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
auto name = attribute.get_path ().as_string ();
rust_error_at (attribute.get_locus (), "malformed %qs attribute input",
name.c_str ());
rust_inform (attribute.get_locus (),
"must be of the form: %<#[%s(lint1, lint2, ...)]%>",
name.c_str ());
}
}
void
link_name (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
rust_error_at (attribute.get_locus (),
"malformed %<link_name%> attribute input");
rust_inform (attribute.get_locus (),
"must be of the form: %<#[link_name = \"name\"]%>");
}
}
namespace {
void
check_crate_type (const AST::Attribute &attribute)
{
if (!Session::get_instance ().options.is_proc_macro ())
{
auto name = attribute.get_path ().as_string ();
rust_error_at (attribute.get_locus (),
"the %<#[%s]%> attribute is only usable with crates of "
"the %<proc-macro%> crate type",
name.c_str ());
}
}
} // namespace
static void
proc_macro_derive (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
auto name = attribute.get_path ().as_string ();
rust_error_at (attribute.get_locus (), "malformed %qs attribute input",
name.c_str ());
rust_inform (attribute.get_locus (),
"must be of the form: %<#[proc_macro_derive(TraitName, "
"/*opt*/ attributes(name1, name2, ...))]%>");
}
check_crate_type (attribute);
}
static void
proc_macro (const AST::Attribute &attribute)
{
check_crate_type (attribute);
}
static void
target_feature (const AST::Attribute &attribute)
{
if (!attribute.has_attr_input ())
{
rust_error_at (attribute.get_locus (),
"malformed %<target_feature%> attribute input");
rust_inform (attribute.get_locus (),
"must be of the form: %<#[target_feature(enable = "
"\"name\")]%>");
}
}
void
no_mangle (const AST::Attribute &attribute)
{
if (attribute.has_attr_input ())
{
rust_error_at (attribute.get_locus (), ErrorCode::E0754,
"malformed %<no_mangle%> attribute input");
rust_inform (attribute.get_locus (),
"must be of the form: %<#[no_mangle]%>");
}
}
} // namespace handlers
const std::unordered_map<std::string, std::function<void (AST::Attribute &)>>
attribute_checking_handlers
= {{Attrs::DOC, handlers::doc},
{Attrs::DEPRECATED, handlers::deprecated},
{Attrs::LINK_SECTION, handlers::link_section},
{Attrs::EXPORT_NAME, handlers::export_name},
{Attrs::NO_MANGLE, handlers::no_mangle},
{Attrs::ALLOW, handlers::lint},
{Attrs::DENY, handlers::lint},
{Attrs::WARN, handlers::lint},
{Attrs::FORBID, handlers::lint},
{Attrs::LINK_NAME, handlers::link_name},
{Attrs::PROC_MACRO_DERIVE, handlers::proc_macro_derive},
{Attrs::PROC_MACRO, handlers::proc_macro},
{Attrs::PROC_MACRO_ATTRIBUTE, handlers::proc_macro},
{Attrs::TARGET_FEATURE, handlers::target_feature}};
tl::optional<std::function<void (AST::Attribute &)>>
lookup_handler (std::string attr_name)
{
auto res = attribute_checking_handlers.find (attr_name);
if (res != attribute_checking_handlers.cend ())
return res->second;
return tl::nullopt;
}
static void
check_no_mangle_function (const AST::Attribute &attribute,
const AST::Function &fun)
{
if (!is_ascii_only (fun.get_function_name ().as_string ()))
rust_error_at (fun.get_function_name ().get_locus (),
"the %<#[no_mangle]%> attribute requires ASCII identifier");
}
/**
* Emit an error when an attribute is attached
* to an incompatable item type. e.g.:
*
* #[cold]
* struct A(u8, u8);
*
* Note that "#[derive]" is handled
* explicitly in rust-derive.cc
*/
void
check_valid_attribute_for_item (const AST::Attribute &attr,
const AST::Item &item)
{
if (item.get_item_kind () != AST::Item::Kind::Function
&& (attr.get_path () == Values::Attributes::TARGET_FEATURE
|| attr.get_path () == Values::Attributes::COLD
|| attr.get_path () == Values::Attributes::INLINE))
{
rust_error_at (attr.get_locus (),
"the %<#[%s]%> attribute may only be applied to functions",
attr.get_path ().as_string ().c_str ());
}
else if (attr.get_path () == Values::Attributes::REPR
&& item.get_item_kind () != AST::Item::Kind::Enum
&& item.get_item_kind () != AST::Item::Kind::Union
&& item.get_item_kind () != AST::Item::Kind::Struct)
{
rust_error_at (attr.get_locus (),
"the %<#[%s]%> attribute may only be applied "
"to structs, enums and unions",
attr.get_path ().as_string ().c_str ());
}
}
BuiltinAttributeChecker::BuiltinAttributeChecker () {}
void
BuiltinAttributeChecker::go (AST::Crate &crate)
{
visit (crate);
}
void
BuiltinAttributeChecker::visit (AST::Crate &crate)
{
for (auto &attr : crate.get_inner_attrs ())
{
check_inner_attribute (attr);
}
AST::DefaultASTVisitor::visit (crate);
}
void
BuiltinAttributeChecker::visit (AST::Attribute &attribute)
{
lookup_handler (attribute.get_path ().as_string ()).map ([&] (auto handler) {
handler (attribute);
});
AST::DefaultASTVisitor::visit (attribute);
}
void
BuiltinAttributeChecker::visit (AST::Module &module)
{
default_outer_attribute_check (module);
}
void
BuiltinAttributeChecker::visit (AST::ExternCrate &extern_crate)
{
default_outer_attribute_check (extern_crate);
}
void
BuiltinAttributeChecker::visit (AST::UseDeclaration &declaration)
{
default_outer_attribute_check (declaration);
}
void
BuiltinAttributeChecker::visit (AST::Function &function)
{
BuiltinAttrDefinition result;
for (auto &attribute : function.get_outer_attrs ())
{
check_valid_attribute_for_item (attribute, function);
auto result = lookup_builtin (attribute);
if (!result)
return;
if (result->name == Attrs::TARGET_FEATURE)
{
if (!function.get_qualifiers ().is_unsafe ())
{
rust_error_at (
attribute.get_locus (),
"the %<#[target_feature]%> attribute can only be applied "
"to %<unsafe%> functions");
}
}
else if (result->name == Attrs::NO_MANGLE)
{
check_no_mangle_function (attribute, function);
}
}
AST::DefaultASTVisitor::visit (function);
}
void
BuiltinAttributeChecker::visit (AST::TypeAlias &alias)
{
default_outer_attribute_check (alias);
}
void
BuiltinAttributeChecker::visit (AST::StructStruct &struct_item)
{
default_outer_attribute_check (struct_item);
}
void
BuiltinAttributeChecker::visit (AST::TupleStruct &tuple_struct)
{
default_outer_attribute_check (tuple_struct);
}
void
BuiltinAttributeChecker::visit (AST::Enum &enumeration)
{
default_outer_attribute_check (enumeration);
}
void
BuiltinAttributeChecker::visit (AST::Union &u)
{
default_outer_attribute_check (u);
}
void
BuiltinAttributeChecker::visit (AST::ConstantItem &item)
{
default_outer_attribute_check (item);
}
void
BuiltinAttributeChecker::visit (AST::StaticItem &item)
{
for (auto &attr : item.get_outer_attrs ())
check_valid_attribute_for_item (attr, item);
AST::DefaultASTVisitor::visit (item);
}
void
BuiltinAttributeChecker::visit (AST::Trait &trait)
{
default_outer_attribute_check (trait);
}
void
BuiltinAttributeChecker::visit (AST::InherentImpl &impl)
{
default_outer_attribute_check (impl);
}
void
BuiltinAttributeChecker::visit (AST::TraitImpl &impl)
{
default_outer_attribute_check (impl);
}
void
BuiltinAttributeChecker::visit (AST::ExternBlock &block)
{
default_outer_attribute_check (block);
}
} // namespace Analysis
} // namespace Rust