[clang] [clang][analyzer] Add OpaqueSTLFunctionsModeling (PR #178910)

Endre Fülöp via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 30 08:03:50 PST 2026


https://github.com/gamesh411 created https://github.com/llvm/llvm-project/pull/178910

This modeling checker is the intended place for suppressing internal STL-specific implementation-detail functions that cannot be modeled adequately by the engine.

If possible, using `evalCall` to not even emit the false positives is more efficient than suppressing them after analysis in `BugReporterVisitor`s, so this modeling checker takes that route.

This patch introduces the conservative evaluation (no inlining) for `__uninitialized_construct_buf_dispatch::__ucr`, an STL internal function used by `std::stable_sort` and `std::inplace_merge` that causes false positives.

Related to #177804.

>From 7b5b9e715f677dd2a60908327059fc1848d2a39e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Fri, 30 Jan 2026 12:02:57 +0100
Subject: [PATCH] [clang][analyzer] Add OpaqueSTLFunctionsModeling

This modeling checker is the intended place for suppressing internal
STL-specific implementation-detail functions that cannot be modeled
adequately by the engine.

If possible, using `evalCall` to not even emit the false positives is
more efficient than suppressing them after analysis in
`BugReporterVisitor`s, so this modeling checker takes that route.

This patch introduces the conservative evaluation (no inlining) for
`__uninitialized_construct_buf_dispatch::__ucr`, an STL internal
function used by `std::stable_sort` and `std::inplace_merge` that
causes false positives.

Related to #177804.
---
 .../clang/StaticAnalyzer/Checkers/Checkers.td |  6 ++
 .../StaticAnalyzer/Checkers/CMakeLists.txt    |  1 +
 .../Checkers/OpaqueSTLFunctionsModeling.cpp   | 76 +++++++++++++++++++
 ...tem-header-simulator-cxx-std-suppression.h | 28 +++++++
 .../implicit-cxx-std-suppression.cpp          | 10 +++
 .../opaque-stl-functions-modeling-tag.cpp     | 14 ++++
 6 files changed, 135 insertions(+)
 create mode 100644 clang/lib/StaticAnalyzer/Checkers/OpaqueSTLFunctionsModeling.cpp
 create mode 100644 clang/test/Analysis/diagnostics/opaque-stl-functions-modeling-tag.cpp

diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 6a409944849e6..99dbae1dbf8b7 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -674,6 +674,12 @@ def PureVirtualCallChecker
       HelpText<
           "Check pure virtual function calls during construction/destruction">,
       Documentation<HasDocumentation>;
+
+def OpaqueSTLFunctionsModeling : Checker<"OpaqueSTLFunctionsModeling">,
+  HelpText<"Force opaque, conservative evaluation for STL internal implementation functions">,
+  Documentation<NotDocumented>,
+  Hidden;
+
 } // end: "cplusplus"
 
 let ParentPackage = CplusplusOptIn in {
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 2df36d8e672ae..3edbd7ebdc1bf 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -86,6 +86,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   ObjCSelfInitChecker.cpp
   ObjCSuperDeallocChecker.cpp
   ObjCUnusedIVarsChecker.cpp
+  OpaqueSTLFunctionsModeling.cpp
   OSObjectCStyleCast.cpp
   PaddingChecker.cpp
   PointerArithChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/OpaqueSTLFunctionsModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/OpaqueSTLFunctionsModeling.cpp
new file mode 100644
index 0000000000000..0e4f8c4bbc4d3
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/OpaqueSTLFunctionsModeling.cpp
@@ -0,0 +1,76 @@
+//===--- OpaqueSTLFunctionsModeling.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Forces conservative evaluation for STL internal implementation functions
+// (prefixed with '__') known to cause false positives. This prevents inlining
+// of complex STL internals and avoids wasting analysis time spent in
+// `BugReporterVisitor`s.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/ProgramPoint.h"
+#include "clang/Basic/IdentifierTable.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class OpaqueSTLFunctionsModeling : public Checker<eval::Call> {
+public:
+  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+  bool shouldForceConservativeEval(const CallEvent &Call) const;
+};
+} // anonymous namespace
+
+bool OpaqueSTLFunctionsModeling::evalCall(const CallEvent &Call,
+                                          CheckerContext &C) const {
+  if (!shouldForceConservativeEval(Call))
+    return false;
+
+  ProgramStateRef State = C.getState();
+  State = Call.invalidateRegions(C.blockCount(), State);
+  static const SimpleProgramPointTag OpaqueCallTag{getDebugTag(),
+                                                   "Forced Opaque Call"};
+  C.addTransition(State, &OpaqueCallTag);
+  return true;
+}
+
+bool OpaqueSTLFunctionsModeling::shouldForceConservativeEval(
+    const CallEvent &Call) const {
+  const Decl *D = Call.getDecl();
+  if (!D || !AnalysisDeclContext::isInStdNamespace(D))
+    return false;
+
+  // __uninitialized_construct_buf_dispatch::__ucr is used by stable_sort
+  // and inplace_merge.
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) {
+    if (const IdentifierInfo *II = MD->getIdentifier()) {
+      if (II->getName() == "__ucr") {
+        const CXXRecordDecl *RD = MD->getParent();
+        if (RD->getName().starts_with("__uninitialized_construct_buf_dispatch"))
+          return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+void ento::registerOpaqueSTLFunctionsModeling(CheckerManager &Mgr) {
+  Mgr.registerChecker<OpaqueSTLFunctionsModeling>();
+}
+
+bool ento::shouldRegisterOpaqueSTLFunctionsModeling(const CheckerManager &Mgr) {
+  return Mgr.getLangOpts().CPlusPlus;
+}
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-suppression.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-suppression.h
index dc53af269c9c2..f4e3454f905b5 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-suppression.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx-std-suppression.h
@@ -142,5 +142,33 @@ shared_ptr<_Tp>::shared_ptr(nullptr_t) {
 }
 
 #endif // __has_feature(cxx_decltype)
+
+// __uninitialized_construct_buf_dispatch::__ucr is used by stable_sort
+// and inplace_merge.
+template<typename _Tp>
+struct __uninitialized_construct_buf_dispatch {
+  template<typename _ForwardIterator, typename _Allocator>
+  static void __ucr(_ForwardIterator __first, _ForwardIterator __last,
+                    _Allocator& __a) {
+    // Fake error trigger
+    int z = 0;
+    z = 5/z;
+  }
+};
+
+template<typename _RandomAccessIterator>
+void stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last) {
+  allocator<int> alloc;
+  __uninitialized_construct_buf_dispatch<int>::__ucr(__first, __last, alloc);
+}
+
+template<typename _BidirectionalIterator>
+void inplace_merge(_BidirectionalIterator __first,
+                   _BidirectionalIterator,
+                   _BidirectionalIterator __last) {
+  allocator<int> alloc;
+  __uninitialized_construct_buf_dispatch<int>::__ucr(__first, __last, alloc);
+}
+
 }
 
diff --git a/clang/test/Analysis/diagnostics/implicit-cxx-std-suppression.cpp b/clang/test/Analysis/diagnostics/implicit-cxx-std-suppression.cpp
index 35f8798c81ae1..f49d6f78c165a 100644
--- a/clang/test/Analysis/diagnostics/implicit-cxx-std-suppression.cpp
+++ b/clang/test/Analysis/diagnostics/implicit-cxx-std-suppression.cpp
@@ -37,3 +37,13 @@ void testSuppression_std_shared_pointer() {
 
   p = nullptr; // no-warning
 }
+
+void testSuppression_stable_sort() {
+  int arr[5];
+  std::stable_sort(arr, arr + 5); // no-warning
+}
+
+void testSuppression_inplace_merge() {
+  int arr[5];
+  std::inplace_merge(arr, arr + 2, arr + 5); // no-warning
+}
diff --git a/clang/test/Analysis/diagnostics/opaque-stl-functions-modeling-tag.cpp b/clang/test/Analysis/diagnostics/opaque-stl-functions-modeling-tag.cpp
new file mode 100644
index 0000000000000..bab43b0622b8d
--- /dev/null
+++ b/clang/test/Analysis/diagnostics/opaque-stl-functions-modeling-tag.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.OpaqueSTLFunctionsModeling \
+// RUN:   -analyzer-dump-egraph=%t.dot -std=c++11 %s
+// RUN: cat %t.dot | FileCheck %s
+
+#include "../Inputs/system-header-simulator-cxx-std-suppression.h"
+
+void testOpaqueSTLTags() {
+  int arr[5];
+  std::stable_sort(arr, arr + 5);
+// CHECK: \"tag\": \"cplusplus.OpaqueSTLFunctionsModeling : Forced Opaque Call\"
+  std::inplace_merge(arr, arr + 2, arr + 5);
+// CHECK: \"tag\": \"cplusplus.OpaqueSTLFunctionsModeling : Forced Opaque Call\"
+}
+



More information about the cfe-commits mailing list