diff options
author | Jordan Rose <jordan_rose@apple.com> | 2013-01-10 18:50:46 +0000 |
---|---|---|
committer | Jordan Rose <jordan_rose@apple.com> | 2013-01-10 18:50:46 +0000 |
commit | 98e7369d9ef5e53964cc8ae48ec2dfcf6c4633c0 (patch) | |
tree | 63d8e50e1b9c307e7223e567470130d3e34cf8be | |
parent | 3f8c7f3a07df65580a9ca0fe3e956b8a40956a79 (diff) |
Error if an anonymous DiagGroup is referenced multiple times.
Not only is this inefficient for TableGen, it's annoying for maintenance
when renaming warning flags (unusual) or adding those flags to a group
(more likely).
This uses the new fix-it infrastructure for LLVM's SourceMgr/SMDiagnostic,
as well as a few changes to TableGen to track more source information.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@172087 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | test/TableGen/DiagnosticBase.inc | 35 | ||||
-rw-r--r-- | test/TableGen/anonymous-groups.td | 42 | ||||
-rw-r--r-- | test/TableGen/lit.local.cfg | 1 | ||||
-rw-r--r-- | test/TableGen/tg-fixits.td | 41 | ||||
-rw-r--r-- | utils/TableGen/ClangDiagnosticsEmitter.cpp | 121 |
5 files changed, 238 insertions, 2 deletions
diff --git a/test/TableGen/DiagnosticBase.inc b/test/TableGen/DiagnosticBase.inc new file mode 100644 index 0000000000..afa85f538b --- /dev/null +++ b/test/TableGen/DiagnosticBase.inc @@ -0,0 +1,35 @@ +// Define the diagnostic mappings. +class DiagMapping; +def MAP_IGNORE : DiagMapping; +def MAP_WARNING : DiagMapping; +def MAP_ERROR : DiagMapping; +def MAP_FATAL : DiagMapping; + +// Define the diagnostic classes. +class DiagClass; +def CLASS_NOTE : DiagClass; +def CLASS_WARNING : DiagClass; +def CLASS_EXTENSION : DiagClass; +def CLASS_ERROR : DiagClass; + +class DiagGroup<string Name, list<DiagGroup> subgroups = []> { + string GroupName = Name; + list<DiagGroup> SubGroups = subgroups; + string CategoryName = ""; +} +class InGroup<DiagGroup G> { DiagGroup Group = G; } + +// All diagnostics emitted by the compiler are an indirect subclass of this. +class Diagnostic<string text, DiagClass DC, DiagMapping defaultmapping> { + string Text = text; + DiagClass Class = DC; + DiagMapping DefaultMapping = defaultmapping; + DiagGroup Group; + string CategoryName = ""; +} + +class Error<string str> : Diagnostic<str, CLASS_ERROR, MAP_ERROR>; +class Warning<string str> : Diagnostic<str, CLASS_WARNING, MAP_WARNING>; +class Extension<string str> : Diagnostic<str, CLASS_EXTENSION, MAP_IGNORE>; +class ExtWarn<string str> : Diagnostic<str, CLASS_EXTENSION, MAP_WARNING>; +class Note<string str> : Diagnostic<str, CLASS_NOTE, MAP_FATAL/*ignored*/>; diff --git a/test/TableGen/anonymous-groups.td b/test/TableGen/anonymous-groups.td new file mode 100644 index 0000000000..acc0a211b6 --- /dev/null +++ b/test/TableGen/anonymous-groups.td @@ -0,0 +1,42 @@ +// RUN: clang-tblgen -gen-clang-diag-groups -I%S %s -o /dev/null 2>&1 | FileCheck --strict-whitespace %s +include "DiagnosticBase.inc" + +// Do not move this line; it is referred to by absolute line number in the +// FileCheck lines below. +def NamedGroup : DiagGroup<"name">; + + +def InNamedGroup : Warning<"">, InGroup<DiagGroup<"name">>; +// CHECK: anonymous-groups.td:[[@LINE-1]]:41: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^def InNamedGroup : Warning<"">, InGroup<DiagGroup<"name">>;}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: {{^ InGroup<NamedGroup>}} +// CHECK-NEXT: anonymous-groups.td:6:1: note: group defined here +// CHECK-NEXT: def NamedGroup : DiagGroup<"name">; +// CHECK-NEXT: ^ + + +def AlsoInNamedGroup : Warning<"">, InGroup < DiagGroup<"name"> >; +// CHECK: anonymous-groups.td:[[@LINE-1]]:48: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^def AlsoInNamedGroup : Warning<"">, InGroup < DiagGroup<"name"> >;}} +// CHECK-NEXT: {{^ ~~~~~~~~~~~\^~~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: {{^ InGroup<NamedGroup>}} +// CHECK-NEXT: anonymous-groups.td:6:1: note: group defined here +// CHECK-NEXT: def NamedGroup : DiagGroup<"name">; +// CHECK-NEXT: ^ + + +def AnonymousGroup : Warning<"">, InGroup<DiagGroup<"anonymous">>; +def AlsoAnonymousGroup : Warning<"">, InGroup<DiagGroup<"anonymous">>; +def AnonymousGroupAgain : Warning<"">, + InGroup<DiagGroup<"anonymous">>; + +// CHECK: anonymous-groups.td:[[@LINE-5]]:43: error: group 'anonymous' is referred to anonymously +// CHECK-NEXT: {{^def AnonymousGroup : Warning<"">, InGroup<DiagGroup<"anonymous">>;}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: anonymous-groups.td:[[@LINE-7]]:47: note: also referenced here +// CHECK-NEXT: {{^def AlsoAnonymousGroup : Warning<"">, InGroup<DiagGroup<"anonymous">>;}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: anonymous-groups.td:[[@LINE-8]]:11: note: also referenced here +// CHECK-NEXT: {{^ InGroup<DiagGroup<"anonymous">>;}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~~~~~~~~~~~~~~}} diff --git a/test/TableGen/lit.local.cfg b/test/TableGen/lit.local.cfg new file mode 100644 index 0000000000..9a4a0144f7 --- /dev/null +++ b/test/TableGen/lit.local.cfg @@ -0,0 +1 @@ +config.suffixes = ['.td'] diff --git a/test/TableGen/tg-fixits.td b/test/TableGen/tg-fixits.td new file mode 100644 index 0000000000..d04a6a67e4 --- /dev/null +++ b/test/TableGen/tg-fixits.td @@ -0,0 +1,41 @@ +// RUN: clang-tblgen -gen-clang-diag-groups -I%S %s -o /dev/null 2>&1 | FileCheck --strict-whitespace %s +include "DiagnosticBase.inc" + +def NamedGroup : DiagGroup<"name">; + +def InNamedGroup : Warning<"">, InGroup<DiagGroup<"name">>; +// CHECK: tg-fixits.td:[[@LINE-1]]:41: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^def InNamedGroup : Warning<"">, InGroup<DiagGroup<"name">>;}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: {{^ InGroup<NamedGroup>}} + +def Wrapped : Warning<"">, InGroup<DiagGroup< + "name">>; +// CHECK: tg-fixits.td:[[@LINE-2]]:36: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^def Wrapped : Warning<"">, InGroup<DiagGroup<}} +// CHECK-NEXT: {{^ ~~~~~~~~\^~~~~~~~~~}} +// CHECK-NEXT: {{^ InGroup<NamedGroup>}} + +def AlsoWrapped : Warning<"">, InGroup< + DiagGroup<"name">>; +// CHECK: tg-fixits.td:[[@LINE-1]]:3: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^ DiagGroup<"name">>;}} +// CHECK-NEXT: {{^~~\^~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: {{^InGroup<NamedGroup>}} + +// The following lines contain hard tabs (\t); do not change this! +def HardTabs : Warning<"">, + InGroup< DiagGroup<"name"> >; +// CHECK: tg-fixits.td:[[@LINE-1]]:11: error: group 'name' is referred to anonymously +// CHECK-NEXT: {{^ InGroup< DiagGroup<"name"> >;}} +// CHECK-NEXT: {{^ ~~~~~~~~~~~~~~~~\^~~~~~~~~~~~~~~~~~~~~~~~~}} +// CHECK-NEXT: {{^ InGroup<NamedGrop>}} + +// The following line has Unicode characters in it; do not change them! +// FIXME: For now, we just give up on printing carets/ranges/fixits for +// lines with Unicode in them, because SMDiagnostic don't keep a byte<->column +// map around to line things up like Clang does. +def Unicode : Warning<"ユニコード">, InGroup<DiagGroup<"name">>; +// CHECK: tg-fixits.td:[[@LINE-1]]:51: error: group 'name' is referred to anonymously +// CHECK-NEXT: def Unicode : Warning<"{{[^"]+}}">, InGroup<DiagGroup<"name">>; +// CHECK-NEXT: note: diff --git a/utils/TableGen/ClangDiagnosticsEmitter.cpp b/utils/TableGen/ClangDiagnosticsEmitter.cpp index b7b8e09d88..448fb7013f 100644 --- a/utils/TableGen/ClangDiagnosticsEmitter.cpp +++ b/utils/TableGen/ClangDiagnosticsEmitter.cpp @@ -14,8 +14,12 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" #include "llvm/TableGen/Error.h" @@ -127,14 +131,41 @@ namespace { std::vector<const Record*> DiagsInGroup; std::vector<std::string> SubGroups; unsigned IDNo; + + const Record *ExplicitDef; + + GroupInfo() : ExplicitDef(0) {} }; } // end anonymous namespace. +static bool beforeThanCompare(const Record *LHS, const Record *RHS) { + assert(!LHS->getLoc().empty() && !RHS->getLoc().empty()); + return + LHS->getLoc().front().getPointer() < RHS->getLoc().front().getPointer(); +} + +static bool beforeThanCompareGroups(const GroupInfo *LHS, const GroupInfo *RHS){ + assert(!LHS->DiagsInGroup.empty() && !RHS->DiagsInGroup.empty()); + return beforeThanCompare(LHS->DiagsInGroup.front(), + RHS->DiagsInGroup.front()); +} + +static SMRange findSuperClassRange(const Record *R, StringRef SuperName) { + ArrayRef<Record *> Supers = R->getSuperClasses(); + + for (size_t i = 0, e = Supers.size(); i < e; ++i) + if (Supers[i]->getName() == SuperName) + return R->getSuperClassRanges()[i]; + + return SMRange(); +} + /// \brief Invert the 1-[0/1] mapping of diags to group into a one to many /// mapping of groups to diags in the group. static void groupDiagnostics(const std::vector<Record*> &Diags, const std::vector<Record*> &DiagGroups, std::map<std::string, GroupInfo> &DiagsInGroup) { + for (unsigned i = 0, e = Diags.size(); i != e; ++i) { const Record *R = Diags[i]; DefInit *DI = dyn_cast<DefInit>(R->getValueInit("Group")); @@ -144,13 +175,25 @@ static void groupDiagnostics(const std::vector<Record*> &Diags, std::string GroupName = DI->getDef()->getValueAsString("GroupName"); DiagsInGroup[GroupName].DiagsInGroup.push_back(R); } - + + typedef SmallPtrSet<GroupInfo *, 16> GroupSetTy; + GroupSetTy ImplicitGroups; + // Add all DiagGroup's to the DiagsInGroup list to make sure we pick up empty // groups (these are warnings that GCC supports that clang never produces). for (unsigned i = 0, e = DiagGroups.size(); i != e; ++i) { Record *Group = DiagGroups[i]; GroupInfo &GI = DiagsInGroup[Group->getValueAsString("GroupName")]; - + if (Group->isAnonymous()) { + if (GI.DiagsInGroup.size() > 1) + ImplicitGroups.insert(&GI); + } else { + if (GI.ExplicitDef) + assert(GI.ExplicitDef == Group); + else + GI.ExplicitDef = Group; + } + std::vector<Record*> SubGroups = Group->getValueAsListOfDefs("SubGroups"); for (unsigned j = 0, e = SubGroups.size(); j != e; ++j) GI.SubGroups.push_back(SubGroups[j]->getValueAsString("GroupName")); @@ -161,6 +204,80 @@ static void groupDiagnostics(const std::vector<Record*> &Diags, for (std::map<std::string, GroupInfo>::iterator I = DiagsInGroup.begin(), E = DiagsInGroup.end(); I != E; ++I, ++IDNo) I->second.IDNo = IDNo; + + // Sort the implicit groups, so we can warn about them deterministically. + SmallVector<GroupInfo *, 16> SortedGroups(ImplicitGroups.begin(), + ImplicitGroups.end()); + for (SmallVectorImpl<GroupInfo *>::iterator I = SortedGroups.begin(), + E = SortedGroups.end(); + I != E; ++I) { + MutableArrayRef<const Record *> GroupDiags = (*I)->DiagsInGroup; + std::sort(GroupDiags.begin(), GroupDiags.end(), beforeThanCompare); + } + std::sort(SortedGroups.begin(), SortedGroups.end(), beforeThanCompareGroups); + + // Warn about the same group being used anonymously in multiple places. + for (SmallVectorImpl<GroupInfo *>::const_iterator I = SortedGroups.begin(), + E = SortedGroups.end(); + I != E; ++I) { + ArrayRef<const Record *> GroupDiags = (*I)->DiagsInGroup; + + if ((*I)->ExplicitDef) { + std::string Name = (*I)->ExplicitDef->getValueAsString("GroupName"); + for (ArrayRef<const Record *>::const_iterator DI = GroupDiags.begin(), + DE = GroupDiags.end(); + DI != DE; ++DI) { + const DefInit *GroupInit = cast<DefInit>((*DI)->getValueInit("Group")); + const Record *NextDiagGroup = GroupInit->getDef(); + if (NextDiagGroup == (*I)->ExplicitDef) + continue; + + SMRange InGroupRange = findSuperClassRange(*DI, "InGroup"); + SmallString<64> Replacement; + if (InGroupRange.isValid()) { + Replacement += "InGroup<"; + Replacement += (*I)->ExplicitDef->getName(); + Replacement += ">"; + } + SMFixIt FixIt(InGroupRange, Replacement.str()); + + SrcMgr.PrintMessage(NextDiagGroup->getLoc().front(), + SourceMgr::DK_Error, + Twine("group '") + Name + + "' is referred to anonymously", + ArrayRef<SMRange>(), + InGroupRange.isValid() ? FixIt + : ArrayRef<SMFixIt>()); + SrcMgr.PrintMessage((*I)->ExplicitDef->getLoc().front(), + SourceMgr::DK_Note, "group defined here"); + } + } else { + // If there's no existing named group, we should just warn once and use + // notes to list all the other cases. + ArrayRef<const Record *>::const_iterator DI = GroupDiags.begin(), + DE = GroupDiags.end(); + assert(DI != DE && "We only care about groups with multiple uses!"); + + const DefInit *GroupInit = cast<DefInit>((*DI)->getValueInit("Group")); + const Record *NextDiagGroup = GroupInit->getDef(); + std::string Name = NextDiagGroup->getValueAsString("GroupName"); + + SMRange InGroupRange = findSuperClassRange(*DI, "InGroup"); + SrcMgr.PrintMessage(NextDiagGroup->getLoc().front(), + SourceMgr::DK_Error, + Twine("group '") + Name + + "' is referred to anonymously", + InGroupRange); + + for (++DI; DI != DE; ++DI) { + GroupInit = cast<DefInit>((*DI)->getValueInit("Group")); + InGroupRange = findSuperClassRange(*DI, "InGroup"); + SrcMgr.PrintMessage(GroupInit->getDef()->getLoc().front(), + SourceMgr::DK_Note, "also referenced here", + InGroupRange); + } + } + } } //===----------------------------------------------------------------------===// |