Skip to content

Commit

Permalink
[AST] Add RecoveryExpr to retain expressions on semantic errors
Browse files Browse the repository at this point in the history
Normally clang avoids creating expressions when it encounters semantic
errors, even if the parser knows which expression to produce.

This works well for the compiler. However, this is not ideal for
source-level tools that have to deal with broken code, e.g. clangd is
not able to provide navigation features even for names that compiler
knows how to resolve.

The new RecoveryExpr aims to capture the minimal set of information
useful for the tools that need to deal with incorrect code:

source range of the expression being dropped,
subexpressions of the expression.
We aim to make constructing RecoveryExprs as simple as possible to
ensure writing code to avoid dropping expressions is easy.

Producing RecoveryExprs can result in new code paths being taken in the
frontend. In particular, clang can produce some new diagnostics now and
we aim to suppress bogus ones based on Expr::containsErrors.

We deliberately produce RecoveryExprs only in the parser for now to
minimize the code affected by this patch. Producing RecoveryExprs in
Sema potentially allows to preserve more information (e.g. type of an
expression), but also results in more code being affected. E.g.
SFINAE checks will have to take presence of RecoveryExprs into account.

Initial implementation only works in C++ mode, as it relies on compiler
postponing diagnostics on dependent expressions. C and ObjC often do not
do this, so they require more work to make sure we do not produce too
many bogus diagnostics on the new expressions.

See documentation of RecoveryExpr for more details.

original patch from Ilya
This change is based on https://reviews.llvm.org/D61722

Reviewers: sammccall, rsmith

Reviewed By: sammccall, rsmith

Tags: #clang

Differential Revision: https://reviews.llvm.org/D69330
  • Loading branch information
hokein committed Mar 24, 2020
1 parent e0279d7 commit 733edf9
Show file tree
Hide file tree
Showing 30 changed files with 424 additions and 24 deletions.
2 changes: 2 additions & 0 deletions clang/include/clang/AST/ComputeDependence.h
Expand Up @@ -45,6 +45,7 @@ class ExtVectorElementExpr;
class BlockExpr;
class AsTypeExpr;
class DeclRefExpr;
class RecoveryExpr;
class CXXRewrittenBinaryOperator;
class CXXStdInitializerListExpr;
class CXXTypeidExpr;
Expand Down Expand Up @@ -122,6 +123,7 @@ ExprDependence computeDependence(ExtVectorElementExpr *E);
ExprDependence computeDependence(BlockExpr *E);
ExprDependence computeDependence(AsTypeExpr *E);
ExprDependence computeDependence(DeclRefExpr *E, const ASTContext &Ctx);
ExprDependence computeDependence(RecoveryExpr *E);
ExprDependence computeDependence(CXXRewrittenBinaryOperator *E);
ExprDependence computeDependence(CXXStdInitializerListExpr *E);
ExprDependence computeDependence(CXXTypeidExpr *E);
Expand Down
63 changes: 63 additions & 0 deletions clang/include/clang/AST/Expr.h
Expand Up @@ -5911,6 +5911,69 @@ class TypoExpr : public Expr {
}

};

/// Frontend produces RecoveryExprs on semantic errors that prevent creating
/// other well-formed expressions. E.g. when type-checking of a binary operator
/// fails, we cannot produce a BinaryOperator expression. Instead, we can choose
/// to produce a recovery expression storing left and right operands.
///
/// RecoveryExpr does not have any semantic meaning in C++, it is only useful to
/// preserve expressions in AST that would otherwise be dropped. It captures
/// subexpressions of some expression that we could not construct and source
/// range covered by the expression.
///
/// For now, RecoveryExpr is type-, value- and instantiation-dependent to take
/// advantage of existing machinery to deal with dependent code in C++, e.g.
/// RecoveryExpr is preserved in `decltype(<broken-expr>)` as part of the
/// `DependentDecltypeType`. In addition to that, clang does not report most
/// errors on dependent expressions, so we get rid of bogus errors for free.
/// However, note that unlike other dependent expressions, RecoveryExpr can be
/// produced in non-template contexts.
///
/// One can also reliably suppress all bogus errors on expressions containing
/// recovery expressions by examining results of Expr::containsErrors().
class RecoveryExpr final : public Expr,
private llvm::TrailingObjects<RecoveryExpr, Expr *> {
public:
static RecoveryExpr *Create(ASTContext &Ctx, SourceLocation BeginLoc,
SourceLocation EndLoc, ArrayRef<Expr *> SubExprs);
static RecoveryExpr *CreateEmpty(ASTContext &Ctx, unsigned NumSubExprs);

ArrayRef<Expr *> subExpressions() {
auto *B = getTrailingObjects<Expr *>();
return llvm::makeArrayRef(B, B + NumExprs);
}

ArrayRef<const Expr *> subExpressions() const {
return const_cast<RecoveryExpr *>(this)->subExpressions();
}

child_range children() {
Stmt **B = reinterpret_cast<Stmt **>(getTrailingObjects<Expr *>());
return child_range(B, B + NumExprs);
}

SourceLocation getBeginLoc() const { return BeginLoc; }
SourceLocation getEndLoc() const { return EndLoc; }

static bool classof(const Stmt *T) {
return T->getStmtClass() == RecoveryExprClass;
}

private:
RecoveryExpr(ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc,
ArrayRef<Expr *> SubExprs);
RecoveryExpr(EmptyShell Empty) : Expr(RecoveryExprClass, Empty) {}

size_t numTrailingObjects(OverloadToken<Stmt *>) const { return NumExprs; }

SourceLocation BeginLoc, EndLoc;
unsigned NumExprs;
friend TrailingObjects;
friend class ASTStmtReader;
friend class ASTStmtWriter;
};

} // end namespace clang

#endif // LLVM_CLANG_AST_EXPR_H
1 change: 1 addition & 0 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Expand Up @@ -2668,6 +2668,7 @@ DEF_TRAVERSE_STMT(CXXRewrittenBinaryOperator, {
})
DEF_TRAVERSE_STMT(OpaqueValueExpr, {})
DEF_TRAVERSE_STMT(TypoExpr, {})
DEF_TRAVERSE_STMT(RecoveryExpr, {})
DEF_TRAVERSE_STMT(CUDAKernelCallExpr, {})

// These operators (all of them) do not need any action except
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/LangOptions.def
Expand Up @@ -148,6 +148,8 @@ LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of template t

LANGOPT(DoubleSquareBracketAttributes, 1, 0, "'[[]]' attributes extension for all language standard modes")

COMPATIBLE_LANGOPT(RecoveryAST, 1, 0, "Preserve expressions in AST when encountering errors")

BENIGN_LANGOPT(ThreadsafeStatics , 1, 1, "thread-safe static initializers")
LANGOPT(POSIXThreads , 1, 0, "POSIX thread support")
LANGOPT(Blocks , 1, 0, "blocks extension to C")
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/StmtNodes.td
Expand Up @@ -195,6 +195,7 @@ def ConvertVectorExpr : StmtNode<Expr>;
def BlockExpr : StmtNode<Expr>;
def OpaqueValueExpr : StmtNode<Expr>;
def TypoExpr : StmtNode<Expr>;
def RecoveryExpr : StmtNode<Expr>;
def BuiltinBitCastExpr : StmtNode<ExplicitCastExpr>;

// Microsoft Extensions.
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Driver/CC1Options.td
Expand Up @@ -566,6 +566,11 @@ def fno_concept_satisfaction_caching : Flag<["-"],
"fno-concept-satisfaction-caching">,
HelpText<"Disable satisfaction caching for C++2a Concepts.">;

def frecovery_ast : Flag<["-"], "frecovery-ast">,
HelpText<"Preserve expressions in AST rather than dropping them when "
"encountering semantic errors">;
def fno_recovery_ast : Flag<["-"], "fno-recovery-ast">;

let Group = Action_Group in {

def Eonly : Flag<["-"], "Eonly">,
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -3885,6 +3885,10 @@ class Sema final {
void DiagnoseAmbiguousLookup(LookupResult &Result);
//@}

/// Attempts to produce a RecoveryExpr after some AST node cannot be created.
ExprResult CreateRecoveryExpr(SourceLocation Begin, SourceLocation End,
ArrayRef<Expr *> SubExprs);

ObjCInterfaceDecl *getObjCInterfaceDecl(IdentifierInfo *&Id,
SourceLocation IdLoc,
bool TypoCorrection = false);
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Expand Up @@ -1634,6 +1634,9 @@ namespace serialization {
/// An AtomicExpr record.
EXPR_ATOMIC,

/// A RecoveryExpr record.
EXPR_RECOVERY,

// Objective-C

/// An ObjCStringLiteral record.
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/AST/ComputeDependence.cpp
Expand Up @@ -456,6 +456,15 @@ ExprDependence clang::computeDependence(DeclRefExpr *E, const ASTContext &Ctx) {
return Deps;
}

ExprDependence clang::computeDependence(RecoveryExpr *E) {
// FIXME: drop type+value+instantiation once Error is sufficient to suppress
// bogus dianostics.
auto D = ExprDependence::TypeValueInstantiation | ExprDependence::Error;
for (auto *S : E->subExpressions())
D |= S->getDependence();
return D;
}

ExprDependence clang::computeDependence(PredefinedExpr *E) {
return toExprDependence(E->getType()->getDependence()) &
~ExprDependence::UnexpandedPack;
Expand Down
29 changes: 29 additions & 0 deletions clang/lib/AST/Expr.cpp
Expand Up @@ -2375,6 +2375,7 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
// If we don't know precisely what we're looking at, let's not warn.
case UnresolvedLookupExprClass:
case CXXUnresolvedConstructExprClass:
case RecoveryExprClass:
return false;

case CXXTemporaryObjectExprClass:
Expand Down Expand Up @@ -3227,6 +3228,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
case SubstNonTypeTemplateParmPackExprClass:
case FunctionParmPackExprClass:
case TypoExprClass:
case RecoveryExprClass:
case CXXFoldExprClass:
llvm_unreachable("shouldn't see dependent / unresolved nodes here");

Expand Down Expand Up @@ -4467,3 +4469,30 @@ QualType OMPArraySectionExpr::getBaseOriginalType(const Expr *Base) {
}
return OriginalTy;
}

RecoveryExpr::RecoveryExpr(ASTContext &Ctx, SourceLocation BeginLoc,
SourceLocation EndLoc, ArrayRef<Expr *> SubExprs)
: Expr(RecoveryExprClass, Ctx.DependentTy, VK_LValue, OK_Ordinary),
BeginLoc(BeginLoc), EndLoc(EndLoc), NumExprs(SubExprs.size()) {
#ifndef NDEBUG
for (auto *E : SubExprs)
assert(E != nullptr);
#endif

llvm::copy(SubExprs, getTrailingObjects<Expr *>());
setDependence(computeDependence(this));
}

RecoveryExpr *RecoveryExpr::Create(ASTContext &Ctx, SourceLocation BeginLoc,
SourceLocation EndLoc,
ArrayRef<Expr *> SubExprs) {
void *Mem = Ctx.Allocate(totalSizeToAlloc<Expr *>(SubExprs.size()),
alignof(RecoveryExpr));
return new (Mem) RecoveryExpr(Ctx, BeginLoc, EndLoc, SubExprs);
}

RecoveryExpr *RecoveryExpr::CreateEmpty(ASTContext &Ctx, unsigned NumSubExprs) {
void *Mem = Ctx.Allocate(totalSizeToAlloc<Expr *>(NumSubExprs),
alignof(RecoveryExpr));
return new (Mem) RecoveryExpr(EmptyShell());
}
1 change: 1 addition & 0 deletions clang/lib/AST/ExprClassification.cpp
Expand Up @@ -129,6 +129,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
case Expr::UnresolvedLookupExprClass:
case Expr::UnresolvedMemberExprClass:
case Expr::TypoExprClass:
case Expr::RecoveryExprClass:
case Expr::DependentCoawaitExprClass:
case Expr::CXXDependentScopeMemberExprClass:
case Expr::DependentScopeDeclRefExprClass:
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ExprConstant.cpp
Expand Up @@ -14189,6 +14189,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::CXXPseudoDestructorExprClass:
case Expr::UnresolvedLookupExprClass:
case Expr::TypoExprClass:
case Expr::RecoveryExprClass:
case Expr::DependentScopeDeclRefExprClass:
case Expr::CXXConstructExprClass:
case Expr::CXXInheritedCtorInitExprClass:
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/AST/ItaniumMangle.cpp
Expand Up @@ -3672,7 +3672,8 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity) {
case Expr::LambdaExprClass:
case Expr::MSPropertyRefExprClass:
case Expr::MSPropertySubscriptExprClass:
case Expr::TypoExprClass: // This should no longer exist in the AST by now.
case Expr::TypoExprClass: // This should no longer exist in the AST by now.
case Expr::RecoveryExprClass:
case Expr::OMPArraySectionExprClass:
case Expr::CXXInheritedCtorInitExprClass:
llvm_unreachable("unexpected statement kind");
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/AST/StmtPrinter.cpp
Expand Up @@ -2506,6 +2506,17 @@ void StmtPrinter::VisitTypoExpr(TypoExpr *Node) {
llvm_unreachable("Cannot print TypoExpr nodes");
}

void StmtPrinter::VisitRecoveryExpr(RecoveryExpr *Node) {
OS << "<recovery-expr>(";
const char *Sep = "";
for (Expr *E : Node->subExpressions()) {
OS << Sep;
PrintExpr(E);
Sep = ", ";
}
OS << ')';
}

void StmtPrinter::VisitAsTypeExpr(AsTypeExpr *Node) {
OS << "__builtin_astype(";
PrintExpr(Node->getSrcExpr());
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/StmtProfile.cpp
Expand Up @@ -2025,6 +2025,8 @@ void StmtProfiler::VisitSourceLocExpr(const SourceLocExpr *E) {
VisitExpr(E);
}

void StmtProfiler::VisitRecoveryExpr(const RecoveryExpr *E) { VisitExpr(E); }

void StmtProfiler::VisitObjCStringLiteral(const ObjCStringLiteral *S) {
VisitExpr(S);
}
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Frontend/CompilerInvocation.cpp
Expand Up @@ -2908,6 +2908,8 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK,
!Args.hasArg(OPT_fno_concept_satisfaction_caching);
if (Args.hasArg(OPT_fconcepts_ts))
Diags.Report(diag::warn_fe_concepts_ts_flag);
Opts.RecoveryAST =
Args.hasFlag(OPT_frecovery_ast, OPT_fno_recovery_ast, false);
Opts.HeinousExtensions = Args.hasArg(OPT_fheinous_gnu_extensions);
Opts.AccessControl = !Args.hasArg(OPT_fno_access_control);
Opts.ElideConstructors = !Args.hasArg(OPT_fno_elide_constructors);
Expand Down
78 changes: 60 additions & 18 deletions clang/lib/Parse/ParseExpr.cpp
Expand Up @@ -625,13 +625,31 @@ Parser::ParseRHSOfBinaryExpression(ExprResult LHS, prec::Level MinPrec) {
SourceRange(Actions.getExprRange(LHS.get()).getBegin(),
Actions.getExprRange(RHS.get()).getEnd()));

LHS = Actions.ActOnBinOp(getCurScope(), OpToken.getLocation(),
OpToken.getKind(), LHS.get(), RHS.get());

ExprResult BinOp =
Actions.ActOnBinOp(getCurScope(), OpToken.getLocation(),
OpToken.getKind(), LHS.get(), RHS.get());
if (BinOp.isInvalid())
BinOp = Actions.CreateRecoveryExpr(LHS.get()->getBeginLoc(),
RHS.get()->getEndLoc(),
{LHS.get(), RHS.get()});

LHS = BinOp;
} else {
LHS = Actions.ActOnConditionalOp(OpToken.getLocation(), ColonLoc,
LHS.get(), TernaryMiddle.get(),
RHS.get());
ExprResult CondOp = Actions.ActOnConditionalOp(
OpToken.getLocation(), ColonLoc, LHS.get(), TernaryMiddle.get(),
RHS.get());
if (CondOp.isInvalid()) {
std::vector<clang::Expr *> Args;
// TernaryMiddle can be null for the GNU conditional expr extension.
if (TernaryMiddle.get())
Args = {LHS.get(), TernaryMiddle.get(), RHS.get()};
else
Args = {LHS.get(), RHS.get()};
CondOp = Actions.CreateRecoveryExpr(LHS.get()->getBeginLoc(),
RHS.get()->getEndLoc(), Args);
}

LHS = CondOp;
}
// In this case, ActOnBinOp or ActOnConditionalOp performed the
// CorrectDelayedTyposInExpr check.
Expand Down Expand Up @@ -1305,9 +1323,14 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
UnconsumeToken(SavedTok);
return ExprError();
}
if (!Res.isInvalid())
if (!Res.isInvalid()) {
Expr *Arg = Res.get();
Res = Actions.ActOnUnaryOp(getCurScope(), SavedTok.getLocation(),
SavedKind, Res.get());
SavedKind, Arg);
if (Res.isInvalid())
Res = Actions.CreateRecoveryExpr(SavedTok.getLocation(),
Arg->getEndLoc(), Arg);
}
return Res;
}
case tok::amp: { // unary-expression: '&' cast-expression
Expand All @@ -1317,8 +1340,13 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
SourceLocation SavedLoc = ConsumeToken();
PreferredType.enterUnary(Actions, Tok.getLocation(), tok::amp, SavedLoc);
Res = ParseCastExpression(AnyCastExpr, true);
if (!Res.isInvalid())
Res = Actions.ActOnUnaryOp(getCurScope(), SavedLoc, SavedKind, Res.get());
if (!Res.isInvalid()) {
Expr *Arg = Res.get();
Res = Actions.ActOnUnaryOp(getCurScope(), SavedLoc, SavedKind, Arg);
if (Res.isInvalid())
Res = Actions.CreateRecoveryExpr(Tok.getLocation(), Arg->getEndLoc(),
Arg);
}
return Res;
}

Expand All @@ -1334,8 +1362,12 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
SourceLocation SavedLoc = ConsumeToken();
PreferredType.enterUnary(Actions, Tok.getLocation(), SavedKind, SavedLoc);
Res = ParseCastExpression(AnyCastExpr);
if (!Res.isInvalid())
Res = Actions.ActOnUnaryOp(getCurScope(), SavedLoc, SavedKind, Res.get());
if (!Res.isInvalid()) {
Expr *Arg = Res.get();
Res = Actions.ActOnUnaryOp(getCurScope(), SavedLoc, SavedKind, Arg);
if (Res.isInvalid())
Res = Actions.CreateRecoveryExpr(SavedLoc, Arg->getEndLoc(), Arg);
}
return Res;
}

Expand Down Expand Up @@ -1941,12 +1973,18 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
PT.consumeClose();
LHS = ExprError();
} else {
assert((ArgExprs.size() == 0 ||
ArgExprs.size()-1 == CommaLocs.size())&&
"Unexpected number of commas!");
LHS = Actions.ActOnCallExpr(getCurScope(), LHS.get(), Loc,
ArgExprs, Tok.getLocation(),
assert(
(ArgExprs.size() == 0 || ArgExprs.size() - 1 == CommaLocs.size()) &&
"Unexpected number of commas!");
Expr *Fn = LHS.get();
SourceLocation RParLoc = Tok.getLocation();
LHS = Actions.ActOnCallExpr(getCurScope(), Fn, Loc, ArgExprs, RParLoc,
ExecConfig);
if (LHS.isInvalid()) {
ArgExprs.insert(ArgExprs.begin(), Fn);
LHS =
Actions.CreateRecoveryExpr(Fn->getBeginLoc(), RParLoc, ArgExprs);
}
PT.consumeClose();
}

Expand Down Expand Up @@ -2069,8 +2107,12 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
case tok::plusplus: // postfix-expression: postfix-expression '++'
case tok::minusminus: // postfix-expression: postfix-expression '--'
if (!LHS.isInvalid()) {
Expr *Arg = LHS.get();
LHS = Actions.ActOnPostfixUnaryOp(getCurScope(), Tok.getLocation(),
Tok.getKind(), LHS.get());
Tok.getKind(), Arg);
if (LHS.isInvalid())
LHS = Actions.CreateRecoveryExpr(Arg->getBeginLoc(),
Tok.getLocation(), Arg);
}
ConsumeToken();
break;
Expand Down

0 comments on commit 733edf9

Please sign in to comment.