// Copyright (C) 2020-2025 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
// .
#include "rust-unsafe-checker.h"
#include "rust-hir.h"
#include "rust-hir-expr.h"
#include "rust-hir-stmt.h"
#include "rust-hir-item.h"
#include "rust-attribute-values.h"
#include "rust-system.h"
namespace Rust {
namespace HIR {
UnsafeChecker::UnsafeChecker ()
: context (*Resolver::TypeCheckContext::get ()),
resolver (*Resolver::Resolver::get ()),
mappings (Analysis::Mappings::get ())
{}
void
UnsafeChecker::go (HIR::Crate &crate)
{
for (auto &item : crate.get_items ())
item->accept_vis (*this);
}
static void
check_static_mut (HIR::Item *maybe_static, location_t locus)
{
if (maybe_static->get_hir_kind () == Node::BaseKind::VIS_ITEM)
{
auto item = static_cast- (maybe_static);
if (item->get_item_kind () == Item::ItemKind::Static)
{
auto static_item = static_cast (item);
if (static_item->is_mut ())
rust_error_at (
locus, "use of mutable static requires unsafe function or block");
}
}
}
static void
check_extern_static (HIR::ExternalItem *maybe_static, location_t locus)
{
if (maybe_static->get_extern_kind () == ExternalItem::ExternKind::Static)
rust_error_at (locus,
"use of extern static requires unsafe function or block");
}
void
UnsafeChecker::check_use_of_static (HirId node_id, location_t locus)
{
if (unsafe_context.is_in_context ())
return;
if (auto maybe_static_mut = mappings.lookup_hir_item (node_id))
check_static_mut (*maybe_static_mut, locus);
if (auto maybe_extern_static = mappings.lookup_hir_extern_item (node_id))
check_extern_static (static_cast (
maybe_extern_static->first),
locus);
}
static void
check_unsafe_call (HIR::Function *fn, location_t locus, const std::string &kind)
{
if (fn->get_qualifiers ().is_unsafe ())
rust_error_at (locus, ErrorCode::E0133,
"call to unsafe %s requires unsafe function or block",
kind.c_str ());
}
static bool
is_safe_intrinsic (const std::string &fn_name)
{
static const std::unordered_set safe_intrinsics = {
"abort",
"size_of",
"min_align_of",
"needs_drop",
"caller_location",
"add_with_overflow",
"sub_with_overflow",
"mul_with_overflow",
"wrapping_add",
"wrapping_sub",
"wrapping_mul",
"saturating_add",
"saturating_sub",
"rotate_left",
"rotate_right",
"ctpop",
"ctlz",
"cttz",
"bswap",
"bitreverse",
"discriminant_value",
"type_id",
"likely",
"unlikely",
"ptr_guaranteed_eq",
"ptr_guaranteed_ne",
"minnumf32",
"minnumf64",
"maxnumf32",
"rustc_peek",
"maxnumf64",
"type_name",
"forget",
"black_box",
"variant_count",
};
return safe_intrinsics.find (fn_name) != safe_intrinsics.end ();
}
static void
check_extern_call (HIR::ExternalItem *maybe_fn, HIR::ExternBlock *parent_block,
location_t locus)
{
// We have multiple operations to perform here
// 1. Is the item an actual function we're calling
// 2. Is the block it's defined in an FFI block or an `extern crate` block
//
// It is not unsafe to call into other crates, so items defined in an `extern
// crate` must be callable without being in an unsafe context. On the other
// hand, any function defined in a block with a specific ABI (even `extern
// "Rust"` blocks) is unsafe to call
if (maybe_fn->get_extern_kind () != ExternalItem::ExternKind::Function)
return;
// Some intrinsics are safe to call
if (parent_block->get_abi () == Rust::ABI::INTRINSIC
&& is_safe_intrinsic (maybe_fn->get_item_name ().as_string ()))
return;
rust_error_at (locus,
"call to extern function requires unsafe function or block");
}
void
UnsafeChecker::check_function_call (HirId node_id, location_t locus)
{
if (unsafe_context.is_in_context ())
return;
auto maybe_fn = mappings.lookup_hir_item (node_id);
if (maybe_fn
&& maybe_fn.value ()->get_item_kind () == Item::ItemKind::Function)
check_unsafe_call (static_cast (*maybe_fn), locus, "function");
if (auto maybe_extern = mappings.lookup_hir_extern_item (node_id))
check_extern_call (static_cast (maybe_extern->first),
*mappings.lookup_hir_extern_block (maybe_extern->second),
locus);
}
static void
check_target_attr (HIR::Function *fn, location_t locus)
{
if (std::any_of (fn->get_outer_attrs ().begin (),
fn->get_outer_attrs ().end (),
[] (const AST::Attribute &attr) {
return attr.get_path ().as_string ()
== Values::Attributes::TARGET_FEATURE;
}))
rust_error_at (locus,
"call to function with %<#[target_feature]%> requires "
"unsafe function or block");
}
void
UnsafeChecker::check_function_attr (HirId node_id, location_t locus)
{
if (unsafe_context.is_in_context ())
return;
auto maybe_fn = mappings.lookup_hir_item (node_id);
if (maybe_fn
&& maybe_fn.value ()->get_item_kind () == Item::ItemKind::Function)
check_target_attr (static_cast (*maybe_fn), locus);
}
void
UnsafeChecker::visit (Lifetime &)
{}
void
UnsafeChecker::visit (LifetimeParam &)
{}
void
UnsafeChecker::visit (PathInExpression &path)
{
NodeId ast_node_id = path.get_mappings ().get_nodeid ();
NodeId ref_node_id;
if (!resolver.lookup_resolved_name (ast_node_id, &ref_node_id))
return;
if (auto definition_id = mappings.lookup_node_to_hir (ref_node_id))
{
check_use_of_static (*definition_id, path.get_locus ());
}
else
{
rust_unreachable ();
}
}
void
UnsafeChecker::visit (TypePathSegment &)
{}
void
UnsafeChecker::visit (TypePathSegmentGeneric &)
{}
void
UnsafeChecker::visit (TypePathSegmentFunction &)
{}
void
UnsafeChecker::visit (TypePath &)
{}
void
UnsafeChecker::visit (QualifiedPathInExpression &)
{}
void
UnsafeChecker::visit (QualifiedPathInType &)
{}
void
UnsafeChecker::visit (LiteralExpr &)
{}
void
UnsafeChecker::visit (BorrowExpr &expr)
{
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (DereferenceExpr &expr)
{
TyTy::BaseType *to_deref_type;
auto to_deref = expr.get_expr ()->get_mappings ().get_hirid ();
rust_assert (context.lookup_type (to_deref, &to_deref_type));
if (to_deref_type->get_kind () == TyTy::TypeKind::POINTER
&& !unsafe_context.is_in_context ())
rust_error_at (expr.get_locus (), "dereference of raw pointer requires "
"unsafe function or block");
}
void
UnsafeChecker::visit (ErrorPropagationExpr &expr)
{
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (NegationExpr &expr)
{
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ArithmeticOrLogicalExpr &expr)
{
expr.get_lhs ()->accept_vis (*this);
expr.get_rhs ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ComparisonExpr &expr)
{
expr.get_lhs ()->accept_vis (*this);
expr.get_rhs ()->accept_vis (*this);
}
void
UnsafeChecker::visit (LazyBooleanExpr &expr)
{
expr.get_lhs ()->accept_vis (*this);
expr.get_rhs ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TypeCastExpr &expr)
{
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (AssignmentExpr &expr)
{
expr.get_lhs ()->accept_vis (*this);
expr.get_rhs ()->accept_vis (*this);
}
void
UnsafeChecker::visit (CompoundAssignmentExpr &expr)
{
expr.get_lhs ()->accept_vis (*this);
expr.get_rhs ()->accept_vis (*this);
}
void
UnsafeChecker::visit (GroupedExpr &expr)
{
expr.get_expr_in_parens ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ArrayElemsValues &elems)
{
for (auto &elem : elems.get_values ())
elem->accept_vis (*this);
}
void
UnsafeChecker::visit (ArrayElemsCopied &elems)
{
elems.get_elem_to_copy ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ArrayExpr &expr)
{
expr.get_internal_elements ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ArrayIndexExpr &expr)
{
expr.get_array_expr ()->accept_vis (*this);
expr.get_index_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TupleExpr &expr)
{
for (auto &elem : expr.get_tuple_elems ())
elem->accept_vis (*this);
}
void
UnsafeChecker::visit (TupleIndexExpr &expr)
{
expr.get_tuple_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (StructExprStruct &)
{}
void
UnsafeChecker::visit (StructExprFieldIdentifier &)
{}
void
UnsafeChecker::visit (StructExprFieldIdentifierValue &field)
{
field.get_value ()->accept_vis (*this);
}
void
UnsafeChecker::visit (StructExprFieldIndexValue &field)
{
field.get_value ()->accept_vis (*this);
}
void
UnsafeChecker::visit (StructExprStructFields &expr)
{
for (auto &field : expr.get_fields ())
field->accept_vis (*this);
}
void
UnsafeChecker::visit (StructExprStructBase &)
{}
void
UnsafeChecker::visit (CallExpr &expr)
{
if (!expr.get_fnexpr ())
return;
NodeId ast_node_id = expr.get_fnexpr ()->get_mappings ().get_nodeid ();
NodeId ref_node_id;
// There are no unsafe types, and functions are defined in the name resolver.
// If we can't find the name, then we're dealing with a type and should return
// early.
if (!resolver.lookup_resolved_name (ast_node_id, &ref_node_id))
return;
if (auto definition_id = mappings.lookup_node_to_hir (ref_node_id))
{
// At this point we have the function's HIR Id. There are three checks we
// must perform:
// 1. The function is an unsafe one
// 2. The function is an extern one
// 3. The function is marked with a target_feature attribute
check_function_call (*definition_id, expr.get_locus ());
check_function_attr (*definition_id, expr.get_locus ());
if (expr.has_params ())
for (auto &arg : expr.get_arguments ())
arg->accept_vis (*this);
}
else
{
rust_unreachable ();
}
}
void
UnsafeChecker::visit (MethodCallExpr &expr)
{
TyTy::BaseType *method_type;
context.lookup_type (expr.get_method_name ().get_mappings ().get_hirid (),
&method_type);
auto fn = *static_cast (method_type);
auto method = mappings.lookup_hir_implitem (fn.get_ref ());
if (!unsafe_context.is_in_context () && method)
check_unsafe_call (static_cast (method->first),
expr.get_locus (), "method");
expr.get_receiver ()->accept_vis (*this);
for (auto &arg : expr.get_arguments ())
arg->accept_vis (*this);
}
void
UnsafeChecker::visit (FieldAccessExpr &expr)
{
expr.get_receiver_expr ()->accept_vis (*this);
if (unsafe_context.is_in_context ())
return;
TyTy::BaseType *receiver_ty;
auto ok = context.lookup_type (
expr.get_receiver_expr ()->get_mappings ().get_hirid (), &receiver_ty);
rust_assert (ok);
if (receiver_ty->get_kind () == TyTy::TypeKind::ADT)
{
auto maybe_union = static_cast (receiver_ty);
if (maybe_union->is_union ())
rust_error_at (
expr.get_locus (),
"access to union field requires unsafe function or block");
}
}
void
UnsafeChecker::visit (ClosureExpr &expr)
{
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (BlockExpr &expr)
{
for (auto &stmt : expr.get_statements ())
stmt->accept_vis (*this);
if (expr.has_expr ())
expr.get_final_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ContinueExpr &)
{}
void
UnsafeChecker::visit (BreakExpr &expr)
{
if (expr.has_break_expr ())
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (RangeFromToExpr &expr)
{
expr.get_from_expr ()->accept_vis (*this);
expr.get_to_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (RangeFromExpr &expr)
{
expr.get_from_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (RangeToExpr &expr)
{
expr.get_to_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (RangeFullExpr &)
{}
void
UnsafeChecker::visit (RangeFromToInclExpr &expr)
{
expr.get_from_expr ()->accept_vis (*this);
expr.get_to_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (RangeToInclExpr &expr)
{
expr.get_to_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ReturnExpr &expr)
{
if (expr.has_return_expr ())
expr.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (UnsafeBlockExpr &expr)
{
unsafe_context.enter (expr.get_mappings ().get_hirid ());
expr.get_block_expr ()->accept_vis (*this);
unsafe_context.exit ();
}
void
UnsafeChecker::visit (LoopExpr &expr)
{
expr.get_loop_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (WhileLoopExpr &expr)
{
expr.get_predicate_expr ()->accept_vis (*this);
expr.get_loop_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (WhileLetLoopExpr &expr)
{
expr.get_cond ()->accept_vis (*this);
expr.get_loop_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (IfExpr &expr)
{
expr.get_if_condition ()->accept_vis (*this);
expr.get_if_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (IfExprConseqElse &expr)
{
expr.get_if_condition ()->accept_vis (*this);
expr.get_if_block ()->accept_vis (*this);
expr.get_else_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (IfLetExpr &expr)
{
expr.get_scrutinee_expr ()->accept_vis (*this);
expr.get_if_block ()->accept_vis (*this);
}
void
UnsafeChecker::visit (IfLetExprConseqElse &expr)
{
expr.get_scrutinee_expr ()->accept_vis (*this);
expr.get_if_block ()->accept_vis (*this);
// TODO: Visit else expression
}
void
UnsafeChecker::visit (MatchExpr &expr)
{
expr.get_scrutinee_expr ()->accept_vis (*this);
for (auto &match_arm : expr.get_match_cases ())
match_arm.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (AwaitExpr &)
{
// TODO: Visit expression
}
void
UnsafeChecker::visit (AsyncBlockExpr &)
{
// TODO: Visit block expression
}
void
UnsafeChecker::visit (InlineAsm &expr)
{
if (unsafe_context.is_in_context ())
return;
rust_error_at (
expr.get_locus (), ErrorCode::E0133,
"use of inline assembly is unsafe and requires unsafe function or block");
}
void
UnsafeChecker::visit (TypeParam &)
{}
void
UnsafeChecker::visit (ConstGenericParam &)
{}
void
UnsafeChecker::visit (LifetimeWhereClauseItem &)
{}
void
UnsafeChecker::visit (TypeBoundWhereClauseItem &)
{}
void
UnsafeChecker::visit (Module &module)
{
for (auto &item : module.get_items ())
item->accept_vis (*this);
}
void
UnsafeChecker::visit (ExternCrate &)
{}
void
UnsafeChecker::visit (UseTreeGlob &)
{}
void
UnsafeChecker::visit (UseTreeList &)
{}
void
UnsafeChecker::visit (UseTreeRebind &)
{}
void
UnsafeChecker::visit (UseDeclaration &)
{}
void
UnsafeChecker::visit (Function &function)
{
auto is_unsafe_fn = function.get_qualifiers ().is_unsafe ();
if (is_unsafe_fn)
unsafe_context.enter (function.get_mappings ().get_hirid ());
function.get_definition ()->accept_vis (*this);
if (is_unsafe_fn)
unsafe_context.exit ();
}
void
UnsafeChecker::visit (TypeAlias &)
{
// FIXME: What do we need to do to handle type aliasing? Is it possible to
// have unsafe types? Type aliases on unsafe functions?
}
void
UnsafeChecker::visit (StructStruct &)
{}
void
UnsafeChecker::visit (TupleStruct &)
{}
void
UnsafeChecker::visit (EnumItem &)
{}
void
UnsafeChecker::visit (EnumItemTuple &)
{}
void
UnsafeChecker::visit (EnumItemStruct &)
{}
void
UnsafeChecker::visit (EnumItemDiscriminant &)
{}
void
UnsafeChecker::visit (Enum &)
{}
void
UnsafeChecker::visit (Union &)
{}
void
UnsafeChecker::visit (ConstantItem &const_item)
{
const_item.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (StaticItem &static_item)
{
static_item.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TraitItemFunc &item)
{
if (item.has_block_defined ())
item.get_block_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TraitItemConst &item)
{
if (item.has_expr ())
item.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TraitItemType &)
{}
void
UnsafeChecker::visit (Trait &trait)
{
// FIXME: Handle unsafe traits
for (auto &item : trait.get_trait_items ())
item->accept_vis (*this);
}
void
UnsafeChecker::visit (ImplBlock &impl)
{
bool safe = !impl.is_unsafe ();
// Check for unsafe-only attributes on generics and lifetimes
if (safe)
for (auto &parm : impl.get_generic_params ())
{
for (auto o_attr : parm->get_outer_attrs ())
{
rust_assert (!o_attr.is_inner_attribute ());
Rust::AST::SimplePath path = o_attr.get_path ();
if (path == Values::Attributes::MAY_DANGLE)
rust_error_at (
o_attr.get_locus (), ErrorCode::E0569,
"use of % is unsafe and requires unsafe impl");
}
}
for (auto &item : impl.get_impl_items ())
item->accept_vis (*this);
}
void
UnsafeChecker::visit (ExternalStaticItem &)
{}
void
UnsafeChecker::visit (ExternalFunctionItem &)
{}
void
UnsafeChecker::visit (ExternalTypeItem &)
{}
void
UnsafeChecker::visit (ExternBlock &block)
{
// FIXME: Do we need to do this?
for (auto &item : block.get_extern_items ())
item->accept_vis (*this);
}
void
UnsafeChecker::visit (LiteralPattern &)
{}
void
UnsafeChecker::visit (IdentifierPattern &)
{}
void
UnsafeChecker::visit (WildcardPattern &)
{}
void
UnsafeChecker::visit (RangePatternBoundLiteral &)
{}
void
UnsafeChecker::visit (RangePatternBoundPath &)
{}
void
UnsafeChecker::visit (RangePatternBoundQualPath &)
{}
void
UnsafeChecker::visit (RangePattern &)
{}
void
UnsafeChecker::visit (ReferencePattern &)
{}
void
UnsafeChecker::visit (StructPatternFieldTuplePat &)
{}
void
UnsafeChecker::visit (StructPatternFieldIdentPat &)
{}
void
UnsafeChecker::visit (StructPatternFieldIdent &)
{}
void
UnsafeChecker::visit (StructPattern &)
{}
void
UnsafeChecker::visit (TupleStructItemsNoRange &)
{}
void
UnsafeChecker::visit (TupleStructItemsRange &)
{}
void
UnsafeChecker::visit (TupleStructPattern &)
{}
void
UnsafeChecker::visit (TuplePatternItemsMultiple &)
{}
void
UnsafeChecker::visit (TuplePatternItemsRanged &)
{}
void
UnsafeChecker::visit (TuplePattern &)
{}
void
UnsafeChecker::visit (SlicePattern &)
{}
void
UnsafeChecker::visit (AltPattern &)
{}
void
UnsafeChecker::visit (EmptyStmt &)
{}
void
UnsafeChecker::visit (LetStmt &stmt)
{
if (stmt.has_init_expr ())
stmt.get_init_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (ExprStmt &stmt)
{
stmt.get_expr ()->accept_vis (*this);
}
void
UnsafeChecker::visit (TraitBound &)
{}
void
UnsafeChecker::visit (ImplTraitType &)
{}
void
UnsafeChecker::visit (TraitObjectType &)
{}
void
UnsafeChecker::visit (ParenthesisedType &)
{}
void
UnsafeChecker::visit (ImplTraitTypeOneBound &)
{}
void
UnsafeChecker::visit (TupleType &)
{}
void
UnsafeChecker::visit (NeverType &)
{}
void
UnsafeChecker::visit (RawPointerType &)
{}
void
UnsafeChecker::visit (ReferenceType &)
{}
void
UnsafeChecker::visit (ArrayType &)
{}
void
UnsafeChecker::visit (SliceType &)
{}
void
UnsafeChecker::visit (InferredType &)
{}
void
UnsafeChecker::visit (BareFunctionType &)
{}
} // namespace HIR
} // namespace Rust