|
| 1 | +/// Helper for specifying the retain semantics for a given selector family. |
| 2 | +/// |
| 3 | +/// Note that we can't actually check if a method is in a method family; only |
| 4 | +/// whether the _selector_ is in a _selector_ family. |
| 5 | +/// |
| 6 | +/// The slight difference here is: |
| 7 | +/// - The method may be annotated with the `objc_method_family` attribute, |
| 8 | +/// which would cause it to be in a different family. That this is not the |
| 9 | +/// case is part of the `unsafe` contract of `msg_send_id!`. |
| 10 | +/// - The method may not obey the added restrictions of the method family. |
| 11 | +/// The added restrictions are: |
| 12 | +/// - `new`, `alloc`, `copy` and `mutableCopy`: The method must return a |
| 13 | +/// retainable object pointer type - we ensure this by making |
| 14 | +/// `message_send_id` return `Id`. |
| 15 | +/// - `init`: The method must be an instance method and must return an |
| 16 | +/// Objective-C pointer type - We ensure this by taking |
| 17 | +/// `Option<Allocated<T>>`, which means it can't be a class method! |
| 18 | +/// |
| 19 | +/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retainable-object-pointers-as-operands-and-arguments> |
| 20 | +// TODO: Use an enum instead of u8 here when stable |
| 21 | +#[derive(Debug)] |
| 22 | +pub struct RetainSemantics<const INNER: u8> {} |
| 23 | + |
| 24 | +pub type New = RetainSemantics<1>; |
| 25 | +pub type Alloc = RetainSemantics<2>; |
| 26 | +pub type Init = RetainSemantics<3>; |
| 27 | +pub type CopyOrMutCopy = RetainSemantics<4>; |
| 28 | +pub type Other = RetainSemantics<5>; |
| 29 | + |
| 30 | +pub const fn retain_semantics(selector: &str) -> u8 { |
| 31 | + let selector = selector.as_bytes(); |
| 32 | + match ( |
| 33 | + in_selector_family(selector, b"new"), |
| 34 | + in_selector_family(selector, b"alloc"), |
| 35 | + in_selector_family(selector, b"init"), |
| 36 | + in_selector_family(selector, b"copy"), |
| 37 | + in_selector_family(selector, b"mutableCopy"), |
| 38 | + ) { |
| 39 | + (true, false, false, false, false) => 1, |
| 40 | + (false, true, false, false, false) => 2, |
| 41 | + (false, false, true, false, false) => 3, |
| 42 | + (false, false, false, true, false) => 4, |
| 43 | + (false, false, false, false, true) => 4, |
| 44 | + (false, false, false, false, false) => 5, |
| 45 | + _ => unreachable!(), |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +/// Checks whether a given selector is said to be in a given selector family. |
| 50 | +/// |
| 51 | +/// <https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families> |
| 52 | +const fn in_selector_family(mut selector: &[u8], mut family: &[u8]) -> bool { |
| 53 | + // Skip leading underscores from selector |
| 54 | + loop { |
| 55 | + selector = match selector { |
| 56 | + [b'_', rest @ ..] => rest, |
| 57 | + _ => break, |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + // Compare each character |
| 62 | + loop { |
| 63 | + (selector, family) = match (selector, family) { |
| 64 | + // Remaining items |
| 65 | + ([s, selector @ ..], [f, family @ ..]) => { |
| 66 | + if *s == *f { |
| 67 | + // Next iteration |
| 68 | + (selector, family) |
| 69 | + } else { |
| 70 | + // Family does not begin with selector |
| 71 | + return false; |
| 72 | + } |
| 73 | + } |
| 74 | + // Equal |
| 75 | + ([], []) => { |
| 76 | + return true; |
| 77 | + } |
| 78 | + // Selector can't be part of familiy if smaller than it |
| 79 | + ([], _) => { |
| 80 | + return false; |
| 81 | + } |
| 82 | + // Remaining items in selector |
| 83 | + // -> ensure next character is not lowercase |
| 84 | + ([s, ..], []) => { |
| 85 | + return !s.is_ascii_lowercase(); |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +#[cfg(test)] |
| 92 | +mod tests { |
| 93 | + use alloc::string::ToString; |
| 94 | + |
| 95 | + use super::*; |
| 96 | + |
| 97 | + #[test] |
| 98 | + fn test_in_selector_family() { |
| 99 | + #[track_caller] |
| 100 | + fn assert_in_family(selector: &str, family: &str) { |
| 101 | + assert!(in_selector_family(selector.as_bytes(), family.as_bytes())); |
| 102 | + let selector = selector.to_string() + "\0"; |
| 103 | + assert!(in_selector_family(selector.as_bytes(), family.as_bytes())); |
| 104 | + } |
| 105 | + |
| 106 | + #[track_caller] |
| 107 | + fn assert_not_in_family(selector: &str, family: &str) { |
| 108 | + assert!(!in_selector_family(selector.as_bytes(), family.as_bytes())); |
| 109 | + let selector = selector.to_string() + "\0"; |
| 110 | + assert!(!in_selector_family(selector.as_bytes(), family.as_bytes())); |
| 111 | + } |
| 112 | + |
| 113 | + // Common cases |
| 114 | + |
| 115 | + assert_in_family("alloc", "alloc"); |
| 116 | + assert_in_family("allocWithZone:", "alloc"); |
| 117 | + assert_not_in_family("dealloc", "alloc"); |
| 118 | + assert_not_in_family("initialize", "init"); |
| 119 | + assert_not_in_family("decimalNumberWithDecimal:", "init"); |
| 120 | + assert_in_family("initWithCapacity:", "init"); |
| 121 | + assert_in_family("_initButPrivate:withParam:", "init"); |
| 122 | + assert_not_in_family("description", "init"); |
| 123 | + assert_not_in_family("inIT", "init"); |
| 124 | + |
| 125 | + assert_not_in_family("init", "copy"); |
| 126 | + assert_not_in_family("copyingStuff:", "copy"); |
| 127 | + assert_in_family("copyWithZone:", "copy"); |
| 128 | + assert_not_in_family("initWithArray:copyItems:", "copy"); |
| 129 | + assert_in_family("copyItemAtURL:toURL:error:", "copy"); |
| 130 | + |
| 131 | + assert_not_in_family("mutableCopying", "mutableCopy"); |
| 132 | + assert_in_family("mutableCopyWithZone:", "mutableCopy"); |
| 133 | + assert_in_family("mutableCopyWithZone:", "mutableCopy"); |
| 134 | + |
| 135 | + assert_in_family( |
| 136 | + "newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", |
| 137 | + "new", |
| 138 | + ); |
| 139 | + assert_in_family( |
| 140 | + "newScriptingObjectOfClass:forValueForKey:withContentsValue:properties:", |
| 141 | + "new", |
| 142 | + ); |
| 143 | + assert_not_in_family("newsstandAssetDownload", "new"); |
| 144 | + |
| 145 | + // Trying to weed out edge-cases: |
| 146 | + |
| 147 | + assert_in_family("__abcDef", "abc"); |
| 148 | + assert_in_family("_abcDef", "abc"); |
| 149 | + assert_in_family("abcDef", "abc"); |
| 150 | + assert_in_family("___a", "a"); |
| 151 | + assert_in_family("__a", "a"); |
| 152 | + assert_in_family("_a", "a"); |
| 153 | + assert_in_family("a", "a"); |
| 154 | + |
| 155 | + assert_not_in_family("_abcdef", "abc"); |
| 156 | + assert_not_in_family("_abcdef", "def"); |
| 157 | + assert_not_in_family("_bcdef", "abc"); |
| 158 | + assert_not_in_family("a_bc", "abc"); |
| 159 | + assert_not_in_family("abcdef", "abc"); |
| 160 | + assert_not_in_family("abcdef", "def"); |
| 161 | + assert_not_in_family("abcdef", "abb"); |
| 162 | + assert_not_in_family("___", "a"); |
| 163 | + assert_not_in_family("_", "a"); |
| 164 | + assert_not_in_family("", "a"); |
| 165 | + |
| 166 | + assert_in_family("copy", "copy"); |
| 167 | + assert_in_family("copy:", "copy"); |
| 168 | + assert_in_family("copyMe", "copy"); |
| 169 | + assert_in_family("_copy", "copy"); |
| 170 | + assert_in_family("_copy:", "copy"); |
| 171 | + assert_in_family("_copyMe", "copy"); |
| 172 | + assert_not_in_family("copying", "copy"); |
| 173 | + assert_not_in_family("copying:", "copy"); |
| 174 | + assert_not_in_family("_copying", "copy"); |
| 175 | + assert_not_in_family("Copy", "copy"); |
| 176 | + assert_not_in_family("COPY", "copy"); |
| 177 | + |
| 178 | + // Empty family (not supported) |
| 179 | + assert_in_family("___", ""); |
| 180 | + assert_in_family("__", ""); |
| 181 | + assert_in_family("_", ""); |
| 182 | + assert_in_family("", ""); |
| 183 | + assert_not_in_family("_a", ""); |
| 184 | + assert_not_in_family("a", ""); |
| 185 | + assert_in_family("_A", ""); |
| 186 | + assert_in_family("A", ""); |
| 187 | + |
| 188 | + // Double-colon selectors |
| 189 | + assert_in_family("abc::abc::", "abc"); |
| 190 | + assert_in_family("abc:::", "abc"); |
| 191 | + assert_in_family("abcDef::xyz:", "abc"); |
| 192 | + // Invalid selector (probably) |
| 193 | + assert_not_in_family("::abc:", "abc"); |
| 194 | + } |
| 195 | +} |
0 commit comments