Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -2915,6 +2915,11 @@ /// information. MemberSpecializationInfo *SpecializationInfo; + /// FlagBits - Cached calculation of all the bits that are set in enumerators + /// for use with the flag_enum attribute. This is not valid except on a + /// complete definition with that attribute. + llvm::APInt FlagBits; + EnumDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, SourceLocation IdLoc, IdentifierInfo *Id, EnumDecl *PrevDecl, bool Scoped, bool ScopedUsingClassTag, bool Fixed) @@ -3059,6 +3064,14 @@ NumNegativeBits = Num; } + llvm::APInt *getFlagBits() { + return &FlagBits; + } + + const llvm::APInt *getFlagBits() const { + return &FlagBits; + } + /// \brief Returns true if this is a C++11 scoped enumeration. bool isScoped() const { return IsScoped; Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -696,6 +696,12 @@ let Documentation = [Undocumented]; } +def FlagEnum : InheritableAttr { + let Spellings = [GNU<"flag_enum">]; + let Subjects = SubjectList<[Enum]>; + let Documentation = [FlagEnumDocs]; +} + def Flatten : InheritableAttr { let Spellings = [GCC<"flatten">]; let Subjects = SubjectList<[Function], ErrorDiag>; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -1031,6 +1031,16 @@ }]; } +def FlagEnumDocs : Documentation { + let Category = DocCatType; + let Content = [{ +This attribute can be added to an enumerator to signal to the compiler that it +is intended to be used as a flag type. This will cause the compiler to assume +that the range of the type includes all of the values that you can get by +manipulating bits of the enumerator when issuing warnings. + }]; +} + def MSInheritanceDocs : Documentation { let Category = DocCatType; let Heading = "__single_inhertiance, __multiple_inheritance, __virtual_inheritance"; Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -187,6 +187,7 @@ def DanglingElse: DiagGroup<"dangling-else">; def DanglingField : DiagGroup<"dangling-field">; def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">; +def FlagEnum : DiagGroup<"flag-enum">; def InfiniteRecursion : DiagGroup<"infinite-recursion">; def GNUImaginaryConstant : DiagGroup<"gnu-imaginary-constant">; def IgnoredQualifiers : DiagGroup<"ignored-qualifiers">; Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2219,7 +2219,7 @@ "%0 attribute only applies to %select{functions|unions|" "variables and functions|functions and methods|parameters|" "functions, methods and blocks|functions, methods, and classes|" - "functions, methods, and parameters|classes|variables|methods|" + "functions, methods, and parameters|classes|enums|variables|methods|" "variables, functions and labels|fields and global variables|structs|" "variables and typedefs|thread-local variables|" "variables and fields|variables, data members and tag types|" @@ -4005,6 +4005,9 @@ def ext_enumerator_increment_too_large : ExtWarn< "incremented enumerator value %0 is not representable in the " "largest integer type">, InGroup; +def warn_flag_enum_constant_out_of_range : Warning< + "enumeration value %0 is out of range of flags in enumeration type %1">, + InGroup; def warn_illegal_constant_array_size : Extension< "size of static array must be an integer constant expression">; Index: include/clang/Sema/AttributeList.h =================================================================== --- include/clang/Sema/AttributeList.h +++ include/clang/Sema/AttributeList.h @@ -822,6 +822,7 @@ ExpectedFunctionMethodOrClass, ExpectedFunctionMethodOrParameter, ExpectedClass, + ExpectedEnum, ExpectedVariable, ExpectedMethod, ExpectedVariableFunctionOrLabel, Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -7847,6 +7847,12 @@ Expr *SrcExpr, AssignmentAction Action, bool *Complained = nullptr); + /// IsValueInFlagEnum - Determine if a value is allowed as part of a flag + /// enum. If AllowMask is true, then we also allow the complement of a valid + /// value, to be used as a mask. + bool IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt *Val, + bool AllowMask); + /// DiagnoseAssignmentEnum - Warn if assignment to enum is a constant /// integer not in the range of enum values. void DiagnoseAssignmentEnum(QualType DstType, QualType SrcType, Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -13474,6 +13474,48 @@ } } +bool +Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt *Val, + bool AllowMask) { + assert(ED->hasAttr() && "looking for value in non-flag enum"); + + llvm::APInt FlagMask = ~*ED->getFlagBits(); + unsigned Width = FlagMask.getBitWidth(); + + // We will try a zero-extended value for the regular check first. + llvm::APInt ExtVal = Val->zextOrSelf(Width); + + // A value is in a flag enum if either its bits are a subset of the enum's + // flag bits (the first condition) or we are allowing masks and the same is + // true of its complement (the second condition). When masks are allowed, we + // allow the common idiom of ~(enum1 | enum2) to be a valid enum value. + // + // While it's true that any value could be used as a mask, the assumption is + // that a mask will have all of the insignificant bits set. Anything else is + // likely a logic error. + if (!(FlagMask & ExtVal)) + return true; + + if (AllowMask) { + // Try a one-extended value instead. This can happen if the enum is wider + // than the constant used, in C with extensions to allow for wider enums. + // The mask will still have the correct behaviour, so we give the user the + // benefit of the doubt. + // + // FIXME: This heuristic can cause weird results if the enum was extended + // to a larger type and is signed, because then bit-masks of smaller types + // that get extended will fall out of range (e.g. ~0x1u). We currently don't + // detect that case and will get a false positive for it. In most cases, + // though, it can be fixed by making it a signed type (e.g. ~0x1), so it may + // be fine just to accept this as a warning. + ExtVal |= llvm::APInt::getHighBitsSet(Width, Width - Val->getBitWidth()); + if (!(FlagMask & ~ExtVal)) + return true; + } + + return false; +} + void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceLocation LBraceLoc, SourceLocation RBraceLoc, Decl *EnumDeclX, ArrayRef Elements, @@ -13559,10 +13601,8 @@ BestPromotionType = Context.getPromotedIntegerType(BestType); else BestPromotionType = BestType; - // We don't need to set BestWidth, because BestType is going to be the type - // of the enumerators, but we do anyway because otherwise some compilers - // warn that it might be used uninitialized. - BestWidth = CharWidth; + + BestWidth = Context.getIntWidth(BestType); } else if (NumNegativeBits) { // If there is a negative value, figure out the smallest integer type (of @@ -13627,8 +13667,14 @@ } } + bool FlagType = Enum->hasAttr(); + llvm::APInt *FlagBits = Enum->getFlagBits(); + if (FlagType) + *FlagBits = llvm::APInt(BestWidth, 0); + // Loop over all of the enumerator constants, changing their types to match - // the type of the enum if needed. + // the type of the enum if needed. If we have a flag type, we also prepare the + // FlagBits cache. for (unsigned i = 0, e = Elements.size(); i != e; ++i) { EnumConstantDecl *ECD = cast_or_null(Elements[i]); if (!ECD) continue; // Already issued a diagnostic. @@ -13660,7 +13706,7 @@ // enum-specifier, each enumerator has the type of its // enumeration. ECD->setType(EnumType); - continue; + goto flagbits; } else { NewTy = BestType; NewWidth = BestWidth; @@ -13687,8 +13733,32 @@ ECD->setType(EnumType); else ECD->setType(NewTy); + +flagbits: + // Check to see if we have a constant with exactly one bit set. Note that x + // & (x - 1) will be nonzero if and only if x has more than one bit set. + if (FlagType) { + llvm::APInt ExtVal = InitVal.zextOrSelf(BestWidth); + if (ExtVal != 0 && !(ExtVal & (ExtVal - 1))) { + *FlagBits |= ExtVal; + } + } } + if (FlagType) { + for (unsigned i = 0, e = Elements.size(); i != e; ++i) { + EnumConstantDecl *ECD = cast_or_null(Elements[i]); + if (!ECD) continue; // Already issued a diagnostic. + + llvm::APSInt InitVal = ECD->getInitVal(); + if (InitVal != 0 && !IsValueInFlagEnum(Enum, &InitVal, true)) + Diag(ECD->getLocation(), diag::warn_flag_enum_constant_out_of_range) + << ECD->getName() << Enum->getName(); + } + } + + + Enum->completeDefinition(BestType, BestPromotionType, NumPositiveBits, NumNegativeBits); Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -4284,6 +4284,9 @@ case AttributeList::AT_OptimizeNone: handleOptimizeNoneAttr(S, D, Attr); break; + case AttributeList::AT_FlagEnum: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_Flatten: handleSimpleAttribute(S, D, Attr); break; Index: lib/Sema/SemaStmt.cpp =================================================================== --- lib/Sema/SemaStmt.cpp +++ lib/Sema/SemaStmt.cpp @@ -1038,13 +1038,21 @@ EnumValsTy::iterator EIend = std::unique(EnumVals.begin(), EnumVals.end(), EqEnumVals); + bool FlagType = ED->hasAttr(); // See which case values aren't in enum. EnumValsTy::const_iterator EI = EnumVals.begin(); for (CaseValsTy::const_iterator CI = CaseVals.begin(); - CI != CaseVals.end(); CI++) { - while (EI != EIend && EI->first < CI->first) - EI++; - if (EI == EIend || EI->first > CI->first) { + CI != CaseVals.end(); CI++) { + bool Diagnose; + if (FlagType) { + Diagnose = !IsValueInFlagEnum(ED, &CI->first, false); + } else { + while (EI != EIend && EI->first < CI->first) + EI++; + Diagnose = EI == EIend || EI->first > CI->first; + } + + if (Diagnose) { Expr *CaseExpr = CI->second->getLHS(); if (ShouldDiagnoseSwitchCaseNotInEnum(Context, ED, CaseExpr)) Diag(CaseExpr->getExprLoc(), diag::warn_not_in_enum) @@ -1054,23 +1062,36 @@ // See which of case ranges aren't in enum EI = EnumVals.begin(); for (CaseRangesTy::const_iterator RI = CaseRanges.begin(); - RI != CaseRanges.end() && EI != EIend; RI++) { - while (EI != EIend && EI->first < RI->first) - EI++; - - if (EI == EIend || EI->first != RI->first) { - Expr *CaseExpr = RI->second->getLHS(); - if (ShouldDiagnoseSwitchCaseNotInEnum(Context, ED, CaseExpr)) - Diag(CaseExpr->getExprLoc(), diag::warn_not_in_enum) - << CondTypeBeforePromotion; + RI != CaseRanges.end() && EI != EIend; RI++) { + bool Diagnose; + if (FlagType) { + Diagnose = !IsValueInFlagEnum(ED, &RI->first, false); + } else { + while (EI != EIend && EI->first < RI->first) + EI++; + Diagnose = EI == EIend || EI->first != RI->first; + + if (Diagnose) { + Expr *CaseExpr = RI->second->getLHS(); + if (ShouldDiagnoseSwitchCaseNotInEnum(Context, ED, CaseExpr)) + Diag(CaseExpr->getExprLoc(), diag::warn_not_in_enum) + << CondTypeBeforePromotion; + } } llvm::APSInt Hi = RI->second->getRHS()->EvaluateKnownConstInt(Context); AdjustAPSInt(Hi, CondWidth, CondIsSigned); - while (EI != EIend && EI->first < Hi) - EI++; - if (EI == EIend || EI->first != Hi) { + + if (FlagType) { + Diagnose = !IsValueInFlagEnum(ED, &Hi, false); + } else { + while (EI != EIend && EI->first < Hi) + EI++; + Diagnose = EI == EIend || EI->first != Hi; + } + + if (Diagnose) { Expr *CaseExpr = RI->second->getRHS(); if (ShouldDiagnoseSwitchCaseNotInEnum(Context, ED, CaseExpr)) Diag(CaseExpr->getExprLoc(), diag::warn_not_in_enum) @@ -1172,30 +1193,37 @@ llvm::APSInt RhsVal = SrcExpr->EvaluateKnownConstInt(Context); AdjustAPSInt(RhsVal, DstWidth, DstIsSigned); const EnumDecl *ED = ET->getDecl(); - typedef SmallVector, 64> - EnumValsTy; - EnumValsTy EnumVals; - - // Gather all enum values, set their type and sort them, - // allowing easier comparison with rhs constant. - for (auto *EDI : ED->enumerators()) { - llvm::APSInt Val = EDI->getInitVal(); - AdjustAPSInt(Val, DstWidth, DstIsSigned); - EnumVals.push_back(std::make_pair(Val, EDI)); - } - if (EnumVals.empty()) - return; - std::stable_sort(EnumVals.begin(), EnumVals.end(), CmpEnumVals); - EnumValsTy::iterator EIend = - std::unique(EnumVals.begin(), EnumVals.end(), EqEnumVals); - - // See which values aren't in the enum. - EnumValsTy::const_iterator EI = EnumVals.begin(); - while (EI != EIend && EI->first < RhsVal) - EI++; - if (EI == EIend || EI->first != RhsVal) { - Diag(SrcExpr->getExprLoc(), diag::warn_not_in_enum_assignment) + + if (ED->hasAttr()) { + if (!IsValueInFlagEnum(ED, &RhsVal, true)) + Diag(SrcExpr->getExprLoc(), diag::warn_not_in_enum_assignment) << DstType.getUnqualifiedType(); + } else { + typedef SmallVector, 64> + EnumValsTy; + EnumValsTy EnumVals; + + // Gather all enum values, set their type and sort them, + // allowing easier comparison with rhs constant. + for (auto *EDI : ED->enumerators()) { + llvm::APSInt Val = EDI->getInitVal(); + AdjustAPSInt(Val, DstWidth, DstIsSigned); + EnumVals.push_back(std::make_pair(Val, EDI)); + } + if (EnumVals.empty()) + return; + std::stable_sort(EnumVals.begin(), EnumVals.end(), CmpEnumVals); + EnumValsTy::iterator EIend = + std::unique(EnumVals.begin(), EnumVals.end(), EqEnumVals); + + // See which values aren't in the enum. + EnumValsTy::const_iterator EI = EnumVals.begin(); + while (EI != EIend && EI->first < RhsVal) + EI++; + if (EI == EIend || EI->first != RhsVal) { + Diag(SrcExpr->getExprLoc(), diag::warn_not_in_enum_assignment) + << DstType.getUnqualifiedType(); + } } } } Index: utils/TableGen/ClangAttrEmitter.cpp =================================================================== --- utils/TableGen/ClangAttrEmitter.cpp +++ utils/TableGen/ClangAttrEmitter.cpp @@ -2170,7 +2170,8 @@ Namespace = 1U << 11, Field = 1U << 12, CXXMethod = 1U << 13, - ObjCProtocol = 1U << 14 + ObjCProtocol = 1U << 14, + Enum = 1U << 15 }; uint32_t SubMask = 0; @@ -2204,6 +2205,7 @@ .Case("Namespace", Namespace) .Case("Field", Field) .Case("CXXMethod", CXXMethod) + .Case("Enum", Enum) .Default(0); if (!V) { // Something wasn't in our mapping, so be helpful and let the developer @@ -2222,6 +2224,7 @@ case Var: return "ExpectedVariable"; case Param: return "ExpectedParameter"; case Class: return "ExpectedClass"; + case Enum: return "ExpectedEnum"; case CXXMethod: // FIXME: Currently, this maps to ExpectedMethod based on existing code, // but should map to something a bit more accurate at some point.