Add -Wunsequenced (with compatibility alias -Wsequence-point) to warn on

expressions which have undefined behavior due to multiple unsequenced
modifications or an unsequenced modification and use of a variable.

llvm-svn: 172690
This commit is contained in:
Richard Smith 2013-01-17 01:17:56 +00:00
parent 8c6cbe776b
commit c406cb7364
10 changed files with 531 additions and 5 deletions

View File

@ -49,6 +49,9 @@ public:
}
void VisitChooseExpr(ChooseExpr *E) {
// Don't visit either child expression if the condition is dependent.
if (E->getCond()->isValueDependent())
return;
// Only the selected subexpression matters; the other one is not evaluated.
return this->Visit(E->getChosenSubExpr(Context));
}

View File

@ -198,7 +198,6 @@ def SelfAssignment : DiagGroup<"self-assign", [SelfAssignmentField]>;
def SemiBeforeMethodBody : DiagGroup<"semicolon-before-method-body">;
def Sentinel : DiagGroup<"sentinel">;
def MissingMethodReturnType : DiagGroup<"missing-method-return-type">;
def : DiagGroup<"sequence-point">;
def Shadow : DiagGroup<"shadow">;
def Shorten64To32 : DiagGroup<"shorten-64-to-32">;
def : DiagGroup<"sign-promo">;
@ -218,6 +217,10 @@ def HeaderHygiene : DiagGroup<"header-hygiene">;
def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
def CompareDistinctPointerType : DiagGroup<"compare-distinct-pointer-types">;
def Unsequenced : DiagGroup<"unsequenced">;
// GCC name for -Wunsequenced
def : DiagGroup<"sequence-point", [Unsequenced]>;
// Preprocessor warnings.
def AmbiguousMacro : DiagGroup<"ambiguous-macro">;

View File

@ -1323,6 +1323,11 @@ def note_uninit_fixit_remove_cond : Note<
"is always %select{false|true}2">;
def err_init_incomplete_type : Error<"initialization of incomplete type %0">;
def warn_unsequenced_mod_mod : Warning<
"multiple unsequenced modifications to %0">, InGroup<Unsequenced>;
def warn_unsequenced_mod_use : Warning<
"unsequenced modification and access to %0">, InGroup<Unsequenced>;
def err_temp_copy_no_viable : Error<
"no viable constructor %select{copying variable|copying parameter|"
"returning object|throwing object|copying member subobject|copying array "

View File

@ -7319,6 +7319,11 @@ private:
SourceLocation ReturnLoc);
void CheckFloatComparison(SourceLocation Loc, Expr* LHS, Expr* RHS);
void CheckImplicitConversions(Expr *E, SourceLocation CC = SourceLocation());
void CheckUnsequencedOperations(Expr *E);
/// \brief Perform semantic checks on a completed expression. This will either
/// be a full-expression or a default argument expression.
void CheckCompletedExpr(Expr *E, SourceLocation CheckLoc = SourceLocation());
void CheckBitFieldInitialization(SourceLocation InitLoc, FieldDecl *Field,
Expr *Init);

View File

@ -5171,6 +5171,424 @@ void Sema::CheckImplicitConversions(Expr *E, SourceLocation CC) {
AnalyzeImplicitConversions(*this, E, CC);
}
namespace {
/// \brief Visitor for expressions which looks for unsequenced operations on the
/// same object.
class SequenceChecker : public EvaluatedExprVisitor<SequenceChecker> {
/// \brief A tree of sequenced regions within an expression. Two regions are
/// unsequenced if one is an ancestor or a descendent of the other. When we
/// finish processing an expression with sequencing, such as a comma
/// expression, we fold its tree nodes into its parent, since they are
/// unsequenced with respect to nodes we will visit later.
class SequenceTree {
struct Value {
explicit Value(unsigned Parent) : Parent(Parent), Merged(false) {}
unsigned Parent : 31;
bool Merged : 1;
};
llvm::SmallVector<Value, 8> Values;
public:
/// \brief A region within an expression which may be sequenced with respect
/// to some other region.
class Seq {
explicit Seq(unsigned N) : Index(N) {}
unsigned Index;
friend class SequenceTree;
public:
Seq() : Index(0) {}
};
SequenceTree() { Values.push_back(Value(0)); }
Seq root() const { return Seq(0); }
/// \brief Create a new sequence of operations, which is an unsequenced
/// subset of \p Parent. This sequence of operations is sequenced with
/// respect to other children of \p Parent.
Seq allocate(Seq Parent) {
Values.push_back(Value(Parent.Index));
return Seq(Values.size() - 1);
}
/// \brief Merge a sequence of operations into its parent.
void merge(Seq S) {
Values[S.Index].Merged = true;
}
/// \brief Determine whether two operations are unsequenced. This operation
/// is asymmetric: \p Cur should be the more recent sequence, and \p Old
/// should have been merged into its parent as appropriate.
bool isUnsequenced(Seq Cur, Seq Old) {
unsigned C = representative(Cur.Index);
unsigned Target = representative(Old.Index);
while (C >= Target) {
if (C == Target)
return true;
C = Values[C].Parent;
}
return false;
}
private:
/// \brief Pick a representative for a sequence.
unsigned representative(unsigned K) {
if (Values[K].Merged)
// Perform path compression as we go.
return Values[K].Parent = representative(Values[K].Parent);
return K;
}
};
/// An object for which we can track unsequenced uses.
typedef NamedDecl *Object;
/// Different flavors of object usage which we track. We only track the
/// least-sequenced usage of each kind.
enum UsageKind {
/// A read of an object. Multiple unsequenced reads are OK.
UK_Use,
/// A modification of an object which is sequenced before the value
/// computation of the expression, such as ++n.
UK_ModAsValue,
/// A modification of an object which is not sequenced before the value
/// computation of the expression, such as n++.
UK_ModAsSideEffect,
UK_Count = UK_ModAsSideEffect + 1
};
struct Usage {
Usage() : Use(0), Seq() {}
Expr *Use;
SequenceTree::Seq Seq;
};
struct UsageInfo {
UsageInfo() : Diagnosed(false) {}
Usage Uses[UK_Count];
/// Have we issued a diagnostic for this variable already?
bool Diagnosed;
};
typedef llvm::SmallDenseMap<Object, UsageInfo, 16> UsageInfoMap;
Sema &SemaRef;
/// Sequenced regions within the expression.
SequenceTree Tree;
/// Declaration modifications and references which we have seen.
UsageInfoMap UsageMap;
/// The region we are currently within.
SequenceTree::Seq Region;
/// Filled in with declarations which were modified as a side-effect
/// (that is, post-increment operations).
llvm::SmallVectorImpl<std::pair<Object, Usage> > *ModAsSideEffect;
/// RAII object wrapping the visitation of a sequenced subexpression of an
/// expression. At the end of this process, the side-effects of the evaluation
/// become sequenced with respect to the value computation of the result, so
/// we downgrade any UK_ModAsSideEffect within the evaluation to
/// UK_ModAsValue.
struct SequencedSubexpression {
SequencedSubexpression(SequenceChecker &Self)
: Self(Self), OldModAsSideEffect(Self.ModAsSideEffect) {
Self.ModAsSideEffect = &ModAsSideEffect;
}
~SequencedSubexpression() {
for (unsigned I = 0, E = ModAsSideEffect.size(); I != E; ++I) {
UsageInfo &U = Self.UsageMap[ModAsSideEffect[I].first];
U.Uses[UK_ModAsSideEffect] = ModAsSideEffect[I].second;
Self.addUsage(U, ModAsSideEffect[I].first,
ModAsSideEffect[I].second.Use, UK_ModAsValue);
}
Self.ModAsSideEffect = OldModAsSideEffect;
}
SequenceChecker &Self;
llvm::SmallVector<std::pair<Object, Usage>, 4> ModAsSideEffect;
llvm::SmallVectorImpl<std::pair<Object, Usage> > *OldModAsSideEffect;
};
/// \brief Find the object which is produced by the specified expression,
/// if any.
Object getObject(Expr *E, bool Mod) const {
E = E->IgnoreParenCasts();
if (UnaryOperator *UO = dyn_cast<UnaryOperator>(E)) {
if (Mod && (UO->getOpcode() == UO_PreInc || UO->getOpcode() == UO_PreDec))
return getObject(UO->getSubExpr(), Mod);
} else if (BinaryOperator *BO = dyn_cast<BinaryOperator>(E)) {
if (BO->getOpcode() == BO_Comma)
return getObject(BO->getRHS(), Mod);
if (Mod && BO->isAssignmentOp())
return getObject(BO->getLHS(), Mod);
} else if (MemberExpr *ME = dyn_cast<MemberExpr>(E)) {
// FIXME: Check for more interesting cases, like "x.n = ++x.n".
if (isa<CXXThisExpr>(ME->getBase()->IgnoreParenCasts()))
return ME->getMemberDecl();
} else if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E))
// FIXME: If this is a reference, map through to its value.
return DRE->getDecl();
return 0;
}
/// \brief Note that an object was modified or used by an expression.
void addUsage(UsageInfo &UI, Object O, Expr *Ref, UsageKind UK) {
Usage &U = UI.Uses[UK];
if (!U.Use || !Tree.isUnsequenced(Region, U.Seq)) {
if (UK == UK_ModAsSideEffect && ModAsSideEffect)
ModAsSideEffect->push_back(std::make_pair(O, U));
U.Use = Ref;
U.Seq = Region;
}
}
/// \brief Check whether a modification or use conflicts with a prior usage.
void checkUsage(Object O, UsageInfo &UI, Expr *Ref, UsageKind OtherKind,
bool IsModMod) {
if (UI.Diagnosed)
return;
const Usage &U = UI.Uses[OtherKind];
if (!U.Use || !Tree.isUnsequenced(Region, U.Seq))
return;
Expr *Mod = U.Use;
Expr *ModOrUse = Ref;
if (OtherKind == UK_Use)
std::swap(Mod, ModOrUse);
SemaRef.Diag(Mod->getExprLoc(),
IsModMod ? diag::warn_unsequenced_mod_mod
: diag::warn_unsequenced_mod_use)
<< O << SourceRange(ModOrUse->getExprLoc());
UI.Diagnosed = true;
}
void notePreUse(Object O, Expr *Use) {
UsageInfo &U = UsageMap[O];
// Uses conflict with other modifications.
checkUsage(O, U, Use, UK_ModAsValue, false);
}
void notePostUse(Object O, Expr *Use) {
UsageInfo &U = UsageMap[O];
checkUsage(O, U, Use, UK_ModAsSideEffect, false);
addUsage(U, O, Use, UK_Use);
}
void notePreMod(Object O, Expr *Mod) {
UsageInfo &U = UsageMap[O];
// Modifications conflict with other modifications and with uses.
checkUsage(O, U, Mod, UK_ModAsValue, true);
checkUsage(O, U, Mod, UK_Use, false);
}
void notePostMod(Object O, Expr *Use, UsageKind UK) {
UsageInfo &U = UsageMap[O];
checkUsage(O, U, Use, UK_ModAsSideEffect, true);
addUsage(U, O, Use, UK);
}
public:
SequenceChecker(Sema &S, Expr *E)
: EvaluatedExprVisitor(S.Context), SemaRef(S), Region(Tree.root()),
ModAsSideEffect(0) {
Visit(E);
}
void VisitStmt(Stmt *S) {
// Skip all statements which aren't expressions for now.
}
void VisitExpr(Expr *E) {
// By default, just recurse to evaluated subexpressions.
EvaluatedExprVisitor::VisitStmt(E);
}
void VisitCastExpr(CastExpr *E) {
Object O = Object();
if (E->getCastKind() == CK_LValueToRValue)
O = getObject(E->getSubExpr(), false);
if (O)
notePreUse(O, E);
VisitExpr(E);
if (O)
notePostUse(O, E);
}
void VisitBinComma(BinaryOperator *BO) {
// C++11 [expr.comma]p1:
// Every value computation and side effect associated with the left
// expression is sequenced before every value computation and side
// effect associated with the right expression.
SequenceTree::Seq LHS = Tree.allocate(Region);
SequenceTree::Seq RHS = Tree.allocate(Region);
SequenceTree::Seq OldRegion = Region;
{
SequencedSubexpression SeqLHS(*this);
Region = LHS;
Visit(BO->getLHS());
}
Region = RHS;
Visit(BO->getRHS());
Region = OldRegion;
// Forget that LHS and RHS are sequenced. They are both unsequenced
// with respect to other stuff.
Tree.merge(LHS);
Tree.merge(RHS);
}
void VisitBinAssign(BinaryOperator *BO) {
// The modification is sequenced after the value computation of the LHS
// and RHS, so check it before inspecting the operands and update the
// map afterwards.
Object O = getObject(BO->getLHS(), true);
if (!O)
return VisitExpr(BO);
notePreMod(O, BO);
// C++11 [expr.ass]p7:
// E1 op= E2 is equivalent to E1 = E1 op E2, except that E1 is evaluated
// only once.
//
// Therefore, for a compound assignment operator, O is considered used
// everywhere except within the evaluation of E1 itself.
if (isa<CompoundAssignOperator>(BO))
notePreUse(O, BO);
Visit(BO->getLHS());
if (isa<CompoundAssignOperator>(BO))
notePostUse(O, BO);
Visit(BO->getRHS());
notePostMod(O, BO, UK_ModAsValue);
}
void VisitCompoundAssignOperator(CompoundAssignOperator *CAO) {
VisitBinAssign(CAO);
}
void VisitUnaryPreInc(UnaryOperator *UO) { VisitUnaryPreIncDec(UO); }
void VisitUnaryPreDec(UnaryOperator *UO) { VisitUnaryPreIncDec(UO); }
void VisitUnaryPreIncDec(UnaryOperator *UO) {
Object O = getObject(UO->getSubExpr(), true);
if (!O)
return VisitExpr(UO);
notePreMod(O, UO);
Visit(UO->getSubExpr());
notePostMod(O, UO, UK_ModAsValue);
}
void VisitUnaryPostInc(UnaryOperator *UO) { VisitUnaryPostIncDec(UO); }
void VisitUnaryPostDec(UnaryOperator *UO) { VisitUnaryPostIncDec(UO); }
void VisitUnaryPostIncDec(UnaryOperator *UO) {
Object O = getObject(UO->getSubExpr(), true);
if (!O)
return VisitExpr(UO);
notePreMod(O, UO);
Visit(UO->getSubExpr());
notePostMod(O, UO, UK_ModAsSideEffect);
}
/// Don't visit the RHS of '&&' or '||' if it might not be evaluated.
void VisitBinLOr(BinaryOperator *BO) {
// The side-effects of the LHS of an '&&' are sequenced before the
// value computation of the RHS, and hence before the value computation
// of the '&&' itself, unless the LHS evaluates to zero. We treat them
// as if they were unconditionally sequenced.
{
SequencedSubexpression Sequenced(*this);
Visit(BO->getLHS());
}
bool Result;
if (!BO->getLHS()->isValueDependent() &&
BO->getLHS()->EvaluateAsBooleanCondition(Result, SemaRef.Context) &&
!Result)
Visit(BO->getRHS());
}
void VisitBinLAnd(BinaryOperator *BO) {
{
SequencedSubexpression Sequenced(*this);
Visit(BO->getLHS());
}
bool Result;
if (!BO->getLHS()->isValueDependent() &&
BO->getLHS()->EvaluateAsBooleanCondition(Result, SemaRef.Context) &&
Result)
Visit(BO->getRHS());
}
// Only visit the condition, unless we can be sure which subexpression will
// be chosen.
void VisitAbstractConditionalOperator(AbstractConditionalOperator *CO) {
SequencedSubexpression Sequenced(*this);
Visit(CO->getCond());
bool Result;
if (!CO->getCond()->isValueDependent() &&
CO->getCond()->EvaluateAsBooleanCondition(Result, SemaRef.Context))
Visit(Result ? CO->getTrueExpr() : CO->getFalseExpr());
}
void VisitCXXConstructExpr(CXXConstructExpr *CCE) {
if (!CCE->isListInitialization())
return VisitExpr(CCE);
// In C++11, list initializations are sequenced.
llvm::SmallVector<SequenceTree::Seq, 32> Elts;
SequenceTree::Seq Parent = Region;
for (CXXConstructExpr::arg_iterator I = CCE->arg_begin(),
E = CCE->arg_end();
I != E; ++I) {
Region = Tree.allocate(Parent);
Elts.push_back(Region);
Visit(*I);
}
// Forget that the initializers are sequenced.
Region = Parent;
for (unsigned I = 0; I < Elts.size(); ++I)
Tree.merge(Elts[I]);
}
void VisitInitListExpr(InitListExpr *ILE) {
if (!SemaRef.getLangOpts().CPlusPlus11)
return VisitExpr(ILE);
// In C++11, list initializations are sequenced.
llvm::SmallVector<SequenceTree::Seq, 32> Elts;
SequenceTree::Seq Parent = Region;
for (unsigned I = 0; I < ILE->getNumInits(); ++I) {
Expr *E = ILE->getInit(I);
if (!E) continue;
Region = Tree.allocate(Parent);
Elts.push_back(Region);
Visit(E);
}
// Forget that the initializers are sequenced.
Region = Parent;
for (unsigned I = 0; I < Elts.size(); ++I)
Tree.merge(Elts[I]);
}
};
}
void Sema::CheckUnsequencedOperations(Expr *E) {
SequenceChecker(*this, E);
}
void Sema::CheckCompletedExpr(Expr *E, SourceLocation CheckLoc) {
CheckImplicitConversions(E, CheckLoc);
CheckUnsequencedOperations(E);
}
void Sema::CheckBitFieldInitialization(SourceLocation InitLoc,
FieldDecl *BitField,
Expr *Init) {

View File

@ -252,7 +252,7 @@ Sema::SetParamDefaultArgument(ParmVarDecl *Param, Expr *Arg,
return true;
Arg = Result.takeAs<Expr>();
CheckImplicitConversions(Arg, EqualLoc);
CheckCompletedExpr(Arg, EqualLoc);
Arg = MaybeCreateExprWithCleanups(Arg);
// Okay: add the default argument to the parameter

View File

@ -3534,7 +3534,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
return ExprError();
Expr *Arg = Result.takeAs<Expr>();
CheckImplicitConversions(Arg, Param->getOuterLocStart());
CheckCompletedExpr(Arg, Param->getOuterLocStart());
// Build the default argument expression.
return Owned(CXXDefaultArgExpr::Create(Context, CallLoc, Param, Arg));
}

View File

@ -18,6 +18,7 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/TypeLoc.h"
@ -5496,7 +5497,7 @@ ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
return ExprError();
}
CheckImplicitConversions(FullExpr.get(), CC);
CheckCompletedExpr(FullExpr.get(), CC);
return MaybeCreateExprWithCleanups(FullExpr);
}

View File

@ -62,7 +62,7 @@ void test2()
vector float vf;
vf++;
++vi=vi;
++vi=vi; // expected-warning {{unsequenced}}
(++vi)[1]=1;
template_f(vi);
}

View File

@ -0,0 +1,91 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wno-unused %s
int f(int, int);
struct A {
int x, y;
};
struct S {
S(int, int);
};
void test() {
int a;
int xs[10];
++a = 0; // ok
a + ++a; // expected-warning {{unsequenced modification and access to 'a'}}
a = ++a; // ok
a + a++; // expected-warning {{unsequenced modification and access to 'a'}}
a = a++; // expected-warning {{multiple unsequenced modifications to 'a'}}
++ ++a; // ok
(a++, a++); // ok
++a + ++a; // expected-warning {{multiple unsequenced modifications to 'a'}}
a++ + a++; // expected-warning {{multiple unsequenced modifications}}
(a++, a) = 0; // ok, increment is sequenced before value computation of LHS
a = xs[++a]; // ok
a = xs[a++]; // expected-warning {{multiple unsequenced modifications}}
(a ? xs[0] : xs[1]) = ++a; // expected-warning {{unsequenced modification and access}}
a = (++a, ++a); // ok
a = (a++, ++a); // ok
a = (a++, a++); // expected-warning {{multiple unsequenced modifications}}
f(a, a); // ok
f(a = 0, a); // expected-warning {{unsequenced modification and access}}
f(a, a += 0); // expected-warning {{unsequenced modification and access}}
f(a = 0, a = 0); // expected-warning {{multiple unsequenced modifications}}
// Compound assignment "A OP= B" is equivalent to "A = A OP B" except that A
// is evaluated only once.
(++a, a) = 1; // ok
(++a, a) += 1; // ok
a = ++a; // ok
a += ++a; // expected-warning {{unsequenced modification and access}}
A agg1 = { a++, a++ }; // ok
A agg2 = { a++ + a, a++ }; // expected-warning {{unsequenced modification and access}}
S str1(a++, a++); // expected-warning {{multiple unsequenced modifications}}
S str2 = { a++, a++ }; // ok
S str3 = { a++ + a, a++ }; // expected-warning {{unsequenced modification and access}}
(xs[2] && (a = 0)) + a; // ok
(0 && (a = 0)) + a; // ok
(1 && (a = 0)) + a; // expected-warning {{unsequenced modification and access}}
(xs[3] || (a = 0)) + a; // ok
(0 || (a = 0)) + a; // expected-warning {{unsequenced modification and access}}
(1 || (a = 0)) + a; // ok
(xs[4] ? a : ++a) + a; // ok
(0 ? a : ++a) + a; // expected-warning {{unsequenced modification and access}}
(1 ? a : ++a) + a; // ok
(xs[5] ? ++a : ++a) + a; // FIXME: warn here
(++a, xs[6] ? ++a : 0) + a; // expected-warning {{unsequenced modification and access}}
// Here, the read of the fourth 'a' might happen before or after the write to
// the second 'a'.
a += (a++, a) + a; // expected-warning {{unsequenced modification and access}}
int *p = xs;
a = *(a++, p); // ok
a = a++ && a; // ok
A *q = &agg1;
(q = &agg2)->y = q->x; // expected-warning {{unsequenced modification and access to 'q'}}
// This has undefined behavior if a == 0; otherwise, the side-effect of the
// increment is sequenced before the value computation of 'f(a, a)', which is
// sequenced before the value computation of the '&&', which is sequenced
// before the assignment. We treat the sequencing in '&&' as being
// unconditional.
a = a++ && f(a, a);
// This has undefined behavior if a != 0. FIXME: We should diagnose this.
(a && a++) + a;
(xs[7] && ++a) * (!xs[7] && ++a); // ok
xs[0] = (a = 1, a); // ok
(a -= 128) &= 128; // ok
++a += 1; // ok
}