Skip to content

Commit

Permalink
Merge pull request #359 from madsmtm:sendability-attributes
Browse files Browse the repository at this point in the history
Sendability attributes
  • Loading branch information
madsmtm authored Sep 3, 2023
2 parents b1c04fe + 53e3cc4 commit a9ff910
Show file tree
Hide file tree
Showing 24 changed files with 571 additions and 127 deletions.
6 changes: 0 additions & 6 deletions crates/header-translator/framework-includes.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
// Workaround for clang < 13, only used in NSBundle.h
#define NS_FORMAT_ARGUMENT(A)

// Workaround for clang < 13
#define _Nullable_result _Nullable

#include <TargetConditionals.h>

#if TARGET_OS_OSX
Expand Down
90 changes: 87 additions & 3 deletions crates/header-translator/src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::mem;

use crate::config::Config;
Expand All @@ -7,16 +7,38 @@ use crate::id::ItemIdentifier;
use crate::method::Method;
use crate::output::Output;
use crate::stmt::Stmt;
use crate::Mutability;

/// A helper struct for doing global analysis on the output.
#[derive(Debug, PartialEq, Clone)]
pub struct Cache<'a> {
config: &'a Config,
mainthreadonly_classes: BTreeSet<ItemIdentifier>,
}

impl<'a> Cache<'a> {
pub fn new(_output: &Output, config: &'a Config) -> Self {
Self { config }
pub fn new(output: &Output, config: &'a Config) -> Self {
let mut mainthreadonly_classes = BTreeSet::new();

for library in output.libraries.values() {
for file in library.files.values() {
for stmt in file.stmts.iter() {
if let Stmt::ClassDecl {
id,
mutability: Mutability::MainThreadOnly,
..
} = stmt
{
mainthreadonly_classes.insert(id.clone());
}
}
}
}

Self {
config,
mainthreadonly_classes,
}
}

pub fn update(&self, output: &mut Output) {
Expand Down Expand Up @@ -68,6 +90,67 @@ impl<'a> Cache<'a> {
}
}

// Add `mainthreadonly` to relevant methods
for stmt in file.stmts.iter_mut() {
match stmt {
Stmt::Methods {
cls: id, methods, ..
}
| Stmt::ProtocolDecl { id, methods, .. } => {
for method in methods.iter_mut() {
let mut result_type_contains_mainthreadonly: bool = false;
method.result_type.visit_required_types(&mut |id| {
if self.mainthreadonly_classes.contains(id) {
result_type_contains_mainthreadonly = true;
}
});

match (method.is_class, self.mainthreadonly_classes.contains(id)) {
// MainThreadOnly class with static method
(true, true) => {
// Assume the method needs main thread
result_type_contains_mainthreadonly = true;
}
// Class with static method
(true, false) => {
// Continue with the normal check
}
// MainThreadOnly class with non-static method
(false, true) => {
// Method is already required to run on main
// thread, so no need to add MainThreadMarker
continue;
}
// Class with non-static method
(false, false) => {
// Continue with the normal check
}
}

if result_type_contains_mainthreadonly {
let mut any_argument_contains_mainthreadonly: bool = false;
for (_, argument) in method.arguments.iter() {
// Important: We only visit the top-level types, to not
// include e.g. `Option<&NSView>` or `&NSArray<NSView>`.
argument.visit_toplevel_types(&mut |id| {
if self.mainthreadonly_classes.contains(id) {
any_argument_contains_mainthreadonly = true;
}
});
}

// Apply main thread only, unless a (required)
// argument was main thread only.
if !any_argument_contains_mainthreadonly {
method.mainthreadonly = true;
}
}
}
}
_ => {}
}
}

// Fix up a few typedef + enum declarations
let mut iter = mem::take(&mut file.stmts).into_iter().peekable();
while let Some(stmt) = iter.next() {
Expand All @@ -84,6 +167,7 @@ impl<'a> Cache<'a> {
ty: enum_ty,
kind: _,
variants: _,
sendable: _,
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(&id.name) {
Expand Down
4 changes: 4 additions & 0 deletions crates/header-translator/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ impl ItemIdentifier {
self.library == "Foundation" && self.name == "NSString"
}

pub fn is_nscomparator(&self) -> bool {
self.library == "Foundation" && self.name == "NSComparator"
}

pub fn feature(&self) -> Option<impl fmt::Display + '_> {
struct ItemIdentifierFeature<'a>(&'a ItemIdentifier);

Expand Down
62 changes: 56 additions & 6 deletions crates/header-translator/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ struct MethodModifiers {
returns_retained: bool,
returns_not_retained: bool,
designated_initializer: bool,
non_isolated: bool,
sendable: Option<bool>,
mainthreadonly: bool,
}

impl MethodModifiers {
Expand All @@ -68,6 +71,18 @@ impl MethodModifiers {
UnexposedAttr::ReturnsNotRetained => {
this.returns_not_retained = true;
}
UnexposedAttr::NonIsolated => {
this.non_isolated = true;
}
UnexposedAttr::Sendable => {
this.sendable = Some(true);
}
UnexposedAttr::NonSendable => {
this.sendable = Some(false);
}
UnexposedAttr::UIActor => {
this.mainthreadonly = true;
}
attr => error!(?attr, "unknown attribute"),
}
}
Expand Down Expand Up @@ -214,6 +229,7 @@ impl MemoryManagement {
consumes_self: false,
returns_retained: false,
returns_not_retained: false,
..
} = modifiers
{
Self::Normal
Expand All @@ -233,11 +249,14 @@ pub struct Method {
pub is_class: bool,
is_optional_protocol: bool,
memory_management: MemoryManagement,
arguments: Vec<(String, Ty)>,
pub(crate) arguments: Vec<(String, Ty)>,
pub result_type: Ty,
safe: bool,
mutating: bool,
is_protocol: bool,
// Thread-safe, even on main-thread only (@MainActor/@UIActor) classes
non_isolated: bool,
pub(crate) mainthreadonly: bool,
}

impl Method {
Expand Down Expand Up @@ -367,6 +386,10 @@ impl<'tu> PartialMethod<'tu> {

let modifiers = MethodModifiers::parse(&entity, context);

if modifiers.sendable.is_some() {
error!("sendable on method");
}

let mut arguments: Vec<_> = entity
.get_arguments()
.expect("method arguments")
Expand All @@ -377,6 +400,8 @@ impl<'tu> PartialMethod<'tu> {
let qualifier = entity
.get_objc_qualifiers()
.map(MethodArgumentQualifier::parse);
let mut sendable = None;
let mut no_escape = false;

immediate_children(&entity, |entity, _span| match entity.get_kind() {
EntityKind::ObjCClassRef
Expand All @@ -390,7 +415,12 @@ impl<'tu> PartialMethod<'tu> {
}
EntityKind::UnexposedAttr => {
if let Some(attr) = UnexposedAttr::parse(&entity, context) {
error!(?attr, "unknown attribute");
match attr {
UnexposedAttr::Sendable => sendable = Some(true),
UnexposedAttr::NonSendable => sendable = Some(false),
UnexposedAttr::NoEscape => no_escape = true,
attr => error!(?attr, "unknown attribute"),
}
}
}
// For some reason we recurse into array types
Expand All @@ -399,7 +429,7 @@ impl<'tu> PartialMethod<'tu> {
});

let ty = entity.get_type().expect("argument type");
let ty = Ty::parse_method_argument(ty, qualifier, context);
let ty = Ty::parse_method_argument(ty, qualifier, sendable, no_escape, context);

(name, ty)
})
Expand Down Expand Up @@ -463,6 +493,8 @@ impl<'tu> PartialMethod<'tu> {
// immutable subclass, or as a property.
mutating: data.mutating.unwrap_or(parent_is_mutable),
is_protocol,
non_isolated: modifiers.non_isolated,
mainthreadonly: modifiers.mainthreadonly,
},
))
}
Expand Down Expand Up @@ -519,6 +551,7 @@ impl PartialProperty<'_> {
let ty = Ty::parse_property_return(
entity.get_type().expect("property type"),
is_copy,
modifiers.sendable,
context,
);

Expand All @@ -538,6 +571,8 @@ impl PartialProperty<'_> {
// is, so let's default to immutable.
mutating: getter_data.mutating.unwrap_or(false),
is_protocol,
non_isolated: modifiers.non_isolated,
mainthreadonly: modifiers.mainthreadonly,
})
} else {
None
Expand All @@ -546,8 +581,12 @@ impl PartialProperty<'_> {
let setter = if let Some(setter_name) = setter_name {
let setter_data = setter_data.expect("setter_data must be present if setter_name was");
if !setter_data.skipped {
let ty =
Ty::parse_property(entity.get_type().expect("property type"), is_copy, context);
let ty = Ty::parse_property(
entity.get_type().expect("property type"),
is_copy,
modifiers.sendable,
context,
);

let selector = setter_name.clone() + ":";
let memory_management =
Expand All @@ -566,6 +605,8 @@ impl PartialProperty<'_> {
// Setters are usually mutable if the class itself is.
mutating: setter_data.mutating.unwrap_or(parent_is_mutable),
is_protocol,
non_isolated: modifiers.non_isolated,
mainthreadonly: modifiers.mainthreadonly,
})
} else {
None
Expand Down Expand Up @@ -595,6 +636,11 @@ impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _span = debug_span!("method", self.fn_name).entered();

// TODO: Use this somehow?
// if self.non_isolated {
// writeln!(f, "// non_isolated")?;
// }

//
// Attributes
//
Expand Down Expand Up @@ -648,7 +694,11 @@ impl fmt::Display for Method {
// Arguments
for (param, arg_ty) in &self.arguments {
let param = handle_reserved(&crate::to_snake_case(param));
write!(f, "{param}: {arg_ty},")?;
write!(f, "{param}: {arg_ty}, ")?;
}
// FIXME: Skipping main thread only on protocols for now
if self.mainthreadonly && !self.is_protocol {
write!(f, "mtm: MainThreadMarker")?;
}
write!(f, ")")?;

Expand Down
Loading

0 comments on commit a9ff910

Please sign in to comment.