Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test for issue 1269. #1271

Closed
wants to merge 7 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions engine/src/conversion/analysis/fun/mod.rs
Original file line number Diff line number Diff line change
@@ -728,7 +728,7 @@ impl<'a> FnAnalyzer<'a> {
// and it would be nice to have some idea of the function name
// for diagnostics whilst we do that.
let initial_rust_name = fun.ident.to_string();
let diagnostic_display_name = cpp_name.as_ref().unwrap_or(&initial_rust_name);
let diagnostic_display_name = cpp_name.as_ref().unwrap_or(&initial_rust_name).clone();

// Now let's analyze all the parameters.
// See if any have annotations which our fork of bindgen has craftily inserted...
@@ -739,7 +739,7 @@ impl<'a> FnAnalyzer<'a> {
self.convert_fn_arg(
i,
ns,
diagnostic_display_name,
&diagnostic_display_name,
&fun.synthesized_this_type,
&fun.references,
true,
@@ -1310,6 +1310,9 @@ impl<'a> FnAnalyzer<'a> {
self.config
.uniquify_name_per_mod(&format!("{cxxbridge_name}{joiner}autocxx_wrapper")),
);
// Retain a memory of the original C++ name so that we can compare
// against allowlists etc. later.
cpp_name = Some(diagnostic_display_name.clone());
let (payload, cpp_function_kind) = match fun.synthetic_cpp.as_ref().cloned() {
Some((payload, cpp_function_kind)) => (payload, cpp_function_kind),
None => match kind {
11 changes: 11 additions & 0 deletions engine/src/conversion/api.rs
Original file line number Diff line number Diff line change
@@ -634,6 +634,17 @@ impl<T: AnalysisPhase> Api<T> {
.unwrap_or_else(|| self.name().get_final_item())
}

/// The full qualified C++ name, for use in matching against allowlist
/// or blocklists.
pub(crate) fn effective_cpp_qualified_name(&self) -> String {
let effective_cpp_name = self.effective_cpp_name();
self.name()
.ns_segment_iter()
.map(|s| s.as_str())
.chain(std::iter::once(effective_cpp_name))
.join("::")
}

/// If this API turns out to have the same QualifiedName as another,
/// whether it's OK to just discard it?
pub(crate) fn discard_duplicates(&self) -> bool {
8 changes: 7 additions & 1 deletion engine/src/conversion/codegen_cpp/type_to_cpp.rs
Original file line number Diff line number Diff line change
@@ -19,8 +19,14 @@ use syn::{Token, Type};
/// Map from QualifiedName to original C++ name. Original C++ name does not
/// include the namespace; this can be assumed to be the same as the namespace
/// in the QualifiedName.
///
/// This is a temporary hash table which can be built from the information
/// stored in any [`ApiVec`].
///
/// The "original C++ name" is mostly relevant in the case of nested types,
/// where the typename might be A::B within a namespace C::D.
/// where the typename might be A::B within a namespace C::D. We also store
/// the original name in here for functions where we've generated a wrapper
/// function, so we can compare the name against the original allowlist.
pub(crate) struct CppNameMap(HashMap<QualifiedName, String>);

impl CppNameMap {
6 changes: 4 additions & 2 deletions engine/src/conversion/convert_error.rs
Original file line number Diff line number Diff line change
@@ -29,6 +29,10 @@ pub enum ConvertError {
#[error(transparent)]
#[diagnostic(transparent)]
Rust(LocatedConvertErrorFromRust),
#[error("You used a generate! directive to explicitly request code generation for an API ({}). This API could not be generated because {}.", .0, .1)]
ExplicitlyRequestedApiWasIgnored(String, ConvertErrorFromCpp),
#[error("The 'generate' or 'generate_pod' directive for '{0}' did not result in any code being generated. Perhaps this was mis-spelled or you didn't qualify the name with any namespaces? Otherwise please report a bug.")]
DidNotGenerateAnything(String),
}

/// Errors that can occur during conversion which are detected from some C++
@@ -81,8 +85,6 @@ pub enum ConvertErrorFromCpp {
InvalidPointerPointee,
#[error("Pointer pointed to something unsupported (autocxx only supports pointers to named types): {0}")]
InvalidPointee(String),
#[error("The 'generate' or 'generate_pod' directive for '{0}' did not result in any code being generated. Perhaps this was mis-spelled or you didn't qualify the name with any namespaces? Otherwise please report a bug.")]
DidNotGenerateAnything(String),
#[error("Found an attempt at using a forward declaration ({}) inside a templated cxx type such as UniquePtr or CxxVector. If the forward declaration is a typedef, perhaps autocxx wasn't sure whether or not it involved a forward declaration. If you're sure it didn't, then you may be able to solve this by using instantiable!.", .0.to_cpp_name())]
TypeContainingForwardDeclaration(QualifiedName),
#[error("Found an attempt at using a type marked as blocked! ({})", .0.to_cpp_name())]
44 changes: 43 additions & 1 deletion engine/src/conversion/mod.rs
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ use autocxx_parser::IncludeCppConfig;
pub(crate) use codegen_cpp::CppCodeGenerator;
pub(crate) use convert_error::ConvertError;
use convert_error::ConvertErrorFromCpp;
use indexmap::IndexMap as HashMap;
use indexmap::IndexSet as HashSet;
use itertools::Itertools;
use syn::{Item, ItemMod};

@@ -45,7 +47,7 @@ use self::{
replace_hopeless_typedef_targets,
tdef::convert_typedef_targets,
},
api::AnalysisPhase,
api::{AnalysisPhase, Api},
apivec::ApiVec,
codegen_rs::RsCodeGenerator,
parse::ParseBindgen,
@@ -191,6 +193,11 @@ impl<'a> BridgeConverter<'a> {
// 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);
// If at any stage we found any problem with a given API,
// we will have replaced it with an Api::IgnoredItem. If any of
// those were `generate!` directives, then we want to turn that
// into a hard error.
self.confirm_all_generate_directives_obeyed(&analyzed_apis)?;
// And finally pass them to the code gen phases, which outputs
// code suitable for cxx to consume.
let cxxgen_header_name = codegen_options
@@ -221,4 +228,39 @@ impl<'a> BridgeConverter<'a> {
}
}
}

/// See if we created Api::IgnoredItems for anything which was explicitly
/// requested with a generate! call.
fn confirm_all_generate_directives_obeyed(
&self,
apis: &ApiVec<FnPhase>,
) -> Result<(), ConvertError> {
let fail_api_reasons: HashMap<_, _> = apis
.iter()
.filter_map(|api| match api {
Api::IgnoredItem { err, .. } => {
Some((api.effective_cpp_qualified_name(), err.clone()))
}
_ => None,
})
.collect();
let successful_api_names: HashSet<_> = apis
.iter()
.filter(|api| !matches!(api, Api::IgnoredItem { .. }))
.map(|api| api.effective_cpp_qualified_name())
.collect();
for generate_directive in self.config.must_generate_list() {
if !successful_api_names.contains(&generate_directive) {
// Try to give a specific error message if we can.
return Err(match fail_api_reasons.get(&generate_directive) {
Some(error) => ConvertError::ExplicitlyRequestedApiWasIgnored(
generate_directive,
error.clone(),
),
None => ConvertError::DidNotGenerateAnything(generate_directive),
});
}
}
Ok(())
}
}
18 changes: 0 additions & 18 deletions engine/src/conversion/parse/parse_bindgen.rs
Original file line number Diff line number Diff line change
@@ -85,8 +85,6 @@ impl<'a> ParseBindgen<'a> {
.map_err(ConvertError::Rust)?;
let root_ns = Namespace::new();
self.parse_mod_items(items, root_ns);
self.confirm_all_generate_directives_obeyed()
.map_err(ConvertError::Cpp)?;
self.replace_extern_cpp_types();
Ok(self.apis)
}
@@ -390,20 +388,4 @@ impl<'a> ParseBindgen<'a> {
.filter_map(|f| f.ident.as_ref())
.any(|id| id == desired_id)
}

fn confirm_all_generate_directives_obeyed(&self) -> Result<(), ConvertErrorFromCpp> {
let api_names: HashSet<_> = self
.apis
.iter()
.map(|api| api.name().to_cpp_name())
.collect();
for generate_directive in self.config.must_generate_list() {
if !api_names.contains(&generate_directive) {
return Err(ConvertErrorFromCpp::DidNotGenerateAnything(
generate_directive,
));
}
}
Ok(())
}
}
22 changes: 22 additions & 0 deletions integration-tests/tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -5990,6 +5990,28 @@ fn test_error_generated_for_pod_with_nontrivial_destructor() {
run_test_expect_fail("", hdr, rs, &["take_a"], &["A"]);
}

#[test]
fn test_error_generated_for_double_underscore() {
let hdr = indoc! {"
inline void __thingy() {}
"};
let rs = quote! {};
run_test_expect_fail("", hdr, rs, &["__thingy"], &[]);
}

#[test]
fn test_error_generated_for_double_underscore_take_value() {
// Forces wrapper generation
let hdr = indoc! {"
struct A {
int a;
};
inline void __thingy(A a) {}
"};
let rs = quote! {};
run_test_expect_fail("", hdr, rs, &["__thingy"], &[]);
}

#[test]
fn test_error_generated_for_pod_with_nontrivial_move_constructor() {
// take_a is necessary here because cxx won't generate the required