We have code for converting C/C++/Objc keywords that appear in protos to convert them so that they can be compiled. One of the things we need to be careful of is accidentally overriding methods that Apple declares in NSObject. It turns out that we have run into issues where we conflict with "hidden" methods in NSObject or methods added by categories. method_dump.sh collects all of the methods we care about for macOS and iOS and dumps them into objectivec_nsobject_methods.h which is then included in objectivec_helpers.cc as part of the build. Added a pile of tests to verify that conversions are happening as expected.pull/5321/head
parent
cd95c79149
commit
be83b29bdd
6 changed files with 587 additions and 87 deletions
@ -0,0 +1,179 @@ |
||||
#!/bin/bash |
||||
|
||||
# Updates objectivec_nsobject_methods.h by generating a list of all of the properties |
||||
# and methods on NSObject that Protobufs should not overload from iOS and macOS combined. |
||||
# |
||||
# The rules: |
||||
# - No property should ever be overloaded. |
||||
# - Do not overload any methods that have 0 args such as "autorelease". |
||||
# - Do not overload any methods that start with "set[A-Z]" and have 1 arg such as |
||||
# "setValuesForKeysWithDictionary:". Note that these will end up in the list as just |
||||
# the "proto field" name, so "setValuesForKeysWithDictionary:" will become |
||||
# "valuesForKeysWithDictionary". |
||||
|
||||
set -eu |
||||
|
||||
trim() { |
||||
local var="$*" |
||||
# remove leading whitespace characters |
||||
var="${var#"${var%%[![:space:]]*}"}" |
||||
# remove trailing whitespace characters |
||||
var="${var%"${var##*[![:space:]]}"}" |
||||
echo -n "$var" |
||||
} |
||||
|
||||
objc_code=$(cat <<'END_CODE' |
||||
#import <Foundation/Foundation.h> |
||||
#import <objc/runtime.h> |
||||
|
||||
int main(int argc, const char * argv[]) { |
||||
@autoreleasepool { |
||||
Class cls = [NSObject class]; |
||||
|
||||
// List out the protocols on NSObject just so we are aware if they change. |
||||
unsigned int protocolCount; |
||||
__unsafe_unretained Protocol **protocols = |
||||
class_copyProtocolList(cls, &protocolCount); |
||||
for (unsigned int i = 0; i < protocolCount; i++) { |
||||
printf("// Protocol: %s\n", protocol_getName(protocols[i])); |
||||
} |
||||
free(protocols); |
||||
|
||||
// Grab all the properties. |
||||
unsigned int propCount; |
||||
objc_property_t *props = class_copyPropertyList(cls, &propCount); |
||||
NSMutableSet *reservedNames = [[NSMutableSet alloc] init]; |
||||
for (unsigned int i = 0; i < propCount; ++i) { |
||||
NSString *propertyName = [NSString stringWithUTF8String:property_getName(props[i])]; |
||||
[reservedNames addObject:propertyName]; |
||||
} |
||||
free(props); |
||||
|
||||
// Note that methods have 2 defaults args (_cmd and SEL) so a method "0 arg method" |
||||
// actually has 2. |
||||
unsigned int methodCount; |
||||
Method *methods = class_copyMethodList(cls, &methodCount); |
||||
for (unsigned int i = 0; i < methodCount; ++i) { |
||||
int argCount = method_getNumberOfArguments(methods[i]); |
||||
NSString *methodName = |
||||
[NSString stringWithUTF8String:sel_getName(method_getName(methods[i]))]; |
||||
if (argCount == 2) { |
||||
[reservedNames addObject:methodName]; |
||||
} |
||||
if (argCount == 3 && [methodName hasPrefix:@"set"] && methodName.length > 4) { |
||||
NSString *firstLetter = [methodName substringWithRange:NSMakeRange(3,1)]; |
||||
NSString *lowerFirstLetter = [firstLetter lowercaseString]; |
||||
if ([lowerFirstLetter isEqual:firstLetter]) { |
||||
// Make sure the next letter is a capital letter so we do not take things like |
||||
// settingSomething: |
||||
continue; |
||||
} |
||||
// -5 because 3 for set, 1 for the firstLetter and 1 for the colon on the end. |
||||
NSString *restOfString = |
||||
[methodName substringWithRange:NSMakeRange(4, methodName.length - 5)]; |
||||
methodName = [lowerFirstLetter stringByAppendingString:restOfString]; |
||||
[reservedNames addObject:methodName]; |
||||
} |
||||
} |
||||
free(methods); |
||||
|
||||
SEL sortSelector = @selector(caseInsensitiveCompare:); |
||||
NSArray *array = [reservedNames.allObjects sortedArrayUsingSelector:sortSelector]; |
||||
for (NSString *item in array) { |
||||
// Some items with _ in them get returned in quotes, so do not add more. |
||||
if ([item hasPrefix:@"\""]) { |
||||
printf("\t%s,\n", item.UTF8String); |
||||
} else { |
||||
printf("\t\"%s\",\n", item.UTF8String); |
||||
} |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
END_CODE |
||||
) |
||||
|
||||
file_header=$(cat <<'END_HEADER' |
||||
// NSObject methods |
||||
// Autogenerated by method_dump.sh. Do not edit by hand. |
||||
// Date: %DATE% |
||||
// macOS: %MACOS% |
||||
// iOS: %IOS% |
||||
|
||||
const char* const kNSObjectMethodsList[] = { |
||||
END_HEADER |
||||
) |
||||
|
||||
file_footer=$(cat <<'END_FOOTER' |
||||
}; |
||||
END_FOOTER |
||||
) |
||||
|
||||
# Check to make sure we are updating the correct file. |
||||
if [[ ! -e "objectivec_nsobject_methods.h" ]]; then |
||||
echo "error: Must be run in the src/google/protobuf/compiler/objectivec directory" |
||||
exit 1 |
||||
fi |
||||
|
||||
temp_dir=$(mktemp -d) |
||||
|
||||
echo "$objc_code" >> "$temp_dir"/method_dump.m |
||||
|
||||
# Compile up iphonesimulator and macos version of cmd line app. |
||||
iphone_simulator_sdk=$(xcrun --sdk iphonesimulator --show-sdk-path) |
||||
clang -isysroot "$iphone_simulator_sdk" -o "$temp_dir"/method_dump_ios \ |
||||
-framework Foundation -framework UIKit "$temp_dir"/method_dump.m |
||||
macos_sdk=$(xcrun --sdk macosx --show-sdk-path) |
||||
clang -isysroot "$macos_sdk" -o "$temp_dir"/method_dump_macos -framework Foundation \ |
||||
-framework Cocoa "$temp_dir"/method_dump.m |
||||
|
||||
# Create a device of the latest phone and iphonesimulator SDK and run our iOS cmd line. |
||||
device_type=$(xcrun simctl list devicetypes | grep \.iPhone- | tail -1 | sed 's/.*(\(.*\))/\1/') |
||||
# runtimes come with a space at the end (for Xcode 10) so let's trim all of our input to |
||||
# be safe. |
||||
device_type=$(trim "$device_type") |
||||
runtime=$(xcrun simctl list runtimes | grep \.iOS- | tail -1 | \ |
||||
sed 's/.*\(com\.apple.\CoreSimulator\.SimRuntime\.iOS.*\)/\1/') |
||||
runtime=$(trim "$runtime") |
||||
uuid=$(uuidgen) |
||||
device_name="method_dump_device_$uuid" |
||||
device=$(xcrun simctl create "$device_name" "$device_type" "$runtime") |
||||
xcrun simctl spawn "$device" "$temp_dir"/method_dump_ios > "$temp_dir"/methods_unsorted_ios.txt |
||||
xcrun simctl delete "$device" |
||||
|
||||
# Run the Mac version |
||||
"$temp_dir"/method_dump_macos >> "$temp_dir"/methods_unsorted_macos.txt |
||||
|
||||
# Generate sorted output |
||||
echo "$file_header" | sed -e "s|%DATE%|$(date)|" -e "s|%MACOS%|$(basename $macos_sdk)|" \ |
||||
-e "s|%IOS%|$(basename $iphone_simulator_sdk)|" > "$temp_dir"/methods_sorted.txt |
||||
sort -u "$temp_dir"/methods_unsorted_ios.txt \ |
||||
"$temp_dir"/methods_unsorted_macos.txt >> "$temp_dir"/methods_sorted.txt |
||||
echo $"$file_footer" >> "$temp_dir"/methods_sorted.txt |
||||
|
||||
# Check for differences. Turn off error checking because we expect diff to fail when |
||||
# there are no differences. |
||||
set +e |
||||
diff_out=$(diff -I "^//.*$" "$temp_dir"/methods_sorted.txt objectivec_nsobject_methods.h) |
||||
removed_methods=$(echo "$diff_out" | grep '^>.*$') |
||||
set -e |
||||
if [[ -n "$removed_methods" ]]; then |
||||
echo "error: Methods removed from NSObject" |
||||
echo "It appears that some methods may have been removed from NSObject." |
||||
echo "This could mean that there may be some backwards compatibility issues." |
||||
echo "You could potentially build apps that may not work on earlier systems than:" |
||||
echo "$iphone_simulator_sdk" |
||||
echo "$macos_sdk" |
||||
echo "If they declare protobuf types that use any of the following as names:" |
||||
echo "$removed_methods" |
||||
echo "" |
||||
echo "New Version: $temp_dir/methods_sorted.txt" |
||||
echo "Old Version: objectivec_nsobject_methods.h" |
||||
exit 1 |
||||
fi |
||||
if [[ -n "$diff_out" ]]; then |
||||
echo "Added Methods:" |
||||
echo "$(echo "$diff_out" | grep '^<.*$' | sed -e 's/^< "\(.*\)",$/ \1/')" |
||||
fi; |
||||
cp "$temp_dir"/methods_sorted.txt objectivec_nsobject_methods.h |
||||
rm -rf "$temp_dir" |
@ -0,0 +1,197 @@ |
||||
// NSObject methods
|
||||
// Autogenerated by method_dump.sh. Do not edit by hand.
|
||||
// Date: Thu Nov 1 14:12:16 PDT 2018
|
||||
// macOS: MacOSX10.14.sdk
|
||||
// iOS: iPhoneSimulator12.1.sdk
|
||||
|
||||
const char* const kNSObjectMethodsList[] = { |
||||
"CAMLType", |
||||
"CA_copyRenderValue", |
||||
"CA_prepareRenderValue", |
||||
"NS_copyCGImage", |
||||
"NS_tiledLayerVisibleRect", |
||||
"___tryRetain_OA", |
||||
"__autorelease_OA", |
||||
"__dealloc_zombie", |
||||
"__release_OA", |
||||
"__retain_OA", |
||||
"_accessibilityFinalize", |
||||
"_accessibilityIsTableViewDescendant", |
||||
"_accessibilityUIElementSpecifier", |
||||
"_accessibilityUseConvenienceAPI", |
||||
"_allowsDirectEncoding", |
||||
"_asScriptTerminologyNameArray", |
||||
"_asScriptTerminologyNameString", |
||||
"_bindingAdaptor", |
||||
"_cfTypeID", |
||||
"_copyDescription", |
||||
"_destroyObserverList", |
||||
"_didEndKeyValueObserving", |
||||
"_implicitObservationInfo", |
||||
"_internalAccessibilityAttributedHint", |
||||
"_internalAccessibilityAttributedLabel", |
||||
"_internalAccessibilityAttributedValue", |
||||
"_isAXConnector", |
||||
"_isAccessibilityContainerSectionCandidate", |
||||
"_isAccessibilityContentNavigatorSectionCandidate", |
||||
"_isAccessibilityContentSectionCandidate", |
||||
"_isAccessibilityTopLevelNavigatorSectionCandidate", |
||||
"_isDeallocating", |
||||
"_isKVOA", |
||||
"_isToManyChangeInformation", |
||||
"_ivarDescription", |
||||
"_localClassNameForClass", |
||||
"_methodDescription", |
||||
"_observerStorage", |
||||
"_overrideUseFastBlockObservers", |
||||
"_propertyDescription", |
||||
"_releaseBindingAdaptor", |
||||
"_scriptingCount", |
||||
"_scriptingCountNonrecursively", |
||||
"_scriptingDebugDescription", |
||||
"_scriptingExists", |
||||
"_scriptingShouldCheckObjectIndexes", |
||||
"_shortMethodDescription", |
||||
"_shouldSearchChildrenForSection", |
||||
"_traitStorageList", |
||||
"_tryRetain", |
||||
"_ui_descriptionBuilder", |
||||
"_uikit_variesByTraitCollections", |
||||
"_web_description", |
||||
"_webkit_invokeOnMainThread", |
||||
"_willBeginKeyValueObserving", |
||||
"accessibilityActivate", |
||||
"accessibilityActivationPoint", |
||||
"accessibilityAllowsOverriddenAttributesWhenIgnored", |
||||
"accessibilityAssistiveTechnologyFocusedIdentifiers", |
||||
"accessibilityAttributedHint", |
||||
"accessibilityAttributedLabel", |
||||
"accessibilityAttributedValue", |
||||
"accessibilityContainer", |
||||
"accessibilityContainerType", |
||||
"accessibilityCustomActions", |
||||
"accessibilityCustomRotors", |
||||
"accessibilityDecrement", |
||||
"accessibilityDragSourceDescriptors", |
||||
"accessibilityDropPointDescriptors", |
||||
"accessibilityElementCount", |
||||
"accessibilityElementDidBecomeFocused", |
||||
"accessibilityElementDidLoseFocus", |
||||
"accessibilityElementIsFocused", |
||||
"accessibilityElements", |
||||
"accessibilityElementsHidden", |
||||
"accessibilityFrame", |
||||
"accessibilityHeaderElements", |
||||
"accessibilityHint", |
||||
"accessibilityIdentification", |
||||
"accessibilityIdentifier", |
||||
"accessibilityIncrement", |
||||
"accessibilityLabel", |
||||
"accessibilityLanguage", |
||||
"accessibilityLocalizedStringKey", |
||||
"accessibilityNavigationStyle", |
||||
"accessibilityOverriddenAttributes", |
||||
"accessibilityParameterizedAttributeNames", |
||||
"accessibilityPath", |
||||
"accessibilityPerformEscape", |
||||
"accessibilityPerformMagicTap", |
||||
"accessibilityPresenterProcessIdentifier", |
||||
"accessibilityShouldUseUniqueId", |
||||
"accessibilitySupportsNotifications", |
||||
"accessibilitySupportsOverriddenAttributes", |
||||
"accessibilityTemporaryChildren", |
||||
"accessibilityTraits", |
||||
"accessibilityValue", |
||||
"accessibilityViewIsModal", |
||||
"accessibilityVisibleArea", |
||||
"allPropertyKeys", |
||||
"allowsWeakReference", |
||||
"attributeKeys", |
||||
"autoContentAccessingProxy", |
||||
"autorelease", |
||||
"awakeFromNib", |
||||
"boolValueSafe", |
||||
"bs_encoded", |
||||
"bs_isPlistableType", |
||||
"bs_secureEncoded", |
||||
"cl_json_serializeKey", |
||||
"class", |
||||
"classCode", |
||||
"classDescription", |
||||
"classForArchiver", |
||||
"classForCoder", |
||||
"classForKeyedArchiver", |
||||
"classForPortCoder", |
||||
"className", |
||||
"clearProperties", |
||||
"copy", |
||||
"dealloc", |
||||
"debugDescription", |
||||
"defaultAccessibilityTraits", |
||||
"description", |
||||
"doubleValueSafe", |
||||
"entityName", |
||||
"exposedBindings", |
||||
"finalize", |
||||
"finishObserving", |
||||
"flushKeyBindings", |
||||
"hash", |
||||
"init", |
||||
"int64ValueSafe", |
||||
"isAccessibilityElement", |
||||
"isAccessibilityElementByDefault", |
||||
"isElementAccessibilityExposedToInterfaceBuilder", |
||||
"isFault", |
||||
"isNSArray__", |
||||
"isNSCFConstantString__", |
||||
"isNSData__", |
||||
"isNSDate__", |
||||
"isNSDictionary__", |
||||
"isNSNumber__", |
||||
"isNSObject__", |
||||
"isNSOrderedSet__", |
||||
"isNSSet__", |
||||
"isNSString__", |
||||
"isNSTimeZone__", |
||||
"isNSValue__", |
||||
"isProxy", |
||||
"mutableCopy", |
||||
"nilValueForKey", |
||||
"objectSpecifier", |
||||
"observationInfo", |
||||
"pep_onDetachedThread", |
||||
"pep_onMainThread", |
||||
"pep_onMainThreadIfNecessary", |
||||
"prepareForInterfaceBuilder", |
||||
"release", |
||||
"releaseOnMainThread", |
||||
"retain", |
||||
"retainCount", |
||||
"retainWeakReference", |
||||
"scriptingProperties", |
||||
"self", |
||||
"shouldGroupAccessibilityChildren", |
||||
"storedAccessibilityActivationPoint", |
||||
"storedAccessibilityContainerType", |
||||
"storedAccessibilityElementsHidden", |
||||
"storedAccessibilityFrame", |
||||
"storedAccessibilityNavigationStyle", |
||||
"storedAccessibilityTraits", |
||||
"storedAccessibilityViewIsModal", |
||||
"storedIsAccessibilityElement", |
||||
"storedShouldGroupAccessibilityChildren", |
||||
"stringValueSafe", |
||||
"superclass", |
||||
"toManyRelationshipKeys", |
||||
"toOneRelationshipKeys", |
||||
"traitStorageList", |
||||
"un_safeBoolValue", |
||||
"userInterfaceItemIdentifier", |
||||
"utf8ValueSafe", |
||||
"valuesForKeysWithDictionary", |
||||
"zone", |
||||
// Protocol: CAAnimatableValue
|
||||
// Protocol: CARenderValue
|
||||
// Protocol: NSObject
|
||||
// Protocol: ROCKRemoteInvocationInterface
|
||||
}; |
Loading…
Reference in new issue