Skip to content

Commit 9983df9

Browse files
committed
[ClangImporter] Don't crash when a bad override affects NSErrors. (swiftlang#7907)
Most of the time the name importer does a good job deciding whether to import a particular method as throwing or not. However, when a method is an override, it skips all that work and assumes the decisions made for the superclass method apply here as well---which makes sense, since you're going to get the subclass implementation if you call the superclass's entry point. This can really throw things off if the types /don't/ match up, though. Handle the one case where this is legal according to the rules of Objective-C, and make sure we don't import methods in the other cases. rdar://problem/30705461
1 parent c982578 commit 9983df9

File tree

3 files changed

+67
-3
lines changed

3 files changed

+67
-3
lines changed

lib/ClangImporter/ImportType.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,15 +1771,28 @@ adjustResultTypeForThrowingFunction(ForeignErrorConvention::Info errorInfo,
17711771
switch (errorInfo.TheKind) {
17721772
case ForeignErrorConvention::ZeroResult:
17731773
case ForeignErrorConvention::NonZeroResult:
1774+
// Check for a bad override.
1775+
if (resultTy->isVoid())
1776+
return Type();
17741777
return TupleType::getEmpty(resultTy->getASTContext());
17751778

17761779
case ForeignErrorConvention::NilResult:
1777-
resultTy = resultTy->getAnyOptionalObjectType();
1778-
assert(resultTy &&
1779-
"result type of NilResult convention was not imported as optional");
1780+
if (Type unwrappedTy = resultTy->getAnyOptionalObjectType())
1781+
return unwrappedTy;
1782+
// Check for a bad override.
1783+
if (resultTy->isVoid())
1784+
return Type();
1785+
// It's possible an Objective-C method overrides the base method to never
1786+
// fail, and marks the method _Nonnull to indicate that. Swift can't
1787+
// represent that, but it shouldn't fall over either.
17801788
return resultTy;
17811789

17821790
case ForeignErrorConvention::ZeroPreservedResult:
1791+
// Check for a bad override.
1792+
if (resultTy->isVoid())
1793+
return Type();
1794+
return resultTy;
1795+
17831796
case ForeignErrorConvention::NonNilError:
17841797
return resultTy;
17851798
}

test/ClangImporter/foreign_errors.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-silgen -parse-as-library -verify %s
2+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-sil -O -parse-as-library -DEMIT_SIL %s
23

34
// REQUIRES: objc_interop
45

56
import Foundation
67
import errors
78

9+
#if !EMIT_SIL
810
func test0() {
911
try ErrorProne.fail() // expected-error {{errors thrown from here are not handled}}
1012
}
13+
#endif
1114

1215
// Test "AndReturnError" stripping.
1316
// rdar://20722195
@@ -71,10 +74,12 @@ func testBlockFinal() throws {
7174
try ErrorProne.runSwiftly(5000, callback: {})
7275
}
7376

77+
#if !EMIT_SIL
7478
func testNonBlockFinal() throws {
7579
ErrorProne.runWithError(count: 0) // expected-error {{missing argument for parameter #1 in call}}
7680
ErrorProne.run(count: 0) // expected-error {{incorrect argument label in call (have 'count:', expected 'callback:')}}
7781
}
82+
#endif
7883

7984
class VeryErrorProne : ErrorProne {
8085
override class func fail() throws {}
@@ -108,3 +113,24 @@ func testNSErrorExhaustive() {
108113
}
109114
}
110115
}
116+
117+
func testBadOverrides(obj: FoolishErrorSub) throws {
118+
try obj.performRiskyOperation()
119+
let _: FoolishErrorSub = try obj.produceRiskyOutput()
120+
let _: String = try obj.produceRiskyString()
121+
122+
let _: NSObject = try obj.badNullResult()
123+
let _: CInt = try obj.badNullResult2() // This is unfortunate but consistent.
124+
let _: CInt = try obj.badZeroResult()
125+
try obj.badNonzeroResult() as Void
126+
127+
let base = obj as SensibleErrorBase
128+
try base.performRiskyOperation()
129+
let _: NSObject = try base.produceRiskyOutput()
130+
let _: String = try base.produceRiskyString()
131+
132+
let _: NSObject = try base.badNullResult()
133+
let _: NSObject = try base.badNullResult2()
134+
let _: CInt = try base.badZeroResult()
135+
try base.badNonzeroResult() as Void
136+
}

test/Inputs/clang-importer-sdk/usr/include/errors.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,28 @@
5959
- (BOOL) obliterate: (NSError**) error;
6060
- (BOOL) invigorate: (NSError**) error callback: (void(^)(void)) block;
6161
@end
62+
63+
64+
@interface SensibleErrorBase: NSObject
65+
- (BOOL)performRiskyOperationAndReturnError:(NSError **)error;
66+
- (nullable NSObject *)produceRiskyOutputAndReturnError:(NSError **)error;
67+
- (nullable NSString *)produceRiskyStringAndReturnError:(NSError **)error;
68+
69+
- (nullable NSObject *)badNullResult:(NSError **)err __attribute__((swift_error(null_result)));
70+
- (nullable NSObject *)badNullResult2:(NSError **)err __attribute__((swift_error(null_result)));
71+
- (int)badZeroResult:(NSError **)err __attribute__((swift_error(zero_result)));
72+
- (int)badNonzeroResult:(NSError **)err __attribute__((swift_error(nonzero_result)));
73+
@end
74+
75+
@interface FoolishErrorSub : SensibleErrorBase
76+
// This is invalid, but Swift shouldn't crash when it sees it.
77+
- (void)performRiskyOperationAndReturnError:(NSError **)error;
78+
// This should technically be valid in Objective-C as a covariant return.
79+
- (nonnull FoolishErrorSub *)produceRiskyOutputAndReturnError:(NSError **)error;
80+
- (nonnull NSString *)produceRiskyStringAndReturnError:(NSError **)error;
81+
82+
- (void)badNullResult:(NSError **)err;
83+
- (int)badNullResult2:(NSError **)err;
84+
- (void)badZeroResult:(NSError **)err;
85+
- (void)badNonzeroResult:(NSError **)err;
86+
@end

0 commit comments

Comments
 (0)