// 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-privacy-reporter.h"
#include "rust-session-manager.h"
#include "rust-hir-expr.h"
#include "rust-hir-stmt.h"
#include "rust-hir-item.h"
#include "rust-attribute-values.h"
#include "rust-immutable-name-resolution-context.h"
namespace Rust {
namespace Privacy {
PrivacyReporter::PrivacyReporter (
Analysis::Mappings &mappings, Resolver::Resolver &resolver,
const Rust::Resolver::TypeCheckContext &ty_ctx)
: mappings (mappings), resolver (resolver), ty_ctx (ty_ctx),
current_module (tl::nullopt)
{}
// Find a proc_macro, proc_macro_derive or proc_macro_attribute
// attribute in a vector of attribute
static tl::optional
find_proc_macro_attribute (const AST::AttrVec &outer_attrs)
{
for (auto &a : outer_attrs)
{
auto &segments = a.get_path ().get_segments ();
if (segments.size () != 1)
continue;
auto name = segments.at (0).get_segment_name ();
if (name == Values::Attributes::PROC_MACRO
|| name == Values::Attributes::PROC_MACRO_ATTRIBUTE
|| name == Values::Attributes::PROC_MACRO_DERIVE)
return name;
}
return tl::nullopt;
}
// Common check on crate items when dealing with 'proc-macro' crate type.
static void
proc_macro_privacy_check (std::unique_ptr &item)
{
if (item->get_hir_kind () == HIR::Node::BaseKind::VIS_ITEM)
{
auto attribute = find_proc_macro_attribute (item->get_outer_attrs ());
bool pub_item = static_cast (item.get ())
->get_visibility ()
.is_public ();
if (pub_item && !attribute.has_value ()) // Public non proc-macro
rust_error_at (
item->get_locus (),
"% crate types currently cannot export any "
"items other than functions tagged with %<#[proc_macro]%>, "
"%<#[proc_macro_derive]%> or %<#[proc_macro_attribute]%>");
else if (!pub_item && attribute.has_value ()) // Private proc-macro
rust_error_at (item->get_locus (),
"functions tagged with %<#[%s]%> must be %",
attribute.value ().c_str ());
}
}
void
PrivacyReporter::go (HIR::Crate &crate)
{
for (auto &item : crate.get_items ())
{
if (Session::get_instance ().options.is_proc_macro ())
proc_macro_privacy_check (item);
item->accept_vis (*this);
}
}
static bool
is_child_module (Analysis::Mappings &mappings, NodeId parent,
NodeId possible_child)
{
if (flag_name_resolution_2_0)
{
auto &nr_ctx
= Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
return nr_ctx.values.is_module_descendant (parent, possible_child);
}
auto children = mappings.lookup_module_children (parent);
if (!children)
return false;
// Visit all toplevel children
for (auto &child : *children)
if (child == possible_child)
return true;
// Now descend recursively in the child module tree
for (auto &child : *children)
if (is_child_module (mappings, child, possible_child))
return true;
return false;
}
// FIXME: This function needs a lot of refactoring
void
PrivacyReporter::check_for_privacy_violation (const NodeId &use_id,
const location_t locus)
{
NodeId ref_node_id = UNKNOWN_NODEID;
if (flag_name_resolution_2_0)
{
auto &nr_ctx
= Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
if (auto id = nr_ctx.lookup (use_id))
ref_node_id = *id;
}
// FIXME: Don't assert here - we might be dealing with a type
else if (!resolver.lookup_resolved_name (use_id, &ref_node_id))
resolver.lookup_resolved_type (use_id, &ref_node_id);
// FIXME: Assert here. For now, we return since this causes issues when
// checking inferred types (#1260)
// rust_assert (ref_node_id != UNKNOWN_NODEID);
if (ref_node_id == UNKNOWN_NODEID)
return;
auto vis = mappings.lookup_visibility (ref_node_id);
// FIXME: Can we really return here if the item has no visibility?
if (!vis)
return;
auto valid = true;
switch (vis->get_kind ())
{
case ModuleVisibility::Public:
break;
case ModuleVisibility::Restricted: {
// If we are in the crate, everything is restricted correctly, but we
// can't get a module for it
if (!current_module.has_value ())
return;
auto module = mappings.lookup_defid (vis->get_module_id ()).value ();
auto mod_node_id = module->get_mappings ().get_nodeid ();
// We are in the module referenced by the pub(restricted) visibility.
// This is valid
if (mod_node_id == current_module.value ())
break;
// FIXME: This needs a LOT of TLC: hinting about the definition, a
// string to say if it's a module, function, type, etc...
if (!is_child_module (mappings, mod_node_id, current_module.value ()))
valid = false;
}
break;
case ModuleVisibility::Unknown:
rust_unreachable ();
break;
}
if (!valid)
{
rich_location richloc (line_table, locus);
richloc.add_fixit_replace ("item is private");
rust_error_at (richloc, ErrorCode::E0603,
"definition is private in this context");
}
}
void
PrivacyReporter::check_base_type_privacy (Analysis::NodeMapping &node_mappings,
const TyTy::BaseType *ty,
const location_t locus)
{
// Avoids repeating commong argument such as `use_id` or `locus` since we're
// doing a lot of recursive calls here
auto recursive_check
= [this, &node_mappings, &locus] (const TyTy::BaseType *ty) {
return check_base_type_privacy (node_mappings, ty, locus);
};
switch (ty->get_kind ())
{
// These "simple" types are our stop condition
case TyTy::BOOL:
case TyTy::CHAR:
case TyTy::INT:
case TyTy::UINT:
case TyTy::FLOAT:
case TyTy::USIZE:
case TyTy::ISIZE:
case TyTy::ADT:
case TyTy::STR: {
auto ref_id = ty->get_ref ();
if (auto lookup_id = mappings.lookup_hir_to_node (ref_id))
return check_for_privacy_violation (*lookup_id, locus);
rust_unreachable ();
}
case TyTy::REF:
return recursive_check (
static_cast (ty)->get_base ());
case TyTy::POINTER:
return recursive_check (
static_cast (ty)->get_base ());
case TyTy::ARRAY:
return recursive_check (
static_cast (ty)->get_element_type ());
case TyTy::SLICE:
return recursive_check (
static_cast (ty)->get_element_type ());
case TyTy::FNPTR:
for (auto ¶m : static_cast (ty)->get_params ())
recursive_check (param.get_tyty ());
return recursive_check (
static_cast (ty)->get_return_type ());
case TyTy::TUPLE:
for (auto ¶m :
static_cast (ty)->get_fields ())
recursive_check (param.get_tyty ());
return;
case TyTy::PLACEHOLDER: {
const auto p = static_cast (ty);
if (!p->can_resolve ())
return;
return recursive_check (p->resolve ());
}
case TyTy::PROJECTION:
return recursive_check (
static_cast (ty)->get ());
case TyTy::CLOSURE:
rust_sorry_at (locus, "privacy pass for closures is not handled yet");
break;
// If we're dealing with a generic param, there's nothing we should be
// doing here
case TyTy::PARAM:
// We are dealing with a function definition that has been assigned
// somewhere else. Nothing to resolve privacy-wise other than the actual
// function, which is resolved as an expression
case TyTy::FNDEF:
// FIXME: Can we really not resolve Dynamic types here? Shouldn't we have
// a look at the path and perform proper privacy analysis?
case TyTy::DYNAMIC:
// The never type is builtin and always available
case TyTy::NEVER:
// We shouldn't have inference types here, ever
case TyTy::INFER:
return;
case TyTy::OPAQUE:
return;
case TyTy::ERROR:
return;
}
}
void
PrivacyReporter::check_type_privacy (const HIR::Type &type)
{
TyTy::BaseType *lookup = nullptr;
rust_assert (ty_ctx.lookup_type (type.get_mappings ().get_hirid (), &lookup));
auto node_mappings = type.get_mappings ();
return check_base_type_privacy (node_mappings, lookup, type.get_locus ());
}
void
PrivacyReporter::visit (HIR::PathInExpression &path)
{
check_for_privacy_violation (path.get_mappings ().get_nodeid (),
path.get_locus ());
}
void
PrivacyReporter::visit (HIR::TypePathSegmentFunction &)
{
// FIXME: Do we need to do anything for this?
}
void
PrivacyReporter::visit (HIR::InlineAsm &)
{}
void
PrivacyReporter::visit (HIR::TypePath &path)
{
check_for_privacy_violation (path.get_mappings ().get_nodeid (),
path.get_locus ());
}
void
PrivacyReporter::visit (HIR::QualifiedPathInExpression &path)
{
check_for_privacy_violation (path.get_mappings ().get_nodeid (),
path.get_locus ());
}
void
PrivacyReporter::visit (HIR::QualifiedPathInType &path)
{
check_for_privacy_violation (path.get_mappings ().get_nodeid (),
path.get_locus ());
}
void
PrivacyReporter::visit (HIR::LiteralExpr &)
{
// Literals cannot contain any sort of privacy violation
}
void
PrivacyReporter::visit (HIR::BorrowExpr &expr)
{
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::DereferenceExpr &expr)
{
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ErrorPropagationExpr &expr)
{
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::NegationExpr &expr)
{
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ArithmeticOrLogicalExpr &expr)
{
expr.get_lhs ().accept_vis (*this);
expr.get_rhs ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ComparisonExpr &expr)
{
expr.get_lhs ().accept_vis (*this);
expr.get_rhs ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::LazyBooleanExpr &expr)
{
expr.get_lhs ().accept_vis (*this);
expr.get_rhs ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::TypeCastExpr &expr)
{
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::AssignmentExpr &expr)
{
expr.get_lhs ().accept_vis (*this);
expr.get_rhs ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::CompoundAssignmentExpr &expr)
{
expr.get_lhs ().accept_vis (*this);
expr.get_rhs ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::GroupedExpr &expr)
{
expr.get_expr_in_parens ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ArrayExpr &expr)
{
HIR::ArrayElems &elements = expr.get_internal_elements ();
switch (elements.get_array_expr_type ())
{
case HIR::ArrayElems::ArrayExprType::VALUES: {
auto &elems = static_cast (elements);
for (auto &value : elems.get_values ())
value->accept_vis (*this);
}
return;
case HIR::ArrayElems::ArrayExprType::COPIED:
auto &elems = static_cast (elements);
elems.get_elem_to_copy ().accept_vis (*this);
}
}
void
PrivacyReporter::visit (HIR::ArrayIndexExpr &expr)
{
expr.get_array_expr ().accept_vis (*this);
expr.get_index_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::TupleExpr &expr)
{
for (auto &value : expr.get_tuple_elems ())
value->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::TupleIndexExpr &expr)
{
expr.get_tuple_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::StructExprStruct &)
{
// FIXME: We need to check the visibility of the type it refers to here
}
void
PrivacyReporter::visit (HIR::StructExprFieldIdentifier &)
{}
void
PrivacyReporter::visit (HIR::StructExprFieldIdentifierValue &field)
{
field.get_value ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::StructExprFieldIndexValue &field)
{
field.get_value ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::StructExprStructFields &expr)
{
for (auto &field : expr.get_fields ())
field->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::CallExpr &expr)
{
expr.get_fnexpr ().accept_vis (*this);
for (auto ¶m : expr.get_arguments ())
param->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::MethodCallExpr &expr)
{
expr.get_receiver ().accept_vis (*this);
for (auto ¶m : expr.get_arguments ())
param->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::FieldAccessExpr &expr)
{
expr.get_receiver_expr ().accept_vis (*this);
// FIXME: We should also check if the field is public?
}
void
PrivacyReporter::visit (HIR::ClosureExpr &)
{
// Not handled yet
}
void
PrivacyReporter::visit (HIR::BlockExpr &expr)
{
for (auto &stmt : expr.get_statements ())
stmt->accept_vis (*this);
if (expr.has_final_expr ())
expr.get_final_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ContinueExpr &)
{}
void
PrivacyReporter::visit (HIR::BreakExpr &expr)
{
if (expr.has_break_expr ())
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::RangeFromToExpr &expr)
{
expr.get_from_expr ().accept_vis (*this);
expr.get_to_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::RangeFromExpr &expr)
{
expr.get_from_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::RangeToExpr &expr)
{
expr.get_to_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::RangeFullExpr &)
{}
void
PrivacyReporter::visit (HIR::RangeFromToInclExpr &expr)
{
expr.get_from_expr ().accept_vis (*this);
expr.get_to_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::RangeToInclExpr &)
{
// Not handled yet
}
void
PrivacyReporter::visit (HIR::ReturnExpr &expr)
{
if (expr.has_expr ())
expr.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::UnsafeBlockExpr &expr)
{
expr.get_block_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::LoopExpr &expr)
{
expr.get_loop_block ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::WhileLoopExpr &expr)
{
expr.get_predicate_expr ().accept_vis (*this);
expr.get_loop_block ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::WhileLetLoopExpr &expr)
{
expr.get_cond ().accept_vis (*this);
expr.get_loop_block ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::IfExpr &expr)
{
expr.get_if_condition ().accept_vis (*this);
expr.get_if_block ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::IfExprConseqElse &expr)
{
expr.get_if_condition ().accept_vis (*this);
expr.get_if_block ().accept_vis (*this);
expr.get_else_block ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::MatchExpr &expr)
{
expr.get_scrutinee_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::AwaitExpr &)
{
// Not handled yet
}
void
PrivacyReporter::visit (HIR::AsyncBlockExpr &)
{
// Not handled yet
}
void
PrivacyReporter::visit (HIR::Module &module)
{
// FIXME: We also need to think about module privacy
auto old_module = current_module;
current_module = module.get_mappings ().get_nodeid ();
for (auto &item : module.get_items ())
item->accept_vis (*this);
current_module = old_module;
}
void
PrivacyReporter::visit (HIR::ExternCrate &)
{}
void
PrivacyReporter::visit (HIR::UseDeclaration &)
{
// FIXME: Is there anything we need to do here?
}
void
PrivacyReporter::visit (HIR::Function &function)
{
for (auto ¶m : function.get_function_params ())
check_type_privacy (param.get_type ());
function.get_definition ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::TypeAlias &)
{
// TODO: Check the type here
}
void
PrivacyReporter::visit (HIR::StructStruct &)
{
// TODO: Check the type of all fields
}
void
PrivacyReporter::visit (HIR::TupleStruct &)
{
// TODO: Check the type of all fields
}
void
PrivacyReporter::visit (HIR::EnumItem &)
{
// TODO: Check the type of all variants
}
void
PrivacyReporter::visit (HIR::EnumItemTuple &)
{
// TODO: Check the type
}
void
PrivacyReporter::visit (HIR::EnumItemStruct &)
{
// TODO: Check the type
}
void
PrivacyReporter::visit (HIR::EnumItemDiscriminant &)
{}
void
PrivacyReporter::visit (HIR::Enum &)
{}
void
PrivacyReporter::visit (HIR::Union &)
{
// TODO: Check the type
}
void
PrivacyReporter::visit (HIR::ConstantItem &const_item)
{
// TODO: We need to visit the type
const_item.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::StaticItem &static_item)
{
// TODO: We need to visit the type
static_item.get_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::Trait &)
{
// FIXME: We need to be an ItemVisitor as well
// for (auto &item : trait.get_trait_items ())
// item->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ImplBlock &impl)
{
for (auto &item : impl.get_impl_items ())
item->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ExternBlock &)
{
// FIXME: We need to be an ItemVisitor as well
// for (auto &block: block.get_extern_items ())
// item->accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::EmptyStmt &)
{}
void
PrivacyReporter::visit (HIR::LetStmt &stmt)
{
if (stmt.has_type ())
check_type_privacy (stmt.get_type ());
if (stmt.has_init_expr ())
stmt.get_init_expr ().accept_vis (*this);
}
void
PrivacyReporter::visit (HIR::ExprStmt &stmt)
{
stmt.get_expr ().accept_vis (*this);
}
} // namespace Privacy
} // namespace Rust