-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[pigeon] fix swift nsnull casting crash #3545
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
Changes from 25 commits
574ee0b
46ca75e
e350040
3d31aff
3be7eb1
ef111ee
6d5271f
a4625f8
29ac0ec
4a7b408
d1d9c6f
241f0e3
8d356cc
04dfdbd
ed57271
1bd3afa
d0f9b86
fab7ca3
2e2d337
5b39dbe
cbb8e0b
7fa0f7d
b0c7653
740abd8
748a0bb
d8f5fe3
06ab71e
547ed02
367009e
cdf35ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## 9.2.1 | ||
|
|
||
| * [swift] Fixes NSNull casting crash. | ||
|
|
||
| ## 9.2.0 | ||
|
|
||
| * [cpp] Removes experimental tags. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,44 +176,17 @@ import FlutterMacOS | |
| indent.addScoped('{', '}', () { | ||
| enumerate(getFieldsInSerializationOrder(klass), | ||
| (int index, final NamedType field) { | ||
| final HostDatatype hostDatatype = _getHostDatatype(root, field); | ||
|
|
||
| final String listValue = 'list[$index]'; | ||
| final String fieldType = _swiftTypeForDartType(field.type); | ||
|
|
||
| if (field.type.isNullable) { | ||
| if (!hostDatatype.isBuiltin && | ||
| customClassNames.contains(field.type.baseName)) { | ||
| indent.writeln('var ${field.name}: $fieldType? = nil'); | ||
| indent.write('if let ${field.name}List = $listValue as! [Any]? '); | ||
| indent.addScoped('{', '}', () { | ||
| indent.writeln( | ||
| '${field.name} = $fieldType.fromList(${field.name}List as [Any])'); | ||
| }); | ||
| } else if (!hostDatatype.isBuiltin && | ||
| customEnumNames.contains(field.type.baseName)) { | ||
| indent.writeln('var ${field.name}: $fieldType? = nil'); | ||
| indent.write('if let ${field.name}RawValue = $listValue as! Int? '); | ||
| indent.addScoped('{', '}', () { | ||
| indent.writeln( | ||
| '${field.name} = $fieldType(rawValue: ${field.name}RawValue)'); | ||
| }); | ||
| } else { | ||
| indent.writeln('let ${field.name} = $listValue as! $fieldType? '); | ||
| } | ||
| } else { | ||
| if (!hostDatatype.isBuiltin && | ||
| customClassNames.contains(field.type.baseName)) { | ||
| indent.writeln( | ||
| 'let ${field.name} = $fieldType.fromList($listValue as! [Any])!'); | ||
| } else if (!hostDatatype.isBuiltin && | ||
| customEnumNames.contains(field.type.baseName)) { | ||
| indent.writeln( | ||
| 'let ${field.name} = $fieldType(rawValue: $listValue as! Int)!'); | ||
| } else { | ||
| indent.writeln('let ${field.name} = $listValue as! $fieldType'); | ||
| } | ||
| } | ||
| _writeDecodeCasting( | ||
| root: root, | ||
| indent: indent, | ||
| value: listValue, | ||
| variableName: field.name, | ||
| type: field.type, | ||
| customClassNames: customClassNames, | ||
| customEnumNames: customEnumNames, | ||
| ); | ||
| }); | ||
|
|
||
| indent.newln(); | ||
|
|
@@ -345,8 +318,13 @@ import FlutterMacOS | |
| }); | ||
| } else { | ||
| indent.addScoped('{ response in', '}', () { | ||
| indent.writeln( | ||
| 'let result = ${_castForceUnwrap("response", func.returnType, root)}'); | ||
| _writeDecodeCasting( | ||
| root: root, | ||
| indent: indent, | ||
| value: 'response', | ||
| variableName: 'result', | ||
| type: func.returnType, | ||
| ); | ||
| indent.writeln('completion(result)'); | ||
| }); | ||
| } | ||
|
|
@@ -461,9 +439,13 @@ import FlutterMacOS | |
| final String argName = | ||
| _getSafeArgumentName(index, arg.namedType); | ||
| final String argIndex = 'args[$index]'; | ||
| indent.writeln( | ||
| 'let $argName = ${_castForceUnwrap(argIndex, arg.type, root)}'); | ||
|
|
||
| _writeDecodeCasting( | ||
| root: root, | ||
| indent: indent, | ||
| value: argIndex, | ||
| variableName: argName, | ||
| type: arg.type, | ||
| ); | ||
| if (arg.label == '_') { | ||
| methodArgument.add(argName); | ||
| } else { | ||
|
|
@@ -607,6 +589,75 @@ import FlutterMacOS | |
| indent.newln(); | ||
| } | ||
|
|
||
| /// Writes decode and casting code for any type. | ||
| /// | ||
| /// Optional parameters are necessary for class decoding only. | ||
| void _writeDecodeCasting({ | ||
| required Root root, | ||
| required Indent indent, | ||
| required String value, | ||
| required String variableName, | ||
| required TypeDeclaration type, | ||
| Set<String>? customClassNames, | ||
| Set<String>? customEnumNames, | ||
| }) { | ||
| String castForceUnwrap(String value, TypeDeclaration type, Root root) { | ||
| if (isEnum(root, type)) { | ||
| assert(!type.isNullable, | ||
| 'nullable enums require special code that this helper does not supply'); | ||
| return '${_swiftTypeForDartType(type)}(rawValue: $value as! Int)!'; | ||
| } else if (type.baseName == 'Object') { | ||
| // Special-cased to avoid warnings about using 'as' with Any. | ||
| return value; | ||
| } else if (type.baseName == 'int') { | ||
| final String orString = | ||
| type.isNullable ? 'nilOrValue($value)' : '$value as! Int64'; | ||
| return '($value is Int32) ? Int64($value as! Int32) : $orString'; | ||
|
||
| } else if (type.isNullable) { | ||
| return 'nilOrValue($value)'; | ||
| } else { | ||
| return '$value as! ${_swiftTypeForDartType(type)}'; | ||
| } | ||
| } | ||
|
|
||
| final String fieldType = _swiftTypeForDartType(type); | ||
|
|
||
| if (type.isNullable) { | ||
| if (customClassNames != null && | ||
| customClassNames.contains(type.baseName)) { | ||
| indent.writeln('var $variableName: $fieldType? = nil'); | ||
| indent.write('if let ${variableName}List = $value as! [Any]? '); | ||
| indent.addScoped('{', '}', () { | ||
| indent.writeln( | ||
| '$variableName = $fieldType.fromList(${variableName}List)'); | ||
| }); | ||
| } else if (customEnumNames != null && | ||
| customEnumNames.contains(type.baseName)) { | ||
| indent.writeln('var $variableName: $fieldType? = nil'); | ||
| indent.writeln( | ||
| 'let ${variableName}EnumVal: Int? = ${castForceUnwrap(value, const TypeDeclaration(baseName: 'Int', isNullable: true), root)}'); | ||
| indent | ||
| .write('if let ${variableName}RawValue = ${variableName}EnumVal '); | ||
| indent.addScoped('{', '}', () { | ||
| indent.writeln( | ||
| '$variableName = $fieldType(rawValue: ${variableName}RawValue)!'); | ||
| }); | ||
| } else { | ||
| indent.writeln( | ||
| 'let $variableName: $fieldType? = ${castForceUnwrap(value, type, root)}'); | ||
| } | ||
| } else { | ||
| if (customClassNames != null && | ||
| customClassNames.contains(type.baseName)) { | ||
| indent.writeln( | ||
| 'let $variableName = $fieldType.fromList($value as! [Any])!'); | ||
| } else { | ||
| indent.writeln( | ||
| 'let $variableName = ${castForceUnwrap(value, type, root)}'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void _writeWrapResult(Indent indent) { | ||
| indent.newln(); | ||
| indent.write('private func wrapResult(_ result: Any?) -> [Any?] '); | ||
|
|
@@ -637,11 +688,21 @@ import FlutterMacOS | |
| }); | ||
| } | ||
|
|
||
| void _writeNilOrValue(Indent indent) { | ||
| indent.format(''' | ||
|
|
||
| private func nilOrValue<T>(_ value: Any?) -> T? { | ||
| if value is NSNull { return nil } | ||
| return (value as Any) as! T? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline - this is indeed a weird Swift behavior that |
||
| }'''); | ||
| } | ||
|
|
||
| @override | ||
| void writeGeneralUtilities( | ||
| SwiftOptions generatorOptions, Root root, Indent indent) { | ||
| _writeWrapResult(indent); | ||
| _writeWrapError(indent); | ||
| _writeNilOrValue(indent); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -667,23 +728,6 @@ String _camelCase(String text) { | |
| return pascal[0].toLowerCase() + pascal.substring(1); | ||
| } | ||
|
|
||
| String _castForceUnwrap(String value, TypeDeclaration type, Root root) { | ||
| final String forceUnwrap = type.isNullable ? '' : '!'; | ||
| final String castUnwrap = type.isNullable ? '?' : ''; | ||
| if (isEnum(root, type)) { | ||
| final String nullableConditionPrefix = | ||
| type.isNullable ? '$value == nil ? nil : ' : ''; | ||
| return '$nullableConditionPrefix${_swiftTypeForDartType(type)}(rawValue: $value as! Int)$forceUnwrap'; | ||
| } else if (type.baseName == 'Object') { | ||
| // Special-cased to avoid warnings about using 'as' with Any. | ||
| return value; | ||
| } else if (type.baseName == 'int') { | ||
| return '($value is Int) ? Int64($value as! Int) : $value as! Int64$castUnwrap'; | ||
tarrinneal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else { | ||
| return '$value as! ${_swiftTypeForDartType(type)}$castUnwrap'; | ||
| } | ||
| } | ||
|
|
||
| /// Converts a [List] of [TypeDeclaration]s to a comma separated [String] to be | ||
| /// used in Swift code. | ||
| String _flattenTypeArguments(List<TypeDeclaration> args) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more subtle than that; I'm pretty sure that passing
customClassNameswould actively break things (creating essentially the bug I fixed for C++ in #3573). I was actually confused at first about how the PR wasn't causing a regression there until I noticed that the call sites weren't passing the class list.If I'm correct about that, I think it would be better to make things much more explicit. E.g., you could rename
customClassNamestolistEncodedClassNames, and change this comment to explicitly say that a value for that must be provided only in the case of class decoding, with a link to flutter/flutter#119351 for context. (Alternately you could leave the name, but add an explicit bool like I did in the C++ generator.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed them. And made the comment more specific.