//===--- 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(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 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(D)) return FD->getBody(); if (auto *TLCD = dyn_cast(D)) return TLCD->getBody(); if (isa(D)) return true; return false; } bool walkToDeclPost(Decl *D) override { popLocalDeclContext(D); return true; } std::pair 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(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(E) && !isa(E)) { // TODO: Try more ways to extract interesting decl refs. if (auto *Subscript = dyn_cast(E)) E = Subscript->getBase(); else if (auto *InOut = dyn_cast(E)) E = InOut->getSubExpr(); else if (auto *MemberRef = dyn_cast(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 insertCheckExpect(Expr *OriginalExpr, Expr *DstExpr) { auto *DstRef = extractDeclOrMemberRef(DstExpr); if (!DstRef) return {true, OriginalExpr}; ValueDecl *DstDecl; if (auto *DRE = dyn_cast(DstRef)) DstDecl = DRE->getDecl(); else { auto *MRE = cast(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(DstDecl)) if (!VD->isParentInitialized() && !(isa(VD) && cast(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 for captured values. TypeChecker::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); } }