Skip to content

Commit

Permalink
[Sema] Diagnose default-initialization, destruction, and copying of
Browse files Browse the repository at this point in the history
non-trivial C union types

This patch diagnoses uses of non-trivial C unions and structs/unions
containing non-trivial C unions in the following contexts, which require
default-initialization, destruction, or copying of the union objects,
instead of disallowing fields of non-trivial types in C unions, which is
what we currently do:

- function parameters.
- function returns.
- assignments.
- compound literals.
- block captures except capturing of `__block` variables by non-escaping
  blocks.
- local and global variable definitions.
- lvalue-to-rvalue conversions of volatile types.

See the discussion in https://reviews.llvm.org/D62988 for more background.

rdar://problem/50679094

Differential Revision: https://reviews.llvm.org/D63753

llvm-svn: 365985
  • Loading branch information
ahatanaka committed Jul 13, 2019
1 parent c7a1db3 commit 81b03d4
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 122 deletions.
24 changes: 24 additions & 0 deletions clang/include/clang/AST/Decl.h
Expand Up @@ -3746,6 +3746,30 @@ class RecordDecl : public TagDecl {
RecordDeclBits.NonTrivialToPrimitiveDestroy = V;
}

bool hasNonTrivialToPrimitiveDefaultInitializeCUnion() const {
return RecordDeclBits.HasNonTrivialToPrimitiveDefaultInitializeCUnion;
}

void setHasNonTrivialToPrimitiveDefaultInitializeCUnion(bool V) {
RecordDeclBits.HasNonTrivialToPrimitiveDefaultInitializeCUnion = V;
}

bool hasNonTrivialToPrimitiveDestructCUnion() const {
return RecordDeclBits.HasNonTrivialToPrimitiveDestructCUnion;
}

void setHasNonTrivialToPrimitiveDestructCUnion(bool V) {
RecordDeclBits.HasNonTrivialToPrimitiveDestructCUnion = V;
}

bool hasNonTrivialToPrimitiveCopyCUnion() const {
return RecordDeclBits.HasNonTrivialToPrimitiveCopyCUnion;
}

void setHasNonTrivialToPrimitiveCopyCUnion(bool V) {
RecordDeclBits.HasNonTrivialToPrimitiveCopyCUnion = V;
}

/// Determine whether this class can be passed in registers. In C++ mode,
/// it must have at least one trivial, non-deleted copy or move constructor.
/// FIXME: This should be set as part of completeDefinition.
Expand Down
9 changes: 8 additions & 1 deletion clang/include/clang/AST/DeclBase.h
Expand Up @@ -1440,6 +1440,13 @@ class DeclContext {
uint64_t NonTrivialToPrimitiveCopy : 1;
uint64_t NonTrivialToPrimitiveDestroy : 1;

/// The following bits indicate whether this is or contains a C union that
/// is non-trivial to default-initialize, destruct, or copy. These bits
/// imply the associated basic non-triviality predicates declared above.
uint64_t HasNonTrivialToPrimitiveDefaultInitializeCUnion : 1;
uint64_t HasNonTrivialToPrimitiveDestructCUnion : 1;
uint64_t HasNonTrivialToPrimitiveCopyCUnion : 1;

/// Indicates whether this struct is destroyed in the callee.
uint64_t ParamDestroyedInCallee : 1;

Expand All @@ -1448,7 +1455,7 @@ class DeclContext {
};

/// Number of non-inherited bits in RecordDeclBitfields.
enum { NumRecordDeclBits = 11 };
enum { NumRecordDeclBits = 14 };

/// Stores the bits used by OMPDeclareReductionDecl.
/// If modified NumOMPDeclareReductionDeclBits and the accessor
Expand Down
45 changes: 39 additions & 6 deletions clang/include/clang/AST/Type.h
Expand Up @@ -1129,12 +1129,6 @@ class QualType {
PCK_Struct
};

/// Check if this is a non-trivial type that would cause a C struct
/// transitively containing this type to be non-trivial. This function can be
/// used to determine whether a field of this type can be declared inside a C
/// union.
bool isNonTrivialPrimitiveCType(const ASTContext &Ctx) const;

/// Check if this is a non-trivial type that would cause a C struct
/// transitively containing this type to be non-trivial to copy and return the
/// kind.
Expand Down Expand Up @@ -1164,6 +1158,22 @@ class QualType {
return isDestructedTypeImpl(*this);
}

/// Check if this is or contains a C union that is non-trivial to
/// default-initialize, which is a union that has a member that is non-trivial
/// to default-initialize. If this returns true,
/// isNonTrivialToPrimitiveDefaultInitialize returns PDIK_Struct.
bool hasNonTrivialToPrimitiveDefaultInitializeCUnion() const;

/// Check if this is or contains a C union that is non-trivial to destruct,
/// which is a union that has a member that is non-trivial to destruct. If
/// this returns true, isDestructedType returns DK_nontrivial_c_struct.
bool hasNonTrivialToPrimitiveDestructCUnion() const;

/// Check if this is or contains a C union that is non-trivial to copy, which
/// is a union that has a member that is non-trivial to copy. If this returns
/// true, isNonTrivialToPrimitiveCopy returns PCK_Struct.
bool hasNonTrivialToPrimitiveCopyCUnion() const;

/// Determine whether expressions of the given type are forbidden
/// from being lvalues in C.
///
Expand Down Expand Up @@ -1236,6 +1246,11 @@ class QualType {
const ASTContext &C);
static QualType IgnoreParens(QualType T);
static DestructionKind isDestructedTypeImpl(QualType type);

/// Check if \param RD is or contains a non-trivial C union.
static bool hasNonTrivialToPrimitiveDefaultInitializeCUnion(const RecordDecl *RD);
static bool hasNonTrivialToPrimitiveDestructCUnion(const RecordDecl *RD);
static bool hasNonTrivialToPrimitiveCopyCUnion(const RecordDecl *RD);
};

} // namespace clang
Expand Down Expand Up @@ -6249,6 +6264,24 @@ inline Qualifiers::GC QualType::getObjCGCAttr() const {
return getQualifiers().getObjCGCAttr();
}

inline bool QualType::hasNonTrivialToPrimitiveDefaultInitializeCUnion() const {
if (auto *RD = getTypePtr()->getBaseElementTypeUnsafe()->getAsRecordDecl())
return hasNonTrivialToPrimitiveDefaultInitializeCUnion(RD);
return false;
}

inline bool QualType::hasNonTrivialToPrimitiveDestructCUnion() const {
if (auto *RD = getTypePtr()->getBaseElementTypeUnsafe()->getAsRecordDecl())
return hasNonTrivialToPrimitiveDestructCUnion(RD);
return false;
}

inline bool QualType::hasNonTrivialToPrimitiveCopyCUnion() const {
if (auto *RD = getTypePtr()->getBaseElementTypeUnsafe()->getAsRecordDecl())
return hasNonTrivialToPrimitiveCopyCUnion(RD);
return false;
}

inline FunctionType::ExtInfo getFunctionExtInfo(const Type &t) {
if (const auto *PT = t.getAs<PointerType>()) {
if (const auto *FT = PT->getPointeeType()->getAs<FunctionType>())
Expand Down
19 changes: 17 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -620,8 +620,23 @@ def warn_cstruct_memaccess : Warning<
InGroup<NonTrivialMemaccess>;
def note_nontrivial_field : Note<
"field is non-trivial to %select{copy|default-initialize}0">;
def err_nontrivial_primitive_type_in_union : Error<
"non-trivial C types are disallowed in union">;
def err_non_trivial_c_union_in_invalid_context : Error<
"cannot %select{"
"use type %1 for a function/method parameter|"
"use type %1 for function/method return|"
"default-initialize an object of type %1|"
"declare an automatic variable of type %1|"
"copy-initialize an object of type %1|"
"assign to a variable of type %1|"
"construct an automatic compound literal of type %1|"
"capture a variable of type %1|"
"cannot use volatile type %1 where it causes an lvalue-to-rvalue conversion"
"}3 "
"since it %select{contains|is}2 a union that is non-trivial to "
"%select{default-initialize|destruct|copy}0">;
def note_non_trivial_c_union : Note<
"%select{%2 has subobjects that are|%3 has type %2 that is}0 "
"non-trivial to %select{default-initialize|destruct|copy}1">;
def warn_dyn_class_memaccess : Warning<
"%select{destination for|source of|first operand of|second operand of}0 this "
"%1 call is a pointer to %select{|class containing a }2dynamic class %3; "
Expand Down
42 changes: 42 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -2114,6 +2114,48 @@ class Sema {
bool SetParamDefaultArgument(ParmVarDecl *Param, Expr *DefaultArg,
SourceLocation EqualLoc);

// Contexts where using non-trivial C union types can be disallowed. This is
// passed to err_non_trivial_c_union_in_invalid_context.
enum NonTrivialCUnionContext {
// Function parameter.
NTCUC_FunctionParam,
// Function return.
NTCUC_FunctionReturn,
// Default-initialized object.
NTCUC_DefaultInitializedObject,
// Variable with automatic storage duration.
NTCUC_AutoVar,
// Initializer expression that might copy from another object.
NTCUC_CopyInit,
// Assignment.
NTCUC_Assignment,
// Compound literal.
NTCUC_CompoundLiteral,
// Block capture.
NTCUC_BlockCapture,
// lvalue-to-rvalue conversion of volatile type.
NTCUC_LValueToRValueVolatile,
};

/// Emit diagnostics if the initializer or any of its explicit or
/// implicitly-generated subexpressions require copying or
/// default-initializing a type that is or contains a C union type that is
/// non-trivial to copy or default-initialize.
void checkNonTrivialCUnionInInitializer(const Expr *Init, SourceLocation Loc);

// These flags are passed to checkNonTrivialCUnion.
enum NonTrivialCUnionKind {
NTCUK_Init = 0x1,
NTCUK_Destruct = 0x2,
NTCUK_Copy = 0x4,
};

/// Emit diagnostics if a non-trivial C union type or a struct that contains
/// a non-trivial C union is used in an invalid context.
void checkNonTrivialCUnion(QualType QT, SourceLocation Loc,
NonTrivialCUnionContext UseContext,
unsigned NonTrivialKind);

void AddInitializerToDecl(Decl *dcl, Expr *init, bool DirectInit);
void ActOnUninitializedDecl(Decl *dcl);
void ActOnInitializerError(Decl *Dcl);
Expand Down
60 changes: 8 additions & 52 deletions clang/lib/AST/Type.cpp
Expand Up @@ -2276,60 +2276,16 @@ bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
getObjCLifetime() != Qualifiers::OCL_Weak;
}

namespace {
// Helper class that determines whether this is a type that is non-trivial to
// primitive copy or move, or is a struct type that has a field of such type.
template <bool IsMove>
struct IsNonTrivialCopyMoveVisitor
: CopiedTypeVisitor<IsNonTrivialCopyMoveVisitor<IsMove>, IsMove, bool> {
using Super =
CopiedTypeVisitor<IsNonTrivialCopyMoveVisitor<IsMove>, IsMove, bool>;
IsNonTrivialCopyMoveVisitor(const ASTContext &C) : Ctx(C) {}
void preVisit(QualType::PrimitiveCopyKind PCK, QualType QT) {}

bool visitWithKind(QualType::PrimitiveCopyKind PCK, QualType QT) {
if (const auto *AT = this->Ctx.getAsArrayType(QT))
return this->asDerived().visit(Ctx.getBaseElementType(AT));
return Super::visitWithKind(PCK, QT);
}

bool visitARCStrong(QualType QT) { return true; }
bool visitARCWeak(QualType QT) { return true; }
bool visitTrivial(QualType QT) { return false; }
// Volatile fields are considered trivial.
bool visitVolatileTrivial(QualType QT) { return false; }

bool visitStruct(QualType QT) {
const RecordDecl *RD = QT->castAs<RecordType>()->getDecl();
// We don't want to apply the C restriction in C++ because C++
// (1) can apply the restriction at a finer grain by banning copying or
// destroying the union, and
// (2) allows users to override these restrictions by declaring explicit
// constructors/etc, which we're not proposing to add to C.
if (isa<CXXRecordDecl>(RD))
return false;
for (const FieldDecl *FD : RD->fields())
if (this->asDerived().visit(FD->getType()))
return true;
return false;
}

const ASTContext &Ctx;
};
bool QualType::hasNonTrivialToPrimitiveDefaultInitializeCUnion(const RecordDecl *RD) {
return RD->hasNonTrivialToPrimitiveDefaultInitializeCUnion();
}

} // namespace
bool QualType::hasNonTrivialToPrimitiveDestructCUnion(const RecordDecl *RD) {
return RD->hasNonTrivialToPrimitiveDestructCUnion();
}

bool QualType::isNonTrivialPrimitiveCType(const ASTContext &Ctx) const {
if (isNonTrivialToPrimitiveDefaultInitialize())
return true;
DestructionKind DK = isDestructedType();
if (DK != DK_none && DK != DK_cxx_destructor)
return true;
if (IsNonTrivialCopyMoveVisitor<false>(Ctx).visit(*this))
return true;
if (IsNonTrivialCopyMoveVisitor<true>(Ctx).visit(*this))
return true;
return false;
bool QualType::hasNonTrivialToPrimitiveCopyCUnion(const RecordDecl *RD) {
return RD->hasNonTrivialToPrimitiveCopyCUnion();
}

QualType::PrimitiveDefaultInitializeKind
Expand Down
18 changes: 15 additions & 3 deletions clang/lib/Sema/Sema.cpp
Expand Up @@ -1658,12 +1658,24 @@ static void markEscapingByrefs(const FunctionScopeInfo &FSI, Sema &S) {
// Set the EscapingByref flag of __block variables captured by
// escaping blocks.
for (const BlockDecl *BD : FSI.Blocks) {
if (BD->doesNotEscape())
continue;
for (const BlockDecl::Capture &BC : BD->captures()) {
VarDecl *VD = BC.getVariable();
if (VD->hasAttr<BlocksAttr>())
if (VD->hasAttr<BlocksAttr>()) {
// Nothing to do if this is a __block variable captured by a
// non-escaping block.
if (BD->doesNotEscape())
continue;
VD->setEscapingByref();
}
// Check whether the captured variable is or contains an object of
// non-trivial C union type.
QualType CapType = BC.getVariable()->getType();
if (CapType.hasNonTrivialToPrimitiveDestructCUnion() ||
CapType.hasNonTrivialToPrimitiveCopyCUnion())
S.checkNonTrivialCUnion(BC.getVariable()->getType(),
BD->getCaretLocation(),
Sema::NTCUC_BlockCapture,
Sema::NTCUK_Destruct|Sema::NTCUK_Copy);
}
}

Expand Down

0 comments on commit 81b03d4

Please sign in to comment.