Files
swift-mirror/lib/Sema/DebuggerTestingTransform.cpp
Doug Gregor 599e07e5d9 [Type checker] Keep the type checker alive as long as the ASTContext is.
It is possible for the SIL optimizers, IRGen, etc. to request information
from the AST that only the type checker can provide, but the type checker
is typically torn down after the “type checking” phase. This can lead to
various crashes late in the compilation cycle.

Keep the type checker instance around as long as the ASTContext is alive
or until someone asks for it to be destroyed.

Fixes SR-285 / rdar://problem/23677338.
2018-10-10 16:44:42 -07:00

265 lines
9.0 KiB
C++

//===--- DebuggerTestingTransform.cpp - Transform for debugger testing ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file This transform inserts instrumentation which the debugger can use to
/// test its expression evaluation facilities.
///
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTNode.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclContext.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Module.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/Stmt.h"
#include "swift/Subsystems.h"
#include "TypeChecker.h"
using namespace swift;
namespace {
/// Find available closure discriminators.
///
/// The parser typically takes care of assigning unique discriminators to
/// closures, but the parser is unavailable to this transform.
class DiscriminatorFinder : public ASTWalker {
unsigned NextDiscriminator = 0;
public:
Expr *walkToExprPost(Expr *E) override {
auto *ACE = dyn_cast<AbstractClosureExpr>(E);
if (!ACE)
return E;
unsigned Discriminator = ACE->getDiscriminator();
assert(Discriminator != AbstractClosureExpr::InvalidDiscriminator &&
"Existing closures should have valid discriminators");
if (Discriminator >= NextDiscriminator)
NextDiscriminator = Discriminator + 1;
return E;
}
// Get the next available closure discriminator.
unsigned getNextDiscriminator() {
if (NextDiscriminator == AbstractClosureExpr::InvalidDiscriminator)
llvm::report_fatal_error("Out of valid closure discriminators");
return NextDiscriminator++;
}
};
/// Instrument decls with sanity-checks which the debugger can evaluate.
class DebuggerTestingTransform : public ASTWalker {
ASTContext &Ctx;
DiscriminatorFinder &DF;
std::vector<DeclContext *> LocalDeclContextStack;
public:
DebuggerTestingTransform(ASTContext &Ctx, DiscriminatorFinder &DF)
: Ctx(Ctx), DF(DF) {}
bool walkToDeclPre(Decl *D) override {
pushLocalDeclContext(D);
// Skip implicit decls, because the debugger isn't used to step through
// these.
if (D->isImplicit())
return false;
// Whitelist the kinds of decls to transform.
// TODO: Expand the set of decls visited here.
if (auto *FD = dyn_cast<AbstractFunctionDecl>(D))
return FD->getBody();
if (auto *TLCD = dyn_cast<TopLevelCodeDecl>(D))
return TLCD->getBody();
if (isa<NominalTypeDecl>(D))
return true;
return false;
}
bool walkToDeclPost(Decl *D) override {
popLocalDeclContext(D);
return true;
}
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
pushLocalDeclContext(E);
// Whitelist the kinds of exprs to transform.
// TODO: Expand the set of exprs visited here.
if (auto *AE = dyn_cast<AssignExpr>(E))
return insertCheckExpect(AE, AE->getDest());
return {true, E};
}
Expr *walkToExprPost(Expr *E) override {
popLocalDeclContext(E);
return E;
}
private:
/// Return N as a local DeclContext if possible, or return nullptr.
DeclContext *getLocalDeclContext(ASTNode N) const {
DeclContext *DC = N.getAsDeclContext();
return (DC && DC->isLocalContext()) ? DC : nullptr;
}
/// If N is a local DeclContext, push it onto the context stack.
void pushLocalDeclContext(ASTNode N) {
if (auto *LDC = getLocalDeclContext(N))
LocalDeclContextStack.push_back(LDC);
}
/// If N is a local DeclContext, pop it off the context stack.
void popLocalDeclContext(ASTNode N) {
if (getLocalDeclContext(N))
LocalDeclContextStack.pop_back();
}
/// Get the current local DeclContext. This is used to create closures in the
/// right context.
DeclContext *getCurrentDeclContext() const {
assert(!LocalDeclContextStack.empty() && "Missing decl context");
return LocalDeclContextStack.back();
}
/// Try to extract a DeclRefExpr or MemberRefExpr from the expression.
Expr *extractDeclOrMemberRef(Expr *E) {
while (!isa<DeclRefExpr>(E) && !isa<MemberRefExpr>(E)) {
// TODO: Try more ways to extract interesting decl refs.
if (auto *Subscript = dyn_cast<SubscriptExpr>(E))
E = Subscript->getBase();
else if (auto *InOut = dyn_cast<InOutExpr>(E))
E = InOut->getSubExpr();
else if (auto *MemberRef = dyn_cast<MemberRefExpr>(E))
E = MemberRef->getBase();
else
return nullptr;
}
return E;
}
/// Attempt to create a functionally-equivalent replacement for OriginalExpr,
/// given that DstExpr identifies the target of some mutating update, and that
/// DstExpr is a subexpression of OriginalExpr.
///
/// The return value contains 1) a flag indicating whether or not to
/// recursively transform the children of the transformed expression, and 2)
/// the transformed expression itself.
std::pair<bool, Expr *> insertCheckExpect(Expr *OriginalExpr, Expr *DstExpr) {
auto *DstRef = extractDeclOrMemberRef(DstExpr);
if (!DstRef)
return {true, OriginalExpr};
ValueDecl *DstDecl;
if (auto *DRE = dyn_cast<DeclRefExpr>(DstRef))
DstDecl = DRE->getDecl();
else {
auto *MRE = cast<MemberRefExpr>(DstRef);
DstDecl = MRE->getMember().getDecl();
}
if (!DstDecl->hasName())
return {true, OriginalExpr};
// Don't capture variables which aren't default-initialized.
if (auto *VD = dyn_cast<VarDecl>(DstDecl))
if (!VD->getParentInitializer() && !VD->isInOut())
return {true, OriginalExpr};
// Rewrite the original expression into this:
// call
// closure {
// $OriginalExpr
// checkExpect("$Varname", _stringForPrintObject($Varname))
// }
// Create "$Varname".
llvm::SmallString<256> DstNameBuf;
DeclName DstDN = DstDecl->getFullName();
StringRef DstName = Ctx.AllocateCopy(DstDN.getString(DstNameBuf));
assert(!DstName.empty() && "Varname must be non-empty");
Expr *Varname = new (Ctx) StringLiteralExpr(DstName, SourceRange());
Varname->setImplicit(true);
// Create _stringForPrintObject($Varname).
auto *PODeclRef = new (Ctx)
UnresolvedDeclRefExpr(Ctx.getIdentifier("_stringForPrintObject"),
DeclRefKind::Ordinary, DeclNameLoc());
Expr *POArgs[] = {DstRef};
Identifier POLabels[] = {Identifier()};
auto *POCall = CallExpr::createImplicit(Ctx, PODeclRef, POArgs, POLabels);
POCall->setThrows(false);
// Create the call to checkExpect.
Identifier CheckExpectLabels[] = {Identifier(), Identifier()};
Expr *CheckExpectArgs[] = {Varname, POCall};
UnresolvedDeclRefExpr *CheckExpectDRE = new (Ctx)
UnresolvedDeclRefExpr(Ctx.getIdentifier("_debuggerTestingCheckExpect"),
DeclRefKind::Ordinary, DeclNameLoc());
auto *CheckExpectExpr = CallExpr::createImplicit(
Ctx, CheckExpectDRE, CheckExpectArgs, CheckExpectLabels);
CheckExpectExpr->setThrows(false);
// Create the closure.
TypeChecker &TC = TypeChecker::createForContext(Ctx);
auto *Params = ParameterList::createEmpty(Ctx);
auto *Closure = new (Ctx)
ClosureExpr(Params, SourceLoc(), SourceLoc(), SourceLoc(), TypeLoc(),
DF.getNextDiscriminator(), getCurrentDeclContext());
Closure->setImplicit(true);
// TODO: Save and return the value of $OriginalExpr.
ASTNode ClosureElements[] = {OriginalExpr, CheckExpectExpr};
auto *ClosureBody = BraceStmt::create(Ctx, SourceLoc(), ClosureElements,
SourceLoc(), /*Implicit=*/true);
Closure->setBody(ClosureBody, /*isSingleExpression=*/false);
// Call the closure.
auto *ClosureCall = CallExpr::createImplicit(Ctx, Closure, {}, {});
ClosureCall->setThrows(false);
// TODO: typeCheckExpression() seems to assign types to everything here,
// but may not be sufficient in some cases.
Expr *FinalExpr = ClosureCall;
if (!TC.typeCheckExpression(FinalExpr, getCurrentDeclContext()))
llvm::report_fatal_error("Could not type-check instrumentation");
// Captures have to be computed after the closure is type-checked. This
// ensures that the type checker can infer <noescape> for captured values.
TC.computeCaptures(Closure);
return {false, FinalExpr};
}
};
} // end anonymous namespace
void swift::performDebuggerTestingTransform(SourceFile &SF) {
// Walk over all decls in the file to find the next available closure
// discriminator.
DiscriminatorFinder DF;
for (Decl *D : SF.Decls)
D->walk(DF);
// Instrument the decls with checkExpect() sanity-checks.
for (Decl *D : SF.Decls) {
DebuggerTestingTransform Transform{D->getASTContext(), DF};
D->walk(Transform);
swift::verify(D);
}
}