llvm-project/clang/lib/Sema/JumpDiagnostics.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1016 lines
39 KiB
C++
Raw Normal View History

//===--- JumpDiagnostics.cpp - Protected scope jump analysis ------*- C++ -*-=//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the JumpScopeChecker class, which is used to diagnose
// jumps that enter a protected scope in an invalid way.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtObjC.h"
#include "clang/AST/StmtOpenMP.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Sema/SemaInternal.h"
#include "llvm/ADT/BitVector.h"
using namespace clang;
namespace {
/// JumpScopeChecker - This object is used by Sema to diagnose invalid jumps
/// into VLA and other protected scopes. For example, this rejects:
/// goto L;
/// int a[n];
/// L:
///
/// We also detect jumps out of protected scopes when it's not possible to do
/// cleanups properly. Indirect jumps and ASM jumps can't do cleanups because
/// the target is unknown. Return statements with \c [[clang::musttail]] cannot
/// handle any cleanups due to the nature of a tail call.
class JumpScopeChecker {
Sema &S;
/// Permissive - True when recovering from errors, in which case precautions
/// are taken to handle incomplete scope information.
const bool Permissive;
/// GotoScope - This is a record that we use to keep track of all of the
/// scopes that are introduced by VLAs and other things that scope jumps like
/// gotos. This scope tree has nothing to do with the source scope tree,
/// because you can have multiple VLA scopes per compound statement, and most
/// compound statements don't introduce any scopes.
struct GotoScope {
/// ParentScope - The index in ScopeMap of the parent scope. This is 0 for
/// the parent scope is the function body.
unsigned ParentScope;
/// InDiag - The note to emit if there is a jump into this scope.
unsigned InDiag;
/// OutDiag - The note to emit if there is an indirect jump out
/// of this scope. Direct jumps always clean up their current scope
/// in an orderly way.
unsigned OutDiag;
/// Loc - Location to emit the diagnostic.
SourceLocation Loc;
GotoScope(unsigned parentScope, unsigned InDiag, unsigned OutDiag,
SourceLocation L)
: ParentScope(parentScope), InDiag(InDiag), OutDiag(OutDiag), Loc(L) {}
};
SmallVector<GotoScope, 48> Scopes;
llvm::DenseMap<Stmt*, unsigned> LabelAndGotoScopes;
SmallVector<Stmt*, 16> Jumps;
SmallVector<Stmt*, 4> IndirectJumps;
SmallVector<Stmt*, 4> AsmJumps;
SmallVector<AttributedStmt *, 4> MustTailStmts;
SmallVector<LabelDecl*, 4> IndirectJumpTargets;
SmallVector<LabelDecl*, 4> AsmJumpTargets;
public:
JumpScopeChecker(Stmt *Body, Sema &S);
private:
void BuildScopeInformation(Decl *D, unsigned &ParentScope);
void BuildScopeInformation(VarDecl *D, const BlockDecl *BDecl,
unsigned &ParentScope);
void BuildScopeInformation(CompoundLiteralExpr *CLE, unsigned &ParentScope);
void BuildScopeInformation(Stmt *S, unsigned &origParentScope);
void VerifyJumps();
void VerifyIndirectOrAsmJumps(bool IsAsmGoto);
void VerifyMustTailStmts();
void NoteJumpIntoScopes(ArrayRef<unsigned> ToScopes);
void DiagnoseIndirectOrAsmJump(Stmt *IG, unsigned IGScope, LabelDecl *Target,
unsigned TargetScope);
void CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
unsigned JumpDiag, unsigned JumpDiagWarning,
unsigned JumpDiagCXX98Compat);
void CheckGotoStmt(GotoStmt *GS);
const Attr *GetMustTailAttr(AttributedStmt *AS);
unsigned GetDeepestCommonScope(unsigned A, unsigned B);
};
} // end anonymous namespace
#define CHECK_PERMISSIVE(x) (assert(Permissive || !(x)), (Permissive && (x)))
JumpScopeChecker::JumpScopeChecker(Stmt *Body, Sema &s)
: S(s), Permissive(s.hasAnyUnrecoverableErrorsInThisFunction()) {
// Add a scope entry for function scope.
Scopes.push_back(GotoScope(~0U, ~0U, ~0U, SourceLocation()));
// Build information for the top level compound statement, so that we have a
// defined scope record for every "goto" and label.
unsigned BodyParentScope = 0;
BuildScopeInformation(Body, BodyParentScope);
// Check that all jumps we saw are kosher.
VerifyJumps();
VerifyIndirectOrAsmJumps(false);
VerifyIndirectOrAsmJumps(true);
VerifyMustTailStmts();
}
/// GetDeepestCommonScope - Finds the innermost scope enclosing the
/// two scopes.
unsigned JumpScopeChecker::GetDeepestCommonScope(unsigned A, unsigned B) {
while (A != B) {
// Inner scopes are created after outer scopes and therefore have
// higher indices.
if (A < B) {
assert(Scopes[B].ParentScope < B);
B = Scopes[B].ParentScope;
} else {
assert(Scopes[A].ParentScope < A);
A = Scopes[A].ParentScope;
}
}
return A;
}
typedef std::pair<unsigned,unsigned> ScopePair;
/// GetDiagForGotoScopeDecl - If this decl induces a new goto scope, return a
/// diagnostic that should be emitted if control goes over it. If not, return 0.
static ScopePair GetDiagForGotoScopeDecl(Sema &S, const Decl *D) {
if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
unsigned InDiag = 0;
unsigned OutDiag = 0;
if (VD->getType()->isVariablyModifiedType())
InDiag = diag::note_protected_by_vla;
if (VD->hasAttr<BlocksAttr>())
return ScopePair(diag::note_protected_by___block,
diag::note_exits___block);
if (VD->hasAttr<CleanupAttr>())
return ScopePair(diag::note_protected_by_cleanup,
diag::note_exits_cleanup);
if (VD->hasLocalStorage()) {
switch (VD->getType().isDestructedType()) {
case QualType::DK_objc_strong_lifetime:
return ScopePair(diag::note_protected_by_objc_strong_init,
diag::note_exits_objc_strong);
case QualType::DK_objc_weak_lifetime:
return ScopePair(diag::note_protected_by_objc_weak_init,
diag::note_exits_objc_weak);
case QualType::DK_nontrivial_c_struct:
return ScopePair(diag::note_protected_by_non_trivial_c_struct_init,
diag::note_exits_dtor);
case QualType::DK_cxx_destructor:
OutDiag = diag::note_exits_dtor;
break;
case QualType::DK_none:
break;
}
}
const Expr *Init = VD->getInit();
if (S.Context.getLangOpts().CPlusPlus && VD->hasLocalStorage() && Init) {
// C++11 [stmt.dcl]p3:
// A program that jumps from a point where a variable with automatic
// storage duration is not in scope to a point where it is in scope
// is ill-formed unless the variable has scalar type, class type with
// a trivial default constructor and a trivial destructor, a
// cv-qualified version of one of these types, or an array of one of
// the preceding types and is declared without an initializer.
// C++03 [stmt.dcl.p3:
// A program that jumps from a point where a local variable
// with automatic storage duration is not in scope to a point
// where it is in scope is ill-formed unless the variable has
// POD type and is declared without an initializer.
InDiag = diag::note_protected_by_variable_init;
// For a variable of (array of) class type declared without an
// initializer, we will have call-style initialization and the initializer
// will be the CXXConstructExpr with no intervening nodes.
if (const CXXConstructExpr *CCE = dyn_cast<CXXConstructExpr>(Init)) {
const CXXConstructorDecl *Ctor = CCE->getConstructor();
if (Ctor->isTrivial() && Ctor->isDefaultConstructor() &&
VD->getInitStyle() == VarDecl::CallInit) {
if (OutDiag)
InDiag = diag::note_protected_by_variable_nontriv_destructor;
else if (!Ctor->getParent()->isPOD())
InDiag = diag::note_protected_by_variable_non_pod;
else
InDiag = 0;
}
}
}
return ScopePair(InDiag, OutDiag);
}
if (const TypedefNameDecl *TD = dyn_cast<TypedefNameDecl>(D)) {
if (TD->getUnderlyingType()->isVariablyModifiedType())
return ScopePair(isa<TypedefDecl>(TD)
? diag::note_protected_by_vla_typedef
: diag::note_protected_by_vla_type_alias,
0);
}
return ScopePair(0U, 0U);
}
/// Build scope information for a declaration that is part of a DeclStmt.
void JumpScopeChecker::BuildScopeInformation(Decl *D, unsigned &ParentScope) {
// If this decl causes a new scope, push and switch to it.
std::pair<unsigned,unsigned> Diags = GetDiagForGotoScopeDecl(S, D);
if (Diags.first || Diags.second) {
Scopes.push_back(GotoScope(ParentScope, Diags.first, Diags.second,
D->getLocation()));
ParentScope = Scopes.size()-1;
}
// If the decl has an initializer, walk it with the potentially new
// scope we just installed.
if (VarDecl *VD = dyn_cast<VarDecl>(D))
if (Expr *Init = VD->getInit())
BuildScopeInformation(Init, ParentScope);
}
/// Build scope information for a captured block literal variables.
void JumpScopeChecker::BuildScopeInformation(VarDecl *D,
const BlockDecl *BDecl,
unsigned &ParentScope) {
// exclude captured __block variables; there's no destructor
// associated with the block literal for them.
if (D->hasAttr<BlocksAttr>())
return;
QualType T = D->getType();
QualType::DestructionKind destructKind = T.isDestructedType();
if (destructKind != QualType::DK_none) {
std::pair<unsigned,unsigned> Diags;
switch (destructKind) {
case QualType::DK_cxx_destructor:
Diags = ScopePair(diag::note_enters_block_captures_cxx_obj,
diag::note_exits_block_captures_cxx_obj);
break;
case QualType::DK_objc_strong_lifetime:
Diags = ScopePair(diag::note_enters_block_captures_strong,
diag::note_exits_block_captures_strong);
break;
case QualType::DK_objc_weak_lifetime:
Diags = ScopePair(diag::note_enters_block_captures_weak,
diag::note_exits_block_captures_weak);
break;
case QualType::DK_nontrivial_c_struct:
Diags = ScopePair(diag::note_enters_block_captures_non_trivial_c_struct,
diag::note_exits_block_captures_non_trivial_c_struct);
break;
case QualType::DK_none:
llvm_unreachable("non-lifetime captured variable");
}
SourceLocation Loc = D->getLocation();
if (Loc.isInvalid())
Loc = BDecl->getLocation();
Scopes.push_back(GotoScope(ParentScope,
Diags.first, Diags.second, Loc));
ParentScope = Scopes.size()-1;
}
}
/// Build scope information for compound literals of C struct types that are
/// non-trivial to destruct.
void JumpScopeChecker::BuildScopeInformation(CompoundLiteralExpr *CLE,
unsigned &ParentScope) {
unsigned InDiag = diag::note_enters_compound_literal_scope;
unsigned OutDiag = diag::note_exits_compound_literal_scope;
Scopes.push_back(GotoScope(ParentScope, InDiag, OutDiag, CLE->getExprLoc()));
ParentScope = Scopes.size() - 1;
}
/// BuildScopeInformation - The statements from CI to CE are known to form a
/// coherent VLA scope with a specified parent node. Walk through the
/// statements, adding any labels or gotos to LabelAndGotoScopes and recursively
/// walking the AST as needed.
void JumpScopeChecker::BuildScopeInformation(Stmt *S,
unsigned &origParentScope) {
// If this is a statement, rather than an expression, scopes within it don't
// propagate out into the enclosing scope. Otherwise we have to worry
// about block literals, which have the lifetime of their enclosing statement.
unsigned independentParentScope = origParentScope;
unsigned &ParentScope = ((isa<Expr>(S) && !isa<StmtExpr>(S))
? origParentScope : independentParentScope);
unsigned StmtsToSkip = 0u;
// If we found a label, remember that it is in ParentScope scope.
switch (S->getStmtClass()) {
case Stmt::AddrLabelExprClass:
IndirectJumpTargets.push_back(cast<AddrLabelExpr>(S)->getLabel());
break;
case Stmt::ObjCForCollectionStmtClass: {
auto *CS = cast<ObjCForCollectionStmt>(S);
unsigned Diag = diag::note_protected_by_objc_fast_enumeration;
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope, Diag, 0, S->getBeginLoc()));
BuildScopeInformation(CS->getBody(), NewParentScope);
return;
}
case Stmt::IndirectGotoStmtClass:
// "goto *&&lbl;" is a special case which we treat as equivalent
// to a normal goto. In addition, we don't calculate scope in the
// operand (to avoid recording the address-of-label use), which
// works only because of the restricted set of expressions which
// we detect as constant targets.
if (cast<IndirectGotoStmt>(S)->getConstantTarget()) {
LabelAndGotoScopes[S] = ParentScope;
Jumps.push_back(S);
return;
}
LabelAndGotoScopes[S] = ParentScope;
IndirectJumps.push_back(S);
break;
case Stmt::SwitchStmtClass:
// Evaluate the C++17 init stmt and condition variable
// before entering the scope of the switch statement.
if (Stmt *Init = cast<SwitchStmt>(S)->getInit()) {
BuildScopeInformation(Init, ParentScope);
++StmtsToSkip;
}
if (VarDecl *Var = cast<SwitchStmt>(S)->getConditionVariable()) {
BuildScopeInformation(Var, ParentScope);
++StmtsToSkip;
}
LLVM_FALLTHROUGH;
case Stmt::GotoStmtClass:
// Remember both what scope a goto is in as well as the fact that we have
// it. This makes the second scan not have to walk the AST again.
LabelAndGotoScopes[S] = ParentScope;
Jumps.push_back(S);
break;
case Stmt::GCCAsmStmtClass:
if (auto *GS = dyn_cast<GCCAsmStmt>(S))
if (GS->isAsmGoto()) {
// Remember both what scope a goto is in as well as the fact that we
// have it. This makes the second scan not have to walk the AST again.
LabelAndGotoScopes[S] = ParentScope;
AsmJumps.push_back(GS);
for (auto *E : GS->labels())
AsmJumpTargets.push_back(E->getLabel());
}
break;
case Stmt::IfStmtClass: {
IfStmt *IS = cast<IfStmt>(S);
if (!(IS->isConstexpr() || IS->isObjCAvailabilityCheck()))
break;
unsigned Diag = IS->isConstexpr() ? diag::note_protected_by_constexpr_if
: diag::note_protected_by_if_available;
if (VarDecl *Var = IS->getConditionVariable())
BuildScopeInformation(Var, ParentScope);
// Cannot jump into the middle of the condition.
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getBeginLoc()));
BuildScopeInformation(IS->getCond(), NewParentScope);
// Jumps into either arm of an 'if constexpr' are not allowed.
NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getBeginLoc()));
BuildScopeInformation(IS->getThen(), NewParentScope);
if (Stmt *Else = IS->getElse()) {
NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getBeginLoc()));
BuildScopeInformation(Else, NewParentScope);
}
return;
}
case Stmt::CXXTryStmtClass: {
CXXTryStmt *TS = cast<CXXTryStmt>(S);
{
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_cxx_try,
diag::note_exits_cxx_try,
TS->getSourceRange().getBegin()));
if (Stmt *TryBlock = TS->getTryBlock())
BuildScopeInformation(TryBlock, NewParentScope);
}
// Jump from the catch into the try is not allowed either.
for (unsigned I = 0, E = TS->getNumHandlers(); I != E; ++I) {
CXXCatchStmt *CS = TS->getHandler(I);
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_cxx_catch,
diag::note_exits_cxx_catch,
CS->getSourceRange().getBegin()));
BuildScopeInformation(CS->getHandlerBlock(), NewParentScope);
}
return;
}
case Stmt::SEHTryStmtClass: {
SEHTryStmt *TS = cast<SEHTryStmt>(S);
{
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_seh_try,
diag::note_exits_seh_try,
TS->getSourceRange().getBegin()));
if (Stmt *TryBlock = TS->getTryBlock())
BuildScopeInformation(TryBlock, NewParentScope);
}
// Jump from __except or __finally into the __try are not allowed either.
if (SEHExceptStmt *Except = TS->getExceptHandler()) {
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_seh_except,
diag::note_exits_seh_except,
Except->getSourceRange().getBegin()));
BuildScopeInformation(Except->getBlock(), NewParentScope);
} else if (SEHFinallyStmt *Finally = TS->getFinallyHandler()) {
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_seh_finally,
diag::note_exits_seh_finally,
Finally->getSourceRange().getBegin()));
BuildScopeInformation(Finally->getBlock(), NewParentScope);
}
return;
}
case Stmt::DeclStmtClass: {
// If this is a declstmt with a VLA definition, it defines a scope from here
// to the end of the containing context.
DeclStmt *DS = cast<DeclStmt>(S);
// The decl statement creates a scope if any of the decls in it are VLAs
// or have the cleanup attribute.
for (auto *I : DS->decls())
BuildScopeInformation(I, origParentScope);
return;
}
case Stmt::ObjCAtTryStmtClass: {
// Disallow jumps into any part of an @try statement by pushing a scope and
// walking all sub-stmts in that scope.
ObjCAtTryStmt *AT = cast<ObjCAtTryStmt>(S);
// Recursively walk the AST for the @try part.
{
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_objc_try,
diag::note_exits_objc_try,
AT->getAtTryLoc()));
if (Stmt *TryPart = AT->getTryBody())
BuildScopeInformation(TryPart, NewParentScope);
}
// Jump from the catch to the finally or try is not valid.
for (unsigned I = 0, N = AT->getNumCatchStmts(); I != N; ++I) {
ObjCAtCatchStmt *AC = AT->getCatchStmt(I);
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_objc_catch,
diag::note_exits_objc_catch,
AC->getAtCatchLoc()));
// @catches are nested and it isn't
BuildScopeInformation(AC->getCatchBody(), NewParentScope);
}
// Jump from the finally to the try or catch is not valid.
if (ObjCAtFinallyStmt *AF = AT->getFinallyStmt()) {
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_objc_finally,
diag::note_exits_objc_finally,
AF->getAtFinallyLoc()));
BuildScopeInformation(AF, NewParentScope);
}
return;
}
Initial support for Win64 SEH IR emission The lowering looks a lot like normal EH lowering, with the exception that the exceptions are caught by executing filter expression code instead of matching typeinfo globals. The filter expressions are outlined into functions which are used in landingpad clauses where typeinfo would normally go. Major aspects that still need work: - Non-call exceptions in __try bodies won't work yet. The plan is to outline the __try block in the frontend to keep things simple. - Filter expressions cannot use local variables until capturing is implemented. - __finally blocks will not run after exceptions. Fixing this requires work in the LLVM SEH preparation pass. The IR lowering looks like this: // C code: bool safe_div(int n, int d, int *r) { __try { *r = normal_div(n, d); } __except(_exception_code() == EXCEPTION_INT_DIVIDE_BY_ZERO) { return false; } return true; } ; LLVM IR: define i32 @filter(i8* %e, i8* %fp) { %ehptrs = bitcast i8* %e to i32** %ehrec = load i32** %ehptrs %code = load i32* %ehrec %matches = icmp eq i32 %code, i32 u0xC0000094 %matches.i32 = zext i1 %matches to i32 ret i32 %matches.i32 } define i1 zeroext @safe_div(i32 %n, i32 %d, i32* %r) { %rr = invoke i32 @normal_div(i32 %n, i32 %d) to label %normal unwind to label %lpad normal: store i32 %rr, i32* %r ret i1 1 lpad: %ehvals = landingpad {i8*, i32} personality i32 (...)* @__C_specific_handler catch i8* bitcast (i32 (i8*, i8*)* @filter to i8*) %ehptr = extractvalue {i8*, i32} %ehvals, i32 0 %sel = extractvalue {i8*, i32} %ehvals, i32 1 %filter_sel = call i32 @llvm.eh.seh.typeid.for(i8* bitcast (i32 (i8*, i8*)* @filter to i8*)) %matches = icmp eq i32 %sel, %filter_sel br i1 %matches, label %eh.except, label %eh.resume eh.except: ret i1 false eh.resume: resume } Reviewers: rjmccall, rsmith, majnemer Differential Revision: http://reviews.llvm.org/D5607 llvm-svn: 226760
2015-01-22 09:36:17 +08:00
case Stmt::ObjCAtSynchronizedStmtClass: {
// Disallow jumps into the protected statement of an @synchronized, but
// allow jumps into the object expression it protects.
ObjCAtSynchronizedStmt *AS = cast<ObjCAtSynchronizedStmt>(S);
// Recursively walk the AST for the @synchronized object expr, it is
// evaluated in the normal scope.
BuildScopeInformation(AS->getSynchExpr(), ParentScope);
// Recursively walk the AST for the @synchronized part, protected by a new
// scope.
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_objc_synchronized,
diag::note_exits_objc_synchronized,
AS->getAtSynchronizedLoc()));
BuildScopeInformation(AS->getSynchBody(), NewParentScope);
return;
}
case Stmt::ObjCAutoreleasePoolStmtClass: {
// Disallow jumps into the protected statement of an @autoreleasepool.
ObjCAutoreleasePoolStmt *AS = cast<ObjCAutoreleasePoolStmt>(S);
// Recursively walk the AST for the @autoreleasepool part, protected by a
// new scope.
unsigned NewParentScope = Scopes.size();
Scopes.push_back(GotoScope(ParentScope,
diag::note_protected_by_objc_autoreleasepool,
diag::note_exits_objc_autoreleasepool,
AS->getAtLoc()));
BuildScopeInformation(AS->getSubStmt(), NewParentScope);
return;
}
case Stmt::ExprWithCleanupsClass: {
// Disallow jumps past full-expressions that use blocks with
// non-trivial cleanups of their captures. This is theoretically
// implementable but a lot of work which we haven't felt up to doing.
ExprWithCleanups *EWC = cast<ExprWithCleanups>(S);
for (unsigned i = 0, e = EWC->getNumObjects(); i != e; ++i) {
if (auto *BDecl = EWC->getObject(i).dyn_cast<BlockDecl *>())
for (const auto &CI : BDecl->captures()) {
VarDecl *variable = CI.getVariable();
BuildScopeInformation(variable, BDecl, origParentScope);
}
else if (auto *CLE = EWC->getObject(i).dyn_cast<CompoundLiteralExpr *>())
BuildScopeInformation(CLE, origParentScope);
else
llvm_unreachable("unexpected cleanup object type");
}
break;
}
case Stmt::MaterializeTemporaryExprClass: {
// Disallow jumps out of scopes containing temporaries lifetime-extended to
// automatic storage duration.
MaterializeTemporaryExpr *MTE = cast<MaterializeTemporaryExpr>(S);
if (MTE->getStorageDuration() == SD_Automatic) {
SmallVector<const Expr *, 4> CommaLHS;
SmallVector<SubobjectAdjustment, 4> Adjustments;
const Expr *ExtendedObject =
MTE->getSubExpr()->skipRValueSubobjectAdjustments(CommaLHS,
Adjustments);
if (ExtendedObject->getType().isDestructedType()) {
Scopes.push_back(GotoScope(ParentScope, 0,
diag::note_exits_temporary_dtor,
ExtendedObject->getExprLoc()));
origParentScope = Scopes.size()-1;
}
}
break;
}
case Stmt::CaseStmtClass:
case Stmt::DefaultStmtClass:
case Stmt::LabelStmtClass:
LabelAndGotoScopes[S] = ParentScope;
break;
case Stmt::AttributedStmtClass: {
AttributedStmt *AS = cast<AttributedStmt>(S);
if (GetMustTailAttr(AS)) {
LabelAndGotoScopes[AS] = ParentScope;
MustTailStmts.push_back(AS);
}
break;
}
default:
if (auto *ED = dyn_cast<OMPExecutableDirective>(S)) {
if (!ED->isStandaloneDirective()) {
unsigned NewParentScope = Scopes.size();
Scopes.emplace_back(ParentScope,
diag::note_omp_protected_structured_block,
diag::note_omp_exits_structured_block,
ED->getStructuredBlock()->getBeginLoc());
BuildScopeInformation(ED->getStructuredBlock(), NewParentScope);
return;
}
}
break;
}
for (Stmt *SubStmt : S->children()) {
if (!SubStmt)
continue;
if (StmtsToSkip) {
--StmtsToSkip;
continue;
}
// Cases, labels, and defaults aren't "scope parents". It's also
// important to handle these iteratively instead of recursively in
// order to avoid blowing out the stack.
while (true) {
Stmt *Next;
if (SwitchCase *SC = dyn_cast<SwitchCase>(SubStmt))
Next = SC->getSubStmt();
else if (LabelStmt *LS = dyn_cast<LabelStmt>(SubStmt))
Next = LS->getSubStmt();
else
break;
LabelAndGotoScopes[SubStmt] = ParentScope;
SubStmt = Next;
}
// Recursively walk the AST.
BuildScopeInformation(SubStmt, ParentScope);
}
}
/// VerifyJumps - Verify each element of the Jumps array to see if they are
/// valid, emitting diagnostics if not.
void JumpScopeChecker::VerifyJumps() {
while (!Jumps.empty()) {
Stmt *Jump = Jumps.pop_back_val();
// With a goto,
if (GotoStmt *GS = dyn_cast<GotoStmt>(Jump)) {
// The label may not have a statement if it's coming from inline MS ASM.
if (GS->getLabel()->getStmt()) {
CheckJump(GS, GS->getLabel()->getStmt(), GS->getGotoLoc(),
diag::err_goto_into_protected_scope,
diag::ext_goto_into_protected_scope,
diag::warn_cxx98_compat_goto_into_protected_scope);
}
CheckGotoStmt(GS);
continue;
}
// We only get indirect gotos here when they have a constant target.
if (IndirectGotoStmt *IGS = dyn_cast<IndirectGotoStmt>(Jump)) {
LabelDecl *Target = IGS->getConstantTarget();
CheckJump(IGS, Target->getStmt(), IGS->getGotoLoc(),
diag::err_goto_into_protected_scope,
diag::ext_goto_into_protected_scope,
diag::warn_cxx98_compat_goto_into_protected_scope);
continue;
}
SwitchStmt *SS = cast<SwitchStmt>(Jump);
for (SwitchCase *SC = SS->getSwitchCaseList(); SC;
SC = SC->getNextSwitchCase()) {
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(SC)))
continue;
SourceLocation Loc;
if (CaseStmt *CS = dyn_cast<CaseStmt>(SC))
Loc = CS->getBeginLoc();
else if (DefaultStmt *DS = dyn_cast<DefaultStmt>(SC))
Loc = DS->getBeginLoc();
else
Loc = SC->getBeginLoc();
CheckJump(SS, SC, Loc, diag::err_switch_into_protected_scope, 0,
diag::warn_cxx98_compat_switch_into_protected_scope);
}
}
}
/// VerifyIndirectOrAsmJumps - Verify whether any possible indirect goto or
/// asm goto jump might cross a protection boundary. Unlike direct jumps,
/// indirect or asm goto jumps count cleanups as protection boundaries:
/// since there's no way to know where the jump is going, we can't implicitly
/// run the right cleanups the way we can with direct jumps.
/// Thus, an indirect/asm jump is "trivial" if it bypasses no
/// initializations and no teardowns. More formally, an indirect/asm jump
/// from A to B is trivial if the path out from A to DCA(A,B) is
/// trivial and the path in from DCA(A,B) to B is trivial, where
/// DCA(A,B) is the deepest common ancestor of A and B.
/// Jump-triviality is transitive but asymmetric.
///
/// A path in is trivial if none of the entered scopes have an InDiag.
/// A path out is trivial is none of the exited scopes have an OutDiag.
///
/// Under these definitions, this function checks that the indirect
/// jump between A and B is trivial for every indirect goto statement A
/// and every label B whose address was taken in the function.
void JumpScopeChecker::VerifyIndirectOrAsmJumps(bool IsAsmGoto) {
SmallVector<Stmt*, 4> GotoJumps = IsAsmGoto ? AsmJumps : IndirectJumps;
if (GotoJumps.empty())
return;
SmallVector<LabelDecl *, 4> JumpTargets =
IsAsmGoto ? AsmJumpTargets : IndirectJumpTargets;
// If there aren't any address-of-label expressions in this function,
// complain about the first indirect goto.
if (JumpTargets.empty()) {
assert(!IsAsmGoto &&"only indirect goto can get here");
S.Diag(GotoJumps[0]->getBeginLoc(),
diag::err_indirect_goto_without_addrlabel);
return;
}
// Collect a single representative of every scope containing an
// indirect or asm goto. For most code bases, this substantially cuts
// down on the number of jump sites we'll have to consider later.
typedef std::pair<unsigned, Stmt*> JumpScope;
SmallVector<JumpScope, 32> JumpScopes;
{
llvm::DenseMap<unsigned, Stmt*> JumpScopesMap;
for (SmallVectorImpl<Stmt *>::iterator I = GotoJumps.begin(),
E = GotoJumps.end();
I != E; ++I) {
Stmt *IG = *I;
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(IG)))
continue;
unsigned IGScope = LabelAndGotoScopes[IG];
Stmt *&Entry = JumpScopesMap[IGScope];
if (!Entry) Entry = IG;
}
JumpScopes.reserve(JumpScopesMap.size());
for (llvm::DenseMap<unsigned, Stmt *>::iterator I = JumpScopesMap.begin(),
E = JumpScopesMap.end();
I != E; ++I)
JumpScopes.push_back(*I);
}
// Collect a single representative of every scope containing a
// label whose address was taken somewhere in the function.
// For most code bases, there will be only one such scope.
llvm::DenseMap<unsigned, LabelDecl*> TargetScopes;
for (SmallVectorImpl<LabelDecl *>::iterator I = JumpTargets.begin(),
E = JumpTargets.end();
I != E; ++I) {
LabelDecl *TheLabel = *I;
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(TheLabel->getStmt())))
continue;
unsigned LabelScope = LabelAndGotoScopes[TheLabel->getStmt()];
LabelDecl *&Target = TargetScopes[LabelScope];
if (!Target) Target = TheLabel;
}
// For each target scope, make sure it's trivially reachable from
// every scope containing a jump site.
//
// A path between scopes always consists of exitting zero or more
// scopes, then entering zero or more scopes. We build a set of
// of scopes S from which the target scope can be trivially
// entered, then verify that every jump scope can be trivially
// exitted to reach a scope in S.
llvm::BitVector Reachable(Scopes.size(), false);
for (llvm::DenseMap<unsigned,LabelDecl*>::iterator
TI = TargetScopes.begin(), TE = TargetScopes.end(); TI != TE; ++TI) {
unsigned TargetScope = TI->first;
LabelDecl *TargetLabel = TI->second;
Reachable.reset();
// Mark all the enclosing scopes from which you can safely jump
// into the target scope. 'Min' will end up being the index of
// the shallowest such scope.
unsigned Min = TargetScope;
while (true) {
Reachable.set(Min);
// Don't go beyond the outermost scope.
if (Min == 0) break;
// Stop if we can't trivially enter the current scope.
if (Scopes[Min].InDiag) break;
Min = Scopes[Min].ParentScope;
}
// Walk through all the jump sites, checking that they can trivially
// reach this label scope.
for (SmallVectorImpl<JumpScope>::iterator
I = JumpScopes.begin(), E = JumpScopes.end(); I != E; ++I) {
unsigned Scope = I->first;
// Walk out the "scope chain" for this scope, looking for a scope
// we've marked reachable. For well-formed code this amortizes
// to O(JumpScopes.size() / Scopes.size()): we only iterate
// when we see something unmarked, and in well-formed code we
// mark everything we iterate past.
bool IsReachable = false;
while (true) {
if (Reachable.test(Scope)) {
// If we find something reachable, mark all the scopes we just
// walked through as reachable.
for (unsigned S = I->first; S != Scope; S = Scopes[S].ParentScope)
Reachable.set(S);
IsReachable = true;
break;
}
// Don't walk out if we've reached the top-level scope or we've
// gotten shallower than the shallowest reachable scope.
if (Scope == 0 || Scope < Min) break;
// Don't walk out through an out-diagnostic.
if (Scopes[Scope].OutDiag) break;
Scope = Scopes[Scope].ParentScope;
}
// Only diagnose if we didn't find something.
if (IsReachable) continue;
DiagnoseIndirectOrAsmJump(I->second, I->first, TargetLabel, TargetScope);
}
}
}
/// Return true if a particular error+note combination must be downgraded to a
/// warning in Microsoft mode.
static bool IsMicrosoftJumpWarning(unsigned JumpDiag, unsigned InDiagNote) {
return (JumpDiag == diag::err_goto_into_protected_scope &&
(InDiagNote == diag::note_protected_by_variable_init ||
InDiagNote == diag::note_protected_by_variable_nontriv_destructor));
}
/// Return true if a particular note should be downgraded to a compatibility
/// warning in C++11 mode.
static bool IsCXX98CompatWarning(Sema &S, unsigned InDiagNote) {
return S.getLangOpts().CPlusPlus11 &&
InDiagNote == diag::note_protected_by_variable_non_pod;
}
/// Produce primary diagnostic for an indirect jump statement.
static void DiagnoseIndirectOrAsmJumpStmt(Sema &S, Stmt *Jump,
LabelDecl *Target, bool &Diagnosed) {
if (Diagnosed)
return;
bool IsAsmGoto = isa<GCCAsmStmt>(Jump);
S.Diag(Jump->getBeginLoc(), diag::err_indirect_goto_in_protected_scope)
<< IsAsmGoto;
S.Diag(Target->getStmt()->getIdentLoc(), diag::note_indirect_goto_target)
<< IsAsmGoto;
Diagnosed = true;
}
/// Produce note diagnostics for a jump into a protected scope.
void JumpScopeChecker::NoteJumpIntoScopes(ArrayRef<unsigned> ToScopes) {
if (CHECK_PERMISSIVE(ToScopes.empty()))
return;
for (unsigned I = 0, E = ToScopes.size(); I != E; ++I)
if (Scopes[ToScopes[I]].InDiag)
S.Diag(Scopes[ToScopes[I]].Loc, Scopes[ToScopes[I]].InDiag);
}
/// Diagnose an indirect jump which is known to cross scopes.
void JumpScopeChecker::DiagnoseIndirectOrAsmJump(Stmt *Jump, unsigned JumpScope,
LabelDecl *Target,
unsigned TargetScope) {
if (CHECK_PERMISSIVE(JumpScope == TargetScope))
return;
unsigned Common = GetDeepestCommonScope(JumpScope, TargetScope);
bool Diagnosed = false;
// Walk out the scope chain until we reach the common ancestor.
for (unsigned I = JumpScope; I != Common; I = Scopes[I].ParentScope)
if (Scopes[I].OutDiag) {
DiagnoseIndirectOrAsmJumpStmt(S, Jump, Target, Diagnosed);
S.Diag(Scopes[I].Loc, Scopes[I].OutDiag);
}
SmallVector<unsigned, 10> ToScopesCXX98Compat;
// Now walk into the scopes containing the label whose address was taken.
for (unsigned I = TargetScope; I != Common; I = Scopes[I].ParentScope)
if (IsCXX98CompatWarning(S, Scopes[I].InDiag))
ToScopesCXX98Compat.push_back(I);
else if (Scopes[I].InDiag) {
DiagnoseIndirectOrAsmJumpStmt(S, Jump, Target, Diagnosed);
S.Diag(Scopes[I].Loc, Scopes[I].InDiag);
}
// Diagnose this jump if it would be ill-formed in C++98.
if (!Diagnosed && !ToScopesCXX98Compat.empty()) {
bool IsAsmGoto = isa<GCCAsmStmt>(Jump);
S.Diag(Jump->getBeginLoc(),
diag::warn_cxx98_compat_indirect_goto_in_protected_scope)
<< IsAsmGoto;
S.Diag(Target->getStmt()->getIdentLoc(), diag::note_indirect_goto_target)
<< IsAsmGoto;
NoteJumpIntoScopes(ToScopesCXX98Compat);
}
}
/// CheckJump - Validate that the specified jump statement is valid: that it is
/// jumping within or out of its current scope, not into a deeper one.
void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
unsigned JumpDiagError, unsigned JumpDiagWarning,
unsigned JumpDiagCXX98Compat) {
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(From)))
return;
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(To)))
return;
unsigned FromScope = LabelAndGotoScopes[From];
unsigned ToScope = LabelAndGotoScopes[To];
// Common case: exactly the same scope, which is fine.
if (FromScope == ToScope) return;
// Warn on gotos out of __finally blocks.
if (isa<GotoStmt>(From) || isa<IndirectGotoStmt>(From)) {
// If FromScope > ToScope, FromScope is more nested and the jump goes to a
// less nested scope. Check if it crosses a __finally along the way.
for (unsigned I = FromScope; I > ToScope; I = Scopes[I].ParentScope) {
if (Scopes[I].InDiag == diag::note_protected_by_seh_finally) {
S.Diag(From->getBeginLoc(), diag::warn_jump_out_of_seh_finally);
break;
}
if (Scopes[I].InDiag == diag::note_omp_protected_structured_block) {
S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope);
S.Diag(To->getBeginLoc(), diag::note_omp_exits_structured_block);
break;
}
}
}
unsigned CommonScope = GetDeepestCommonScope(FromScope, ToScope);
// It's okay to jump out from a nested scope.
if (CommonScope == ToScope) return;
// Pull out (and reverse) any scopes we might need to diagnose skipping.
SmallVector<unsigned, 10> ToScopesCXX98Compat;
SmallVector<unsigned, 10> ToScopesError;
SmallVector<unsigned, 10> ToScopesWarning;
for (unsigned I = ToScope; I != CommonScope; I = Scopes[I].ParentScope) {
if (S.getLangOpts().MSVCCompat && JumpDiagWarning != 0 &&
IsMicrosoftJumpWarning(JumpDiagError, Scopes[I].InDiag))
ToScopesWarning.push_back(I);
else if (IsCXX98CompatWarning(S, Scopes[I].InDiag))
ToScopesCXX98Compat.push_back(I);
else if (Scopes[I].InDiag)
ToScopesError.push_back(I);
}
// Handle warnings.
if (!ToScopesWarning.empty()) {
S.Diag(DiagLoc, JumpDiagWarning);
NoteJumpIntoScopes(ToScopesWarning);
[Windows SEH]: HARDWARE EXCEPTION HANDLING (MSVC -EHa) - Part 1 This patch is the Part-1 (FE Clang) implementation of HW Exception handling. This new feature adds the support of Hardware Exception for Microsoft Windows SEH (Structured Exception Handling). This is the first step of this project; only X86_64 target is enabled in this patch. Compiler options: For clang-cl.exe, the option is -EHa, the same as MSVC. For clang.exe, the extra option is -fasync-exceptions, plus -triple x86_64-windows -fexceptions and -fcxx-exceptions as usual. NOTE:: Without the -EHa or -fasync-exceptions, this patch is a NO-DIFF change. The rules for C code: For C-code, one way (MSVC approach) to achieve SEH -EHa semantic is to follow three rules: * First, no exception can move in or out of _try region., i.e., no "potential faulty instruction can be moved across _try boundary. * Second, the order of exceptions for instructions 'directly' under a _try must be preserved (not applied to those in callees). * Finally, global states (local/global/heap variables) that can be read outside of _try region must be updated in memory (not just in register) before the subsequent exception occurs. The impact to C++ code: Although SEH is a feature for C code, -EHa does have a profound effect on C++ side. When a C++ function (in the same compilation unit with option -EHa ) is called by a SEH C function, a hardware exception occurs in C++ code can also be handled properly by an upstream SEH _try-handler or a C++ catch(...). As such, when that happens in the middle of an object's life scope, the dtor must be invoked the same way as C++ Synchronous Exception during unwinding process. Design: A natural way to achieve the rules above in LLVM today is to allow an EH edge added on memory/computation instruction (previous iload/istore idea) so that exception path is modeled in Flow graph preciously. However, tracking every single memory instruction and potential faulty instruction can create many Invokes, complicate flow graph and possibly result in negative performance impact for downstream optimization and code generation. Making all optimizations be aware of the new semantic is also substantial. This design does not intend to model exception path at instruction level. Instead, the proposed design tracks and reports EH state at BLOCK-level to reduce the complexity of flow graph and minimize the performance-impact on CPP code under -EHa option. One key element of this design is the ability to compute State number at block-level. Our algorithm is based on the following rationales: A _try scope is always a SEME (Single Entry Multiple Exits) region as jumping into a _try is not allowed. The single entry must start with a seh_try_begin() invoke with a correct State number that is the initial state of the SEME. Through control-flow, state number is propagated into all blocks. Side exits marked by seh_try_end() will unwind to parent state based on existing SEHUnwindMap[]. Note side exits can ONLY jump into parent scopes (lower state number). Thus, when a block succeeds various states from its predecessors, the lowest State triumphs others. If some exits flow to unreachable, propagation on those paths terminate, not affecting remaining blocks. For CPP code, object lifetime region is usually a SEME as SEH _try. However there is one rare exception: jumping into a lifetime that has Dtor but has no Ctor is warned, but allowed: Warning: jump bypasses variable with a non-trivial destructor In that case, the region is actually a MEME (multiple entry multiple exits). Our solution is to inject a eha_scope_begin() invoke in the side entry block to ensure a correct State. Implementation: Part-1: Clang implementation described below. Two intrinsic are created to track CPP object scopes; eha_scope_begin() and eha_scope_end(). _scope_begin() is immediately added after ctor() is called and EHStack is pushed. So it must be an invoke, not a call. With that it's also guaranteed an EH-cleanup-pad is created regardless whether there exists a call in this scope. _scope_end is added before dtor(). These two intrinsics make the computation of Block-State possible in downstream code gen pass, even in the presence of ctor/dtor inlining. Two intrinsic, seh_try_begin() and seh_try_end(), are added for C-code to mark _try boundary and to prevent from exceptions being moved across _try boundary. All memory instructions inside a _try are considered as 'volatile' to assure 2nd and 3rd rules for C-code above. This is a little sub-optimized. But it's acceptable as the amount of code directly under _try is very small. Part-2 (will be in Part-2 patch): LLVM implementation described below. For both C++ & C-code, the state of each block is computed at the same place in BE (WinEHPreparing pass) where all other EH tables/maps are calculated. In addition to _scope_begin & _scope_end, the computation of block state also rely on the existing State tracking code (UnwindMap and InvokeStateMap). For both C++ & C-code, the state of each block with potential trap instruction is marked and reported in DAG Instruction Selection pass, the same place where the state for -EHsc (synchronous exceptions) is done. If the first instruction in a reported block scope can trap, a Nop is injected before this instruction. This nop is needed to accommodate LLVM Windows EH implementation, in which the address in IPToState table is offset by +1. (note the purpose of that is to ensure the return address of a call is in the same scope as the call address. The handler for catch(...) for -EHa must handle HW exception. So it is 'adjective' flag is reset (it cannot be IsStdDotDot (0x40) that only catches C++ exceptions). Suppress push/popTerminate() scope (from noexcept/noTHrow) so that HW exceptions can be passed through. Original llvm-dev [RFC] discussions can be found in these two threads below: https://lists.llvm.org/pipermail/llvm-dev/2020-March/140541.html https://lists.llvm.org/pipermail/llvm-dev/2020-April/141338.html Differential Revision: https://reviews.llvm.org/D80344/new/
2021-05-18 13:06:32 +08:00
assert(isa<LabelStmt>(To));
LabelStmt *Label = cast<LabelStmt>(To);
Label->setSideEntry(true);
}
// Handle errors.
if (!ToScopesError.empty()) {
S.Diag(DiagLoc, JumpDiagError);
NoteJumpIntoScopes(ToScopesError);
}
// Handle -Wc++98-compat warnings if the jump is well-formed.
if (ToScopesError.empty() && !ToScopesCXX98Compat.empty()) {
S.Diag(DiagLoc, JumpDiagCXX98Compat);
NoteJumpIntoScopes(ToScopesCXX98Compat);
}
}
void JumpScopeChecker::CheckGotoStmt(GotoStmt *GS) {
if (GS->getLabel()->isMSAsmLabel()) {
S.Diag(GS->getGotoLoc(), diag::err_goto_ms_asm_label)
<< GS->getLabel()->getIdentifier();
S.Diag(GS->getLabel()->getLocation(), diag::note_goto_ms_asm_label)
<< GS->getLabel()->getIdentifier();
}
}
void JumpScopeChecker::VerifyMustTailStmts() {
for (AttributedStmt *AS : MustTailStmts) {
for (unsigned I = LabelAndGotoScopes[AS]; I; I = Scopes[I].ParentScope) {
if (Scopes[I].OutDiag) {
S.Diag(AS->getBeginLoc(), diag::err_musttail_scope);
S.Diag(Scopes[I].Loc, Scopes[I].OutDiag);
}
}
}
}
const Attr *JumpScopeChecker::GetMustTailAttr(AttributedStmt *AS) {
ArrayRef<const Attr *> Attrs = AS->getAttrs();
const auto *Iter =
llvm::find_if(Attrs, [](const Attr *A) { return isa<MustTailAttr>(A); });
return Iter != Attrs.end() ? *Iter : nullptr;
}
void Sema::DiagnoseInvalidJumps(Stmt *Body) {
(void)JumpScopeChecker(Body, *this);
}