<div dir="ltr">I'm suggesting you make the below change. For testing, I'd just try building locally.<div><br></div><div>$ git diff<br>diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt<br>index 8c383be2d74..af8a35d9251 100644<br>--- a/clang/unittests/Tooling/CMakeLists.txt<br>+++ b/clang/unittests/Tooling/CMakeLists.txt<br>@@ -1,6 +1,7 @@<br> set(LLVM_LINK_COMPONENTS<br>   ${LLVM_TARGETS_TO_BUILD}<br>   Support<br>+  TestingSupport<br>   )<br> <br> # By default MSVC has a 2^16 limit on the number of sections in an object file,<br>@@ -70,8 +71,6 @@ target_link_libraries(ToolingTests<br>   clangToolingCore<br>   clangToolingInclusions<br>   clangToolingRefactor<br>-  LLVMSupport<br>-  LLVMTestingSupport<br>   )<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, May 20, 2019 at 9:49 PM Yitzhak Mandelbaum <<a href="mailto:yitzhakm@google.com" target="_blank">yitzhakm@google.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Nice, thanks for <span style="font-family:Arial,Helvetica,sans-serif">r361208.  As for support -- no, I didn't try that. I was following a pattern I saw elsewhere.  Do you suggest that I make that change? If so, any particular way to test it?</span></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><span style="font-family:Arial,Helvetica,sans-serif"><br></span></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><span style="font-family:Arial,Helvetica,sans-serif">thanks</span></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, May 20, 2019 at 8:26 PM Nico Weber <<a href="mailto:thakis@chromium.org" target="_blank">thakis@chromium.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">+  LLVMSupport<br>+  LLVMTestingSupport<br><div><br></div><div>clang/unittests/Tooling/CMakeLists already has this at the top:</div><div>set(LLVM_LINK_COMPONENTS<br>  ${LLVM_TARGETS_TO_BUILD}<br>  Support<br>  )<br></div><div><br></div><div>I think we link Support twice now. Did adding TestingSupport up there not work? I'd expect that your addition would break shared library builds, or something like that.</div><div><br></div><div>(Also, I tried to fix a build error that old gcc versions had with this change in r361208.)</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr"><strong>From: </strong>Yitzhak Mandelbaum via cfe-commits <span dir="ltr"><<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a>></span><br><strong>Date: </strong>Mon, May 20, 2019 at 9:12 AM<br><strong>To: </strong> <<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a>><br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Author: ymandel<br>
Date: Mon May 20 06:15:14 2019<br>
New Revision: 361152<br>
<br>
URL: <a href="http://llvm.org/viewvc/llvm-project?rev=361152&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=361152&view=rev</a><br>
Log:<br>
[LibTooling] Add RangeSelector library for defining source ranges based on bound AST nodes.<br>
<br>
Summary:<br>
<br>
The RangeSelector library defines a combinator language for specifying source<br>
ranges based on bound ids for AST nodes.  The combinator approach follows the<br>
design of the AST matchers.  The RangeSelectors defined here will be used in<br>
both RewriteRule, for specifying source affected by edit, and in Stencil for<br>
specifying source to use constructively in a replacement.<br>
<br>
Reviewers: ilya-biryukov<br>
<br>
Subscribers: mgorny, cfe-commits<br>
<br>
Tags: #clang<br>
<br>
Differential Revision: <a href="https://reviews.llvm.org/D61774" rel="noreferrer" target="_blank">https://reviews.llvm.org/D61774</a><br>
<br>
Added:<br>
    cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h<br>
    cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp<br>
    cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp<br>
Modified:<br>
    cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt<br>
    cfe/trunk/unittests/Tooling/CMakeLists.txt<br>
<br>
Added: cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h?rev=361152&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h?rev=361152&view=auto</a><br>
==============================================================================<br>
--- cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h (added)<br>
+++ cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h Mon May 20 06:15:14 2019<br>
@@ -0,0 +1,80 @@<br>
+//===--- RangeSelector.h - Source-selection library ---------*- C++ -*-===//<br>
+//<br>
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.<br>
+// See <a href="https://llvm.org/LICENSE.txt" rel="noreferrer" target="_blank">https://llvm.org/LICENSE.txt</a> for license information.<br>
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception<br>
+//<br>
+//===----------------------------------------------------------------------===//<br>
+///<br>
+///  \file<br>
+///  Defines a combinator library supporting the definition of _selectors_,<br>
+///  which select source ranges based on (bound) AST nodes.<br>
+///<br>
+//===----------------------------------------------------------------------===//<br>
+<br>
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_<br>
+#define LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_<br>
+<br>
+#include "clang/ASTMatchers/ASTMatchFinder.h"<br>
+#include "clang/Basic/SourceLocation.h"<br>
+#include "llvm/ADT/StringRef.h"<br>
+#include "llvm/Support/Error.h"<br>
+#include <functional><br>
+<br>
+namespace clang {<br>
+namespace tooling {<br>
+using RangeSelector = std::function<Expected<CharSourceRange>(<br>
+    const ast_matchers::MatchFinder::MatchResult &)>;<br>
+<br>
+inline RangeSelector charRange(CharSourceRange R) {<br>
+  return [R](const ast_matchers::MatchFinder::MatchResult &)<br>
+             -> Expected<CharSourceRange> { return R; };<br>
+}<br>
+<br>
+/// Selects from the start of \p Begin and to the end of \p End.<br>
+RangeSelector range(RangeSelector Begin, RangeSelector End);<br>
+<br>
+/// Convenience version of \c range where end-points are bound nodes.<br>
+RangeSelector range(StringRef BeginID, StringRef EndID);<br>
+<br>
+/// Selects a node, including trailing semicolon (for non-expression<br>
+/// statements). \p ID is the node's binding in the match result.<br>
+RangeSelector node(StringRef ID);<br>
+<br>
+/// Selects a node, including trailing semicolon (always). Useful for selecting<br>
+/// expression statements. \p ID is the node's binding in the match result.<br>
+RangeSelector statement(StringRef ID);<br>
+<br>
+/// Given a \c MemberExpr, selects the member token. \p ID is the node's<br>
+/// binding in the match result.<br>
+RangeSelector member(StringRef ID);<br>
+<br>
+/// Given a node with a "name", (like \c NamedDecl, \c DeclRefExpr or \c<br>
+/// CxxCtorInitializer) selects the name's token.  Only selects the final<br>
+/// identifier of a qualified name, but not any qualifiers or template<br>
+/// arguments.  For example, for `::foo::bar::baz` and `::foo::bar::baz<int>`,<br>
+/// it selects only `baz`.<br>
+///<br>
+/// \param ID is the node's binding in the match result.<br>
+RangeSelector name(StringRef ID);<br>
+<br>
+// Given a \c CallExpr (bound to \p ID), selects the arguments' source text (all<br>
+// source between the call's parentheses).<br>
+RangeSelector callArgs(StringRef ID);<br>
+<br>
+// Given a \c CompoundStmt (bound to \p ID), selects the source of the<br>
+// statements (all source between the braces).<br>
+RangeSelector statements(StringRef ID);<br>
+<br>
+// Given a \c InitListExpr (bound to \p ID), selects the range of the elements<br>
+// (all source between the braces).<br>
+RangeSelector initListElements(StringRef ID);<br>
+<br>
+/// Selects the range from which `S` was expanded (possibly along with other<br>
+/// source), if `S` is an expansion, and `S` itself, otherwise.  Corresponds to<br>
+/// `SourceManager::getExpansionRange`.<br>
+RangeSelector expansion(RangeSelector S);<br>
+} // namespace tooling<br>
+} // namespace clang<br>
+<br>
+#endif // LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_<br>
<br>
Modified: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff</a><br>
==============================================================================<br>
--- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt (original)<br>
+++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt Mon May 20 06:15:14 2019<br>
@@ -6,6 +6,7 @@ add_clang_library(clangToolingRefactor<br>
   AtomicChange.cpp<br>
   Extract/Extract.cpp<br>
   Extract/SourceExtraction.cpp<br>
+  RangeSelector.cpp<br>
   RefactoringActions.cpp<br>
   Rename/RenamingAction.cpp<br>
   Rename/SymbolOccurrences.cpp<br>
<br>
Added: cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp?rev=361152&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp?rev=361152&view=auto</a><br>
==============================================================================<br>
--- cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp (added)<br>
+++ cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp Mon May 20 06:15:14 2019<br>
@@ -0,0 +1,264 @@<br>
+//===--- RangeSelector.cpp - RangeSelector implementations ------*- C++ -*-===//<br>
+//<br>
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.<br>
+// See <a href="https://llvm.org/LICENSE.txt" rel="noreferrer" target="_blank">https://llvm.org/LICENSE.txt</a> for license information.<br>
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception<br>
+//<br>
+//===----------------------------------------------------------------------===//<br>
+<br>
+#include "clang/Tooling/Refactoring/RangeSelector.h"<br>
+#include "clang/AST/Expr.h"<br>
+#include "clang/ASTMatchers/ASTMatchFinder.h"<br>
+#include "clang/Basic/SourceLocation.h"<br>
+#include "clang/Lex/Lexer.h"<br>
+#include "clang/Tooling/Refactoring/SourceCode.h"<br>
+#include "llvm/ADT/StringRef.h"<br>
+#include "llvm/Support/Errc.h"<br>
+#include "llvm/Support/Error.h"<br>
+#include <string><br>
+#include <utility><br>
+#include <vector><br>
+<br>
+using namespace clang;<br>
+using namespace tooling;<br>
+<br>
+using ast_matchers::MatchFinder;<br>
+using ast_type_traits::ASTNodeKind;<br>
+using ast_type_traits::DynTypedNode;<br>
+using llvm::Error;<br>
+using llvm::StringError;<br>
+<br>
+using MatchResult = MatchFinder::MatchResult;<br>
+<br>
+static Error invalidArgumentError(Twine Message) {<br>
+  return llvm::make_error<StringError>(llvm::errc::invalid_argument, Message);<br>
+}<br>
+<br>
+static Error typeError(StringRef ID, const ASTNodeKind &Kind) {<br>
+  return invalidArgumentError("mismatched type (node id=" + ID +<br>
+                              " kind=" + Kind.asStringRef() + ")");<br>
+}<br>
+<br>
+static Error typeError(StringRef ID, const ASTNodeKind &Kind,<br>
+                       Twine ExpectedType) {<br>
+  return invalidArgumentError("mismatched type: expected one of " +<br>
+                              ExpectedType + " (node id=" + ID +<br>
+                              " kind=" + Kind.asStringRef() + ")");<br>
+}<br>
+<br>
+static Error missingPropertyError(StringRef ID, Twine Description,<br>
+                                  StringRef Property) {<br>
+  return invalidArgumentError(Description + " requires property '" + Property +<br>
+                              "' (node id=" + ID + ")");<br>
+}<br>
+<br>
+static Expected<DynTypedNode> getNode(const ast_matchers::BoundNodes &Nodes,<br>
+                                      StringRef ID) {<br>
+  auto &NodesMap = Nodes.getMap();<br>
+  auto It = NodesMap.find(ID);<br>
+  if (It == NodesMap.end())<br>
+    return invalidArgumentError("ID not bound: " + ID);<br>
+  return It->second;<br>
+}<br>
+<br>
+// FIXME: handling of macros should be configurable.<br>
+static SourceLocation findPreviousTokenStart(SourceLocation Start,<br>
+                                             const SourceManager &SM,<br>
+                                             const LangOptions &LangOpts) {<br>
+  if (Start.isInvalid() || Start.isMacroID())<br>
+    return SourceLocation();<br>
+<br>
+  SourceLocation BeforeStart = Start.getLocWithOffset(-1);<br>
+  if (BeforeStart.isInvalid() || BeforeStart.isMacroID())<br>
+    return SourceLocation();<br>
+<br>
+  return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts);<br>
+}<br>
+<br>
+// Finds the start location of the previous token of kind \p TK.<br>
+// FIXME: handling of macros should be configurable.<br>
+static SourceLocation findPreviousTokenKind(SourceLocation Start,<br>
+                                            const SourceManager &SM,<br>
+                                            const LangOptions &LangOpts,<br>
+                                            tok::TokenKind TK) {<br>
+  while (true) {<br>
+    SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts);<br>
+    if (L.isInvalid() || L.isMacroID())<br>
+      return SourceLocation();<br>
+<br>
+    Token T;<br>
+    if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true))<br>
+      return SourceLocation();<br>
+<br>
+    if (T.is(TK))<br>
+      return T.getLocation();<br>
+<br>
+    Start = L;<br>
+  }<br>
+}<br>
+<br>
+static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM,<br>
+                                    const LangOptions &LangOpts) {<br>
+  SourceLocation EndLoc =<br>
+      E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc();<br>
+  return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren);<br>
+}<br>
+<br>
+RangeSelector tooling::node(StringRef ID) {<br>
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);<br>
+    if (!Node)<br>
+      return Node.takeError();<br>
+    return Node->get<Stmt>() != nullptr && Node->get<Expr>() == nullptr<br>
+               ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context)<br>
+               : CharSourceRange::getTokenRange(Node->getSourceRange());<br>
+  };<br>
+}<br>
+<br>
+RangeSelector tooling::statement(StringRef ID) {<br>
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);<br>
+    if (!Node)<br>
+      return Node.takeError();<br>
+    return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context);<br>
+  };<br>
+}<br>
+<br>
+RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) {<br>
+  return [Begin, End](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<CharSourceRange> BeginRange = Begin(Result);<br>
+    if (!BeginRange)<br>
+      return BeginRange.takeError();<br>
+    Expected<CharSourceRange> EndRange = End(Result);<br>
+    if (!EndRange)<br>
+      return EndRange.takeError();<br>
+    SourceLocation B = BeginRange->getBegin();<br>
+    SourceLocation E = EndRange->getEnd();<br>
+    // Note: we are precluding the possibility of sub-token ranges in the case<br>
+    // that EndRange is a token range.<br>
+    if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) {<br>
+      return invalidArgumentError("Bad range: out of order");<br>
+    }<br>
+    return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange());<br>
+  };<br>
+}<br>
+<br>
+RangeSelector tooling::range(StringRef BeginID, StringRef EndID) {<br>
+  return tooling::range(node(BeginID), node(EndID));<br>
+}<br>
+<br>
+RangeSelector tooling::member(StringRef ID) {<br>
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<DynTypedNode> Node = getNode(Result.Nodes, ID);<br>
+    if (!Node)<br>
+      return Node.takeError();<br>
+    if (auto *M = Node->get<clang::MemberExpr>())<br>
+      return CharSourceRange::getTokenRange(<br>
+          M->getMemberNameInfo().getSourceRange());<br>
+    return typeError(ID, Node->getNodeKind(), "MemberExpr");<br>
+  };<br>
+}<br>
+<br>
+RangeSelector tooling::name(StringRef ID) {<br>
+  return [ID](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<DynTypedNode> N = getNode(Result.Nodes, ID);<br>
+    if (!N)<br>
+      return N.takeError();<br>
+    auto &Node = *N;<br>
+    if (const auto *D = Node.get<NamedDecl>()) {<br>
+      if (!D->getDeclName().isIdentifier())<br>
+        return missingPropertyError(ID, "name", "identifier");<br>
+      SourceLocation L = D->getLocation();<br>
+      auto R = CharSourceRange::getTokenRange(L, L);<br>
+      // Verify that the range covers exactly the name.<br>
+      // FIXME: extend this code to support cases like `operator +` or<br>
+      // `foo<int>` for which this range will be too short.  Doing so will<br>
+      // require subcasing `NamedDecl`, because it doesn't provide virtual<br>
+      // access to the \c DeclarationNameInfo.<br>
+      if (getText(R, *Result.Context) != D->getName())<br>
+        return CharSourceRange();<br>
+      return R;<br>
+    }<br>
+    if (const auto *E = Node.get<DeclRefExpr>()) {<br>
+      if (!E->getNameInfo().getName().isIdentifier())<br>
+        return missingPropertyError(ID, "name", "identifier");<br>
+      SourceLocation L = E->getLocation();<br>
+      return CharSourceRange::getTokenRange(L, L);<br>
+    }<br>
+    if (const auto *I = Node.get<CXXCtorInitializer>()) {<br>
+      if (!I->isMemberInitializer() && I->isWritten())<br>
+        return missingPropertyError(ID, "name", "explicit member initializer");<br>
+      SourceLocation L = I->getMemberLocation();<br>
+      return CharSourceRange::getTokenRange(L, L);<br>
+    }<br>
+    return typeError(ID, Node.getNodeKind(),<br>
+                     "DeclRefExpr, NamedDecl, CXXCtorInitializer");<br>
+  };<br>
+}<br>
+<br>
+namespace {<br>
+// Creates a selector from a range-selection function \p Func, which selects a<br>
+// range that is relative to a bound node id.  \c T is the node type expected by<br>
+// \p Func.<br>
+template <typename T, CharSourceRange (*Func)(const MatchResult &, const T &)><br>
+class RelativeSelector {<br>
+  std::string ID;<br>
+<br>
+public:<br>
+  RelativeSelector(StringRef ID) : ID(ID) {}<br>
+<br>
+  Expected<CharSourceRange> operator()(const MatchResult &Result) {<br>
+    Expected<DynTypedNode> N = getNode(Result.Nodes, ID);<br>
+    if (!N)<br>
+      return N.takeError();<br>
+    if (const auto *Arg = N->get<T>())<br>
+      return Func(Result, *Arg);<br>
+    return typeError(ID, N->getNodeKind());<br>
+  }<br>
+};<br>
+} // namespace<br>
+<br>
+// Returns the range of the statements (all source between the braces).<br>
+static CharSourceRange getStatementsRange(const MatchResult &,<br>
+                                          const CompoundStmt &CS) {<br>
+  return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1),<br>
+                                       CS.getRBracLoc());<br>
+}<br>
+<br>
+RangeSelector tooling::statements(StringRef ID) {<br>
+  return RelativeSelector<CompoundStmt, getStatementsRange>(ID);<br>
+}<br>
+<br>
+// Returns the range of the source between the call's parentheses.<br>
+static CharSourceRange getCallArgumentsRange(const MatchResult &Result,<br>
+                                             const CallExpr &CE) {<br>
+  return CharSourceRange::getCharRange(<br>
+      findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts())<br>
+          .getLocWithOffset(1),<br>
+      CE.getRParenLoc());<br>
+}<br>
+<br>
+RangeSelector tooling::callArgs(StringRef ID) {<br>
+  return RelativeSelector<CallExpr, getCallArgumentsRange>(ID);<br>
+}<br>
+<br>
+// Returns the range of the elements of the initializer list. Includes all<br>
+// source between the braces.<br>
+static CharSourceRange getElementsRange(const MatchResult &,<br>
+                                        const InitListExpr &E) {<br>
+  return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1),<br>
+                                       E.getRBraceLoc());<br>
+}<br>
+<br>
+RangeSelector tooling::initListElements(StringRef ID) {<br>
+  return RelativeSelector<InitListExpr, getElementsRange>(ID);<br>
+}<br>
+<br>
+RangeSelector tooling::expansion(RangeSelector S) {<br>
+  return [S](const MatchResult &Result) -> Expected<CharSourceRange> {<br>
+    Expected<CharSourceRange> SRange = S(Result);<br>
+    if (!SRange)<br>
+      return SRange.takeError();<br>
+    return Result.SourceManager->getExpansionRange(*SRange);<br>
+  };<br>
+}<br>
<br>
Modified: cfe/trunk/unittests/Tooling/CMakeLists.txt<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff</a><br>
==============================================================================<br>
--- cfe/trunk/unittests/Tooling/CMakeLists.txt (original)<br>
+++ cfe/trunk/unittests/Tooling/CMakeLists.txt Mon May 20 06:15:14 2019<br>
@@ -22,6 +22,7 @@ add_clang_unittest(ToolingTests<br>
   LexicallyOrderedRecursiveASTVisitorTest.cpp<br>
   LookupTest.cpp<br>
   QualTypeNamesTest.cpp<br>
+  RangeSelectorTest.cpp<br>
   RecursiveASTVisitorTests/Attr.cpp<br>
   RecursiveASTVisitorTests/Class.cpp<br>
   RecursiveASTVisitorTests/ConstructExpr.cpp<br>
@@ -69,6 +70,8 @@ target_link_libraries(ToolingTests<br>
   clangToolingCore<br>
   clangToolingInclusions<br>
   clangToolingRefactor<br>
+  LLVMSupport<br>
+  LLVMTestingSupport<br>
   )<br>
<br>
<br>
<br>
Added: cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp<br>
URL: <a href="http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp?rev=361152&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp?rev=361152&view=auto</a><br>
==============================================================================<br>
--- cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp (added)<br>
+++ cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp Mon May 20 06:15:14 2019<br>
@@ -0,0 +1,496 @@<br>
+//===- unittest/Tooling/RangeSelectorTest.cpp -----------------------------===//<br>
+//<br>
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.<br>
+// See <a href="https://llvm.org/LICENSE.txt" rel="noreferrer" target="_blank">https://llvm.org/LICENSE.txt</a> for license information.<br>
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception<br>
+//<br>
+//===----------------------------------------------------------------------===//<br>
+<br>
+#include "clang/Tooling/Refactoring/RangeSelector.h"<br>
+#include "clang/ASTMatchers/ASTMatchers.h"<br>
+#include "clang/Tooling/FixIt.h"<br>
+#include "clang/Tooling/Tooling.h"<br>
+#include "llvm/Support/Error.h"<br>
+#include "llvm/Testing/Support/Error.h"<br>
+#include "gmock/gmock.h"<br>
+#include "gtest/gtest.h"<br>
+<br>
+using namespace clang;<br>
+using namespace tooling;<br>
+using namespace ast_matchers;<br>
+<br>
+namespace {<br>
+using ::testing::AllOf;<br>
+using ::testing::HasSubstr;<br>
+using MatchResult = MatchFinder::MatchResult;<br>
+using ::llvm::Expected;<br>
+using ::llvm::Failed;<br>
+using ::llvm::HasValue;<br>
+using ::llvm::StringError;<br>
+<br>
+struct TestMatch {<br>
+  // The AST unit from which `result` is built. We bundle it because it backs<br>
+  // the result. Users are not expected to access it.<br>
+  std::unique_ptr<ASTUnit> ASTUnit;<br>
+  // The result to use in the test. References `ast_unit`.<br>
+  MatchResult Result;<br>
+};<br>
+<br>
+template <typename M> TestMatch matchCode(StringRef Code, M Matcher) {<br>
+  auto ASTUnit = buildASTFromCode(Code);<br>
+  assert(ASTUnit != nullptr && "AST construction failed");<br>
+<br>
+  ASTContext &Context = ASTUnit->getASTContext();<br>
+  assert(!Context.getDiagnostics().hasErrorOccurred() && "Compilation error");<br>
+<br>
+  auto Matches = ast_matchers::match(Matcher, Context);<br>
+  // We expect a single, exact match.<br>
+  assert(Matches.size() != 0 && "no matches found");<br>
+  assert(Matches.size() == 1 && "too many matches");<br>
+<br>
+  return TestMatch{std::move(ASTUnit), MatchResult(Matches[0], &Context)};<br>
+}<br>
+<br>
+// Applies \p Selector to \p Match and, on success, returns the selected source.<br>
+Expected<StringRef> apply(RangeSelector Selector, const TestMatch &Match) {<br>
+  Expected<CharSourceRange> Range = Selector(Match.Result);<br>
+  if (!Range)<br>
+    return Range.takeError();<br>
+  return fixit::internal::getText(*Range, *Match.Result.Context);<br>
+}<br>
+<br>
+// Applies \p Selector to a trivial match with only a single bound node with id<br>
+// "bound_node_id".  For use in testing unbound-node errors.<br>
+Expected<CharSourceRange> applyToTrivial(const RangeSelector &Selector) {<br>
+  // We need to bind the result to something, or the match will fail. Use a<br>
+  // binding that is not used in the unbound node tests.<br>
+  TestMatch Match =<br>
+      matchCode("static int x = 0;", varDecl().bind("bound_node_id"));<br>
+  return Selector(Match.Result);<br>
+}<br>
+<br>
+// Matches the message expected for unbound-node failures.<br>
+testing::Matcher<StringError> withUnboundNodeMessage() {<br>
+  return testing::Property(<br>
+      &StringError::getMessage,<br>
+      AllOf(HasSubstr("unbound_id"), HasSubstr("not bound")));<br>
+}<br>
+<br>
+// Applies \p Selector to code containing assorted node types, where the match<br>
+// binds each one: a statement ("stmt"), a (non-member) ctor-initializer<br>
+// ("init"), an expression ("expr") and a (nameless) declaration ("decl").  Used<br>
+// to test failures caused by applying selectors to nodes of the wrong type.<br>
+Expected<CharSourceRange> applyToAssorted(RangeSelector Selector) {<br>
+  StringRef Code = R"cc(<br>
+      struct A {};<br>
+      class F : public A {<br>
+       public:<br>
+        F(int) {}<br>
+      };<br>
+      void g() { F f(1); }<br>
+    )cc";<br>
+<br>
+  auto Matcher =<br>
+      compoundStmt(<br>
+          hasDescendant(<br>
+              cxxConstructExpr(<br>
+                  hasDeclaration(<br>
+                      decl(hasDescendant(cxxCtorInitializer(isBaseInitializer())<br>
+                                             .bind("init")))<br>
+                          .bind("decl")))<br>
+                  .bind("expr")))<br>
+          .bind("stmt");<br>
+<br>
+  return Selector(matchCode(Code, Matcher).Result);<br>
+}<br>
+<br>
+// Matches the message expected for type-error failures.<br>
+testing::Matcher<StringError> withTypeErrorMessage(StringRef NodeID) {<br>
+  return testing::Property(<br>
+      &StringError::getMessage,<br>
+      AllOf(HasSubstr(NodeID), HasSubstr("mismatched type")));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, UnboundNode) {<br>
+  EXPECT_THAT_EXPECTED(applyToTrivial(node("unbound_id")),<br>
+                       Failed<StringError>(withUnboundNodeMessage()));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, RangeOp) {<br>
+  StringRef Code = R"cc(<br>
+    int f(int x, int y, int z) { return 3; }<br>
+    int g() { return f(/* comment */ 3, 7 /* comment */, 9); }<br>
+  )cc";<br>
+  StringRef Arg0 = "a0";<br>
+  StringRef Arg1 = "a1";<br>
+  StringRef Call = "call";<br>
+  auto Matcher = callExpr(hasArgument(0, expr().bind(Arg0)),<br>
+                          hasArgument(1, expr().bind(Arg1)))<br>
+                     .bind(Call);<br>
+  TestMatch Match = matchCode(Code, Matcher);<br>
+<br>
+  // Node-id specific version:<br>
+  EXPECT_THAT_EXPECTED(apply(range(Arg0, Arg1), Match), HasValue("3, 7"));<br>
+  // General version:<br>
+  EXPECT_THAT_EXPECTED(apply(range(node(Arg0), node(Arg1)), Match),<br>
+                       HasValue("3, 7"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NodeOpStatement) {<br>
+  StringRef Code = "int f() { return 3; }";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, returnStmt().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("return 3;"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NodeOpExpression) {<br>
+  StringRef Code = "int f() { return 3; }";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, expr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("3"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, StatementOp) {<br>
+  StringRef Code = "int f() { return 3; }";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, expr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(statement(ID), Match), HasValue("3;"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, MemberOp) {<br>
+  StringRef Code = R"cc(<br>
+    struct S {<br>
+      int member;<br>
+    };<br>
+    int g() {<br>
+      S s;<br>
+      return s.member;<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("member"));<br>
+}<br>
+<br>
+// Tests that member does not select any qualifiers on the member name.<br>
+TEST(RangeSelectorTest, MemberOpQualified) {<br>
+  StringRef Code = R"cc(<br>
+    struct S {<br>
+      int member;<br>
+    };<br>
+    struct T : public S {<br>
+      int field;<br>
+    };<br>
+    int g() {<br>
+      T t;<br>
+      return t.S::member;<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("member"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, MemberOpTemplate) {<br>
+  StringRef Code = R"cc(<br>
+    struct S {<br>
+      template <typename T> T foo(T t);<br>
+    };<br>
+    int f(int x) {<br>
+      S s;<br>
+      return s.template foo<int>(3);<br>
+    }<br>
+  )cc";<br>
+<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("foo"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, MemberOpOperator) {<br>
+  StringRef Code = R"cc(<br>
+    struct S {<br>
+      int operator*();<br>
+    };<br>
+    int f(int x) {<br>
+      S s;<br>
+      return s.operator *();<br>
+    }<br>
+  )cc";<br>
+<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, memberExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("operator *"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NameOpNamedDecl) {<br>
+  StringRef Code = R"cc(<br>
+    int myfun() {<br>
+      return 3;<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, functionDecl().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(name(ID), Match), HasValue("myfun"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NameOpDeclRef) {<br>
+  StringRef Code = R"cc(<br>
+    int foo(int x) {<br>
+      return x;<br>
+    }<br>
+    int g(int x) { return foo(x) * x; }<br>
+  )cc";<br>
+  StringRef Ref = "ref";<br>
+  TestMatch Match = matchCode(Code, declRefExpr(to(functionDecl())).bind(Ref));<br>
+  EXPECT_THAT_EXPECTED(apply(name(Ref), Match), HasValue("foo"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NameOpCtorInitializer) {<br>
+  StringRef Code = R"cc(<br>
+    class C {<br>
+     public:<br>
+      C() : field(3) {}<br>
+      int field;<br>
+    };<br>
+  )cc";<br>
+  StringRef Init = "init";<br>
+  TestMatch Match = matchCode(Code, cxxCtorInitializer().bind(Init));<br>
+  EXPECT_THAT_EXPECTED(apply(name(Init), Match), HasValue("field"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NameOpErrors) {<br>
+  EXPECT_THAT_EXPECTED(applyToTrivial(name("unbound_id")),<br>
+                       Failed<StringError>(withUnboundNodeMessage()));<br>
+  EXPECT_THAT_EXPECTED(applyToAssorted(name("stmt")),<br>
+                       Failed<StringError>(withTypeErrorMessage("stmt")));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, NameOpDeclRefError) {<br>
+  StringRef Code = R"cc(<br>
+    struct S {<br>
+      int operator*();<br>
+    };<br>
+    int f(int x) {<br>
+      S s;<br>
+      return *s + x;<br>
+    }<br>
+  )cc";<br>
+  StringRef Ref = "ref";<br>
+  TestMatch Match = matchCode(Code, declRefExpr(to(functionDecl())).bind(Ref));<br>
+  EXPECT_THAT_EXPECTED(<br>
+      name(Ref)(Match.Result),<br>
+      Failed<StringError>(testing::Property(<br>
+          &StringError::getMessage,<br>
+          AllOf(HasSubstr(Ref), HasSubstr("requires property 'identifier'")))));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOp) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar(int, int);<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar(3, 4);<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("3, 4"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOpNoArgs) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar();<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar();<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue(""));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOpNoArgsWithComments) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar();<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar(/*empty*/);<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("/*empty*/"));<br>
+}<br>
+<br>
+// Tests that arguments are extracted correctly when a temporary (with parens)<br>
+// is used.<br>
+TEST(RangeSelectorTest, CallArgsOpWithParens) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar(int, int) { return 3; }<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return C().bar(3, 4);<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match =<br>
+      matchCode(Code, callExpr(callee(functionDecl(hasName("bar")))).bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("3, 4"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOpLeadingComments) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar(int, int) { return 3; }<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar(/*leading*/ 3, 4);<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match),<br>
+                       HasValue("/*leading*/ 3, 4"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOpTrailingComments) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar(int, int) { return 3; }<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar(3 /*trailing*/, 4);<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match),<br>
+                       HasValue("3 /*trailing*/, 4"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsOpEolComments) {<br>
+  const StringRef Code = R"cc(<br>
+    struct C {<br>
+      int bar(int, int) { return 3; }<br>
+    };<br>
+    int f() {<br>
+      C x;<br>
+      return x.bar(  // Header<br>
+          1,           // foo<br>
+          2            // bar<br>
+      );<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, callExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue(R"(  // Header<br>
+          1,           // foo<br>
+          2            // bar<br>
+      )"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, CallArgsErrors) {<br>
+  EXPECT_THAT_EXPECTED(applyToTrivial(callArgs("unbound_id")),<br>
+                       Failed<StringError>(withUnboundNodeMessage()));<br>
+  EXPECT_THAT_EXPECTED(applyToAssorted(callArgs("stmt")),<br>
+                       Failed<StringError>(withTypeErrorMessage("stmt")));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, StatementsOp) {<br>
+  StringRef Code = R"cc(<br>
+    void g();<br>
+    void f() { /* comment */ g(); /* comment */ g(); /* comment */ }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, compoundStmt().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(<br>
+      apply(statements(ID), Match),<br>
+      HasValue(" /* comment */ g(); /* comment */ g(); /* comment */ "));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, StatementsOpEmptyList) {<br>
+  StringRef Code = "void f() {}";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, compoundStmt().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(statements(ID), Match), HasValue(""));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, StatementsOpErrors) {<br>
+  EXPECT_THAT_EXPECTED(applyToTrivial(statements("unbound_id")),<br>
+                       Failed<StringError>(withUnboundNodeMessage()));<br>
+  EXPECT_THAT_EXPECTED(applyToAssorted(statements("decl")),<br>
+                       Failed<StringError>(withTypeErrorMessage("decl")));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, ElementsOp) {<br>
+  StringRef Code = R"cc(<br>
+    void f() {<br>
+      int v[] = {/* comment */ 3, /* comment*/ 4 /* comment */};<br>
+      (void)v;<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, initListExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(<br>
+      apply(initListElements(ID), Match),<br>
+      HasValue("/* comment */ 3, /* comment*/ 4 /* comment */"));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, ElementsOpEmptyList) {<br>
+  StringRef Code = R"cc(<br>
+    void f() {<br>
+      int v[] = {};<br>
+      (void)v;<br>
+    }<br>
+  )cc";<br>
+  StringRef ID = "id";<br>
+  TestMatch Match = matchCode(Code, initListExpr().bind(ID));<br>
+  EXPECT_THAT_EXPECTED(apply(initListElements(ID), Match), HasValue(""));<br>
+}<br>
+<br>
+TEST(RangeSelectorTest, ElementsOpErrors) {<br>
+  EXPECT_THAT_EXPECTED(applyToTrivial(initListElements("unbound_id")),<br>
+                       Failed<StringError>(withUnboundNodeMessage()));<br>
+  EXPECT_THAT_EXPECTED(applyToAssorted(initListElements("stmt")),<br>
+                       Failed<StringError>(withTypeErrorMessage("stmt")));<br>
+}<br>
+<br>
+// Tests case where the matched node is the complete expanded text.<br>
+TEST(RangeSelectorTest, ExpansionOp) {<br>
+  StringRef Code = R"cc(<br>
+#define BADDECL(E) int bad(int x) { return E; }<br>
+    BADDECL(x * x)<br>
+  )cc";<br>
+<br>
+  StringRef Fun = "Fun";<br>
+  TestMatch Match = matchCode(Code, functionDecl(hasName("bad")).bind(Fun));<br>
+  EXPECT_THAT_EXPECTED(apply(expansion(node(Fun)), Match),<br>
+                       HasValue("BADDECL(x * x)"));<br>
+}<br>
+<br>
+// Tests case where the matched node is (only) part of the expanded text.<br>
+TEST(RangeSelectorTest, ExpansionOpPartial) {<br>
+  StringRef Code = R"cc(<br>
+#define BADDECL(E) int bad(int x) { return E; }<br>
+    BADDECL(x * x)<br>
+  )cc";<br>
+<br>
+  StringRef Ret = "Ret";<br>
+  TestMatch Match = matchCode(Code, returnStmt().bind(Ret));<br>
+  EXPECT_THAT_EXPECTED(apply(expansion(node(Ret)), Match),<br>
+                       HasValue("BADDECL(x * x)"));<br>
+}<br>
+<br>
+} // namespace<br>
<br>
<br>
_______________________________________________<br>
cfe-commits mailing list<br>
<a href="mailto:cfe-commits@lists.llvm.org" target="_blank">cfe-commits@lists.llvm.org</a><br>
<a href="https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits</a><br>
</blockquote></div>
</blockquote></div>
</blockquote></div>