blob: 5447571caee8cc7c385ca1046a2cc681192df927 [file] [log] [blame]
//===--- ASTSlice.cpp - Represents a portion of the AST -------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ASTSlice.h"
#include "SourceLocationUtilities.h"
#include "StmtUtils.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/SaveAndRestore.h"
#include <algorithm>
using namespace clang;
using namespace clang::tooling;
namespace {
/// Searches for AST nodes around the given source location and range that can
/// be used to initiate a refactoring operation.
class ASTSliceFinder : public clang::RecursiveASTVisitor<ASTSliceFinder> {
public:
explicit ASTSliceFinder(SourceLocation Location, SourceRange SelectionRange,
const ASTContext &Context)
: Location(Location), SelectionRange(SelectionRange), Context(Context) {}
bool TraverseDecl(Decl *D) {
if (!D)
return true;
if (isa<DeclContext>(D) && !D->isImplicit())
collectDeclIfInRange(D);
// TODO: Handle Lambda/Blocks.
if (!isa<FunctionDecl>(D) && !isa<ObjCMethodDecl>(D)) {
RecursiveASTVisitor::TraverseDecl(D);
return true;
}
const Decl *PreviousDecl = CurrentDecl;
CurrentDecl = D;
RecursiveASTVisitor::TraverseDecl(D);
CurrentDecl = PreviousDecl;
return true;
}
bool TraverseStmt(Stmt *S) {
if (!S)
return true;
// PseudoObjectExpressions don't have to be parents.
if (isa<PseudoObjectExpr>(S))
return RecursiveASTVisitor::TraverseStmt(S);
llvm::SaveAndRestore<const Stmt *> Parent(ParentStmt, CurrentStmt);
llvm::SaveAndRestore<const Stmt *> Current(CurrentStmt, S);
RecursiveASTVisitor::TraverseStmt(S);
return true;
}
bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
// Avoid traversing the getter/setter message sends for property
// expressions.
TraverseStmt(E->getSyntacticForm());
return true;
}
bool TraverseObjCPropertyRefExpr(ObjCPropertyRefExpr *E) {
RecursiveASTVisitor::TraverseObjCPropertyRefExpr(E);
// Visit the opaque base manually as it won't be traversed by the
// PseudoObjectExpr.
if (E->isObjectReceiver()) {
if (const auto *Opaque = dyn_cast<OpaqueValueExpr>(E->getBase()))
TraverseStmt(Opaque->getSourceExpr());
}
return true;
}
// Statement visitors:
bool VisitStmt(Stmt *S) {
collectStmtIfInRange(S, S->getSourceRange());
return true;
}
// Ignore some implicit expressions.
bool WalkUpFromMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) {
return true;
}
bool WalkUpFromCXXThisExpr(CXXThisExpr *E) {
if (E->isImplicit())
return true;
return RecursiveASTVisitor::WalkUpFromCXXThisExpr(E);
}
/// Checks if the given statement and its source range has the location
/// of interest or overlaps with the selection range, and adds this node to
/// the set of statements for the slice that's being constructed.
void collectStmtIfInRange(const Stmt *S, SourceRange Range) {
SourceLocation Start = Range.getBegin();
const auto &SM = Context.getSourceManager();
bool IsStartMacroArg = false;
if (Start.isMacroID()) {
if (SM.isMacroArgExpansion(Start)) {
Start = SM.getSpellingLoc(Start);
IsStartMacroArg = true;
} else {
Start = SM.getExpansionLoc(Start);
}
}
SourceLocation End = Range.getEnd();
if (End.isMacroID() && SM.isMacroArgExpansion(End)) {
// Ignore the node that's span across normal code and a macro argument.
if (IsStartMacroArg)
End = SM.getSpellingLoc(End);
}
End = getPreciseTokenLocEnd(End, SM, Context.getLangOpts());
if (!isPairOfFileLocations(Start, End))
return;
if (SelectionRange.isValid()) {
if (!areRangesOverlapping(SelectionRange, SourceRange(Start, End),
Context.getSourceManager()))
return;
} else if (!isPointWithin(Location, Start, End, Context.getSourceManager()))
return;
Matches.emplace_back(S, ParentStmt, CurrentDecl, SourceRange(Start, End));
}
void collectDeclIfInRange(const Decl *D) {
SourceLocation Start = D->getSourceRange().getBegin();
SourceLocation End = getPreciseTokenLocEnd(
getLexicalEndLocForDecl(D, Context.getSourceManager(),
Context.getLangOpts()),
Context.getSourceManager(), Context.getLangOpts());
if (!isPairOfFileLocations(Start, End))
return;
if (SelectionRange.isValid()) {
if (!areRangesOverlapping(SelectionRange, SourceRange(Start, End),
Context.getSourceManager()))
return;
} else if (!isPointWithin(Location, Start, End, Context.getSourceManager()))
return;
Matches.emplace_back(D, CurrentDecl, SourceRange(Start, End));
}
SmallVector<ASTSlice::Node, 16> Matches;
/// The point of interest.
///
/// Represents a location at which refactoring should be initiated.
const SourceLocation Location;
const SourceRange SelectionRange;
const ASTContext &Context;
const Decl *CurrentDecl = nullptr;
const Stmt *ParentStmt = nullptr, *CurrentStmt = nullptr;
};
} // end anonymous namespace
ASTSlice::SelectedStmt::SelectedStmt(ASTSlice &Slice, const Stmt *S,
unsigned Index)
: Slice(Slice), S(S), Index(Index) {
assert(S && "No statement given!");
}
ASTSlice::SelectedDecl::SelectedDecl(const Decl *D) : D(D) {
assert(D && "No decl given!");
}
const Decl *ASTSlice::SelectedStmt::getParentDecl() {
return Slice.parentDeclForIndex(Index);
}
ASTSlice::ASTSlice(SourceLocation Location, SourceRange SelectionRange,
ASTContext &Context)
: Context(Context), SelectionLocation(Location),
SelectionRange(SelectionRange) {
FileID SearchFile = Context.getSourceManager().getFileID(Location);
ASTSliceFinder Visitor(Location, SelectionRange, Context);
SourceLocation EndLoc;
for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) {
if (EndLoc.isValid() &&
!Context.getSourceManager().isBeforeInTranslationUnit(
CurrDecl->getLocStart(), EndLoc))
break;
const SourceLocation FileLoc =
Context.getSourceManager().getSpellingLoc(CurrDecl->getLocStart());
if (Context.getSourceManager().getFileID(FileLoc) == SearchFile)
Visitor.TraverseDecl(CurrDecl);
// We are only interested in looking at a single top level declaration
// even if our selection range spans across multiple top level declarations.
if (!Visitor.Matches.empty()) {
// Objective-C @implementation declarations might have trailing functions
// that are declared outside of the @implementation, so continue looking
// through them.
if (isa<ObjCImplDecl>(CurrDecl)) {
EndLoc = CurrDecl->getLocEnd();
continue;
}
break;
}
}
for (auto I = Visitor.Matches.rbegin(), E = Visitor.Matches.rend(); I != E;
++I)
NodeTree.push_back(*I);
}
bool ASTSlice::isSourceRangeSelected(CharSourceRange Range) const {
SourceRange R = Range.getAsRange();
if (Range.isTokenRange())
R.setEnd(getPreciseTokenLocEnd(R.getEnd(), Context.getSourceManager(),
Context.getLangOpts()));
if (SelectionRange.isInvalid())
return isPointWithin(SelectionLocation, R.getBegin(), R.getEnd(),
Context.getSourceManager());
return areRangesOverlapping(SelectionRange, R, Context.getSourceManager());
}
/// Find the 'if' statement that acts as the start of the
/// 'if'/'else if'/'else' construct.
static std::pair<const IfStmt *, unsigned>
findIfStmtStart(const IfStmt *If, unsigned Index,
ArrayRef<ASTSlice::Node> NodeTree) {
if (Index >= NodeTree.size())
return {If, Index}; // We've reached the top of the tree, return.
const auto *ParentIf =
dyn_cast_or_null<IfStmt>(NodeTree[Index + 1].getStmtOrNull());
// The current 'if' is actually an 'else if' when the next 'if' has an else
// statement that points to the current 'if'.
if (!ParentIf || ParentIf->getElse() != If)
return {If, Index};
return findIfStmtStart(ParentIf, Index + 1, NodeTree);
}
/// Find an expression that best represents the given selected expression.
static std::pair<const Stmt *, unsigned>
canonicalizeSelectedExpr(const Stmt *S, unsigned Index,
ArrayRef<ASTSlice::Node> NodeTree) {
const auto Same = std::make_pair(S, Index);
if (Index + 1 >= NodeTree.size())
return Same;
const Stmt *Parent = NodeTree[Index + 1].getStmtOrNull();
if (!Parent)
return Same;
const auto Next = std::make_pair(Parent, Index + 1);
// The entire pseudo expression is selected when just its syntactic
// form is selected.
if (isa<Expr>(S)) {
if (const auto *POE = dyn_cast_or_null<PseudoObjectExpr>(Parent)) {
if (POE->getSyntacticForm() == S)
return Next;
}
}
// The entire ObjC string literal is selected when just its string
// literal is selected.
if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent))
return Next;
// The entire call should be selected when just the member expression
// that refers to the method is selected.
// FIXME: Check if this can be one of the call arguments.
if (isa<MemberExpr>(S) && isa<CXXMemberCallExpr>(Parent))
return Next;
// The entire call should be selected when just the callee is selected.
if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) {
if (const auto *Call = dyn_cast<CallExpr>(Parent)) {
if (Call->getCalleeDecl() == DRE->getDecl())
return Next;
}
}
return Same;
}
Optional<ASTSlice::SelectedStmt> ASTSlice::nearestSelectedStmt(
llvm::function_ref<bool(const Stmt *)> Predicate) {
for (const auto &Node : llvm::enumerate(NodeTree)) {
const Stmt *S = Node.value().getStmtOrNull();
if (!S || !Predicate(S))
continue;
// Found the match. Perform any additional adjustments.
if (isa<Expr>(S)) {
auto CanonicalExpr = canonicalizeSelectedExpr(S, Node.index(), NodeTree);
return SelectedStmt(*this, CanonicalExpr.first, CanonicalExpr.second);
}
switch (S->getStmtClass()) {
case Stmt::IfStmtClass: {
// TODO: Fix findIfStmtStart bug with Index where it will return the
// index of the last statement.
auto If = findIfStmtStart(cast<IfStmt>(S), Node.index(), NodeTree);
return SelectedStmt(*this, If.first, If.second);
}
default:
break;
}
return SelectedStmt(*this, S, Node.index());
}
return None;
}
Optional<ASTSlice::SelectedStmt>
ASTSlice::nearestSelectedStmt(Stmt::StmtClass Class) {
return nearestSelectedStmt(
[Class](const Stmt *S) -> bool { return S->getStmtClass() == Class; });
}
const Stmt *ASTSlice::nearestStmt(Stmt::StmtClass Class) {
auto Result = nearestSelectedStmt(Class);
return Result ? Result->getStmt() : nullptr;
}
Optional<ASTSlice::SelectedDecl> ASTSlice::innermostSelectedDecl(
llvm::function_ref<bool(const Decl *)> Predicate, unsigned Options) {
if (SelectionRange.isValid()) {
if (Options & ASTSlice::InnermostDeclOnly) {
auto Result = getInnermostCompletelySelectedDecl();
if (!Result)
return None;
if (Predicate(Result->getDecl()))
return Result;
return None;
}
// Traverse down through all of the selected node checking the predicate.
// TODO: Cache the SelectionRangeOverlap kinds properly instead of relying
// on getInnermostCompletelySelectedDecl.
getInnermostCompletelySelectedDecl();
for (const auto &N : NodeTree) {
const Decl *D = N.getDeclOrNull();
if (!D)
continue;
if (N.SelectionRangeOverlap != Node::ContainsSelectionRange)
continue;
if (Predicate(D))
return SelectedDecl(D);
}
return None;
}
for (const auto &Node : llvm::enumerate(NodeTree)) {
const Decl *D = Node.value().getDeclOrNull();
if (!D)
continue;
if (Predicate(D))
return SelectedDecl(D);
if (Options & ASTSlice::InnermostDeclOnly)
return None;
}
return None;
}
Optional<ASTSlice::SelectedDecl>
ASTSlice::innermostSelectedDecl(ArrayRef<Decl::Kind> Classes,
unsigned Options) {
assert(!Classes.empty() && "Expected at least one decl kind");
return innermostSelectedDecl(
[&](const Decl *D) {
for (Decl::Kind Class : Classes) {
if (D->getKind() == Class)
return true;
}
return false;
},
Options);
}
/// Compute the SelectionRangeOverlap kinds for matched AST nodes.
///
/// The overlap kinds are computed only upto the first node that contains the
/// entire selection range.
static void
computeSelectionRangeOverlapKinds(MutableArrayRef<ASTSlice::Node> NodeTree,
SourceRange SelectionRange,
const SourceManager &SM) {
for (ASTSlice::Node &Node : NodeTree) {
bool HasStart =
isPointWithin(SelectionRange.getBegin(), Node.Range.getBegin(),
Node.Range.getEnd(), SM);
bool HasEnd = isPointWithin(SelectionRange.getEnd(), Node.Range.getBegin(),
Node.Range.getEnd(), SM);
if (HasStart && HasEnd)
Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRange;
else if (HasStart)
Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRangeStart;
else if (HasEnd)
Node.SelectionRangeOverlap = ASTSlice::Node::ContainsSelectionRangeEnd;
}
}
const Stmt *findFirstStatementAfter(const CompoundStmt *CS, SourceLocation Loc,
const SourceManager &SM) {
for (const Stmt *S : CS->body()) {
if (!SM.isBeforeInTranslationUnit(S->getLocStart(), Loc))
return S;
}
return nullptr;
}
const Stmt *findLastStatementBefore(const CompoundStmt *CS, SourceLocation Loc,
const Stmt *StartAt,
const SourceManager &SM) {
auto It = std::find(CS->body_begin(), CS->body_end(), StartAt);
assert(It != CS->body_end());
const Stmt *Last = StartAt;
for (auto E = CS->body_end(); It != E; ++It) {
const Stmt *S = *It;
if (!SM.isBeforeInTranslationUnit(S->getLocStart(), Loc))
return Last;
Last = S;
}
return Last;
}
/// Return the source construct that contains the given compound statement.
///
/// This is useful to find the source construct to which the given compound
/// statement belongs to lexically. For example, if we've selected just the
/// body of an if statement, we ideally want to select the entire if statement.
static std::pair<const Stmt *, unsigned>
findCompoundStatementSourceConstruct(const CompoundStmt *CS,
ArrayRef<ASTSlice::Node> NodeTree) {
for (const auto &Node : llvm::enumerate(NodeTree)) {
const Stmt *S = Node.value().getStmtOrNull();
if (!S)
continue;
for (const Stmt *Child : S->children()) {
if (Child == CS) {
if (isa<CompoundStmt>(S))
return {CS, 0};
if (const auto *If = dyn_cast<IfStmt>(S))
return findIfStmtStart(If, Node.index(), NodeTree);
return {S, Node.index()};
}
}
}
// This is the outer compound statement.
return {CS, 0};
}
/// Return the source construct that contains the given switch case.
static std::pair<const Stmt *, unsigned>
findSwitchSourceConstruct(const SwitchCase *Case,
ArrayRef<ASTSlice::Node> NodeTree) {
for (const auto &Node : llvm::enumerate(NodeTree)) {
const Stmt *S = Node.value().getStmtOrNull();
if (!S)
continue;
if (isa<SwitchStmt>(S))
return {S, Node.index()};
}
return {Case, 0};
}
SelectedStmtSet SelectedStmtSet::createFromEntirelySelected(const Stmt *S,
unsigned Index) {
SelectedStmtSet Result;
Result.containsSelectionRange = S;
Result.containsSelectionRangeIndex = Index;
return Result;
}
Optional<ASTSlice::SelectedDecl>
ASTSlice::getInnermostCompletelySelectedDecl() {
assert(SelectionRange.isValid() && "No selection range!");
if (CachedSelectedInnermostDecl)
return *CachedSelectedInnermostDecl;
computeSelectionRangeOverlapKinds(NodeTree, SelectionRange,
Context.getSourceManager());
Optional<SelectedDecl> Result;
for (const auto &N : llvm::enumerate(NodeTree)) {
const Decl *D = N.value().getDeclOrNull();
if (!D)
continue;
if (N.value().SelectionRangeOverlap != Node::ContainsSelectionRange)
continue;
Result = SelectedDecl(D);
break;
}
CachedSelectedInnermostDecl = Result;
return Result;
}
static bool isCaseSelected(const SwitchStmt *S, SourceRange SelectionRange,
const SourceManager &SM) {
for (const SwitchCase *Case = S->getSwitchCaseList(); Case;
Case = Case->getNextSwitchCase()) {
SourceRange Range(Case->getLocStart(), Case->getColonLoc());
if (areRangesOverlapping(Range, SelectionRange, SM))
return true;
}
return false;
}
Optional<SelectedStmtSet> ASTSlice::computeSelectedStmtSet() {
if (SelectionRange.isInvalid())
return None;
computeSelectionRangeOverlapKinds(NodeTree, SelectionRange,
Context.getSourceManager());
SelectedStmtSet Result;
for (const auto &N : llvm::enumerate(NodeTree)) {
const auto *S = N.value().getStmtOrNull();
if (!S)
continue;
switch (N.value().SelectionRangeOverlap) {
case Node::ContainsSelectionRange: {
Result.containsSelectionRange = S;
Result.containsSelectionRangeIndex = N.index();
const auto *CS = dyn_cast<CompoundStmt>(Result.containsSelectionRange);
if (!CS) {
// The entire if should be selected when just the 'else if' overlaps
// with the selection range.
if (const auto *If = dyn_cast<IfStmt>(Result.containsSelectionRange)) {
auto IfConstruct = findIfStmtStart(If, N.index(), NodeTree);
return SelectedStmtSet::createFromEntirelySelected(
IfConstruct.first, IfConstruct.second);
}
// The entire switch should be selected when just a 'case' overlaps
// with the selection range.
if (const auto *Case =
dyn_cast<SwitchCase>(Result.containsSelectionRange)) {
auto Switch = findSwitchSourceConstruct(
Case, makeArrayRef(NodeTree).drop_front(N.index() + 1));
return SelectedStmtSet::createFromEntirelySelected(
Switch.first, N.index() + Switch.second);
}
auto CanonicalExpr = canonicalizeSelectedExpr(S, N.index(), NodeTree);
Result.containsSelectionRange = CanonicalExpr.first;
Result.containsSelectionRangeIndex = CanonicalExpr.second;
return Result;
}
bool IsLBraceSelected =
!Context.getSourceManager().isBeforeInTranslationUnit(
CS->getLBracLoc(), SelectionRange.getBegin());
bool IsRBraceSelected =
Context.getSourceManager().isBeforeInTranslationUnit(
CS->getRBracLoc(), SelectionRange.getEnd());
// Return the entire source construct that has the compound statement
// when one of the braces is selected, or when an actual `case` of the
// switch is selected.
auto Construct = findCompoundStatementSourceConstruct(
CS, makeArrayRef(NodeTree).drop_front(N.index() + 1));
if (Construct.first != CS &&
((IsLBraceSelected || IsRBraceSelected) ||
(isa<SwitchStmt>(Construct.first) &&
isCaseSelected(cast<SwitchStmt>(Construct.first), SelectionRange,
Context.getSourceManager()))))
return SelectedStmtSet::createFromEntirelySelected(
Construct.first, N.index() + Construct.second);
// When both braces are selected the entire compound statement is
// considered to be selected.
if (IsLBraceSelected && IsRBraceSelected)
return Result;
if (IsLBraceSelected)
Result.containsSelectionRangeStart = CS->body_front();
else if (IsRBraceSelected)
Result.containsSelectionRangeEnd = CS->body_back();
if (!Result.containsSelectionRangeStart)
Result.containsSelectionRangeStart = findFirstStatementAfter(
CS, SelectionRange.getBegin(), Context.getSourceManager());
// Return an empty set when the compound statements os empty or the
// selection range starts after the last statement or the selection range
// doesn't overlap with any actual statements.
if (!Result.containsSelectionRangeStart ||
!Context.getSourceManager().isBeforeInTranslationUnit(
Result.containsSelectionRangeStart->getLocStart(),
SelectionRange.getEnd()))
return None;
if (!Result.containsSelectionRangeEnd)
Result.containsSelectionRangeEnd = findLastStatementBefore(
CS, SelectionRange.getEnd(), Result.containsSelectionRangeStart,
Context.getSourceManager());
return Result;
}
case Node::ContainsSelectionRangeStart:
Result.containsSelectionRangeStart = S;
break;
case Node::ContainsSelectionRangeEnd:
Result.containsSelectionRangeEnd = S;
break;
case Node::UnknownOverlap:
break;
}
}
return Result;
}
Optional<SelectedStmtSet> ASTSlice::getSelectedStmtSet() {
if (CachedSelectedStmtSet)
return *CachedSelectedStmtSet;
CachedSelectedStmtSet = computeSelectedStmtSet();
return *CachedSelectedStmtSet;
}
bool ASTSlice::isContainedInCompoundStmt(unsigned Index) {
assert(Index < NodeTree.size() && "Invalid node index");
for (unsigned I = Index + 1, E = NodeTree.size(); I != E; ++I) {
const Stmt *S = NodeTree[I].getStmtOrNull();
if (!S)
continue;
if (isa<CompoundStmt>(S))
return true;
}
return false;
}
const Decl *ASTSlice::parentDeclForIndex(unsigned Index) {
return NodeTree[Index].ParentDecl;
}
const Stmt *ASTSlice::parentStmtForIndex(unsigned Index) {
return NodeTree[Index].ParentStmt;
}