Skip to content

Commit 6ac9170

Browse files
committed
Runtime: Add precondition check to swift_updateClassMetadata()
This initialization pattern can only be used if there is a backward deployment layout (IRGen calls this ClassMetadataStrategy::FixedOrUpdate) or if we are running on a newer Objective-C runtime that supports class metadata update hooks (IRGen calls this ClassMetadataStrategy::Update). If neither condition holds, we must trap here to avoid undefined behavior.
1 parent eec0fcd commit 6ac9170

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

stdlib/public/runtime/Metadata.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,14 @@ _swift_updateClassMetadataImpl(ClassMetadata *self,
27492749
// If we're running on a older Objective-C runtime, just realize
27502750
// the class.
27512751
if (!requiresUpdate) {
2752+
// If we don't have a backward deployment layout, we cannot proceed here.
2753+
if (self->getInstanceSize() == 0 ||
2754+
self->getInstanceAlignMask() == 0) {
2755+
fatalError(0, "class %s does not have a fragile layout; "
2756+
"the deployment target was newer than this OS\n",
2757+
self->getDescription()->Name.get());
2758+
}
2759+
27522760
// Realize the class. This causes the runtime to slide the field offsets
27532761
// stored in the field offset globals.
27542762
//
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift-dylib(%t/%target-library-name(resilient_struct)) -Xfrontend -enable-resilience %S/../../test/Inputs/resilient_struct.swift -emit-module -emit-module-path %t/resilient_struct.swiftmodule -module-name resilient_struct
4+
// RUN: %target-codesign %t/%target-library-name(resilient_struct)
5+
6+
// RUN: %target-build-swift %s -L %t -I %t -lresilient_struct -o %t/main %target-rpath(%t) -target x86_64-apple-macosx10.14.4
7+
// RUN: %target-codesign %t/main
8+
9+
// RUN: %target-run %t/main %t/%target-library-name(resilient_struct) %t/libresilient_class%{target-shared-library-suffix}
10+
11+
12+
// REQUIRES: executable_test
13+
// REQUIRES: objc_interop
14+
// REQUIRES: OS=macosx
15+
// REQUIRES: CPU=x86_64
16+
17+
// Test that on an old Objective-C runtime we crash if attempting to use a
18+
// class that requires the update callback and does not have a fragile layout.
19+
20+
import StdlibUnittest
21+
import resilient_struct
22+
23+
var ClassTestSuite = TestSuite("Update callback")
24+
25+
public class ClassWithResilientField {
26+
var x = ResilientInt(i: 0)
27+
}
28+
29+
@_optimize(none) func blackHole<T>(_: T) {}
30+
31+
@_optimize(none) func forceMetadata() {
32+
blackHole(ClassWithResilientField.self)
33+
}
34+
35+
if #available(macOS 10.14.4, iOS 12.2, tvOS 12.2, watchOS 5.2, *) {
36+
ClassTestSuite.test("RealizeResilientClass") {
37+
print("Nothing to test. We have the new Objective-C runtime.")
38+
forceMetadata()
39+
}
40+
} else {
41+
ClassTestSuite.test("RealizeResilientClass")
42+
.crashOutputMatches("class ClassWithResilientField does not have a fragile layout")
43+
.code {
44+
expectCrashLater()
45+
print("About to crash...")
46+
forceMetadata()
47+
}
48+
}
49+
50+
runAllTests()

0 commit comments

Comments
 (0)