Skip to content

Improve request evaluator cycle diagnostics #36375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/swift/AST/AnyFunctionRef.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class AnyFunctionRef {

SourceLoc getLoc() const {
if (auto afd = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
return afd->getLoc();
return afd->getLocByPrintingIfNeeded();
}
if (auto ce = TheFunction.dyn_cast<AbstractClosureExpr *>()) {
return ce->getLoc();
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,11 @@ class alignas(1 << DeclAlignInBits) Decl {
/// in diagnostics.
SourceLoc getLoc(bool SerializedOK = true) const;

/// Returns a source location for use in diagnostics. If necessary, this
/// method will ask the \c DiagnosticEngine to print the declaration into a
/// source buffer and use that rather than returning an invalid \c SourceLoc.
SourceLoc getLocByPrintingIfNeeded() const;

/// Returns the source range of the entire declaration.
SourceRange getSourceRange() const;

Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,10 @@ namespace swift {
//// \c finishProcessing.
bool finishProcessing();

/// Gets a source location for the indicated declaration, if necessary by
/// pretty-printing it using ASTPrinter.
SourceLoc getDiagnosticLocForDecl(const Decl *decl);

/// Format the given diagnostic text and place the result in the given
/// buffer.
static void formatDiagnosticText(
Expand Down
25 changes: 25 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -2956,6 +2956,31 @@ class TypeCheckCompletionHandlerAsyncAttrRequest
bool isCached() const { return true; }
};

/// Perform a series of recursive calls with the \c first and \c rest decls that
/// will eventually cause a cycle in the request stack.
///
/// This request is used to test diag::circular_reference and
/// diag::circular_reference_through.
class DebugIntentionallyCauseCycleRequest
: public SimpleRequest<
DebugIntentionallyCauseCycleRequest,
evaluator::SideEffect(Decl *, ArrayRef<Decl *>),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation.
evaluator::SideEffect
evaluate(Evaluator &evaluator, Decl *first, ArrayRef<Decl *> rest) const;

public:
// Caching
bool isCached() const { return true; }
};

void simple_display(llvm::raw_ostream &out, Type value);
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,6 @@ SWIFT_REQUEST(TypeChecker, GetImplicitSendableRequest,
SWIFT_REQUEST(TypeChecker, TypeCheckCompletionHandlerAsyncAttrRequest,
bool (AbstractFunctionDecl *, CompletionHandlerAsyncAttr *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DebugIntentionallyCauseCycleRequest,
evaluator::SideEffect(Decl *, ArrayRef<Decl *>), Cached,
NoLocationInfo)
4 changes: 4 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,10 @@ namespace swift {

/// See \ref FrontendOptions.PrintFullConvention
bool PrintFullConvention = false;

/// Deliberately create a request cycle involving the declarations for the
/// types named here. Used to test the request evaluator's diagnostics.
std::vector<std::string> DebugCauseCycleViaDeclNames;
};

/// Options for controlling the behavior of the Clang importer.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ def debug_emit_invalid_swiftinterface_syntax : Flag<["-"], "debug-emit-invalid-s

def debug_cycles : Flag<["-"], "debug-cycles">,
HelpText<"Print out debug dumps when cycles are detected in evaluation">;
def debug_cause_cycle_via_decl : Separate<["-"], "debug-cause-cycle-via-decl">,
HelpText<"Intentially creates a request cycle involving each of the declarations passed with this flag">;

def debug_time_function_bodies : Flag<["-"], "debug-time-function-bodies">,
HelpText<"Dumps the time it takes to type-check each function body">;
Expand Down
6 changes: 5 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,10 @@ Result->X = SM.getLocFromExternalSource(Locs->SourceFilePath, Locs->X.Line, \
return Result;
}

SourceLoc Decl::getLocByPrintingIfNeeded() const {
return getASTContext().Diags.getDiagnosticLocForDecl(this);
}

StringRef Decl::getAlternateModuleName() const {
for (auto *Att: Attrs) {
if (auto *OD = dyn_cast<OriginallyDefinedInAttr>(Att)) {
Expand Down Expand Up @@ -8185,7 +8189,7 @@ void swift::simple_display(llvm::raw_ostream &out, AccessorKind kind) {
}

SourceLoc swift::extractNearestSourceLoc(const Decl *decl) {
auto loc = decl->getLoc();
auto loc = decl->getLocByPrintingIfNeeded();
if (loc.isValid())
return loc;

Expand Down
274 changes: 140 additions & 134 deletions lib/AST/DiagnosticEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,143 @@ static AccessLevel getBufferAccessLevel(const Decl *decl) {
return level;
}

SourceLoc DiagnosticEngine::getDiagnosticLocForDecl(const Decl *decl) {
if (!decl)
return SourceLoc();

if (decl->getLoc().isValid())
return decl->getLoc();

// There is no location we can point to. Pretty-print the declaration
// so we can point to it.
SourceLoc ppLoc = PrettyPrintedDeclarations[decl];
if (ppLoc.isInvalid()) {
class TrackingPrinter : public StreamPrinter {
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries;
AccessLevel bufferAccessLevel;

public:
TrackingPrinter(
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries,
raw_ostream &OS, AccessLevel bufferAccessLevel) :
StreamPrinter(OS), Entries(Entries),
bufferAccessLevel(bufferAccessLevel) {}

void printDeclLoc(const Decl *D) override {
if (getBufferAccessLevel(D) == bufferAccessLevel)
Entries.push_back({ D, OS.tell() });
}
};
SmallVector<std::pair<const Decl *, uint64_t>, 8> entries;
llvm::SmallString<128> buffer;
llvm::SmallString<128> bufferName;
{
// The access level of the buffer we want to print. Declarations below
// this access level will be omitted from the buffer; declarations
// above it will be printed, but (except for Open declarations in the
// Public buffer) will not be recorded in PrettyPrintedDeclarations as
// the "true" SourceLoc for the declaration.
AccessLevel bufferAccessLevel = getBufferAccessLevel(decl);

// Figure out which declaration to print. It's the top-most
// declaration (not a module).
const Decl *ppDecl = decl;
auto dc = decl->getDeclContext();

// FIXME: Horrible, horrible hackaround. We're not getting a
// DeclContext everywhere we should.
if (!dc) {
return SourceLoc();
}

while (!dc->isModuleContext()) {
switch (dc->getContextKind()) {
case DeclContextKind::Module:
llvm_unreachable("Not in a module context!");
break;

case DeclContextKind::FileUnit:
case DeclContextKind::TopLevelCodeDecl:
break;

case DeclContextKind::ExtensionDecl:
ppDecl = cast<ExtensionDecl>(dc);
break;

case DeclContextKind::GenericTypeDecl:
ppDecl = cast<GenericTypeDecl>(dc);
break;

case DeclContextKind::SerializedLocal:
case DeclContextKind::Initializer:
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
case DeclContextKind::EnumElementDecl:
break;
}

dc = dc->getParent();
}

// Build the module name path (in reverse), which we use to
// build the name of the buffer.
SmallVector<StringRef, 4> nameComponents;
while (dc) {
nameComponents.push_back(cast<ModuleDecl>(dc)->getName().str());
dc = dc->getParent();
}

for (unsigned i = nameComponents.size(); i; --i) {
bufferName += nameComponents[i-1];
bufferName += '.';
}

if (auto value = dyn_cast<ValueDecl>(ppDecl)) {
bufferName += value->getBaseName().userFacingName();
} else if (auto ext = dyn_cast<ExtensionDecl>(ppDecl)) {
bufferName += ext->getExtendedType().getString();
}

// If we're using a lowered access level, give the buffer a distinct
// name.
if (bufferAccessLevel != AccessLevel::Public) {
assert(bufferAccessLevel < AccessLevel::Public
&& "Above-public access levels should use public buffer");
bufferName += " (";
bufferName += getAccessLevelSpelling(bufferAccessLevel);
bufferName += ")";
}

// Pretty-print the declaration we've picked.
llvm::raw_svector_ostream out(buffer);
TrackingPrinter printer(entries, out, bufferAccessLevel);
llvm::SaveAndRestore<bool> isPrettyPrinting(IsPrettyPrintingDecl, true);
ppDecl->print(
printer,
PrintOptions::printForDiagnostics(
bufferAccessLevel,
decl->getASTContext().TypeCheckerOpts.PrintFullConvention));
}

// Build a buffer with the pretty-printed declaration.
auto bufferID = SourceMgr.addMemBufferCopy(buffer, bufferName);
auto memBufferStartLoc = SourceMgr.getLocForBufferStart(bufferID);

// Go through all of the pretty-printed entries and record their
// locations.
for (auto entry : entries) {
PrettyPrintedDeclarations[entry.first] =
memBufferStartLoc.getAdvancedLoc(entry.second);
}

// Grab the pretty-printed location.
ppLoc = PrettyPrintedDeclarations[decl];
}

return ppLoc;
}

Optional<DiagnosticInfo>
DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
auto behavior = state.determineBehavior(diagnostic);
Expand All @@ -931,140 +1068,9 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
if (loc.isInvalid() && diagnostic.getDecl()) {
const Decl *decl = diagnostic.getDecl();
// If a declaration was provided instead of a location, and that declaration
// has a location we can point to, use that location.
loc = decl->getLoc();

if (loc.isInvalid()) {
// There is no location we can point to. Pretty-print the declaration
// so we can point to it.
SourceLoc ppLoc = PrettyPrintedDeclarations[decl];
if (ppLoc.isInvalid()) {
class TrackingPrinter : public StreamPrinter {
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries;
AccessLevel bufferAccessLevel;

public:
TrackingPrinter(
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries,
raw_ostream &OS, AccessLevel bufferAccessLevel) :
StreamPrinter(OS), Entries(Entries),
bufferAccessLevel(bufferAccessLevel) {}

void printDeclLoc(const Decl *D) override {
if (getBufferAccessLevel(D) == bufferAccessLevel)
Entries.push_back({ D, OS.tell() });
}
};
SmallVector<std::pair<const Decl *, uint64_t>, 8> entries;
llvm::SmallString<128> buffer;
llvm::SmallString<128> bufferName;
{
// The access level of the buffer we want to print. Declarations below
// this access level will be omitted from the buffer; declarations
// above it will be printed, but (except for Open declarations in the
// Public buffer) will not be recorded in PrettyPrintedDeclarations as
// the "true" SourceLoc for the declaration.
AccessLevel bufferAccessLevel = getBufferAccessLevel(decl);

// Figure out which declaration to print. It's the top-most
// declaration (not a module).
const Decl *ppDecl = decl;
auto dc = decl->getDeclContext();

// FIXME: Horrible, horrible hackaround. We're not getting a
// DeclContext everywhere we should.
if (!dc) {
return None;
}

while (!dc->isModuleContext()) {
switch (dc->getContextKind()) {
case DeclContextKind::Module:
llvm_unreachable("Not in a module context!");
break;

case DeclContextKind::FileUnit:
case DeclContextKind::TopLevelCodeDecl:
break;

case DeclContextKind::ExtensionDecl:
ppDecl = cast<ExtensionDecl>(dc);
break;

case DeclContextKind::GenericTypeDecl:
ppDecl = cast<GenericTypeDecl>(dc);
break;

case DeclContextKind::SerializedLocal:
case DeclContextKind::Initializer:
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
case DeclContextKind::EnumElementDecl:
break;
}

dc = dc->getParent();
}

// Build the module name path (in reverse), which we use to
// build the name of the buffer.
SmallVector<StringRef, 4> nameComponents;
while (dc) {
nameComponents.push_back(cast<ModuleDecl>(dc)->getName().str());
dc = dc->getParent();
}

for (unsigned i = nameComponents.size(); i; --i) {
bufferName += nameComponents[i-1];
bufferName += '.';
}

if (auto value = dyn_cast<ValueDecl>(ppDecl)) {
bufferName += value->getBaseName().userFacingName();
} else if (auto ext = dyn_cast<ExtensionDecl>(ppDecl)) {
bufferName += ext->getExtendedType().getString();
}

// If we're using a lowered access level, give the buffer a distinct
// name.
if (bufferAccessLevel != AccessLevel::Public) {
assert(bufferAccessLevel < AccessLevel::Public
&& "Above-public access levels should use public buffer");
bufferName += " (";
bufferName += getAccessLevelSpelling(bufferAccessLevel);
bufferName += ")";
}

// Pretty-print the declaration we've picked.
llvm::raw_svector_ostream out(buffer);
TrackingPrinter printer(entries, out, bufferAccessLevel);
llvm::SaveAndRestore<bool> isPrettyPrinting(
IsPrettyPrintingDecl, true);
ppDecl->print(
printer,
PrintOptions::printForDiagnostics(
bufferAccessLevel,
decl->getASTContext().TypeCheckerOpts.PrintFullConvention));
}

// Build a buffer with the pretty-printed declaration.
auto bufferID = SourceMgr.addMemBufferCopy(buffer, bufferName);
auto memBufferStartLoc = SourceMgr.getLocForBufferStart(bufferID);

// Go through all of the pretty-printed entries and record their
// locations.
for (auto entry : entries) {
PrettyPrintedDeclarations[entry.first] =
memBufferStartLoc.getAdvancedLoc(entry.second);
}

// Grab the pretty-printed location.
ppLoc = PrettyPrintedDeclarations[decl];
}

loc = ppLoc;
}
// has a location we can point to (or we can generate one), use that
// location.
loc = getDiagnosticLocForDecl(decl);
}

return DiagnosticInfo(
Expand Down
Loading