Skip to content

Commit f22d02a

Browse files
committed
Detect unbreakable metadata dependency cycles and abort with a diagnostic.
1 parent ea7dd7e commit f22d02a

File tree

4 files changed

+238
-52
lines changed

4 files changed

+238
-52
lines changed

include/swift/Runtime/Debug.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ struct RuntimeErrorDetails {
167167
uintptr_t framesToSkip;
168168

169169
// Address of some associated object (if there's any).
170-
void *memoryAddress;
170+
const void *memoryAddress;
171171

172172
// A structure describing an extra thread (and its stack) that is related.
173173
struct Thread {

include/swift/Runtime/Metadata.h

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -195,25 +195,45 @@ using Metadata = TargetMetadata<InProcess>;
195195
/// For performance, functions returning this type should use SWIFT_CC so
196196
/// that the components are returned as separate values.
197197
struct MetadataResponse {
198-
/// For metadata access functions, this is the requested metadata.
199-
///
200-
/// For metadata initialization functions, this is either null,
201-
/// indicating that initialization was successful, or a metadata on
202-
/// which initialization depends for further progress.
198+
/// The requested metadata.
203199
const Metadata *Value;
204200

205-
/// For metadata access functions, this is the current state of the
206-
/// metadata returned. Always use this instead of trying to inspect
207-
/// the metadata directly; an incomplete metadata may be getting
208-
/// initialized concurrently. This can generally be ignored if the
209-
/// metadata request was for abstract metadata or if the request is
210-
/// blocking.
211-
///
212-
/// For metadata initialization functions, this is the state that the
213-
/// given metadata needs to be in before initialization can continue.
201+
/// The current state of the metadata returned. Always use this
202+
/// instead of trying to inspect the metadata directly to see if it
203+
/// satisfies the request. An incomplete metadata may be getting
204+
/// initialized concurrently. But this can generally be ignored if
205+
/// the metadata request was for abstract metadata or if the request
206+
/// is blocking.
214207
MetadataState State;
215208
};
216-
using MetadataDependency = MetadataResponse;
209+
210+
/// A dependency on the metadata progress of other type, indicating that
211+
/// initialization of a metadata cannot progress until another metadata
212+
/// reaches a particular state.
213+
///
214+
/// For performance, functions returning this type should use SWIFT_CC so
215+
/// that the components are returned as separate values.
216+
struct MetadataDependency {
217+
/// Either null, indicating that initialization was successful, or
218+
/// a metadata on which initialization depends for further progress.
219+
const Metadata *Value;
220+
221+
/// The state that Metadata needs to be in before initialization
222+
/// can continue.
223+
MetadataState Requirement;
224+
225+
MetadataDependency() : Value(nullptr) {}
226+
MetadataDependency(const Metadata *metadata, MetadataState requirement)
227+
: Value(metadata), Requirement(requirement) {}
228+
229+
explicit operator bool() const { return Value != nullptr; }
230+
231+
bool operator==(MetadataDependency other) const {
232+
assert(Value && other.Value);
233+
return Value == other.Value &&
234+
Requirement == other.Requirement;
235+
}
236+
};
217237

218238
template <typename Runtime> struct TargetProtocolConformanceDescriptor;
219239

stdlib/public/runtime/Metadata.cpp

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -243,23 +243,18 @@ namespace {
243243

244244
// If this failed with a dependency, infer the current metadata state
245245
// and return.
246-
if (dependency.Value) {
247-
return { inferStateForMetadata(metadata),
248-
dependency.State, dependency.Value };
246+
if (dependency) {
247+
return { inferStateForMetadata(metadata), dependency };
249248
}
250249
}
251250

252251
// Check for transitive completeness.
253-
auto dependency =
254-
checkTransitiveCompleteness(metadata, description);
255-
if (dependency.Value) {
256-
return { PrivateMetadataState::NonTransitiveComplete,
257-
dependency.State, dependency.Value };
252+
if (auto dependency = checkTransitiveCompleteness(metadata, description)){
253+
return { PrivateMetadataState::NonTransitiveComplete, dependency };
258254
}
259255

260256
// We're done.
261-
return { PrivateMetadataState::Complete,
262-
MetadataState::Complete, nullptr };
257+
return { PrivateMetadataState::Complete, MetadataDependency() };
263258
}
264259
};
265260
} // end anonymous namespace
@@ -3333,24 +3328,24 @@ static Result performOnMetadataCache(const Metadata *metadata,
33333328
}
33343329

33353330
bool swift::addToMetadataQueue(MetadataCompletionQueueEntry *queueEntry,
3336-
const Metadata *dependency,
3337-
MetadataState dependencyRequirement) {
3331+
MetadataDependency dependency) {
33383332
struct EnqueueCallbacks {
33393333
MetadataCompletionQueueEntry *QueueEntry;
3334+
MetadataDependency Dependency;
33403335

33413336
bool forGenericMetadata(const Metadata *metadata,
33423337
const TypeContextDescriptor *description,
33433338
GenericMetadataCache &cache,
33443339
MetadataCacheKey key) && {
3345-
return cache.enqueue(key, QueueEntry);
3340+
return cache.enqueue(key, QueueEntry, Dependency);
33463341
}
33473342

33483343
bool forOtherMetadata(const Metadata *metadata) && {
33493344
swift_runtime_unreachable("metadata should always be complete");
33503345
}
3351-
} callbacks = { queueEntry };
3346+
} callbacks = { queueEntry, dependency };
33523347

3353-
return performOnMetadataCache<bool>(dependency, std::move(callbacks));
3348+
return performOnMetadataCache<bool>(dependency.Value, std::move(callbacks));
33543349
}
33553350

33563351
void swift::resumeMetadataCompletion(MetadataCompletionQueueEntry *queueEntry) {
@@ -3553,6 +3548,114 @@ checkTransitiveCompleteness(const Metadata *initialType,
35533548
return { nullptr, MetadataState::Complete };
35543549
}
35553550

3551+
/// Diagnose a metadata dependency cycle.
3552+
LLVM_ATTRIBUTE_NORETURN
3553+
static void diagnoseMetadataDependencyCycle(const Metadata *start,
3554+
ArrayRef<MetadataDependency> links){
3555+
assert(start == links.back().Value);
3556+
3557+
std::string diagnostic =
3558+
"runtime error: unresolvable type metadata dependency cycle detected\n ";
3559+
diagnostic += nameForMetadata(start);
3560+
3561+
for (auto &link : links) {
3562+
// If the diagnostic gets too large, just cut it short.
3563+
if (diagnostic.size() >= 128 * 1024) {
3564+
diagnostic += "\n (limiting diagnostic text at 128KB)";
3565+
break;
3566+
}
3567+
3568+
diagnostic += "\n depends on ";
3569+
3570+
switch (link.Requirement) {
3571+
case MetadataState::Complete:
3572+
diagnostic += "transitive completion of ";
3573+
break;
3574+
case MetadataState::NonTransitiveComplete:
3575+
diagnostic += "completion of ";
3576+
break;
3577+
case MetadataState::LayoutComplete:
3578+
diagnostic += "layout of ";
3579+
break;
3580+
case MetadataState::Abstract:
3581+
diagnostic += "<corruption> of ";
3582+
break;
3583+
}
3584+
3585+
diagnostic += nameForMetadata(link.Value);
3586+
}
3587+
3588+
diagnostic += "\nAborting!\n";
3589+
3590+
if (_swift_shouldReportFatalErrorsToDebugger()) {
3591+
#pragma GCC diagnostic push
3592+
#pragma GCC diagnostic ignored "-Wc99-extensions"
3593+
RuntimeErrorDetails details = {
3594+
.version = RuntimeErrorDetails::currentVersion,
3595+
.errorType = "type-metadata-cycle",
3596+
.currentStackDescription = "fetching metadata", // TODO?
3597+
.framesToSkip = 1, // skip out to the check function
3598+
.memoryAddress = start
3599+
// TODO: describe the cycle using notes instead of one huge message?
3600+
};
3601+
#pragma GCC diagnostic pop
3602+
3603+
_swift_reportToDebugger(RuntimeErrorFlagFatal, diagnostic.c_str(),
3604+
&details);
3605+
}
3606+
3607+
fatalError(0, diagnostic.c_str());
3608+
}
3609+
3610+
/// Check whether the given metadata dependency is satisfied, and if not,
3611+
/// return its current dependency, if one exists.
3612+
static MetadataDependency
3613+
checkMetadataDependency(MetadataDependency dependency) {
3614+
struct IsIncompleteCallbacks {
3615+
MetadataState Requirement;
3616+
MetadataDependency forGenericMetadata(const Metadata *type,
3617+
const TypeContextDescriptor *desc,
3618+
GenericMetadataCache &cache,
3619+
MetadataCacheKey key) && {
3620+
return cache.checkDependency(key, Requirement);
3621+
}
3622+
3623+
MetadataDependency forOtherMetadata(const Metadata *type) && {
3624+
return MetadataDependency();
3625+
}
3626+
} callbacks = { dependency.Requirement };
3627+
3628+
return performOnMetadataCache<MetadataDependency>(dependency.Value,
3629+
std::move(callbacks));
3630+
}
3631+
3632+
/// Check for an unbreakable metadata-dependency cycle.
3633+
void swift::checkMetadataDependencyCycle(const Metadata *startMetadata,
3634+
MetadataDependency firstLink,
3635+
MetadataDependency secondLink) {
3636+
std::vector<MetadataDependency> links;
3637+
auto checkNewLink = [&](MetadataDependency newLink) {
3638+
links.push_back(newLink);
3639+
if (newLink.Value == startMetadata)
3640+
diagnoseMetadataDependencyCycle(startMetadata, links);
3641+
for (auto i = links.begin(), e = links.end() - 1; i != e; ++i) {
3642+
if (i->Value == newLink.Value) {
3643+
auto next = i + 1;
3644+
diagnoseMetadataDependencyCycle(i->Value,
3645+
llvm::makeArrayRef(&*next, links.end() - next));
3646+
}
3647+
}
3648+
};
3649+
3650+
checkNewLink(firstLink);
3651+
checkNewLink(secondLink);
3652+
while (true) {
3653+
auto newLink = checkMetadataDependency(links.back());
3654+
if (!newLink) return;
3655+
checkNewLink(newLink);
3656+
}
3657+
}
3658+
35563659
/***************************************************************************/
35573660
/*** Allocator implementation **********************************************/
35583661
/***************************************************************************/

0 commit comments

Comments
 (0)