Skip to content

Commit ca938a4

Browse files
authored
Merge pull request #36431 from xymus/notes-on-xrefs
[Serialization] Reword xref errors and show notes for common issues
2 parents 8395008 + 9f71f46 commit ca938a4

File tree

3 files changed

+137
-11
lines changed

3 files changed

+137
-11
lines changed

lib/Serialization/Deserialization.cpp

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1469,8 +1469,91 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
14691469
};
14701470

14711471
if (values.empty()) {
1472+
// Couldn't resolve the reference. Try to explain the problem and leave it
1473+
// up to the caller to recover if possible.
1474+
1475+
// Look for types and value decls in other modules. This extra information
1476+
// is mostly for compiler engineers to understand a likely solution at a
1477+
// quick glance.
1478+
SmallVector<char> strScratch;
1479+
SmallVector<std::string, 2> notes;
1480+
auto declName = getXRefDeclNameForError();
1481+
if (recordID == XREF_TYPE_PATH_PIECE ||
1482+
recordID == XREF_VALUE_PATH_PIECE) {
1483+
auto &ctx = getContext();
1484+
for (auto nameAndModule : ctx.getLoadedModules()) {
1485+
auto baseModule = nameAndModule.second;
1486+
1487+
IdentifierID IID;
1488+
IdentifierID privateDiscriminator = 0;
1489+
TypeID TID = 0;
1490+
bool isType = (recordID == XREF_TYPE_PATH_PIECE);
1491+
bool inProtocolExt = false;
1492+
bool importedFromClang = false;
1493+
bool isStatic = false;
1494+
if (isType) {
1495+
XRefTypePathPieceLayout::readRecord(scratch, IID, privateDiscriminator,
1496+
inProtocolExt, importedFromClang);
1497+
} else {
1498+
XRefValuePathPieceLayout::readRecord(scratch, TID, IID, inProtocolExt,
1499+
importedFromClang, isStatic);
1500+
}
1501+
1502+
DeclBaseName name = getDeclBaseName(IID);
1503+
Type filterTy;
1504+
if (!isType) {
1505+
auto maybeType = getTypeChecked(TID);
1506+
// Any error here would have been handled previously.
1507+
if (maybeType) {
1508+
filterTy = maybeType.get();
1509+
}
1510+
}
1511+
1512+
values.clear();
1513+
if (privateDiscriminator) {
1514+
baseModule->lookupMember(values, baseModule, name,
1515+
getIdentifier(privateDiscriminator));
1516+
} else {
1517+
baseModule->lookupQualified(baseModule, DeclNameRef(name),
1518+
NL_QualifiedDefault,
1519+
values);
1520+
}
1521+
1522+
bool hadAMatchBeforeFiltering = !values.empty();
1523+
filterValues(filterTy, nullptr, nullptr, isType, inProtocolExt,
1524+
importedFromClang, isStatic, None, values);
1525+
1526+
strScratch.clear();
1527+
if (!values.empty()) {
1528+
// Found a full match in a different module. It should be a different
1529+
// one because otherwise it would have succeeded on the first search.
1530+
// This is usually caused by the use of poorly modularized headers.
1531+
auto line = "'" +
1532+
declName.getString(strScratch).str() +
1533+
"' was not found in module '" +
1534+
std::string(baseModule->getName().str()) +
1535+
"', but there is one in module '" +
1536+
std::string(nameAndModule.first.str()) +
1537+
"'. If this is imported from clang, please make sure " +
1538+
"the header is part of a single clang module.";
1539+
notes.emplace_back(line);
1540+
} else if (hadAMatchBeforeFiltering) {
1541+
// Found a match that was filtered out. This may be from the same
1542+
// expected module if there's a type difference. This can be caused
1543+
// by the use of different Swift language versions between a library
1544+
// with serialized SIL and a client.
1545+
auto line = "'" +
1546+
declName.getString(strScratch).str() +
1547+
"' in module '" +
1548+
std::string(baseModule->getName().str()) +
1549+
"' was filtered out.";
1550+
notes.emplace_back(line);
1551+
}
1552+
}
1553+
}
1554+
14721555
return llvm::make_error<XRefError>("top-level value not found", pathTrace,
1473-
getXRefDeclNameForError());
1556+
declName, notes);
14741557
}
14751558

14761559
// Filters for values discovered in the remaining path pieces.

lib/Serialization/DeserializationErrors.h

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,11 @@ class XRefTracePath {
222222
}
223223

224224
void print(raw_ostream &os, StringRef leading = "") const {
225-
os << "Cross-reference to module '" << baseM.getName() << "'\n";
226-
for (auto &piece : path) {
227-
os << leading << "... ";
228-
piece.print(os);
229-
os << "\n";
230-
}
225+
os << "Cross-reference to '";
226+
interleave(path,
227+
[&](auto &piece) { piece.print(os); },
228+
[&] { os << '.'; });
229+
os << "' in module '" << baseM.getName() << "'\n";
231230
}
232231
};
233232

@@ -276,16 +275,25 @@ class XRefError : public llvm::ErrorInfo<XRefError, DeclDeserializationError> {
276275

277276
XRefTracePath path;
278277
const char *message;
278+
SmallVector<std::string, 2> notes;
279279
public:
280280
template <size_t N>
281-
XRefError(const char (&message)[N], XRefTracePath path, DeclName name)
282-
: path(path), message(message) {
281+
XRefError(const char (&message)[N], XRefTracePath path, DeclName name,
282+
SmallVector<std::string, 2> notes = {})
283+
: path(path), message(message), notes(notes) {
283284
this->name = name;
284285
}
285286

286287
void log(raw_ostream &OS) const override {
287288
OS << message << "\n";
288289
path.print(OS);
290+
291+
if (!notes.empty()) {
292+
OS << "Notes:\n";
293+
for (auto &line : notes) {
294+
OS << "* " << line << "\n";
295+
}
296+
}
289297
}
290298

291299
std::error_code convertToErrorCode() const override {
@@ -360,9 +368,9 @@ class TypeError : public llvm::ErrorInfo<TypeError, DeclDeserializationError> {
360368
}
361369

362370
void log(raw_ostream &OS) const override {
363-
OS << "could not deserialize type for '" << name << "'";
371+
OS << "Could not deserialize type for '" << name << "'";
364372
if (underlyingReason) {
365-
OS << ": ";
373+
OS << "\nCaused by: ";
366374
underlyingReason->log(OS);
367375
}
368376
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// Test xref error description by removing a type from a module after building
2+
/// a client.
3+
// RUN: %empty-directory(%t)
4+
5+
/// Compile module A with a type and an empty module B.
6+
// RUN: %target-swift-frontend %s -emit-module-path %t/A.swiftmodule -module-name A -D LIB
7+
// RUN: %target-swift-frontend %s -emit-module-path %t/B.swiftmodule -module-name B
8+
#if LIB
9+
public struct SomeType {
10+
public init() {}
11+
}
12+
13+
/// Compile a client using the type from A.
14+
// RUN: %target-swift-frontend %s -emit-module-path %t/Client.swiftmodule -module-name Client -D CLIENT -I %t
15+
#elseif CLIENT
16+
import A
17+
import B
18+
19+
public func foo() -> A.SomeType { fatalError() }
20+
21+
/// Swap A and B around! A is now empty and B defines the type.
22+
// RUN: %target-swift-frontend %s -emit-module-path %t/A.swiftmodule -module-name A
23+
// RUN: %target-swift-frontend %s -emit-module-path %t/B.swiftmodule -module-name B -D LIB
24+
#endif // Empty
25+
26+
/// Read from the client to get an xref error to the type missing from A.
27+
// RUN: not --crash %target-swift-frontend -emit-sil %t/Client.swiftmodule -module-name Client -I %t -disable-deserialization-recovery 2> %t/stderr
28+
29+
// RUN: cat %t/stderr | %FileCheck %s
30+
// CHECK: *** DESERIALIZATION FAILURE (please include this section in any bug report) ***
31+
// CHECK-NEXT: Could not deserialize type for 'foo()'
32+
// CHECK-NEXT: Caused by: top-level value not found
33+
// CHECK-NEXT: Cross-reference to 'SomeType' in module 'A'
34+
// CHECK-NEXT: Notes:
35+
// CHECK-NEXT: * 'SomeType' was not found in module 'B', but there is one in module 'B'. If this is imported from clang, please make sure the header is part of a single clang module.

0 commit comments

Comments
 (0)