// 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 // <http://www.gnu.org/licenses/>. #include "rust-export-metadata.h" #include "rust-hir-visitor.h" #include "rust-hir-full.h" #include "rust-hir-map.h" #include "rust-ast-dump.h" #include "rust-abi.h" #include "rust-item.h" #include "rust-object-export.h" #include "md5.h" #include "rust-system.h" namespace Rust { namespace Metadata { static const std::string extension_path = ".rox"; ExportContext::ExportContext () : mappings (Analysis::Mappings::get ()) {} ExportContext::~ExportContext () {} void ExportContext::push_module_scope (const HIR::Module &module) { module_stack.push_back (module); } const HIR::Module & ExportContext::pop_module_scope () { rust_assert (!module_stack.empty ()); const HIR::Module &poped = module_stack.back (); module_stack.pop_back (); return poped; } void ExportContext::emit_trait (const HIR::Trait &trait) { // lookup the AST node for this AST::Item *item = mappings.lookup_ast_item (trait.get_mappings ().get_nodeid ()).value (); std::stringstream oss; AST::Dump dumper (oss); dumper.go (*item); public_interface_buffer += oss.str (); } void ExportContext::emit_function (const HIR::Function &fn) { // lookup the AST node for this AST::Item *item = mappings.lookup_ast_item (fn.get_mappings ().get_nodeid ()).value (); // is this a CFG macro or not if (item->is_marked_for_strip ()) return; // FIXME add assertion that item must be a vis_item; AST::VisItem &vis_item = static_cast<AST::VisItem &> (*item); // if its a generic function we need to output the full declaration // otherwise we can let people link against this std::stringstream oss; AST::Dump dumper (oss); if (!fn.has_generics ()) { // FIXME assert that this is actually an AST::Function AST::Function &function = static_cast<AST::Function &> (vis_item); std::vector<std::unique_ptr<AST::ExternalItem>> external_items; external_items.push_back (std::unique_ptr<AST::ExternalItem> ( static_cast<AST::ExternalItem *> (&function))); AST::ExternBlock extern_block (get_string_from_abi (Rust::ABI::RUST), std::move (external_items), vis_item.get_visibility (), {}, {}, fn.get_locus ()); dumper.go (extern_block); } else { dumper.go (*item); } // store the dump public_interface_buffer += oss.str (); } void ExportContext::emit_macro (NodeId macro) { std::stringstream oss; AST::Dump dumper (oss); AST::Item *item = mappings.lookup_ast_item (macro).value (); dumper.go (*item); public_interface_buffer += oss.str (); } const std::string & ExportContext::get_interface_buffer () const { return public_interface_buffer; } // implicitly by using HIR nodes we know that these have passed CFG expansion // and they exist in the compilation unit class ExportVisItems : public HIR::HIRVisItemVisitor { public: ExportVisItems (ExportContext &context) : ctx (context) {} void visit (HIR::Module &) override {} void visit (HIR::ExternCrate &) override {} void visit (HIR::UseDeclaration &) override {} void visit (HIR::TypeAlias &) override {} void visit (HIR::StructStruct &) override {} void visit (HIR::TupleStruct &) override {} void visit (HIR::Enum &) override {} void visit (HIR::Union &) override {} void visit (HIR::ConstantItem &) override {} void visit (HIR::StaticItem &) override {} void visit (HIR::ImplBlock &) override {} void visit (HIR::ExternBlock &) override {} void visit (HIR::Trait &trait) override { ctx.emit_trait (trait); } void visit (HIR::Function &function) override { ctx.emit_function (function); } private: ExportContext &ctx; }; PublicInterface::PublicInterface (HIR::Crate &crate) : crate (crate), mappings (Analysis::Mappings::get ()), context () {} void PublicInterface::Export (HIR::Crate &crate) { PublicInterface interface (crate); interface.gather_export_data (); interface.write_to_object_file (); } void PublicInterface::ExportTo (HIR::Crate &crate, const std::string &output_path) { PublicInterface interface (crate); interface.gather_export_data (); interface.write_to_path (output_path); } void PublicInterface::gather_export_data () { ExportVisItems visitor (context); for (auto &item : crate.get_items ()) { bool is_vis_item = item->get_hir_kind () == HIR::Node::BaseKind::VIS_ITEM; if (!is_vis_item) continue; HIR::VisItem &vis_item = static_cast<HIR::VisItem &> (*item.get ()); if (is_crate_public (vis_item)) vis_item.accept_vis (visitor); } for (const auto ¯o : mappings.get_exported_macros ()) context.emit_macro (macro); } void PublicInterface::write_to_object_file () const { // done const auto &buf = context.get_interface_buffer (); std::string size_buffer = std::to_string (buf.size ()); // md5 this struct md5_ctx chksm; unsigned char checksum[16]; md5_init_ctx (&chksm); md5_process_bytes (buf.c_str (), buf.size (), &chksm); md5_finish_ctx (&chksm, checksum); // MAGIC MD5 DLIM DLIM buffer-size DELIM contents const std::string current_crate_name = mappings.get_current_crate_name (); // extern void rust_write_export_data (kMagicHeader, sizeof (kMagicHeader)); rust_write_export_data ((const char *) checksum, sizeof (checksum)); rust_write_export_data (kSzDelim, sizeof (kSzDelim)); rust_write_export_data (current_crate_name.c_str (), current_crate_name.size ()); rust_write_export_data (kSzDelim, sizeof (kSzDelim)); rust_write_export_data (size_buffer.c_str (), size_buffer.size ()); rust_write_export_data (kSzDelim, sizeof (kSzDelim)); rust_write_export_data (buf.c_str (), buf.size ()); } void PublicInterface::write_to_path (const std::string &path) const { // validate path contains correct extension const std::string expected_file_name = expected_metadata_filename (); const char *path_base_name = lbasename (path.c_str ()); if (strcmp (path_base_name, expected_file_name.c_str ()) != 0) { rust_error_at (UNDEF_LOCATION, "expected metadata-output path to have base file name of: " "%qs got %qs", expected_file_name.c_str (), path_base_name); return; } // done const auto &buf = context.get_interface_buffer (); std::string size_buffer = std::to_string (buf.size ()); // md5 this struct md5_ctx chksm; unsigned char checksum[16]; md5_init_ctx (&chksm); md5_process_bytes (buf.c_str (), buf.size (), &chksm); md5_finish_ctx (&chksm, checksum); // MAGIC MD5 DLIM DLIM buffer-size DELIM contents const std::string current_crate_name = mappings.get_current_crate_name (); // write to path FILE *nfd = fopen (path.c_str (), "wb"); if (nfd == NULL) { rust_error_at (UNDEF_LOCATION, "failed to open file %qs for writing: %s", path.c_str (), xstrerror (errno)); return; } // write data if (fwrite (kMagicHeader, sizeof (kMagicHeader), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (checksum, sizeof (checksum), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (current_crate_name.c_str (), current_crate_name.size (), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (size_buffer.c_str (), size_buffer.size (), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (!buf.empty ()) if (fwrite (buf.c_str (), buf.size (), 1, nfd) < 1) { rust_error_at (UNDEF_LOCATION, "failed to write to file %qs: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } // done fclose (nfd); } bool PublicInterface::is_crate_public (const HIR::VisItem &item) { const HIR::Visibility &visibility = item.get_visibility (); bool is_public = visibility.get_vis_type () == HIR::Visibility::VisType::PUBLIC; bool has_path = !visibility.get_path ().is_error (); // FIXME this might be pub(crate) // Arthur magic required here return is_public && !has_path; } std::string PublicInterface::expected_metadata_filename () { auto &mappings = Analysis::Mappings::get (); const std::string current_crate_name = mappings.get_current_crate_name (); return current_crate_name + extension_path; } } // namespace Metadata } // namespace Rust