https://github.com/flovent updated https://github.com/llvm/llvm-project/pull/127049
>From c916dadbaf6021eda606d76784115698a9800571 Mon Sep 17 00:00:00 2001 From: flovent <flb...@protonmail.com> Date: Thu, 13 Feb 2025 20:17:20 +0800 Subject: [PATCH 1/4] [clang][analyzer] fix false positive of BlockInCriticalSectionChecker --- .../BlockInCriticalSectionChecker.cpp | 67 ++++++++++++++++++- clang/test/Analysis/issue-124474.cpp | 49 ++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/issue-124474.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 7460781799d08..db784f2cc77b2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -145,7 +145,8 @@ using MutexDescriptor = std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor, RAIIMutexDescriptor>; -class BlockInCriticalSectionChecker : public Checker<check::PostCall> { +class BlockInCriticalSectionChecker + : public Checker<check::PostCall, check::DeadSymbols> { private: const std::array<MutexDescriptor, 8> MutexDescriptors{ // NOTE: There are standard library implementations where some methods @@ -179,6 +180,8 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> { {CDM::CLibrary, {"read"}}, {CDM::CLibrary, {"recv"}}}; + const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2}; + const BugType BlockInCritSectionBugType{ this, "Call to blocking function in critical section", "Blocking Error"}; @@ -197,6 +200,8 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> { void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call, CheckerContext &C) const; + void handleOpen(const CallEvent &Call, CheckerContext &C) const; + [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call, CheckerContext &C) const; @@ -205,11 +210,14 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> { /// Process lock. /// Process blocking functions (sleep, getc, fgets, read, recv) void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; }; } // end anonymous namespace REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker) +REGISTER_SET_WITH_PROGRAMSTATE(NonBlockFileDescriptor, SymbolRef) // Iterator traits for ImmutableList data structure // that enable the use of STL algorithms. @@ -306,6 +314,25 @@ void BlockInCriticalSectionChecker::handleUnlock( C.addTransition(State); } +void BlockInCriticalSectionChecker::handleOpen(const CallEvent &Call, + CheckerContext &C) const { + const auto *Flag = Call.getArgExpr(1); + static std::optional<int> ValueOfONonBlockVFlag = + tryExpandAsInteger("O_NONBLOCK", C.getBugReporter().getPreprocessor()); + if (!ValueOfONonBlockVFlag) + return; + + SVal FlagSV = C.getState()->getSVal(Flag, C.getLocationContext()); + const llvm::APSInt *FlagV = FlagSV.getAsInteger(); + if (!FlagV) + return; + + if ((*FlagV & ValueOfONonBlockVFlag.value()) != 0) + if (SymbolRef SR = Call.getReturnValue().getAsSymbol()) { + C.addTransition(C.getState()->add<NonBlockFileDescriptor>(SR)); + } +} + bool BlockInCriticalSectionChecker::isBlockingInCritSection( const CallEvent &Call, CheckerContext &C) const { return BlockingFunctions.contains(Call) && @@ -315,6 +342,27 @@ bool BlockInCriticalSectionChecker::isBlockingInCritSection( void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (isBlockingInCritSection(Call, C)) { + // for 'read' and 'recv' call, check whether it's file descriptor(first + // argument) is + // created by 'open' API with O_NONBLOCK flag or is equal to -1, they will + // not cause block in these situations, don't report + StringRef FuncName = Call.getCalleeIdentifier()->getName(); + if (FuncName == "read" || FuncName == "recv") { + const auto *Arg = Call.getArgExpr(0); + if (!Arg) + return; + + SVal SV = C.getSVal(Arg); + if (const auto *IntValue = SV.getAsInteger()) { + if (*IntValue == -1) + return; + } + + SymbolRef SR = C.getSVal(Arg).getAsSymbol(); + if (SR && C.getState()->contains<NonBlockFileDescriptor>(SR)) { + return; + } + } reportBlockInCritSection(Call, C); } else if (std::optional<MutexDescriptor> LockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/true)) { @@ -322,9 +370,26 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, } else if (std::optional<MutexDescriptor> UnlockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/false)) { handleUnlock(*UnlockDesc, Call, C); + } else if (OpenFunction.matches(Call)) { + handleOpen(Call, C); } } +void BlockInCriticalSectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Remove the dead symbols from the NonBlockFileDescriptor set. + NonBlockFileDescriptorTy Tracked = State->get<NonBlockFileDescriptor>(); + for (SymbolRef SR : Tracked) { + if (SymReaper.isDead(SR)) { + State = State->remove<NonBlockFileDescriptor>(SR); + } + } + + C.addTransition(State); +} + void BlockInCriticalSectionChecker::reportBlockInCritSection( const CallEvent &Call, CheckerContext &C) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState()); diff --git a/clang/test/Analysis/issue-124474.cpp b/clang/test/Analysis/issue-124474.cpp new file mode 100644 index 0000000000000..09e3d4f3f9ad9 --- /dev/null +++ b/clang/test/Analysis/issue-124474.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=unix.BlockInCriticalSection \ +// RUN: -std=c++11 \ +// RUN: -analyzer-output text \ +// RUN: -verify %s + +// expected-no-diagnostics + +namespace std { + struct mutex { + void lock() {} + void unlock() {} + }; + template<typename T> + struct lock_guard { + lock_guard<T>(std::mutex) {} + ~lock_guard<T>() {} + }; + template<typename T> + struct unique_lock { + unique_lock<T>(std::mutex) {} + ~unique_lock<T>() {} + }; + template<typename T> + struct not_real_lock { + not_real_lock<T>(std::mutex) {} + }; + } // namespace std + +std::mutex mtx; +using ssize_t = long long; +using size_t = unsigned long long; +int open (const char *__file, int __oflag, ...); +ssize_t read(int fd, void *buf, size_t count); +void close(int fd); +#define O_RDONLY 00 +# define O_NONBLOCK 04000 + +void foo() +{ + std::lock_guard<std::mutex> lock(mtx); + + const char *filename = "example.txt"; + int fd = open(filename, O_RDONLY | O_NONBLOCK); + + char buffer[200] = {}; + read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor + close(fd); +} >From f2f928798e715ccd2739bfa44d3d09fffce25b16 Mon Sep 17 00:00:00 2001 From: flovent <flb...@protonmail.com> Date: Thu, 13 Feb 2025 20:32:53 +0800 Subject: [PATCH 2/4] add new line --- clang/test/Analysis/issue-124474.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Analysis/issue-124474.cpp b/clang/test/Analysis/issue-124474.cpp index 09e3d4f3f9ad9..7136bbb316c67 100644 --- a/clang/test/Analysis/issue-124474.cpp +++ b/clang/test/Analysis/issue-124474.cpp @@ -47,3 +47,4 @@ void foo() read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor close(fd); } + >From 20bec01386e957146250819540a97bedaa97066d Mon Sep 17 00:00:00 2001 From: flovent <flb...@protonmail.com> Date: Fri, 14 Feb 2025 21:05:38 +0800 Subject: [PATCH 3/4] update based on review advice --- .../BlockInCriticalSectionChecker.cpp | 32 ++++++------ .../system-header-simulator-cxx-std-locks.h | 17 ++++++ clang/test/Analysis/issue-124474.cpp | 52 ++++++++----------- 3 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 clang/test/Analysis/Inputs/system-header-simulator-cxx-std-locks.h diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index db784f2cc77b2..4a59c9f39eb7b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -185,6 +185,9 @@ class BlockInCriticalSectionChecker const BugType BlockInCritSectionBugType{ this, "Call to blocking function in critical section", "Blocking Error"}; + using O_NONBLOCKValueTy = std::optional<int>; + mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue; + void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const; [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M, @@ -317,17 +320,16 @@ void BlockInCriticalSectionChecker::handleUnlock( void BlockInCriticalSectionChecker::handleOpen(const CallEvent &Call, CheckerContext &C) const { const auto *Flag = Call.getArgExpr(1); - static std::optional<int> ValueOfONonBlockVFlag = - tryExpandAsInteger("O_NONBLOCK", C.getBugReporter().getPreprocessor()); - if (!ValueOfONonBlockVFlag) - return; - SVal FlagSV = C.getState()->getSVal(Flag, C.getLocationContext()); const llvm::APSInt *FlagV = FlagSV.getAsInteger(); if (!FlagV) return; - if ((*FlagV & ValueOfONonBlockVFlag.value()) != 0) + if (!O_NONBLOCKValue) + O_NONBLOCKValue = + tryExpandAsInteger("O_NONBLOCK", C.getBugReporter().getPreprocessor()); + + if (*O_NONBLOCKValue && ((*FlagV & **O_NONBLOCKValue) != 0)) if (SymbolRef SR = Call.getReturnValue().getAsSymbol()) { C.addTransition(C.getState()->add<NonBlockFileDescriptor>(SR)); } @@ -348,18 +350,16 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, // not cause block in these situations, don't report StringRef FuncName = Call.getCalleeIdentifier()->getName(); if (FuncName == "read" || FuncName == "recv") { - const auto *Arg = Call.getArgExpr(0); - if (!Arg) + SVal SV = Call.getArgSVal(0); + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef state = C.getState(); + ConditionTruthVal CTV = + state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy)); + if (CTV.isConstrainedTrue()) return; - SVal SV = C.getSVal(Arg); - if (const auto *IntValue = SV.getAsInteger()) { - if (*IntValue == -1) - return; - } - - SymbolRef SR = C.getSVal(Arg).getAsSymbol(); - if (SR && C.getState()->contains<NonBlockFileDescriptor>(SR)) { + SymbolRef SR = SV.getAsSymbol(); + if (SR && state->contains<NonBlockFileDescriptor>(SR)) { return; } } diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-locks.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-locks.h new file mode 100644 index 0000000000000..91e7c9f63fb55 --- /dev/null +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-locks.h @@ -0,0 +1,17 @@ +// This is a fake system header with divide-by-zero bugs introduced in +// c++ std library functions. We use these bugs to test hard-coded +// suppression of diagnostics within standard library functions that are known +// to produce false positives. + +#pragma clang system_header +namespace std { +struct mutex { + void lock() {} + void unlock() {} +}; + +template <typename T> struct lock_guard { + lock_guard<T>(std::mutex) {} + ~lock_guard<T>() {} +}; +} // namespace std \ No newline at end of file diff --git a/clang/test/Analysis/issue-124474.cpp b/clang/test/Analysis/issue-124474.cpp index 7136bbb316c67..d4861691a13ac 100644 --- a/clang/test/Analysis/issue-124474.cpp +++ b/clang/test/Analysis/issue-124474.cpp @@ -6,45 +6,35 @@ // expected-no-diagnostics -namespace std { - struct mutex { - void lock() {} - void unlock() {} - }; - template<typename T> - struct lock_guard { - lock_guard<T>(std::mutex) {} - ~lock_guard<T>() {} - }; - template<typename T> - struct unique_lock { - unique_lock<T>(std::mutex) {} - ~unique_lock<T>() {} - }; - template<typename T> - struct not_real_lock { - not_real_lock<T>(std::mutex) {} - }; - } // namespace std +#include "Inputs/system-header-simulator-cxx-std-locks.h" std::mutex mtx; using ssize_t = long long; using size_t = unsigned long long; -int open (const char *__file, int __oflag, ...); +int open(const char *__file, int __oflag, ...); ssize_t read(int fd, void *buf, size_t count); void close(int fd); -#define O_RDONLY 00 -# define O_NONBLOCK 04000 +#define O_RDONLY 00 +#define O_NONBLOCK 04000 -void foo() -{ - std::lock_guard<std::mutex> lock(mtx); +void foo() { + std::lock_guard<std::mutex> lock(mtx); - const char *filename = "example.txt"; - int fd = open(filename, O_RDONLY | O_NONBLOCK); + const char *filename = "example.txt"; + int fd = open(filename, O_RDONLY | O_NONBLOCK); - char buffer[200] = {}; - read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor - close(fd); + char buffer[200] = {}; + read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor or equals to -1 + close(fd); } +void foo1(int fd) { + std::lock_guard<std::mutex> lock(mtx); + + const char *filename = "example.txt"; + char buffer[200] = {}; + if (fd == -1) + read(fd, buffer, 199); // no-warning: consider file descriptor is a symbol equals to -1 + close(fd); +} + \ No newline at end of file >From edac8dc8065dd0e026873440b9939e4c004b0909 Mon Sep 17 00:00:00 2001 From: flovent <flb...@protonmail.com> Date: Fri, 14 Feb 2025 21:47:25 +0800 Subject: [PATCH 4/4] use BugReportVisitor instead set --- .../BlockInCriticalSectionChecker.cpp | 142 ++++++++++-------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 4a59c9f39eb7b..7602813f8bd5d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -145,8 +145,63 @@ using MutexDescriptor = std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor, RAIIMutexDescriptor>; -class BlockInCriticalSectionChecker - : public Checker<check::PostCall, check::DeadSymbols> { +class NonBlockOpenVisitor : public BugReporterVisitor { +private: + SymbolRef SR; + int O_NONBLOCKValue; + const CallDescription OpenFunction; + bool Satisfied; + +public: + NonBlockOpenVisitor(SymbolRef SR, int O_NONBLOCKValue) + : SR(SR), O_NONBLOCKValue(O_NONBLOCKValue), + OpenFunction(CDM::CLibrary, {"open"}, 2), Satisfied(false) {} + + static void *getTag() { + static int Tag = 0; + return static_cast<void *>(&Tag); + } + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(getTag()); + ID.AddPointer(SR); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override { + if (Satisfied) + return nullptr; + + std::optional<StmtPoint> Point = N->getLocation().getAs<StmtPoint>(); + if (!Point) + return nullptr; + + auto *CE = dyn_cast<CallExpr>(Point->getStmt()); + if (!CE || !OpenFunction.matchesAsWritten(*CE)) + return nullptr; + + SVal SV = N->getSVal(CE); + if (SV.getAsSymbol() != SR) + return nullptr; + + Satisfied = true; + + // Check if open's second argument contains O_NONBLOCK + const auto *Flag = CE->getArg(1); + SVal FlagSV = N->getSVal(Flag); + const llvm::APSInt *FlagV = FlagSV.getAsInteger(); + if (!FlagV) + return nullptr; + + if ((*FlagV & O_NONBLOCKValue) != 0) + BR.markInvalid(getTag(), nullptr); + + return nullptr; + } +}; + +class BlockInCriticalSectionChecker : public Checker<check::PostCall> { private: const std::array<MutexDescriptor, 8> MutexDescriptors{ // NOTE: There are standard library implementations where some methods @@ -180,8 +235,6 @@ class BlockInCriticalSectionChecker {CDM::CLibrary, {"read"}}, {CDM::CLibrary, {"recv"}}}; - const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2}; - const BugType BlockInCritSectionBugType{ this, "Call to blocking function in critical section", "Blocking Error"}; @@ -203,8 +256,6 @@ class BlockInCriticalSectionChecker void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call, CheckerContext &C) const; - void handleOpen(const CallEvent &Call, CheckerContext &C) const; - [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call, CheckerContext &C) const; @@ -213,14 +264,11 @@ class BlockInCriticalSectionChecker /// Process lock. /// Process blocking functions (sleep, getc, fgets, read, recv) void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - - void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; }; } // end anonymous namespace REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker) -REGISTER_SET_WITH_PROGRAMSTATE(NonBlockFileDescriptor, SymbolRef) // Iterator traits for ImmutableList data structure // that enable the use of STL algorithms. @@ -317,24 +365,6 @@ void BlockInCriticalSectionChecker::handleUnlock( C.addTransition(State); } -void BlockInCriticalSectionChecker::handleOpen(const CallEvent &Call, - CheckerContext &C) const { - const auto *Flag = Call.getArgExpr(1); - SVal FlagSV = C.getState()->getSVal(Flag, C.getLocationContext()); - const llvm::APSInt *FlagV = FlagSV.getAsInteger(); - if (!FlagV) - return; - - if (!O_NONBLOCKValue) - O_NONBLOCKValue = - tryExpandAsInteger("O_NONBLOCK", C.getBugReporter().getPreprocessor()); - - if (*O_NONBLOCKValue && ((*FlagV & **O_NONBLOCKValue) != 0)) - if (SymbolRef SR = Call.getReturnValue().getAsSymbol()) { - C.addTransition(C.getState()->add<NonBlockFileDescriptor>(SR)); - } -} - bool BlockInCriticalSectionChecker::isBlockingInCritSection( const CallEvent &Call, CheckerContext &C) const { return BlockingFunctions.contains(Call) && @@ -344,25 +374,6 @@ bool BlockInCriticalSectionChecker::isBlockingInCritSection( void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (isBlockingInCritSection(Call, C)) { - // for 'read' and 'recv' call, check whether it's file descriptor(first - // argument) is - // created by 'open' API with O_NONBLOCK flag or is equal to -1, they will - // not cause block in these situations, don't report - StringRef FuncName = Call.getCalleeIdentifier()->getName(); - if (FuncName == "read" || FuncName == "recv") { - SVal SV = Call.getArgSVal(0); - SValBuilder &SVB = C.getSValBuilder(); - ProgramStateRef state = C.getState(); - ConditionTruthVal CTV = - state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy)); - if (CTV.isConstrainedTrue()) - return; - - SymbolRef SR = SV.getAsSymbol(); - if (SR && state->contains<NonBlockFileDescriptor>(SR)) { - return; - } - } reportBlockInCritSection(Call, C); } else if (std::optional<MutexDescriptor> LockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/true)) { @@ -370,26 +381,9 @@ void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, } else if (std::optional<MutexDescriptor> UnlockDesc = checkDescriptorMatch(Call, C, /*IsLock=*/false)) { handleUnlock(*UnlockDesc, Call, C); - } else if (OpenFunction.matches(Call)) { - handleOpen(Call, C); } } -void BlockInCriticalSectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - - // Remove the dead symbols from the NonBlockFileDescriptor set. - NonBlockFileDescriptorTy Tracked = State->get<NonBlockFileDescriptor>(); - for (SymbolRef SR : Tracked) { - if (SymReaper.isDead(SR)) { - State = State->remove<NonBlockFileDescriptor>(SR); - } - } - - C.addTransition(State); -} - void BlockInCriticalSectionChecker::reportBlockInCritSection( const CallEvent &Call, CheckerContext &C) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState()); @@ -402,6 +396,28 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection( << "' inside of critical section"; auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType, os.str(), ErrNode); + // for 'read' and 'recv' call, check whether it's file descriptor(first + // argument) is + // created by 'open' API with O_NONBLOCK flag or is equal to -1, they will + // not cause block in these situations, don't report + StringRef FuncName = Call.getCalleeIdentifier()->getName(); + if (FuncName == "read" || FuncName == "recv") { + SVal SV = Call.getArgSVal(0); + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef state = C.getState(); + ConditionTruthVal CTV = + state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy)); + if (CTV.isConstrainedTrue()) + return; + + if (SymbolRef SR = SV.getAsSymbol()) { + if (!O_NONBLOCKValue) + O_NONBLOCKValue = tryExpandAsInteger( + "O_NONBLOCK", C.getBugReporter().getPreprocessor()); + if (*O_NONBLOCKValue) + R->addVisitor<NonBlockOpenVisitor>(SR, **O_NONBLOCKValue); + } + } R->addRange(Call.getSourceRange()); R->markInteresting(Call.getReturnValue()); C.emitReport(std::move(R)); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits