Skip to content

Commit bae00d9

Browse files
committed
Convert function argument and return types
Fixes #639.
1 parent a6b7029 commit bae00d9

File tree

13 files changed

+245
-110
lines changed

13 files changed

+245
-110
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::sync::LazyLock;
2+
3+
use regex::Regex;
4+
5+
/// Algorithm described in:
6+
/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#auditing-of-c-retainable-pointer-interfaces>
7+
///
8+
/// > A function obeys the create/copy naming convention if its name
9+
/// > contains as a substring:
10+
/// > - either “Create” or “Copy” not followed by a lowercase letter, or
11+
/// > - either “create” or “copy” not followed by a lowercase letter and
12+
/// > not preceded by any letter, whether uppercase or lowercase.
13+
///
14+
/// See also Clang's implementation:
15+
/// <https://github.com/llvm/llvm-project/blob/llvmorg-19.1.6/clang/lib/Analysis/CocoaConventions.cpp#L97-L145>
16+
/// <https://github.com/llvm/llvm-project/blob/llvmorg-19.1.6/clang/lib/Analysis/RetainSummaryManager.cpp>
17+
pub(crate) fn follows_create_rule(name: &str) -> bool {
18+
static RE: LazyLock<Regex> = LazyLock::new(|| {
19+
Regex::new(r"(Create|Copy)([^a-z]|$)|([^a-zA-Z]|^)(create|copy)([^a-z]|$)").unwrap()
20+
});
21+
22+
RE.is_match(name)
23+
}
24+
25+
#[cfg(test)]
26+
mod tests {
27+
use super::*;
28+
29+
#[test]
30+
fn test_follows_create_rule() {
31+
assert!(follows_create_rule("ThingCreate"));
32+
assert!(follows_create_rule("CreateThing"));
33+
assert!(follows_create_rule("CopyCreateThing"));
34+
assert!(follows_create_rule("create_thing"));
35+
36+
assert!(!follows_create_rule("Created"));
37+
assert!(!follows_create_rule("created"));
38+
assert!(!follows_create_rule("GetAbc"));
39+
assert!(!follows_create_rule("recreate"));
40+
41+
assert!(follows_create_rule("CreatedCopy"));
42+
43+
// A few real-world examples
44+
assert!(follows_create_rule("dispatch_data_create"));
45+
assert!(follows_create_rule("dispatch_data_create_map"));
46+
assert!(!follows_create_rule("dispatch_data_get_size"));
47+
assert!(follows_create_rule("MTLCreateSystemDefaultDevice"));
48+
assert!(follows_create_rule("MTLCopyAllDevices"));
49+
assert!(!follows_create_rule("MTLRemoveDeviceObserver"));
50+
assert!(follows_create_rule("CFArrayCreate"));
51+
assert!(follows_create_rule("CFArrayCreateCopy"));
52+
assert!(!follows_create_rule("CFArrayGetCount"));
53+
assert!(!follows_create_rule("CFArrayGetValues"));
54+
}
55+
}

crates/header-translator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod config;
1616
mod context;
1717
mod display_helper;
1818
mod expr;
19+
mod fn_utils;
1920
mod global_analysis;
2021
mod id;
2122
mod library;

crates/header-translator/src/rust_type.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,11 @@ impl Ty {
13671367
}
13681368

13691369
pub(crate) fn is_objc_bool(&self) -> bool {
1370-
matches!(self, Self::Primitive(Primitive::ObjcBool))
1370+
match self {
1371+
Self::Primitive(Primitive::ObjcBool) => true,
1372+
Self::TypeDef { to, .. } => to.is_objc_bool(),
1373+
_ => false,
1374+
}
13711375
}
13721376

13731377
fn plain(&self) -> impl fmt::Display + '_ {
@@ -1678,6 +1682,59 @@ impl Ty {
16781682
})
16791683
}
16801684

1685+
pub(crate) fn fn_return_converter(
1686+
&self,
1687+
returns_retained: bool,
1688+
) -> Option<(
1689+
impl fmt::Display + '_,
1690+
impl fmt::Display + '_,
1691+
impl fmt::Display + '_,
1692+
)> {
1693+
let start = "let ret = ";
1694+
// SAFETY: The function is marked with the correct retain semantics,
1695+
// otherwise it'd be invalid to use from Obj-C with ARC and Swift too.
1696+
let end = |nullability| {
1697+
match (nullability, returns_retained) {
1698+
(Nullability::NonNull, true) => {
1699+
";\nunsafe { Retained::from_raw(ret.as_ptr()) }.expect(\"function was marked as returning non-null, but actually returned NULL\")"
1700+
}
1701+
(Nullability::NonNull, false) => {
1702+
";\nunsafe { Retained::retain_autoreleased(ret.as_ptr()) }.expect(\"function was marked as returning non-null, but actually returned NULL\")"
1703+
}
1704+
(_, true) => ";\nunsafe { Retained::from_raw(ret) }",
1705+
(_, false) => ";\nunsafe { Retained::retain_autoreleased(ret) }",
1706+
}
1707+
};
1708+
1709+
match self {
1710+
_ if self.is_objc_bool() => Some((" -> bool".to_string(), "", ".as_bool()")),
1711+
Self::Pointer {
1712+
nullability,
1713+
lifetime: Lifetime::Unspecified,
1714+
pointee,
1715+
..
1716+
} if pointee.is_object_like() && !pointee.is_static_object() => {
1717+
let res = if *nullability == Nullability::NonNull {
1718+
format!(" -> Retained<{}>", pointee.behind_pointer())
1719+
} else {
1720+
format!(" -> Option<Retained<{}>>", pointee.behind_pointer())
1721+
};
1722+
Some((res, start, end(*nullability)))
1723+
}
1724+
Self::TypeDef {
1725+
id, nullability, ..
1726+
} if self.is_object_like() && !self.is_static_object() => {
1727+
let res = if *nullability == Nullability::NonNull {
1728+
format!(" -> Retained<{}>", id.path())
1729+
} else {
1730+
format!(" -> Option<Retained<{}>>", id.path())
1731+
};
1732+
Some((res, start, end(*nullability)))
1733+
}
1734+
_ => None,
1735+
}
1736+
}
1737+
16811738
pub(crate) fn var(&self) -> impl fmt::Display + '_ {
16821739
FormatterFn(move |f| match self {
16831740
Self::Pointer {
@@ -1764,6 +1821,17 @@ impl Ty {
17641821
})
17651822
}
17661823

1824+
pub(crate) fn fn_argument_converter(
1825+
&self,
1826+
) -> Option<(impl fmt::Display + '_, impl fmt::Display + '_)> {
1827+
if self.is_objc_bool() {
1828+
Some(("bool", "Bool::new"))
1829+
} else {
1830+
// TODO: Support out / autoreleasing pointers?
1831+
None
1832+
}
1833+
}
1834+
17671835
pub(crate) fn method_argument(&self) -> impl fmt::Display + '_ {
17681836
FormatterFn(move |f| match self {
17691837
Self::Primitive(Primitive::C99Bool) => {

crates/header-translator/src/stmt.rs

Lines changed: 102 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::config::{ClassData, MethodData};
1414
use crate::context::Context;
1515
use crate::display_helper::FormatterFn;
1616
use crate::expr::Expr;
17+
use crate::fn_utils::follows_create_rule;
1718
use crate::id::cfg_gate_ln;
1819
use crate::id::ItemIdentifier;
1920
use crate::id::Location;
@@ -531,6 +532,7 @@ pub enum Stmt {
531532
must_use: bool,
532533
can_unwind: bool,
533534
link_name: Option<String>,
535+
returns_retained: bool,
534536
},
535537
/// typedef Type TypedefName;
536538
AliasDecl {
@@ -1395,6 +1397,8 @@ impl Stmt {
13951397
warn!("unexpected static method");
13961398
}
13971399

1400+
let mut returns_retained = follows_create_rule(&id.name);
1401+
13981402
immediate_children(entity, |entity, _span| match entity.get_kind() {
13991403
EntityKind::UnexposedAttr => {
14001404
if let Some(attr) = UnexposedAttr::parse(&entity, context) {
@@ -1403,9 +1407,13 @@ impl Stmt {
14031407
UnexposedAttr::UIActor => {
14041408
warn!("unhandled UIActor on function declaration")
14051409
}
1406-
UnexposedAttr::ReturnsRetained
1407-
| UnexposedAttr::ReturnsNotRetained => {
1408-
// TODO: Ignore for now, but at some point handle in a similar way to in methods
1410+
UnexposedAttr::ReturnsRetained => {
1411+
// Override the inferred value.
1412+
returns_retained = true;
1413+
}
1414+
UnexposedAttr::ReturnsNotRetained => {
1415+
// Override the inferred value.
1416+
returns_retained = false;
14091417
}
14101418
UnexposedAttr::NoThrow => {
14111419
can_unwind = false;
@@ -1464,6 +1472,7 @@ impl Stmt {
14641472
must_use,
14651473
can_unwind,
14661474
link_name,
1475+
returns_retained,
14671476
}]
14681477
}
14691478
EntityKind::UnionDecl => {
@@ -2421,14 +2430,10 @@ impl Stmt {
24212430
}
24222431
Self::FnDecl {
24232432
id,
2424-
availability: _,
24252433
arguments,
24262434
result_type,
24272435
body: Some(_),
2428-
safe: _,
2429-
must_use: _,
2430-
can_unwind: _,
2431-
link_name: _,
2436+
..
24322437
} => {
24332438
write!(f, "// TODO: ")?;
24342439
write!(f, "pub fn {}(", id.name)?;
@@ -2444,83 +2449,107 @@ impl Stmt {
24442449
arguments,
24452450
result_type,
24462451
body: None,
2447-
safe: false,
2452+
safe,
24482453
must_use,
24492454
can_unwind,
24502455
link_name,
2456+
returns_retained,
24512457
} => {
24522458
let abi = if *can_unwind { "C-unwind" } else { "C" };
2453-
writeln!(f, "extern {abi:?} {{")?;
24542459

2455-
write!(f, " {}", self.cfg_gate_ln(config))?;
2456-
write!(f, " {availability}")?;
2457-
if *must_use {
2458-
writeln!(f, " #[must_use]")?;
2459-
}
2460-
if let Some(link_name) = link_name {
2461-
// NOTE: Currently only used on Apple targets.
2462-
writeln!(
2463-
f,
2464-
" #[cfg_attr(target_vendor = \"apple\", link_name = {link_name:?})]"
2465-
)?;
2466-
}
2467-
write!(f, " pub fn {}(", id.name)?;
2468-
for (param, arg_ty) in arguments {
2469-
let param = handle_reserved(&crate::to_snake_case(param));
2470-
write!(f, "{param}: {},", arg_ty.fn_argument())?;
2471-
}
2472-
write!(f, "){}", result_type.fn_return())?;
2473-
writeln!(f, ";")?;
2460+
let return_converter = result_type.fn_return_converter(*returns_retained);
24742461

2475-
writeln!(f, "}}")?;
2476-
}
2477-
Self::FnDecl {
2478-
id,
2479-
availability,
2480-
arguments,
2481-
result_type,
2482-
body: None,
2483-
safe: true,
2484-
must_use,
2485-
can_unwind,
2486-
link_name,
2487-
} => {
2488-
let abi = if *can_unwind { "C-unwind" } else { "C" };
2489-
write!(f, "{}", self.cfg_gate_ln(config))?;
2490-
write!(f, "{availability}")?;
2491-
if *must_use {
2492-
writeln!(f, "#[must_use]")?;
2493-
}
2494-
writeln!(f, "#[inline]")?;
2495-
write!(f, "pub extern {abi:?} fn {}(", id.name)?;
2496-
for (param, arg_ty) in arguments {
2497-
let param = handle_reserved(&crate::to_snake_case(param));
2498-
write!(f, "{param}: {},", arg_ty.fn_argument())?;
2499-
}
2500-
writeln!(f, "){} {{", result_type.fn_return())?;
2462+
let needs_wrapper = *safe
2463+
|| return_converter.is_some()
2464+
|| arguments
2465+
.iter()
2466+
.any(|(_, arg)| arg.fn_argument_converter().is_some());
2467+
2468+
let raw_fn_decl = |f: &mut fmt::Formatter<'_>, vis| {
2469+
if let Some(link_name) = link_name {
2470+
// NOTE: Currently only used on Apple targets.
2471+
writeln!(
2472+
f,
2473+
"#[cfg_attr(target_vendor = \"apple\", link_name = {link_name:?})]"
2474+
)?;
2475+
}
2476+
write!(f, "{vis}fn {}(", id.name)?;
2477+
for (param, arg_ty) in arguments {
2478+
let param = handle_reserved(&crate::to_snake_case(param));
2479+
write!(f, "{param}: {},", arg_ty.fn_argument())?;
2480+
}
2481+
writeln!(f, "){};", result_type.fn_return())?;
25012482

2502-
writeln!(f, " extern {abi:?} {{")?;
2483+
Ok(())
2484+
};
25032485

2504-
if let Some(link_name) = link_name {
2505-
writeln!(f, " #[link_name = {link_name:?}]")?;
2506-
}
2507-
write!(f, " fn {}(", id.name)?;
2508-
for (param, arg_ty) in arguments {
2509-
let param = handle_reserved(&crate::to_snake_case(param));
2510-
write!(f, "{param}: {},", arg_ty.fn_argument())?;
2511-
}
2512-
writeln!(f, "){};", result_type.fn_return())?;
2486+
if needs_wrapper {
2487+
write!(f, "{}", self.cfg_gate_ln(config))?;
2488+
write!(f, "{availability}")?;
2489+
if *must_use {
2490+
writeln!(f, "#[must_use]")?;
2491+
}
2492+
writeln!(f, "#[inline]")?;
2493+
let unsafe_ = if *safe { "" } else { "unsafe " };
2494+
write!(f, "pub {unsafe_}extern {abi:?} fn {}(", id.name)?;
2495+
for (param, arg_ty) in arguments {
2496+
let param = handle_reserved(&crate::to_snake_case(param));
2497+
write!(f, "{param}: ")?;
2498+
if let Some((converted_ty, _)) = arg_ty.fn_argument_converter() {
2499+
write!(f, "{converted_ty}")?;
2500+
} else {
2501+
write!(f, "{}", arg_ty.fn_argument())?;
2502+
}
2503+
write!(f, ",")?;
2504+
}
2505+
write!(f, ")")?;
2506+
if let Some((ty, _, _)) = &return_converter {
2507+
write!(f, "{ty}")?;
2508+
} else {
2509+
write!(f, "{}", result_type.fn_return())?;
2510+
}
2511+
writeln!(f, " {{")?;
25132512

2514-
writeln!(f, " }}")?;
2513+
// Emit raw
2514+
writeln!(f, " extern {abi:?} {{")?;
2515+
raw_fn_decl(f, "")?;
2516+
writeln!(f, " }}")?;
25152517

2516-
write!(f, " unsafe {{ {}(", id.name)?;
2517-
for (param, _) in arguments {
2518-
let param = handle_reserved(&crate::to_snake_case(param));
2519-
write!(f, "{param},")?;
2520-
}
2521-
writeln!(f, ") }}")?;
2518+
// Call raw
2519+
write!(f, " ")?;
2520+
if let Some((_, converter_start, _)) = &return_converter {
2521+
write!(f, "{converter_start}")?;
2522+
}
2523+
write!(f, "unsafe {{ {}(", id.name)?;
2524+
for (param, ty) in arguments {
2525+
let param = handle_reserved(&crate::to_snake_case(param));
2526+
if let Some((_, converter)) = ty.fn_argument_converter() {
2527+
write!(f, "{converter}({param})")?;
2528+
} else {
2529+
write!(f, "{param}")?;
2530+
}
2531+
write!(f, ",")?;
2532+
}
2533+
write!(f, ") }}")?;
2534+
if let Some((_, _, converter_end)) = &return_converter {
2535+
write!(f, "{converter_end}")?;
2536+
}
2537+
writeln!(f)?;
25222538

2523-
writeln!(f, "}}")?;
2539+
writeln!(f, "}}")?;
2540+
} else {
2541+
writeln!(f, "extern {abi:?} {{")?;
2542+
2543+
write!(f, " {}", self.cfg_gate_ln(config))?;
2544+
write!(f, " {availability}")?;
2545+
if *must_use {
2546+
writeln!(f, " #[must_use]")?;
2547+
}
2548+
2549+
raw_fn_decl(f, "pub ")?;
2550+
2551+
writeln!(f, "}}")?;
2552+
}
25242553
}
25252554
Self::AliasDecl {
25262555
id,

0 commit comments

Comments
 (0)