mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This teaches the debugger testing transform how to instrument assignments to class members.
265 lines
9.0 KiB
C++
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{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);
|
|
}
|
|
}
|