Skip to content

Commit 199c90e

Browse files
authored
Merge pull request #58745 from ktoso/wip-init-crash
[Distributed] Enforce strict order of synthesized id and actorSystem properties; avoid wrong offset crashes
2 parents 537312c + 1675669 commit 199c90e

10 files changed

+790
-790
lines changed

lib/AST/DistributedDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ bool AbstractFunctionDecl::isDistributedActorSystemRemoteCall(bool isVoidReturn)
472472
if (!invocationParam->isInOut()) {
473473
return false;
474474
}
475+
475476
// --- Check parameter: throwing: Err.Type
476477
auto thrownTypeParam = params->get(3);
477478
if (thrownTypeParam->getArgumentName() != C.Id_throwing) {

lib/Sema/CodeSynthesisDistributedActor.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,17 @@ static VarDecl *addImplicitDistributedActorIDProperty(
117117
propDecl->getAttrs().add(
118118
new (C) CompilerInitializedAttr(/*IsImplicit=*/true));
119119

120-
nominal->addMember(propDecl);
121-
nominal->addMember(pbDecl);
122-
120+
// IMPORTANT: The `id` MUST be the first field of any distributed actor,
121+
// because when we allocate remote proxy instances, we don't allocate memory
122+
// for anything except the first two fields: id and actorSystem, so they
123+
// MUST be those fields.
124+
//
125+
// Their specific order also matters, because it is enforced this way in IRGen
126+
// and how we emit them in AST MUST match what IRGen expects or cross-module
127+
// things could be using wrong offsets and manifest as reading trash memory on
128+
// id or system accesses.
129+
nominal->addMember(propDecl, /*hint=*/nullptr, /*insertAtHead=*/true);
130+
nominal->addMember(pbDecl, /*hint=*/nullptr, /*insertAtHead=*/true);
123131
return propDecl;
124132
}
125133

lib/Sema/DerivedConformanceDistributedActor.cpp

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -426,32 +426,6 @@ static FuncDecl *deriveDistributedActorSystem_invokeHandlerOnReturn(
426426
/******************************* PROPERTIES ***********************************/
427427
/******************************************************************************/
428428

429-
// TODO(distributed): make use of this after all, but FORCE it?
430-
static ValueDecl *deriveDistributedActor_id(DerivedConformance &derived) {
431-
assert(derived.Nominal->isDistributedActor());
432-
auto &C = derived.Context;
433-
434-
// ```
435-
// nonisolated
436-
// let id: Self.ID // Self.ActorSystem.ActorID
437-
// ```
438-
auto propertyType = getDistributedActorIDType(derived.Nominal);
439-
440-
VarDecl *propDecl;
441-
PatternBindingDecl *pbDecl;
442-
std::tie(propDecl, pbDecl) = derived.declareDerivedProperty(
443-
DerivedConformance::SynthesizedIntroducer::Let, C.Id_id, propertyType,
444-
propertyType,
445-
/*isStatic=*/false, /*isFinal=*/true);
446-
447-
// mark as nonisolated, allowing access to it from everywhere
448-
propDecl->getAttrs().add(
449-
new (C) NonisolatedAttr(/*IsImplicit=*/true));
450-
451-
derived.addMembersToConformanceContext({ propDecl, pbDecl });
452-
return propDecl;
453-
}
454-
455429
static ValueDecl *deriveDistributedActor_actorSystem(
456430
DerivedConformance &derived) {
457431
auto &C = derived.Context;
@@ -460,8 +434,7 @@ static ValueDecl *deriveDistributedActor_actorSystem(
460434
assert(classDecl && derived.Nominal->isDistributedActor());
461435

462436
// ```
463-
// nonisolated
464-
// let actorSystem: ActorSystem
437+
// nonisolated let actorSystem: ActorSystem
465438
// ```
466439
// (no need for @actorIndependent because it is an immutable let)
467440
auto propertyType = getDistributedActorSystemType(classDecl);
@@ -477,7 +450,14 @@ static ValueDecl *deriveDistributedActor_actorSystem(
477450
propDecl->getAttrs().add(
478451
new (C) NonisolatedAttr(/*IsImplicit=*/true));
479452

480-
derived.addMembersToConformanceContext({ propDecl, pbDecl });
453+
// IMPORTANT: `id` MUST be the first field of a distributed actor, and
454+
// `actorSystem` MUST be the second field, because for a remote instance
455+
// we don't allocate memory after those two fields, so their order is very
456+
// important. The `hint` below makes sure the system is inserted right after.
457+
auto id = derived.Nominal->getDistributedActorIDProperty();
458+
derived.addMemberToConformanceContext(pbDecl, /*hint=*/id);
459+
derived.addMemberToConformanceContext(propDecl, /*hint=*/id);
460+
481461
return propDecl;
482462
}
483463

@@ -571,11 +551,14 @@ deriveDistributedActorType_SerializationRequirement(
571551

572552
ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) {
573553
if (auto var = dyn_cast<VarDecl>(requirement)) {
574-
if (var->getName() == Context.Id_id)
575-
return deriveDistributedActor_id(*this);
576-
577554
if (var->getName() == Context.Id_actorSystem)
578555
return deriveDistributedActor_actorSystem(*this);
556+
557+
if (var->getName() == Context.Id_id)
558+
llvm_unreachable("DistributedActor.id MUST be synthesized earlier, "
559+
"because it is forced by the Identifiable conformance. "
560+
"If we attempted to do synthesis here, the earlier phase "
561+
"failed and something is wrong: please report a bug.");
579562
}
580563

581564
if (auto func = dyn_cast<FuncDecl>(requirement)) {

lib/Sema/DerivedConformances.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ void DerivedConformance::addMembersToConformanceContext(
5151
IDC->addMember(child);
5252
}
5353

54+
void DerivedConformance::addMemberToConformanceContext(
55+
Decl *member, Decl *hint) {
56+
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
57+
IDC->addMember(member, hint, /*insertAtHead=*/false);
58+
}
59+
60+
void DerivedConformance::addMemberToConformanceContext(
61+
Decl *member, bool insertAtHead) {
62+
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
63+
IDC->addMember(member, /*hint=*/nullptr, insertAtHead);
64+
}
65+
5466
Type DerivedConformance::getProtocolType() const {
5567
return Protocol->getDeclaredInterfaceType();
5668
}
@@ -325,10 +337,6 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
325337
if (name.isSimpleName(ctx.Id_unownedExecutor))
326338
return getRequirement(KnownProtocolKind::Actor);
327339

328-
// DistributedActor.id
329-
if(name.isSimpleName(ctx.Id_id))
330-
return getRequirement(KnownProtocolKind::DistributedActor);
331-
332340
// DistributedActor.actorSystem
333341
if(name.isSimpleName(ctx.Id_actorSystem))
334342
return getRequirement(KnownProtocolKind::DistributedActor);

lib/Sema/DerivedConformances.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class DerivedConformance {
7070

7171
/// Add \c children as members of the context that declares the conformance.
7272
void addMembersToConformanceContext(ArrayRef<Decl *> children);
73+
/// Add \c member right after the \c hint member which may be the tail
74+
void addMemberToConformanceContext(Decl *member, Decl* hint);
75+
/// Add \c member in front of any other existing members
76+
void addMemberToConformanceContext(Decl *member, bool insertAtHead);
7377

7478
/// Get the declared type of the protocol that this is conformance is for.
7579
Type getProtocolType() const;

stdlib/public/Distributed/LocalTestingDistributedActorSystem.swift

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Swift
14-
import _Concurrency
1514

1615
#if canImport(Darwin)
1716
import Darwin
@@ -27,33 +26,25 @@ import WinSDK
2726
/// for learning about `distributed actor` isolation, as well as early
2827
/// prototyping stages of development where a real system is not necessary yet.
2928
@available(SwiftStdlib 5.7, *)
30-
public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unchecked Sendable {
29+
public final class LocalTestingDistributedActorSystem: DistributedActorSystem, @unchecked Sendable {
3130
public typealias ActorID = LocalTestingActorAddress
3231
public typealias ResultHandler = LocalTestingInvocationResultHandler
3332
public typealias InvocationEncoder = LocalTestingInvocationEncoder
3433
public typealias InvocationDecoder = LocalTestingInvocationDecoder
35-
public typealias SerializationRequirement = any Codable
36-
37-
38-
@usableFromInline
39-
final class _Storage {
34+
public typealias SerializationRequirement = Codable
4035

41-
var activeActors: [ActorID: any DistributedActor] = [:]
42-
let activeActorsLock = _Lock()
36+
private var activeActors: [ActorID: any DistributedActor] = [:]
37+
private let activeActorsLock = _Lock()
4338

44-
var assignedIDs: Set<ActorID> = []
45-
let assignedIDsLock = _Lock()
46-
}
47-
let storage: _Storage
48-
var idProvider: ActorIDProvider = ActorIDProvider()
39+
private var idProvider: ActorIDProvider = ActorIDProvider()
40+
private var assignedIDs: Set<ActorID> = []
41+
private let assignedIDsLock = _Lock()
4942

50-
public init() {
51-
storage = .init()
52-
}
43+
public init() {}
5344

5445
public func resolve<Act>(id: ActorID, as actorType: Act.Type)
5546
throws -> Act? where Act: DistributedActor, Act.ID == ActorID {
56-
guard let anyActor = storage.activeActorsLock.withLock({ self.storage.activeActors[id] }) else {
47+
guard let anyActor = self.activeActorsLock.withLock({ self.activeActors[id] }) else {
5748
throw LocalTestingDistributedActorSystemError(message: "Unable to locate id '\(id)' locally")
5849
}
5950
guard let actor = anyActor as? Act else {
@@ -65,23 +56,25 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche
6556
public func assignID<Act>(_ actorType: Act.Type) -> ActorID
6657
where Act: DistributedActor, Act.ID == ActorID {
6758
let id = self.idProvider.next()
68-
storage.assignedIDsLock.withLock {
69-
self.storage.assignedIDs.insert(id)
59+
self.assignedIDsLock.withLock {
60+
self.assignedIDs.insert(id)
7061
}
7162
return id
7263
}
7364

7465
public func actorReady<Act>(_ actor: Act)
7566
where Act: DistributedActor, Act.ID == ActorID {
76-
guard storage.assignedIDsLock.withLock({ self.storage.assignedIDs.contains(actor.id) }) else {
67+
guard self.assignedIDsLock.withLock({ self.assignedIDs.contains(actor.id) }) else {
7768
fatalError("Attempted to mark an unknown actor '\(actor.id)' ready")
7869
}
79-
self.storage.activeActors[actor.id] = actor
70+
self.activeActorsLock.withLock {
71+
self.activeActors[actor.id] = actor
72+
}
8073
}
8174

8275
public func resignID(_ id: ActorID) {
83-
storage.activeActorsLock.withLock {
84-
self.storage.activeActors.removeValue(forKey: id)
76+
self.activeActorsLock.withLock {
77+
self.activeActors.removeValue(forKey: id)
8578
}
8679
}
8780

@@ -99,7 +92,7 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche
9992
where Act: DistributedActor,
10093
Act.ID == ActorID,
10194
Err: Error,
102-
Res: Codable {
95+
Res: SerializationRequirement {
10396
fatalError("Attempted to make remote call to \(target) on actor \(actor) using a local-only actor system")
10497
}
10598

@@ -115,40 +108,51 @@ public struct LocalTestingDistributedActorSystem: DistributedActorSystem, @unche
115108
fatalError("Attempted to make remote call to \(target) on actor \(actor) using a local-only actor system")
116109
}
117110

118-
@usableFromInline
119-
final class ActorIDProvider {
111+
private struct ActorIDProvider {
120112
private var counter: Int = 0
121113
private let counterLock = _Lock()
122114

123-
@usableFromInline
124115
init() {}
125116

126-
func next() -> LocalTestingActorAddress {
117+
mutating func next() -> LocalTestingActorAddress {
127118
let id: Int = self.counterLock.withLock {
128119
self.counter += 1
129120
return self.counter
130121
}
131-
return LocalTestingActorAddress(id)
122+
return LocalTestingActorAddress(parse: "\(id)")
132123
}
133124
}
134125
}
135126

136127
@available(SwiftStdlib 5.7, *)
137-
public struct LocalTestingActorAddress: Hashable, Sendable, Codable {
138-
public let uuid: String
128+
@available(*, deprecated, renamed: "LocalTestingActorID")
129+
public typealias LocalTestingActorAddress = LocalTestingActorID
139130

140-
public init(_ uuid: Int) {
141-
self.uuid = "\(uuid)"
131+
@available(SwiftStdlib 5.7, *)
132+
public struct LocalTestingActorID: Hashable, Sendable, Codable {
133+
@available(*, deprecated, renamed: "id")
134+
public var address: String {
135+
self.id
136+
}
137+
public let id: String
138+
139+
@available(*, deprecated, renamed: "init(id:)")
140+
public init(parse id: String) {
141+
self.id = id
142+
}
143+
144+
public init(id: String) {
145+
self.id = id
142146
}
143147

144148
public init(from decoder: Decoder) throws {
145149
let container = try decoder.singleValueContainer()
146-
self.uuid = try container.decode(String.self)
150+
self.id = try container.decode(String.self)
147151
}
148152

149153
public func encode(to encoder: Encoder) throws {
150154
var container = encoder.singleValueContainer()
151-
try container.encode(self.uuid)
155+
try container.encode(self.id)
152156
}
153157
}
154158

@@ -228,7 +232,7 @@ public struct LocalTestingDistributedActorSystemError: DistributedActorSystemErr
228232
// === lock ----------------------------------------------------------------
229233

230234
@available(SwiftStdlib 5.7, *)
231-
internal class _Lock {
235+
fileprivate class _Lock {
232236
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
233237
private let underlying: UnsafeMutablePointer<os_unfair_lock>
234238
#elseif os(Windows)
@@ -277,6 +281,7 @@ internal class _Lock {
277281
#endif
278282
}
279283

284+
280285
@discardableResult
281286
func withLock<T>(_ body: () -> T) -> T {
282287
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)

test/Distributed/Runtime/distributed_actor_init_local.swift

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
// UNSUPPORTED: use_os_stdlib
99
// UNSUPPORTED: back_deployment_runtime
1010

11-
// REQUIRES: rdar92910719
12-
1311
import Distributed
1412

1513
enum MyError: Error {
@@ -92,15 +90,6 @@ distributed actor MaybeAfterAssign {
9290
}
9391
}
9492

95-
distributed actor LocalTestingSystemDA {
96-
typealias ActorSystem = LocalTestingDistributedActorSystem
97-
var x: Int
98-
init() {
99-
actorSystem = .init()
100-
x = 100
101-
}
102-
}
103-
10493
distributed actor LocalTestingDA_Int {
10594
typealias ActorSystem = LocalTestingDistributedActorSystem
10695
var int: Int
@@ -111,7 +100,6 @@ distributed actor LocalTestingDA_Int {
111100
}
112101
}
113102

114-
115103
// ==== Fake Transport ---------------------------------------------------------
116104

117105
struct ActorAddress: Sendable, Hashable, Codable {
@@ -286,7 +274,7 @@ func test() async {
286274

287275
let localDA = LocalTestingDA_Int()
288276
print("localDA = \(localDA.id)")
289-
// CHECK: localDA = LocalTestingActorAddress(uuid: "1")
277+
// CHECK: localDA = LocalTestingActorID(id: "1")
290278

291279
// the following tests fail to initialize the actor's identity.
292280
print("-- start of no-assign tests --")
@@ -297,6 +285,7 @@ func test() async {
297285
// CHECK-NOT: assign
298286
// CHECK: -- end of no-assign tests --
299287

288+
300289
// resigns that come out of the deinits:
301290

302291
// CHECK-DAG: resign id:ActorAddress(address: "[[ID1]]")

test/Distributed/distributed_actor_layout.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import FakeDistributedActorSystems
1212
@available(SwiftStdlib 5.7, *)
1313
typealias DefaultDistributedActorSystem = FakeActorSystem
1414

15+
class MyClass { }
16+
1517
// Ensure that the actor layout is (metadata pointer, default actor, id, system,
1618
// <user fields>)
1719

@@ -21,10 +23,10 @@ protocol HasActorSystem {
2123

2224
extension MyActor: HasActorSystem { }
2325

24-
// CHECK: %T27distributed_actor_accessors7MyActorC = type <{ %swift.refcounted, %swift.defaultactor, %T27FakeDistributedActorSystems0C7AddressV, %T27FakeDistributedActorSystems0aC6SystemV, %TSS }>
26+
// CHECK: %T27distributed_actor_accessors7MyActorC = type <{ %swift.refcounted, %swift.defaultactor, %T27FakeDistributedActorSystems0C7AddressV, %T27FakeDistributedActorSystems0aC6SystemV, %T27distributed_actor_accessors7MyClassC* }>
2527
@available(SwiftStdlib 5.7, *)
2628
public distributed actor MyActor {
27-
var field: String = ""
29+
var field: MyClass = MyClass()
2830

2931
init(actorSystem: FakeActorSystem) {
3032
self.actorSystem = actorSystem

0 commit comments

Comments
 (0)