blob: 3043dcf09be23fb6f97c7822d87e75166566504c [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
mod analysis;
mod api;
mod apivec;
mod codegen_cpp;
mod codegen_rs;
#[cfg(test)]
mod conversion_tests;
mod convert_error;
mod doc_attr;
mod error_reporter;
mod parse;
mod utilities;
use analysis::fun::FnAnalyzer;
use autocxx_parser::IncludeCppConfig;
pub(crate) use codegen_cpp::CppCodeGenerator;
pub(crate) use convert_error::ConvertError;
use itertools::Itertools;
use syn::{Item, ItemMod};
use crate::{
conversion::analysis::deps::HasDependencies, CppCodegenOptions, CppFilePair, UnsafePolicy,
};
use self::{
analysis::{
abstract_types::{discard_ignored_functions, mark_types_abstract},
allocators::create_alloc_and_frees,
casts::add_casts,
check_names,
constructor_deps::decorate_types_with_constructor_deps,
fun::FnPhase,
gc::filter_apis_by_following_edges_from_allowlist,
pod::analyze_pod_apis,
remove_ignored::filter_apis_by_ignored_dependents,
replace_hopeless_typedef_targets,
tdef::convert_typedef_targets,
},
api::AnalysisPhase,
apivec::ApiVec,
codegen_rs::RsCodeGenerator,
parse::ParseBindgen,
};
const LOG_APIS: bool = true;
/// Converts the bindings generated by bindgen into a form suitable
/// for use with `cxx`.
/// In fact, most of the actual operation happens within an
/// individual `BridgeConversion`.
///
/// # Flexibility in handling bindgen output
///
/// autocxx is inevitably tied to the details of the bindgen output;
/// e.g. the creation of a 'root' mod when namespaces are enabled.
/// At the moment this crate takes the view that it's OK to panic
/// if the bindgen output is not as expected. It may be in future that
/// we need to be a bit more graceful, but for now, that's OK.
pub(crate) struct BridgeConverter<'a> {
include_list: &'a [String],
config: &'a IncludeCppConfig,
}
/// C++ and Rust code generation output.
pub(crate) struct CodegenResults {
pub(crate) rs: Vec<Item>,
pub(crate) cpp: Option<CppFilePair>,
pub(crate) cxxgen_header_name: String,
}
impl<'a> BridgeConverter<'a> {
pub fn new(include_list: &'a [String], config: &'a IncludeCppConfig) -> Self {
Self {
include_list,
config,
}
}
fn dump_apis<T: AnalysisPhase>(label: &str, apis: &ApiVec<T>) {
if LOG_APIS {
log::info!(
"APIs after {}:\n{}",
label,
apis.iter()
.map(|api| { format!(" {:?}", api) })
.sorted()
.join("\n")
)
}
}
fn dump_apis_with_deps(label: &str, apis: &ApiVec<FnPhase>) {
if LOG_APIS {
log::info!(
"APIs after {}:\n{}",
label,
apis.iter()
.map(|api| { format!(" {:?}, deps={}", api, api.format_deps()) })
.sorted()
.join("\n")
)
}
}
/// Convert a TokenStream of bindgen-generated bindings to a form
/// suitable for cxx.
///
/// This is really the heart of autocxx. It parses the output of `bindgen`
/// (although really by "parse" we mean to interpret the structures already built
/// up by the `syn` crate).
pub(crate) fn convert(
&self,
mut bindgen_mod: ItemMod,
unsafe_policy: UnsafePolicy,
inclusions: String,
cpp_codegen_options: &CppCodegenOptions,
) -> Result<CodegenResults, ConvertError> {
match &mut bindgen_mod.content {
None => Err(ConvertError::NoContent),
Some((_, items)) => {
// Parse the bindgen mod.
let items_to_process = items.drain(..).collect();
let parser = ParseBindgen::new(self.config);
let apis = parser.parse_items(items_to_process)?;
Self::dump_apis("parsing", &apis);
// Inside parse_results, we now have a list of APIs.
// We now enter various analysis phases.
// Next, convert any typedefs.
// "Convert" means replacing bindgen-style type targets
// (e.g. root::std::unique_ptr) with cxx-style targets (e.g. UniquePtr).
let apis = convert_typedef_targets(self.config, apis);
Self::dump_apis("typedefs", &apis);
// Now analyze which of them can be POD (i.e. trivial, movable, pass-by-value
// versus which need to be opaque).
// Specifically, let's confirm that the items requested by the user to be
// POD really are POD, and duly mark any dependent types.
// This returns a new list of `Api`s, which will be parameterized with
// the analysis results. It also returns an object which can be used
// by subsequent phases to work out which objects are POD.
let analyzed_apis = analyze_pod_apis(apis, self.config)?;
Self::dump_apis("pod analysis", &analyzed_apis);
let analyzed_apis = replace_hopeless_typedef_targets(self.config, analyzed_apis);
let analyzed_apis = add_casts(analyzed_apis);
let analyzed_apis = create_alloc_and_frees(analyzed_apis);
// Next, figure out how we materialize different functions.
// Some will be simple entries in the cxx::bridge module; others will
// require C++ wrapper functions. This is probably the most complex
// part of `autocxx`. Again, this returns a new set of `Api`s, but
// parameterized by a richer set of metadata.
Self::dump_apis("adding casts", &analyzed_apis);
let analyzed_apis =
FnAnalyzer::analyze_functions(analyzed_apis, unsafe_policy, self.config);
// If any of those functions turned out to be pure virtual, don't attempt
// to generate UniquePtr implementations for the type, since it can't
// be instantiated.
Self::dump_apis("analyze fns", &analyzed_apis);
let analyzed_apis = mark_types_abstract(analyzed_apis);
Self::dump_apis("marking abstract", &analyzed_apis);
// Annotate structs with a note of any copy/move constructors which
// we may want to retain to avoid garbage collecting them later.
let analyzed_apis = decorate_types_with_constructor_deps(analyzed_apis);
Self::dump_apis_with_deps("adding constructor deps", &analyzed_apis);
let analyzed_apis = discard_ignored_functions(analyzed_apis);
Self::dump_apis_with_deps("ignoring ignorable fns", &analyzed_apis);
// Remove any APIs whose names are not compatible with cxx.
let analyzed_apis = check_names(analyzed_apis);
// During parsing or subsequent processing we might have encountered
// items which we couldn't process due to as-yet-unsupported features.
// There might be other items depending on such things. Let's remove them
// too.
let analyzed_apis = filter_apis_by_ignored_dependents(analyzed_apis);
Self::dump_apis_with_deps("removing ignored dependents", &analyzed_apis);
// We now garbage collect the ones we don't need...
let mut analyzed_apis =
filter_apis_by_following_edges_from_allowlist(analyzed_apis, self.config);
// Determine what variably-sized C types (e.g. int) we need to include
analysis::ctypes::append_ctype_information(&mut analyzed_apis);
Self::dump_apis_with_deps("GC", &analyzed_apis);
// And finally pass them to the code gen phases, which outputs
// code suitable for cxx to consume.
let cxxgen_header_name = cpp_codegen_options.cxxgen_header_namer.name_header();
let cpp = CppCodeGenerator::generate_cpp_code(
inclusions,
&analyzed_apis,
self.config,
cpp_codegen_options,
&cxxgen_header_name,
)?;
let rs = RsCodeGenerator::generate_rs_code(
analyzed_apis,
self.include_list,
bindgen_mod,
self.config,
cpp.as_ref().map(|file_pair| file_pair.header_name.clone()),
);
Ok(CodegenResults {
rs,
cpp,
cxxgen_header_name,
})
}
}
}
}