Skip to content

Commit 1cad754

Browse files
committed
[PlaygroundLogger] Implemented a check to avoid trying to log nil-containing non-Optional objects.
It is undefined behavior for a non-Optional object to be nil in Swift, and at present, PlaygroundLogger triggers crashes in the runtime when trying to log such objects. The most common way for such an object to exist is for an Objective-C API to be annotated as returning nonnull but in fact returns nil. In a regular Swift program, this can be worked around by explicitly assigning the value to an Optional. PlaygroundLogger cannot do that as it only receives an Any, and assigning to Any? does not recover the nil. As a result, inspect the contents of the Any's existential container (i.e. the thing in memory that is the Any itself, not the value inside of the Any) to determine if it contains a non-Optional object whose value is nil. This commit includes additional tests to ensure that we can log this broken value without crashing and that we can log a nil Optional object correctly. This addresses <rdar://problem/56370098>.
1 parent 6b02f88 commit 1cad754

File tree

6 files changed

+225
-5
lines changed

6 files changed

+225
-5
lines changed

PlaygroundLogger/PlaygroundLogger.xcodeproj/project.pbxproj

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
5E1069A323CFD68100C79FC2 /* PGLNilObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1069A223CFD68100C79FC2 /* PGLNilObject.m */; };
11+
5E1069A423CFD68100C79FC2 /* PGLNilObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1069A223CFD68100C79FC2 /* PGLNilObject.m */; };
12+
5E1069A523CFD68100C79FC2 /* PGLNilObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1069A223CFD68100C79FC2 /* PGLNilObject.m */; };
1013
5E184715202BB72700F01AD1 /* TypeName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E184714202BB72700F01AD1 /* TypeName.swift */; };
1114
5E184718202BB80200F01AD1 /* PGLConcurrentMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E184716202BB80200F01AD1 /* PGLConcurrentMap.h */; settings = {ATTRIBUTES = (Public, ); }; };
1215
5E184719202BB80200F01AD1 /* PGLConcurrentMap_MRR.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E184717202BB80200F01AD1 /* PGLConcurrentMap_MRR.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
@@ -192,6 +195,9 @@
192195
/* End PBXCopyFilesBuildPhase section */
193196

194197
/* Begin PBXFileReference section */
198+
5E10699E23CFD68100C79FC2 /* PlaygroundLoggerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PlaygroundLoggerTests-Bridging-Header.h"; sourceTree = "<group>"; };
199+
5E1069A123CFD68100C79FC2 /* PGLNilObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PGLNilObject.h; sourceTree = "<group>"; };
200+
5E1069A223CFD68100C79FC2 /* PGLNilObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PGLNilObject.m; sourceTree = "<group>"; };
195201
5E11805920414E2700B73EE9 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = ../XcodeConfig/Debug.xcconfig; sourceTree = "<group>"; };
196202
5E11805A20414E2700B73EE9 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = ../XcodeConfig/Release.xcconfig; sourceTree = "<group>"; };
197203
5E184714202BB72700F01AD1 /* TypeName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeName.swift; sourceTree = "<group>"; };
@@ -416,6 +422,9 @@
416422
5EB651C3213A081A001CC984 /* LoggerEntrypointTests.swift */,
417423
5EB651BF213A01D0001CC984 /* LegacyEntrypointTests.swift */,
418424
5ECE8F901FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift */,
425+
5E1069A123CFD68100C79FC2 /* PGLNilObject.h */,
426+
5E1069A223CFD68100C79FC2 /* PGLNilObject.m */,
427+
5E10699E23CFD68100C79FC2 /* PlaygroundLoggerTests-Bridging-Header.h */,
419428
);
420429
path = PlaygroundLoggerTests;
421430
sourceTree = "<group>";
@@ -814,12 +823,12 @@
814823
};
815824
5E26462F1FB64876002DC6B6 = {
816825
CreatedOnToolsVersion = 9.1;
817-
LastSwiftMigration = 1010;
826+
LastSwiftMigration = 1130;
818827
ProvisioningStyle = Automatic;
819828
};
820829
5EFE9188203F6CC400E21BAA = {
821830
CreatedOnToolsVersion = 9.3;
822-
LastSwiftMigration = 1010;
831+
LastSwiftMigration = 1130;
823832
ProvisioningStyle = Automatic;
824833
TestTargetID = 5EFE9197203F6DD700E21BAA;
825834
};
@@ -835,7 +844,7 @@
835844
};
836845
5EFE91CC203F6F6900E21BAA = {
837846
CreatedOnToolsVersion = 9.3;
838-
LastSwiftMigration = 1010;
847+
LastSwiftMigration = 1130;
839848
ProvisioningStyle = Automatic;
840849
TestTargetID = 5EFE91AF203F6E8D00E21BAA;
841850
};
@@ -1000,6 +1009,7 @@
10001009
5E5D77802040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
10011010
5E48E0AA2042925B00C7712D /* LogEntryTests.swift in Sources */,
10021011
5EF581532041387C00AC14FE /* CustomPlaygroundDisplayConvertibleTests.swift in Sources */,
1012+
5E1069A323CFD68100C79FC2 /* PGLNilObject.m in Sources */,
10031013
5ECE8F911FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift in Sources */,
10041014
);
10051015
runOnlyForDeploymentPostprocessing = 0;
@@ -1013,6 +1023,7 @@
10131023
5E5D77812040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
10141024
5E48E0AB2042925B00C7712D /* LogEntryTests.swift in Sources */,
10151025
5EF581542041387C00AC14FE /* CustomPlaygroundDisplayConvertibleTests.swift in Sources */,
1026+
5E1069A423CFD68100C79FC2 /* PGLNilObject.m in Sources */,
10161027
5EFE9193203F6CF900E21BAA /* LegacyPlaygroundLoggerTests.swift in Sources */,
10171028
);
10181029
runOnlyForDeploymentPostprocessing = 0;
@@ -1044,6 +1055,7 @@
10441055
5E5D77822040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
10451056
5E48E0AC2042925B00C7712D /* LogEntryTests.swift in Sources */,
10461057
5EF581552041387C00AC14FE /* CustomPlaygroundDisplayConvertibleTests.swift in Sources */,
1058+
5E1069A523CFD68100C79FC2 /* PGLNilObject.m in Sources */,
10471059
5EFE91D7203F6F8100E21BAA /* LegacyPlaygroundLoggerTests.swift in Sources */,
10481060
);
10491061
runOnlyForDeploymentPostprocessing = 0;
@@ -1263,33 +1275,38 @@
12631275
isa = XCBuildConfiguration;
12641276
buildSettings = {
12651277
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1278+
CLANG_ENABLE_MODULES = YES;
12661279
CODE_SIGN_STYLE = Automatic;
12671280
COMBINE_HIDPI_IMAGES = YES;
12681281
INFOPLIST_FILE = PlaygroundLoggerTests_macOS/Info.plist;
12691282
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
12701283
PRODUCT_BUNDLE_IDENTIFIER = "org.swift.PlaygroundLoggerTests-macOS";
12711284
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
12721285
PRODUCT_NAME = "$(TARGET_NAME)";
1286+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
12731287
};
12741288
name = Debug;
12751289
};
12761290
5E2646401FB64876002DC6B6 /* Release */ = {
12771291
isa = XCBuildConfiguration;
12781292
buildSettings = {
12791293
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1294+
CLANG_ENABLE_MODULES = YES;
12801295
CODE_SIGN_STYLE = Automatic;
12811296
COMBINE_HIDPI_IMAGES = YES;
12821297
INFOPLIST_FILE = PlaygroundLoggerTests_macOS/Info.plist;
12831298
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
12841299
PRODUCT_BUNDLE_IDENTIFIER = "org.swift.PlaygroundLoggerTests-macOS";
12851300
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
12861301
PRODUCT_NAME = "$(TARGET_NAME)";
1302+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
12871303
};
12881304
name = Release;
12891305
};
12901306
5EFE918E203F6CC400E21BAA /* Debug */ = {
12911307
isa = XCBuildConfiguration;
12921308
buildSettings = {
1309+
CLANG_ENABLE_MODULES = YES;
12931310
CLANG_ENABLE_OBJC_WEAK = YES;
12941311
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
12951312
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
@@ -1301,6 +1318,7 @@
13011318
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
13021319
PRODUCT_NAME = "$(TARGET_NAME)";
13031320
SDKROOT = iphoneos;
1321+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
13041322
TARGETED_DEVICE_FAMILY = "1,2";
13051323
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PlaygroundLoggerTestHost_iOS.app/PlaygroundLoggerTestHost_iOS";
13061324
};
@@ -1309,6 +1327,7 @@
13091327
5EFE918F203F6CC400E21BAA /* Release */ = {
13101328
isa = XCBuildConfiguration;
13111329
buildSettings = {
1330+
CLANG_ENABLE_MODULES = YES;
13121331
CLANG_ENABLE_OBJC_WEAK = YES;
13131332
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
13141333
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
@@ -1320,6 +1339,7 @@
13201339
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
13211340
PRODUCT_NAME = "$(TARGET_NAME)";
13221341
SDKROOT = iphoneos;
1342+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
13231343
TARGETED_DEVICE_FAMILY = "1,2";
13241344
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PlaygroundLoggerTestHost_iOS.app/PlaygroundLoggerTestHost_iOS";
13251345
VALIDATE_PRODUCT = YES;
@@ -1403,6 +1423,7 @@
14031423
5EFE91D5203F6F6A00E21BAA /* Debug */ = {
14041424
isa = XCBuildConfiguration;
14051425
buildSettings = {
1426+
CLANG_ENABLE_MODULES = YES;
14061427
CLANG_ENABLE_OBJC_WEAK = YES;
14071428
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
14081429
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
@@ -1414,6 +1435,7 @@
14141435
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
14151436
PRODUCT_NAME = "$(TARGET_NAME)";
14161437
SDKROOT = appletvos;
1438+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
14171439
TARGETED_DEVICE_FAMILY = 3;
14181440
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PlaygroundLoggerTestHost_tvOS.app/PlaygroundLoggerTestHost_tvOS";
14191441
};
@@ -1422,6 +1444,7 @@
14221444
5EFE91D6203F6F6A00E21BAA /* Release */ = {
14231445
isa = XCBuildConfiguration;
14241446
buildSettings = {
1447+
CLANG_ENABLE_MODULES = YES;
14251448
CLANG_ENABLE_OBJC_WEAK = YES;
14261449
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
14271450
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
@@ -1433,6 +1456,7 @@
14331456
PRODUCT_MODULE_NAME = PlaygroundLoggerTests;
14341457
PRODUCT_NAME = "$(TARGET_NAME)";
14351458
SDKROOT = appletvos;
1459+
SWIFT_OBJC_BRIDGING_HEADER = "PlaygroundLoggerTests/PlaygroundLoggerTests-Bridging-Header.h";
14361460
TARGETED_DEVICE_FAMILY = 3;
14371461
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PlaygroundLoggerTestHost_tvOS.app/PlaygroundLoggerTestHost_tvOS";
14381462
VALIDATE_PRODUCT = YES;

PlaygroundLogger/PlaygroundLogger/LogEntry+Reflection.swift

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2017-2018 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2017-2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -31,6 +31,32 @@ extension LogEntry {
3131
return
3232
}
3333

34+
// In Swift, it is undefined behavior to have a value of non-Optional class type which is set to nil.
35+
// Unfortunately, these can be encountered in the wild; the most common example is an incorrect Objective-C nullability annotation.
36+
// To prevent PlaygroundLogger from triggering undefined behavior (which, as of this writing, causes a crash in the Swift runtime), perform a safety check before proceeding with logging.
37+
do {
38+
// First, parse the existential container for `instance` so we can examine its contents directly.
39+
let existentialContainer = AnyExistentialContainer(instance)
40+
41+
// Next, get the type of `instance` which is stored in the existential container.
42+
guard let instanceType = existentialContainer.type else {
43+
// The existential container does not contain a type, which is invalid.
44+
// Rather than crash by trying to proceed along from here, return an error LogEntry.
45+
self = .error(reason: "Value does not contain a type")
46+
return
47+
}
48+
49+
// If `instance` contains a non-Optional class type (e.g. AnyObject), then `instanceType is AnyClass` will be true.
50+
// If `instance` contains an Optional class type (e.g. AnyObject? aka Optional<AnyObject>), then `instanceType is AnyClass` will be false, as Optional is *not* a class.
51+
//
52+
// If `instance` contains a non-Optional class type, then the first pointer in the existential container's buffer will be the object pointer. We can check to see if that pointer is nil to see if the object is nil.
53+
if instanceType is AnyClass && existentialContainer.fixedSizeBuffer.0 == nil {
54+
// `instance` is broken. As a result, return a log entry which indicates that the value is nil.
55+
self = .structured(name: name, typeName: passedInTypeName ?? normalizedName(of: instanceType), summary: "nil", totalChildrenCount: 0, children: [], disposition: .aggregate)
56+
return
57+
}
58+
}
59+
3460
// Lazily-load the Mirror for this instance. (This is factored out this way as the Mirror is needed in a few different code paths.)
3561
var _mirrorStorage: Mirror? = nil
3662
var mirror: Mirror {
@@ -261,6 +287,66 @@ extension Mirror {
261287
}
262288
}
263289

290+
/// A struct representing the memory contents of an `Any` -- the existential container for the `Any` rather than the value itself contained in the `Any`.
291+
///
292+
/// The memory layout of an existential container is part of Swift's ABI. It is defined as:
293+
///
294+
/// - Three words of fixed storage for an inline buffer containing either the value or a pointer to a box containing the value
295+
/// - One word containing a pointer to the type metadata for the value stored in the existential
296+
/// - Zero or more words containing pointers to the protocol witness tables for the protocols to which the existential is constrained
297+
///
298+
/// Since `Any` is an existential with no protocol constraints, it ultimately is a four-word value where the first three words are a fixed-size buffer
299+
/// and the last word is a pointer to the type metadata.
300+
///
301+
/// This struct stores the fixed-size buffer as three `UnsafeRawPointer?` values.
302+
/// An `Any.Type?` (aka `Optional<Any.Type>`, not `Optional<Any>.Type`) in Swift is itself a pointer to the type metadata, so this struct loads that pointer as an `Any.Type?` for examination.
303+
fileprivate struct AnyExistentialContainer {
304+
/// A fixed-size, three-word buffer containing the value or a pointer to a box containing the value stored in this existential container.
305+
///
306+
/// The contents of this buffer must only be interpreted in the context of a particular type.
307+
/// For example, if this existential container contains an object, the first word will contain the pointer to that object while the contents of the other two words are undefined.
308+
var fixedSizeBuffer: (UnsafeRawPointer?, UnsafeRawPointer?, UnsafeRawPointer?)
309+
310+
/// The type of the value stored in this existential container.
311+
///
312+
/// This can be used to interpret the value stored in `fixedSizeBuffer`.
313+
/// For example, if this existential container contains an object, `self.type is AnyClass` will be true.
314+
var type: Any.Type?
315+
316+
/// Initializes a new `AnyExistentialContainer` with the contents of `instance`.
317+
///
318+
/// This initializer does this with direct memory access to load the `Any` as its container rather than as the value contained in the `Any`.
319+
///
320+
/// - important: This initializer does not use the Swift runtime to interact with the contents of `instance`.
321+
/// It is **critical** that this remain true so that an untrusted `Any` can be examined by PlaygroundLogger before proceeding with logging.
322+
///
323+
/// - parameter instance: The `Any` whose existential container should be made available in a new instance of `AnyExistentialContainer`
324+
init(_ instance: Any) {
325+
// TODO: If Swift implements support for static/compile-time assertions, we should adopt those here.
326+
327+
// We require that `AnyExistentialContainer` and `Any` have the same memory layouts.
328+
assert(MemoryLayout<AnyExistentialContainer>.size == MemoryLayout<Any>.size)
329+
assert(MemoryLayout<AnyExistentialContainer>.alignment == MemoryLayout<Any>.alignment)
330+
assert(MemoryLayout<AnyExistentialContainer>.stride == MemoryLayout<Any>.stride)
331+
332+
// Ensure that we catch any changes to the struct layout which might move it out of alignment with `Any`.
333+
// Importantly, `fixedSizeBuffer` should be at offset 0, `fixedSizeBuffer` should be exactly three words wide, and `type` should be at an offset immediately after `fixedSizeBuffer`.
334+
assert(MemoryLayout<AnyExistentialContainer>.offset(of: \AnyExistentialContainer.fixedSizeBuffer) == 0)
335+
assert(MemoryLayout<(UnsafeRawPointer?, UnsafeRawPointer?, UnsafeRawPointer?)>.size == (3 * MemoryLayout<UnsafeRawPointer?>.size))
336+
assert(MemoryLayout<AnyExistentialContainer>.offset(of: \AnyExistentialContainer.type) == MemoryLayout<(UnsafeRawPointer?, UnsafeRawPointer?, UnsafeRawPointer?)>.size)
337+
338+
// We also require that `Any.Type?` be a trivial type so that it's safe to load this way.
339+
assert(_isPOD(Any.Type?.self))
340+
341+
// Finally, we require that we are a trivial type as well.
342+
assert(_isPOD(AnyExistentialContainer.self))
343+
344+
self = withUnsafeBytes(of: instance) { bytes in
345+
return bytes.load(as: AnyExistentialContainer.self)
346+
}
347+
}
348+
}
349+
264350
/// Construct the summary for `instance`.
265351
///
266352
/// In precedence order, the rules are:

PlaygroundLogger/PlaygroundLoggerTests/LogEntryTests.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2018-2019 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2018-2020 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -167,4 +167,53 @@ class LogEntryTests: XCTestCase {
167167

168168
XCTAssertEqual(disposition, .membershipContainer)
169169
}
170+
171+
func testOptionalNilObject() throws {
172+
let object: NSObject? = nil
173+
174+
let logEntry = try LogEntry(describing: object as Any, name: "object", policy: .default)
175+
176+
guard case let .structured(name, typeName, summary, totalChildrenCount, children, disposition) = logEntry else {
177+
XCTFail("Expected to receive a structured log entry, but didn't")
178+
return
179+
}
180+
181+
XCTAssertEqual(name, "object")
182+
XCTAssert(typeName.hasPrefix("Optional<"))
183+
XCTAssertEqual(summary, "nil")
184+
XCTAssertEqual(totalChildrenCount, 0)
185+
XCTAssert(children.isEmpty)
186+
187+
guard case .aggregate = disposition else {
188+
XCTFail("Expected to receive an aggregate, but didn't")
189+
return
190+
}
191+
}
192+
193+
func testNonOptionalNilObject() throws {
194+
// PGLNilObject is a type which returns nil from -init.
195+
// This occurs despite the fact that -init is marked as return a nonnull value.
196+
// This allows us to test PlaygroundLogger against Objective-C types which have bad nullability annotations.
197+
let object: PGLNilObject = PGLNilObject()
198+
199+
XCTAssertEqual(Int(bitPattern: ObjectIdentifier(object)), 0, "We expect PGLNilObject to produce a nil object")
200+
201+
let logEntry = try LogEntry(describing: object, name: "object", policy: .default)
202+
203+
guard case let .structured(name, typeName, summary, totalChildrenCount, children, disposition) = logEntry else {
204+
XCTFail("Expected to receive an error log entry, but didn't")
205+
return
206+
}
207+
208+
XCTAssertEqual(name, "object")
209+
XCTAssertEqual(typeName, "PGLNilObject")
210+
XCTAssertEqual(summary, "nil")
211+
XCTAssertEqual(totalChildrenCount, 0)
212+
XCTAssert(children.isEmpty)
213+
214+
guard case .aggregate = disposition else {
215+
XCTFail("Expected to receive an aggregate, but didn't")
216+
return
217+
}
218+
}
170219
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===--- PGLNilObject.h ---------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#import <Foundation/Foundation.h>
14+
15+
NS_ASSUME_NONNULL_BEGIN
16+
17+
/// \c PGLNilObject is a test class which includes a \c -init method which is intentionally mis-annotated.
18+
/// It returns \c nil instead of an instance of \c PGLNilObject to allow PlaygroundLogger's tests to exercise logging this sort of broken value.
19+
///
20+
/// See also: \c LogEntryTests.testNonOptionalNilObject()
21+
@interface PGLNilObject : NSObject
22+
23+
- (instancetype)init;
24+
25+
@end
26+
27+
NS_ASSUME_NONNULL_END
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===--- PGLNilObject.m ---------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#import "PGLNilObject.h"
14+
15+
@implementation PGLNilObject
16+
17+
- (instancetype)init {
18+
return nil;
19+
}
20+
21+
@end

0 commit comments

Comments
 (0)