<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Oct 16, 2015 at 4:43 AM, Angel Garcia Gomez via cfe-commits <span dir="ltr"><<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Author: angelgarcia<br>
Date: Fri Oct 16 06:43:49 2015<br>
New Revision: 250509<br>
<br>
URL: <a href="http://llvm.org/viewvc/llvm-project?rev=250509&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=250509&view=rev</a><br>
Log:<br>
Fix overlapping replacements in clang-tidy.<br>
<br>
Summary: Prevent clang-tidy from applying fixes to errors that overlap with other errors' fixes, with one exception: if one fix is completely contained inside another one, then we can apply the big one.<br>
<br>
Reviewers: bkramer, klimek<br>
<br>
Subscribers: djasper, cfe-commits, alexfh<br>
<br>
Differential Revision: <a href="http://reviews.llvm.org/D13516" rel="noreferrer" target="_blank">http://reviews.llvm.org/D13516</a><br>
<br>
Modified:<br>
    clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp<br>
    clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h<br>
    clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp<br>
<br>
Modified: clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp?rev=250509&r1=250508&r2=250509&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp?rev=250509&r1=250508&r2=250509&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp (original)<br>
+++ clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.cpp Fri Oct 16 06:43:49 2015<br>
@@ -22,8 +22,8 @@<br>
 #include "clang/Basic/DiagnosticOptions.h"<br>
 #include "clang/Frontend/DiagnosticRenderer.h"<br>
 #include "llvm/ADT/SmallString.h"<br>
-#include <set><br>
 #include <tuple><br>
+#include <vector><br>
 using namespace clang;<br>
 using namespace tidy;<br>
<br>
@@ -146,8 +146,7 @@ static llvm::Regex ConsumeGlob(StringRef<br>
 }<br>
<br>
 GlobList::GlobList(StringRef Globs)<br>
-    : Positive(!ConsumeNegativeIndicator(Globs)),<br>
-      Regex(ConsumeGlob(Globs)),<br>
+    : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),<br>
       NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}<br>
<br>
 bool GlobList::contains(StringRef S, bool Contains) {<br>
@@ -222,9 +221,7 @@ const ClangTidyOptions &ClangTidyContext<br>
   return CurrentOptions;<br>
 }<br>
<br>
-void ClangTidyContext::setCheckProfileData(ProfileData *P) {<br>
-  Profile = P;<br>
-}<br>
+void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; }<br>
<br>
 GlobList &ClangTidyContext::getChecksFilter() {<br>
   assert(CheckFilter != nullptr);<br>
@@ -296,16 +293,16 @@ void ClangTidyDiagnosticConsumer::Handle<br>
       // This is a compiler diagnostic without a warning option. Assign check<br>
       // name based on its level.<br>
       switch (DiagLevel) {<br>
-        case DiagnosticsEngine::Error:<br>
-        case DiagnosticsEngine::Fatal:<br>
-          CheckName = "clang-diagnostic-error";<br>
-          break;<br>
-        case DiagnosticsEngine::Warning:<br>
-          CheckName = "clang-diagnostic-warning";<br>
-          break;<br>
-        default:<br>
-          CheckName = "clang-diagnostic-unknown";<br>
-          break;<br>
+      case DiagnosticsEngine::Error:<br>
+      case DiagnosticsEngine::Fatal:<br>
+        CheckName = "clang-diagnostic-error";<br>
+        break;<br>
+      case DiagnosticsEngine::Warning:<br>
+        CheckName = "clang-diagnostic-warning";<br>
+        break;<br>
+      default:<br>
+        CheckName = "clang-diagnostic-unknown";<br>
+        break;<br>
       }<br>
     }<br>
<br>
@@ -340,7 +337,7 @@ bool ClangTidyDiagnosticConsumer::passes<br>
                                                    unsigned LineNumber) const {<br>
   if (Context.getGlobalOptions().LineFilter.empty())<br>
     return true;<br>
-  for (const FileFilter& Filter : Context.getGlobalOptions().LineFilter) {<br>
+  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {<br>
     if (FileName.endswith(Filter.Name)) {<br>
       if (Filter.LineRanges.empty())<br>
         return true;<br>
@@ -398,26 +395,147 @@ llvm::Regex *ClangTidyDiagnosticConsumer<br>
   return HeaderFilter.get();<br>
 }<br>
<br>
+void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(<br>
+    SmallVectorImpl<ClangTidyError> &Errors) const {<br>
+  // Each error is modelled as the set of intervals in which it applies<br>
+  // replacements. To detect overlapping replacements, we use a sweep line<br>
+  // algorithm over these sets of intervals.<br>
+  // An event here consists of the opening or closing of an interval. During the<br>
+  // proccess, we maintain a counter with the amount of open intervals. If we<br>
+  // find an endpoint of an interval and this counter is different from 0, it<br>
+  // means that this interval overlaps with another one, so we set it as<br>
+  // inapplicable.<br>
+  struct Event {<br>
+    // An event can be either the begin or the end of an interval.<br>
+    enum EventType {<br>
+      ET_Begin = 1,<br>
+      ET_End = -1,<br>
+    };<br>
+<br>
+    Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,<br>
+          unsigned ErrorSize)<br>
+        : Type(Type), ErrorId(ErrorId) {<br>
+      // The events are going to be sorted by their position. In case of draw:<br>
+      //<br>
+      // * If an interval ends at the same position at which other interval<br>
+      //   begins, this is not an overlapping, so we want to remove the ending<br>
+      //   interval before adding the starting one: end events have higher<br>
+      //   priority than begin events.<br>
+      //<br>
+      // * If we have several begin points at the same position, we will mark as<br>
+      //   inapplicable the ones that we proccess later, so the first one has to<br>
+      //   be the one with the latest end point, because this one will contain<br>
+      //   all the other intervals. For the same reason, if we have several end<br>
+      //   points in the same position, the last one has to be the one with the<br>
+      //   earliest begin point. In both cases, we sort non-increasingly by the<br>
+      //   position of the complementary.<br>
+      //<br>
+      // * In case of two equal intervals, the one whose error is bigger can<br>
+      //   potentially contain the other one, so we want to proccess its begin<br>
+      //   points before and its end points later.<br>
+      //<br>
+      // * Finally, if we have two equal intervals whose errors have the same<br>
+      //   size, none of them will be strictly contained inside the other.<br>
+      //   Sorting by ErrorId will guarantee that the begin point of the first<br>
+      //   one will be proccessed before, disallowing the second one, and the<br>
+      //   end point of the first one will also be proccessed before,<br>
+      //   disallowing the first one.<br>
+      if (Type == ET_Begin)<br>
+        Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);<br>
+      else<br>
+        Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);<br>
+    }<br>
+<br>
+    bool operator<(const Event &Other) const {<br>
+      return Priority < Other.Priority;<br>
+    }<br>
+<br>
+    // Determines if this event is the begin or the end of an interval.<br>
+    EventType Type;<br>
+    // The index of the error to which the interval that generated this event<br>
+    // belongs.<br>
+    unsigned ErrorId;<br>
+    // The events will be sorted based on this field.<br>
+    std::tuple<unsigned, EventType, int, int, unsigned> Priority;<br>
+  };<br>
+<br>
+  // Compute error sizes.<br>
+  std::vector<int> Sizes;<br>
+  for (const auto &Error : Errors) {<br>
+    int Size = 0;<br>
+    for (const auto &Replace : Error.Fix)<br>
+      Size += Replace.getLength();<br>
+    Sizes.push_back(Size);<br>
+  }<br>
+<br>
+  // Build events from error intervals.<br>
+  std::vector<Event> Events;<br>
+  for (unsigned I = 0; I < Errors.size(); ++I) {<br>
+    for (const auto &Replace : Errors[I].Fix) {<br>
+      unsigned Begin = Replace.getOffset();<br>
+      unsigned End = Begin + Replace.getLength();<br>
+      // FIXME: Handle empty intervals, such as those from insertions.<br>
+      if (Begin == End)<br>
+        continue;<br>
+      Events.push_back(Event(Begin, End, Event::ET_Begin, I, Sizes[I]));<br>
+      Events.push_back(Event(Begin, End, Event::ET_End, I, Sizes[I]));<br>
+    }<br>
+  }<br>
+  std::sort(Events.begin(), Events.end());<br>
+<br>
+  // Sweep.<br>
+  std::vector<bool> Apply(Errors.size(), true);<br>
+  int OpenIntervals = 0;<br>
+  for (const auto &Event : Events) {<br>
+    if (Event.Type == Event::ET_End)<br>
+      --OpenIntervals;<br>
+    // This has to be checked after removing the interval from the count if it<br>
+    // is an end event, or before adding it if it is a begin event.<br>
+    if (OpenIntervals != 0)<br>
+      Apply[Event.ErrorId] = false;<br>
+    if (Event.Type == Event::ET_Begin)<br>
+      ++OpenIntervals;<br>
+  }<br>
+  assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");<br>
+<br>
+  for (unsigned I = 0; I < Errors.size(); ++I) {<br>
+    if (!Apply[I]) {<br>
+      Errors[I].Fix.clear();<br>
+      Errors[I].Notes.push_back(<br>
+          ClangTidyMessage("this fix will not be applied because"<br>
+                           " it overlaps with another fix"));<br>
+    }<br>
+  }<br>
+}<br>
+<br>
 namespace {<br>
 struct LessClangTidyError {<br></blockquote><div><br></div><div>Should this just be op< on ClangTidyError?</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
-  bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {<br>
-    const ClangTidyMessage &M1 = LHS->Message;<br>
-    const ClangTidyMessage &M2 = RHS->Message;<br>
+  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {<br>
+    const ClangTidyMessage &M1 = LHS.Message;<br>
+    const ClangTidyMessage &M2 = RHS.Message;<br>
<br>
     return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <<br>
            std::tie(M2.FilePath, M2.FileOffset, M2.Message); </blockquote><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
   }<br>
 };<br>
+struct EqualClangTidyError {<br></blockquote><div><br></div><div>& this op== on ClangTidyError? (I'd probably define it separately, with another tie, rather than using !<&&!< to reduce the work involved in equality testing)<br><br>Or do both of these operations represent truely non-inherent equality/ordering of ClangTidyErrors? (ie: they don't make sense as the defaults, but they make sense in this particular instance)<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+  static LessClangTidyError Less;<br>
+  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {<br>
+    return !Less(LHS, RHS) && !Less(RHS, LHS);<br>
+  }<br>
+};<br>
 } // end anonymous namespace<br>
<br>
 // Flushes the internal diagnostics buffer to the ClangTidyContext.<br>
 void ClangTidyDiagnosticConsumer::finish() {<br>
   finalizeLastError();<br>
-  std::set<const ClangTidyError*, LessClangTidyError> UniqueErrors;<br>
-  for (const ClangTidyError &Error : Errors)<br>
-    UniqueErrors.insert(&Error);<br>
<br>
-  for (const ClangTidyError *Error : UniqueErrors)<br>
-    Context.storeError(*Error);<br>
+  std::sort(Errors.begin(), Errors.end(), LessClangTidyError());<br>
+  Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),<br>
+               Errors.end());<br>
+  removeIncompatibleErrors(Errors);<br>
+<br>
+  for (const ClangTidyError &Error : Errors)<br>
+    Context.storeError(Error);<br>
   Errors.clear();<br>
 }<br>
<br>
Modified: clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h?rev=250509&r1=250508&r2=250509&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h?rev=250509&r1=250508&r2=250509&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h (original)<br>
+++ clang-tools-extra/trunk/clang-tidy/ClangTidyDiagnosticConsumer.h Fri Oct 16 06:43:49 2015<br>
@@ -179,8 +179,8 @@ public:<br>
   ///<br>
   /// Setting a non-null pointer here will enable profile collection in<br>
   /// clang-tidy.<br>
-  void setCheckProfileData(ProfileData* Profile);<br>
-  ProfileData* getCheckProfileData() const { return Profile; }<br>
+  void setCheckProfileData(ProfileData *Profile);<br>
+  ProfileData *getCheckProfileData() const { return Profile; }<br>
<br>
 private:<br>
   // Calls setDiagnosticsEngine() and storeError().<br>
@@ -231,9 +231,11 @@ public:<br>
 private:<br>
   void finalizeLastError();<br>
<br>
+  void removeIncompatibleErrors(SmallVectorImpl<ClangTidyError> &Errors) const;<br>
+<br>
   /// \brief Returns the \c HeaderFilter constructed for the options set in the<br>
   /// context.<br>
-  llvm::Regex* getHeaderFilter();<br>
+  llvm::Regex *getHeaderFilter();<br>
<br>
   /// \brief Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter<br>
   /// according to the diagnostic \p Location.<br>
<br>
Modified: clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp?rev=250509&r1=250508&r2=250509&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp?rev=250509&r1=250508&r2=250509&view=diff</a><br>
==============================================================================<br>
--- clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp (original)<br>
+++ clang-tools-extra/trunk/unittests/clang-tidy/OverlappingReplacementsTest.cpp Fri Oct 16 06:43:49 2015<br>
@@ -77,11 +77,12 @@ public:<br>
     auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);<br>
     std::string NewName = newName(VD->getName());<br>
<br>
-    auto Diag = diag(VD->getLocation(), "refactor")<br>
+    auto Diag = diag(VD->getLocation(), "refactor %0 into %1")<br>
+                << VD->getName() << NewName<br>
                 << FixItHint::CreateReplacement(<br>
-                    CharSourceRange::getTokenRange(VD->getLocation(),<br>
-                                                   VD->getLocation()),<br>
-                    NewName);<br>
+                       CharSourceRange::getTokenRange(VD->getLocation(),<br>
+                                                      VD->getLocation()),<br>
+                       NewName);<br>
<br>
     class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {<br>
     public:<br>
@@ -281,7 +282,7 @@ TEST(OverlappingReplacementsTest, Replac<br>
<br>
   // Apply the UseCharCheck together with the IfFalseCheck.<br>
   //<br>
-  // The 'If' fix is bigger, so that is the one that has to be applied.<br>
+  // The 'If' fix contains the other, so that is the one that has to be applied.<br>
   // } else if (int a = 0) {<br>
   //            ^^^ -> char<br>
   //            ~~~~~~~~~ -> false<br>
@@ -294,7 +295,9 @@ TEST(OverlappingReplacementsTest, Replac<br>
   }<br>
 })";<br>
   Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);<br>
-  // FIXME: EXPECT_EQ(CharIfFix, Res);<br>
+  EXPECT_EQ(CharIfFix, Res);<br>
+  Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);<br>
+  EXPECT_EQ(CharIfFix, Res);<br>
<br>
   // Apply the IfFalseCheck with the StartsWithPotaCheck.<br>
   //<br>
@@ -303,7 +306,7 @@ TEST(OverlappingReplacementsTest, Replac<br>
   //          ^^^^^^ -> tomato<br>
   //     ~~~~~~~~~~~~~~~ -> false<br>
   //<br>
-  // But the refactoring is bigger here:<br>
+  // But the refactoring is the one that contains the other here:<br>
   // char potato = 0;<br>
   //      ^^^^^^ -> tomato<br>
   // if (potato) potato;<br>
@@ -318,60 +321,87 @@ TEST(OverlappingReplacementsTest, Replac<br>
   }<br>
 })";<br>
   Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);<br>
-  // FIXME: EXPECT_EQ(IfStartsFix, Res);<br>
-<br>
-  // Silence warnings.<br>
-  (void)CharIfFix;<br>
-  (void)IfStartsFix;<br>
+  EXPECT_EQ(IfStartsFix, Res);<br>
+  Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);<br>
+  EXPECT_EQ(IfStartsFix, Res);<br>
 }<br>
<br>
-TEST(OverlappingReplacementsTest, ApplyFullErrorOrNothingWhenOverlapping) {<br>
+TEST(OverlappingReplacements, TwoReplacementsInsideOne) {<br>
   std::string Res;<br>
   const char Code[] =<br>
       R"(void f() {<br>
-  int potato = 0;<br>
-  potato += potato * potato;<br>
-  if (char this_name_make_this_if_really_long = potato) potato;<br>
+  if (int potato = 0) {<br>
+    int a = 0;<br>
+  }<br>
 })";<br>
<br>
-  // StartsWithPotaCheck will try to refactor 'potato' into 'tomato',<br>
-  // and EndsWithTatoCheck will try to use 'pomelo'. We have to apply<br>
-  // either all conversions from one check, or all from the other.<br>
-  const char StartsFix[] =<br>
+  // The two smallest replacements should not be applied.<br>
+  // if (int potato = 0) {<br>
+  //         ^^^^^^ -> tomato<br>
+  //     *** -> char<br>
+  //     ~~~~~~~~~~~~~~ -> false<br>
+  // But other errors from the same checks should not be affected.<br>
+  //   int a = 0;<br>
+  //   *** -> char<br>
+  const char Fix[] =<br>
       R"(void f() {<br>
-  int tomato = 0;<br>
-  tomato += tomato * tomato;<br>
-  if (char this_name_make_this_if_really_long = tomato) tomato;<br>
+  if (false) {<br>
+    char a = 0;<br>
+  }<br>
 })";<br>
-  const char EndsFix[] =<br>
+  Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);<br>
+  EXPECT_EQ(Fix, Res);<br>
+  Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);<br>
+  EXPECT_EQ(Fix, Res);<br>
+}<br>
+<br>
+TEST(OverlappingReplacementsTest,<br>
+     ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {<br>
+  std::string Res;<br>
+  const char Code[] =<br>
       R"(void f() {<br>
-  int pomelo = 0;<br>
-  pomelo += pomelo * pomelo;<br>
-  if (char this_name_make_this_if_really_long = pomelo) pomelo;<br>
+  if (int potato = 0) {<br>
+    int a = potato;<br>
+  }<br>
 })";<br>
-  // In case of overlapping, we will prioritize the biggest fix. However, these<br>
-  // two fixes have the same size and position, so we don't know yet which one<br>
-  // will have preference.<br>
-  Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);<br>
-  // FIXME: EXPECT_TRUE(Res == StartsFix || Res == EndsFix);<br>
<br>
-  // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', but<br>
-  // replacing the 'if' condition is a bigger change than all the refactoring<br>
-  // changes together (48 vs 36), so this is the one that is going to be<br>
-  // applied.<br>
+  // These two replacements overlap, but none of them is completely contained<br>
+  // inside the other.<br>
+  // if (int potato = 0) {<br>
+  //         ^^^^^^ -> tomato<br>
+  //     ~~~~~~~~~~~~~~ -> false<br>
+  //   int a = potato;<br>
+  //           ^^^^^^ -> tomato<br>
+  //<br>
+  // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,<br>
+  // so it is going to be set as inapplicable. The 'if' fix will be applied.<br>
   const char IfFix[] =<br>
       R"(void f() {<br>
+  if (false) {<br>
+    int a = potato;<br>
+  }<br>
+})";<br>
+  Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);<br>
+  EXPECT_EQ(IfFix, Res);<br>
+}<br>
+<br>
+TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {<br>
+  std::string Res;<br>
+  const char Code[] =<br>
+      R"(void f() {<br>
   int potato = 0;<br>
   potato += potato * potato;<br>
-  if (true) potato;<br>
+  if (char a = potato) potato;<br>
 })";<br>
-  Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);<br>
-  // FIXME: EXPECT_EQ(IfFix, Res);<br>
<br>
-  // Silence warnings.<br>
-  (void)StartsFix;<br>
-  (void)EndsFix;<br>
-  (void)IfFix;<br>
+  // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and<br>
+  // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of<br>
+  // ranges. This is a corner case of one error completely containing another:<br>
+  // the other completely contains the first one as well. Both errors are<br>
+  // discarded.<br>
+<br>
+  Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);<br>
+  EXPECT_EQ(Code, Res);<br>
 }<br>
<br>
 } // namespace test<br>
<br>
<br>
_______________________________________________<br>
cfe-commits mailing list<br>
<a href="mailto:cfe-commits@lists.llvm.org">cfe-commits@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits</a><br>
</blockquote></div><br></div></div>