// Copyright (C) 2020-2023 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-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-object-export.h" #include "md5.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 = nullptr; bool ok = mappings->lookup_ast_item (trait.get_mappings ().get_nodeid (), &item); rust_assert (ok); 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 = nullptr; bool ok = mappings->lookup_ast_item (fn.get_mappings ().get_nodeid (), &item); rust_assert (ok); // 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 (*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 (vis_item); // we can emit an extern block with abi of "rust" Identifier item_name = function.get_function_name (); // always empty for extern linkage AST::WhereClause where_clause = AST::WhereClause::create_empty (); std::vector> generic_params; AST::Visibility vis = function.get_visibility (); std::unique_ptr return_type = std::unique_ptr (nullptr); if (function.has_return_type ()) { return_type = function.get_return_type ()->clone_type (); } std::vector function_params; for (AST::FunctionParam ¶m : function.get_function_params ()) { std::string name = param.get_pattern ()->as_string (); std::unique_ptr param_type = param.get_type ()->clone_type (); AST::NamedFunctionParam p (name, std::move (param_type), {}, param.get_locus ()); function_params.push_back (std::move (p)); } AST::ExternalItem *external_item = new AST::ExternalFunctionItem ( item_name, {} /* generic_params */, std::move (return_type), where_clause, std::move (function_params), false /* has_variadics */, {} /* variadic_outer_attrs */, vis, function.get_outer_attrs (), function.get_locus ()); std::vector> external_items; external_items.push_back ( std::unique_ptr (external_item)); 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 (); } 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.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 (*item.get ()); if (is_crate_public (vis_item)) vis_item.accept_vis (visitor); } } 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 = basename (path.c_str ()); if (strcmp (path_base_name, expected_file_name.c_str ()) != 0) { rust_error_at (Location (), "expected metadata-output path to have base file name of: " "%<%s%> got %<%s%>", 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 (Location (), "failed to open file %<%s%> for writing: %s", path.c_str (), xstrerror (errno)); return; } // write data if (fwrite (kMagicHeader, sizeof (kMagicHeader), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (checksum, sizeof (checksum), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %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 (Location (), "failed to write to file %<%s%>: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (size_buffer.c_str (), size_buffer.size (), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %s", path.c_str (), xstrerror (errno)); fclose (nfd); return; } if (fwrite (kSzDelim, sizeof (kSzDelim), 1, nfd) < 1) { rust_error_at (Location (), "failed to write to file %<%s%>: %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 (Location (), "failed to write to file %<%s%>: %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