[Serialization] Drop overriding methods if the base is missing.

Proof-of-concept for this sort of recovery. In the real world, it's
more likely that this will happen due to differences between Swift 3
and Swift 4, rather than changes in what macros are defined, but the
latter can still happen when debugging.

There's a lot to do here to consider this production-ready. There are
no generics involved and no potential circular references, and the
/rest/ of the compiler isn't prepared for this either. But it's cool
to see it working!

Actually recovering is hidden behind the new
-enable-experimental-deserialization-recovery option; without it the
compiler will continue to eagerly abort.
This commit is contained in:
Jordan Rose
2017-04-05 18:32:08 -07:00
parent 6fdb076c5e
commit 73d4526943
4 changed files with 92 additions and 14 deletions

View File

@@ -297,6 +297,26 @@ namespace {
}
};
const char XRefError::ID = '\0';
class OverrideError : public llvm::ErrorInfo<OverrideError> {
friend ErrorInfo;
static const char ID;
DeclName name;
public:
explicit OverrideError(DeclName name) : name(name) {}
void log(raw_ostream &OS) const override {
OS << "could not find '" << name << "' in parent class";
}
std::error_code convertToErrorCode() const override {
// This is a deprecated part of llvm::Error, so we just return a very
// generic value.
return {EINVAL, std::generic_category()};
}
};
const char OverrideError::ID = '\0';
} // end anonymous namespace
@@ -1220,9 +1240,23 @@ bool ModuleFile::readMembers(SmallVectorImpl<Decl *> &Members) {
Members.reserve(rawMemberIDs.size());
for (DeclID rawID : rawMemberIDs) {
Decl *D = getDecl(rawID);
assert(D && "unable to deserialize next member");
Members.push_back(D);
Expected<Decl *> D = getDeclChecked(rawID);
if (!D) {
if (!getContext().LangOpts.EnableDeserializationRecovery)
fatal(D.takeError());
// Silently drop the member if there was a problem.
// FIXME: This isn't sound for protocols; we need to at least record that
// it happened.
llvm::handleAllErrors(D.takeError(),
[](const OverrideError &) { /* expected */ },
[&](std::unique_ptr<llvm::ErrorInfoBase> unhandled){
fatal(std::move(unhandled));
});
continue;
}
assert(D.get() && "unchecked error deserializing next member");
Members.push_back(D.get());
}
return false;
@@ -2916,12 +2950,31 @@ ModuleFile::getDeclChecked(DeclID DID, Optional<DeclContext *> ForcedContext) {
overriddenID, accessorStorageDeclID,
hasCompoundName, rawAddressorKind,
rawAccessLevel, nameIDs);
// Resolve the name ids.
SmallVector<Identifier, 2> names;
for (auto nameID : nameIDs)
names.push_back(getIdentifier(nameID));
DeclName name;
if (!names.empty()) {
if (hasCompoundName)
name = DeclName(ctx, names[0],
llvm::makeArrayRef(names.begin() + 1, names.end()));
else
name = DeclName(names[0]);
}
Expected<Decl *> overridden = getDeclChecked(overriddenID);
if (!overridden) {
llvm::handleAllErrors(overridden.takeError(),
[](const XRefError &) { /* expected */ },
[&](std::unique_ptr<llvm::ErrorInfoBase> unhandled){
fatal(std::move(unhandled));
});
return llvm::make_error<OverrideError>(name);
}
auto DC = getDeclContext(contextID);
if (declOrOffset.isComplete())
return declOrOffset;
@@ -2940,14 +2993,6 @@ ModuleFile::getDeclChecked(DeclID DID, Optional<DeclContext *> ForcedContext) {
if (declOrOffset.isComplete())
return declOrOffset;
DeclName name;
if (!names.empty()) {
if (hasCompoundName)
name = DeclName(ctx, names[0],
llvm::makeArrayRef(names.begin() + 1, names.end()));
else
name = DeclName(names[0]);
}
auto fn = FuncDecl::createDeserialized(
ctx, /*StaticLoc=*/SourceLoc(), staticSpelling.getValue(),
/*FuncLoc=*/SourceLoc(), name, /*NameLoc=*/SourceLoc(),
@@ -3004,8 +3049,8 @@ ModuleFile::getDeclChecked(DeclID DID, Optional<DeclContext *> ForcedContext) {
if (auto errorConvention = maybeReadForeignErrorConvention())
fn->setForeignErrorConvention(*errorConvention);
if (auto overridden = cast_or_null<FuncDecl>(getDecl(overriddenID))) {
fn->setOverriddenDecl(overridden);
if (auto overriddenFunc = cast_or_null<FuncDecl>(overridden.get())) {
fn->setOverriddenDecl(overriddenFunc);
AddAttribute(new (ctx) OverrideAttr(SourceLoc()));
}

View File

@@ -0,0 +1,5 @@
@interface Base
#ifndef BAD
- (void)method;
#endif
@end

View File

@@ -0,0 +1 @@
module Overrides { header "Overrides.h" }

View File

@@ -0,0 +1,27 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules %s
// RUN: %target-swift-ide-test -source-filename=x -print-module -module-to-print Lib -I %t -I %S/Inputs/custom-modules | %FileCheck %s
// RUN: not --crash %target-swift-ide-test -source-filename=x -print-module -module-to-print Lib -I %t -I %S/Inputs/custom-modules -Xcc -DBAD 2>&1 | %FileCheck -check-prefix CHECK-CRASH %s
// RUN: %target-swift-ide-test -source-filename=x -print-module -module-to-print Lib -I %t -I %S/Inputs/custom-modules -Xcc -DBAD -enable-experimental-deserialization-recovery | %FileCheck -check-prefix CHECK-RECOVERY %s
// REQUIRES: objc_interop
import Overrides
public class Sub: Base {
public override func method() {}
}
// CHECK-LABEL: class Sub : Base {
// CHECK-NEXT: func method()
// CHECK-NEXT: {{^}$}}
// CHECK-CRASH: error: fatal error encountered while reading from module 'Lib'; please file a bug report with your project and the crash log
// CHECK-CRASH-LABEL: *** DESERIALIZATION FAILURE (please include this section in any bug report) ***
// CHECK-CRASH: could not find 'method()' in parent class
// CHECK-CRASH: While loading members for 'Sub' in module 'Lib'
// CHECK-RECOVERY-LABEL: class Sub : Base {
// CHECK-RECOVERY-NEXT: {{^}$}}