Author: DonĂ¡t Nagy
Date: 2026-02-23T12:44:58+01:00
New Revision: 36d60b16df50e648f21196f25e5f87dad8659909

URL: 
https://github.com/llvm/llvm-project/commit/36d60b16df50e648f21196f25e5f87dad8659909
DIFF: 
https://github.com/llvm/llvm-project/commit/36d60b16df50e648f21196f25e5f87dad8659909.diff

LOG: [analyzer] New checker: optin.core.UnconditionalVAArg (#175602)

Add a new optin checker which reports variadic functions that
unconditionally use `va_arg()`. It would be undefined behavior to call
such functions without passing any variadic arguments, so the SEI-CERT
rule EXP47-C says that this pattern should be avoided.

As this part of this SEI-CERT rule focuses on a very narrow area, I
aimed to write a simple and stable checker that can report basic "clear"
occurrences of this fault. It would be possible to cover some additional
corner cases, but it isn't worth the effort.

Added: 
    clang/lib/StaticAnalyzer/Checkers/UnconditionalVAArgChecker.cpp
    clang/test/Analysis/unconditional-va_arg.c

Modified: 
    clang/docs/analyzer/checkers.rst
    clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
    clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
    clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
    clang/lib/StaticAnalyzer/Checkers/VAListChecker.cpp
    llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn

Removed: 
    


################################################################################
diff  --git a/clang/docs/analyzer/checkers.rst 
b/clang/docs/analyzer/checkers.rst
index 87a5cb5cab103..d209a5d35132f 100644
--- a/clang/docs/analyzer/checkers.rst
+++ b/clang/docs/analyzer/checkers.rst
@@ -903,6 +903,49 @@ reported.
 Do not use the :ref:`address_space <langext-address_space_documentation>`
 attribute to suppress the reports - it just happens so that the checker also 
doesn't raise issues if the attribute is present.
 
+.. _optin-core-UnconditionalVAArg:
+
+optin.core.UnconditionalVAArg (C, C++)
+""""""""""""""""""""""""""""""""""""""
+Check for variadic functions that unconditionally use ``va_arg()``. It is
+possible to use such functions safely (as long as they always receive at least
+one variadic argument), but their careless use can lead to undefined behavior
+(trying to access a variadic argument when there isn't any), so it is better to
+avoid them.
+
+**Note:** This checker only inspects the *definition* of the variadic functions
+and reports those that *would* fail if they were called with no variadic
+arguments -- even if there is no such call in the codebase.
+
+This design rule is dictated by the SEI CERT rule `EXP47-C
+<https://wiki.sei.cmu.edu/confluence/display/c/EXP47-C.+Do+not+call+va_arg+with+an+argument+of+the+incorrect+type>`_,
+which describes several issues related to the use of ``va_arg()``. (The problem
+reported by this checker is shown in the second code example; the first,
+unrelated code example is covered by the clang diagnostic `-Wvarargs
+<https://clang.llvm.org/docs/DiagnosticsReference.html#wvarargs>`_.)
+
+.. code-block:: cpp
+
+  // This function expects a list of variadic arguments terminated by a NULL 
pointer.
+  void log_message(const char *msg, ...) {
+    va_list va;
+    const char *arg;
+    printf("%s\n", msg);
+    va_start(va, msg);
+    while ((arg = va_arg(va, const char *))) {
+      // warn: calls to 'log_message' always reach this va_arg() expression
+      printf(" * %s\n", arg);
+    }
+    va_end(va);
+  }
+
+Instead of this bugprone pattern, the SEI-CERT coding standard suggests
+approaches where the number of variadic arguments can be specified in a
+
diff erent manner -- for example, it can be encoded in the initial parameter
+like ``void foo(size_t num_varargs, ...)``. Those approaches are still not
+foolproof (e.g. ``foo(3, "a")`` will be undefined behavior), but they reduce
+the chances of an accidental mistake.
+
 .. _optin-cplusplus-UninitializedObject:
 
 optin.cplusplus.UninitializedObject (C++)

diff  --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td 
b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 9e552d171cd26..6b9e0b50e1f59 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -422,6 +422,11 @@ def FixedAddressDereferenceChecker
       HelpText<"Check for dereferences of fixed addresses">,
       Documentation<HasDocumentation>;
 
+def UnconditionalVAArgChecker
+    : Checker<"UnconditionalVAArg">,
+      HelpText<"Check variadic functions unconditionally using va_arg">,
+      Documentation<HasDocumentation>;
+
 } // end "optin.core"
 
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h 
b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
index f6a023368f3d2..3546e203b715f 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h
@@ -256,6 +256,10 @@ class BasicBugReport : public BugReport {
   BasicBugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l)
       : BugReport(Kind::Basic, bt, desc), Location(l) {}
 
+  BasicBugReport(const BugType &BT, StringRef ShortDesc, StringRef Desc,
+                 PathDiagnosticLocation L)
+      : BugReport(Kind::Basic, BT, ShortDesc, Desc), Location(L) {}
+
   static bool classof(const BugReport *R) {
     return R->getKind() == Kind::Basic;
   }

diff  --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt 
b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index dc1feef484302..8a0621077b977 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -115,6 +115,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   TraversalChecker.cpp
   TrustNonnullChecker.cpp
   TrustReturnsNonnullChecker.cpp
+  UnconditionalVAArgChecker.cpp
   UndefBranchChecker.cpp
   UndefCapturedBlockVarChecker.cpp
   UndefResultChecker.cpp

diff  --git a/clang/lib/StaticAnalyzer/Checkers/UnconditionalVAArgChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/UnconditionalVAArgChecker.cpp
new file mode 100644
index 0000000000000..a6eeb8b7f2a18
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/UnconditionalVAArgChecker.cpp
@@ -0,0 +1,175 @@
+//== UnconditionalVAArgChecker.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines a checker to detect functions that unconditionally call
+// va_arg() and would fail if they were called with zero variadic arguments.
+//
+// This checker is only partially path-sensitive: it relies on the symbolic
+// execution of the analyzer engine to follow the execution path from the
+// beginning of a function to a va_arg() call and determine whether there are
+// any branching points on that path -- but it uses BasicBugReport reports
+// because report path wouldn't contain any useful information. (As this
+// checker diagnoses a property of a variadic function, the path before that
+// function is irrelevant; then the unconditional part of path is trivial.)
+//
+// The AST matching framework of Clang Tidy is not powerful enough to express
+// this "no branching on the execution path" relationship, at least not without
+// reimplementing a crude and fragile form of symbolic execution.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace clang;
+using namespace ento;
+using llvm::formatv;
+
+/// Either nullptr or a function under symbolic execution; a non-null value
+/// means that the analyzer didn't see any branching points from the beginning
+/// of that function until the current location.
+REGISTER_TRAIT_WITH_PROGRAMSTATE(HasUnconditionalPath, const FunctionDecl *)
+
+namespace {
+class UnconditionalVAArgChecker
+    : public Checker<check::BeginFunction, check::EndFunction,
+                     check::BranchCondition, check::PreStmt<VAArgExpr>> {
+  const BugType BT{this, "Unconditional use of va_arg()",
+                   categories::MemoryError};
+
+  static const FunctionDecl *getCurrentFunction(CheckerContext &C);
+
+public:
+  void checkBeginFunction(CheckerContext &C) const;
+  void checkEndFunction(const ReturnStmt *, CheckerContext &C) const;
+  void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const;
+  void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const;
+};
+} // end anonymous namespace
+
+const FunctionDecl *
+UnconditionalVAArgChecker::getCurrentFunction(CheckerContext &C) {
+  const Decl *FD = C.getLocationContext()->getDecl();
+  return dyn_cast_if_present<FunctionDecl>(FD);
+}
+
+void UnconditionalVAArgChecker::checkBeginFunction(CheckerContext &C) const {
+  // We only look for unconditional va_arg() use in variadic functions.
+  // Functions that take a va_list argument are just parts of the argument
+  // handling, it is more natural for them to have preconditions.
+  const FunctionDecl *FD = getCurrentFunction(C);
+  if (FD && FD->isVariadic()) {
+    // If a variadic function is inlined in the body of another variadic
+    // function, this overwrites the path tracking for the outer function. As
+    // this situation is fairly rare and it is very unlikely that the "big"
+    // outer function still has an unconditional path, there is no need to
+    // write more complex logic that handles this.
+    // NOTE: Despite this, the checker can sometimes still report the
+    // unconditional va_arg() use in the outer function (probably because there
+    // is an alternative execution path that doesn't enter the inner call).
+    C.addTransition(C.getState()->set<HasUnconditionalPath>(FD));
+  }
+}
+
+void UnconditionalVAArgChecker::checkEndFunction(const ReturnStmt *,
+                                                 CheckerContext &C) const {
+  // This callback is just for the sake of cleanliness, to remove data from the
+  // State after it becomes irrelevant. This checker would function perfectly
+  // correctly without this callback, and the impact on other checkers is also
+  // extremely limited (presence of extra metadata might prevent the
+  // unification of execution paths in some very rare situations).
+  ProgramStateRef State = C.getState();
+  const FunctionDecl *FD = getCurrentFunction(C);
+  if (FD && FD == State->get<HasUnconditionalPath>()) {
+    State = State->set<HasUnconditionalPath>(nullptr);
+    C.addTransition(State);
+  }
+}
+
+void UnconditionalVAArgChecker::checkBranchCondition(const Stmt *Condition,
+                                                     CheckerContext &C) const {
+  // After evaluating a branch condition, the analyzer (which examines
+  // execution paths individually) won't be able to find a va_arg() expression
+  // that is _unconditionally_ reached -- so this callback resets the state
+  // trait HasUnconditionalPath.
+  // NOTES:
+  // 1. This is the right thing to do even if the analyzer sees that _in the
+  // current state_ the execution can only continue in one direction. For
+  // example, if the variadic function isn't the entrypont, then the parameters
+  // recieved from the caller may guarantee that va_arg() is used -- but this
+  // does not mean that the function _unconditionally_ uses va_arg().
+  // 2. After other kinds of state splits (e.g. EagerlyAssueme, callbacks of
+  // StdLibraryFunctions separating 
diff erent cases for the behavior of a
+  // library function etc.) the 
diff erent execution paths will follow the same
+  // code (until they hit a branch condition), so it is reasonable (although
+  // not always correct) to assume that a va_arg() reached after those state
+  // splits is still _unconditionally_ reached if there were no branching
+  // statements.
+  // 3. This checker activates _after_ the evaluation of the branch condition,
+  // so va_arg() in the branch condition can be unconditionally reached.
+  C.addTransition(C.getState()->set<HasUnconditionalPath>(nullptr));
+}
+
+void UnconditionalVAArgChecker::checkPreStmt(const VAArgExpr *VAA,
+                                             CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  const FunctionDecl *PathFrom = State->get<HasUnconditionalPath>();
+  if (!PathFrom)
+    return;
+
+  // Reset this trait in the state to ensure that multiple consecutive
+  // va_arg() calls don't produce repeated warnings.
+  C.addTransition(State->set<HasUnconditionalPath>(nullptr));
+
+  IdentifierInfo *II = PathFrom->getIdentifier();
+  if (!II)
+    return;
+  StringRef FN = II->getName();
+
+  std::string FullMsg = formatv(
+      "Calls to '{0}' always reach this va_arg() expression, so calling "
+      "'{0}' with no variadic arguments would be undefined behavior",
+      FN);
+  SourceRange SR = VAA->getSourceRange();
+  PathDiagnosticLocation PDL(SR.getBegin(),
+                             C.getASTContext().getSourceManager());
+  // We create a non-path-sensitive report because the path wouldn't contain
+  // any useful information: the path leading to the variadic function is
+  // actively ignored by the checker and the unconditional path from the
+  // start of the variadic function is trivial.
+  auto R =
+      std::make_unique<BasicBugReport>(BT, BT.getDescription(), FullMsg, PDL);
+
+  if (getCurrentFunction(C) != PathFrom) {
+    // Highlight the definition of the variadic function in the rare case
+    // when the reached va_arg() expression is in another function.
+    SourceRange DefSR = PathFrom->getSourceRange();
+    PathDiagnosticLocation DefPDL(DefSR.getBegin(),
+                                  C.getASTContext().getSourceManager());
+    std::string NoteMsg =
+        formatv("Variadic function '{0}' is defined here", FN);
+    R->addNote(NoteMsg, DefPDL, DefSR);
+  }
+  R->addRange(SR);
+  R->setDeclWithIssue(PathFrom);
+  C.emitReport(std::move(R));
+}
+
+void ento::registerUnconditionalVAArgChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<UnconditionalVAArgChecker>();
+}
+
+bool ento::shouldRegisterUnconditionalVAArgChecker(const CheckerManager &) {
+  return true;
+}

diff  --git a/clang/lib/StaticAnalyzer/Checkers/VAListChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/VAListChecker.cpp
index 503fa5de868f2..e81c8bfa94cb1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/VAListChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/VAListChecker.cpp
@@ -6,7 +6,7 @@
 //
 
//===----------------------------------------------------------------------===//
 //
-// This defines checkers which detect usage of uninitialized va_list values
+// This defines a checker which detects usage of uninitialized va_list values
 // and va_start calls with no matching va_end.
 //
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/test/Analysis/unconditional-va_arg.c 
b/clang/test/Analysis/unconditional-va_arg.c
new file mode 100644
index 0000000000000..0f20cd2c1c702
--- /dev/null
+++ b/clang/test/Analysis/unconditional-va_arg.c
@@ -0,0 +1,274 @@
+// RUN: %clang_analyze_cc1 -triple hexagon-unknown-linux -verify %s \
+// RUN:   -analyzer-checker=core,optin.core.UnconditionalVAArg \
+// RUN:   -analyzer-output=text
+//
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu -verify %s \
+// RUN:   -analyzer-checker=core,optin.core.UnconditionalVAArg \
+// RUN:   -analyzer-output=text
+
+#include "Inputs/system-header-simulator-for-valist.h"
+int printf(const char *format, ...);
+void abort(void);
+
+void log_message(const char *msg, ...) {
+  // This artifical but plausible example function expects that the list of
+  // variadic arguments is terminated by a NULL pointer. Although careful use
+  // of this function is well-defined, passing no variadic arguments would
+  // trigger undefined behavior, so it may be better to choose a 
diff erent way
+  // to mark the count of variadic arguments.
+  va_list va;
+  const char *arg;
+  printf("%s\n", msg);
+  va_start(va, msg);
+  while ((arg = va_arg(va, const char *))) {
+    // expected-warning@-1 {{Unconditional use of va_arg()}}
+    // expected-note@-2 {{Calls to 'log_message' always reach this va_arg() 
expression, so calling 'log_message' with no variadic arguments would be 
undefined behavior}}
+    printf(" * %s\n", arg);
+  }
+  va_end(va);
+}
+
+
+void never_called(int fst, ...) {
+  // This simple test function is not called in this test file.
+  va_list va;
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'never_called' always reach this va_arg() 
expression, so calling 'never_called' with no variadic arguments would be 
undefined behavior}}
+  va_end(va);
+}
+
+
+void called_with_varargs(int fst, ...) {
+  // This function is identical to never_called(), but it is called by another
+  // function (which will serve as the entrypoint) and the call passes several
+  // variadic arguments. The diagnostic is still raised.
+  va_list va;
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'called_with_varargs' always reach this 
va_arg() expression, so calling 'called_with_varargs' with no variadic 
arguments would be undefined behavior}}
+  va_end(va);
+}
+
+void caller1(void) {
+  called_with_varargs(1, 2, 3, 4);
+}
+
+
+void called_without_varargs(int fst, ...) {
+  // This function is identical to never_called(), but it is called by another
+  // function (which will serve as the entrypoint) and the call passes no
+  // variadic arguments. The diagnostic is still raised.
+  va_list va;
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'called_without_varargs' always reach this 
va_arg() expression, so calling 'called_without_varargs' with no variadic 
arguments would be undefined behavior}}
+  // FIXME: Here the checker should mention that this function is _actually_
+  // called with no variadic arguments.
+  va_end(va);
+}
+
+void caller2(void) {
+  called_without_varargs(1);
+}
+
+
+void multiple_va_arg_calls(int fst, ...) {
+  // This function function unconditionally calls va_arg() multiple times. To
+  // avoid spamming the user, only the first use is reported.
+  va_list va;
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'multiple_va_arg_calls' always reach this 
va_arg() expression, so calling 'multiple_va_arg_calls' with no variadic 
arguments would be undefined behavior}}
+  (void)va_arg(va, int); // no-warning
+  (void)va_arg(va, int); // no-warning
+  va_end(va);
+}
+
+
+void has_conditional_return(int fst, ...) {
+  // The va_arg call is not always executed, so don't report this function.
+  // Actually understanding the conditional logic is infeasible, so the checker
+  // should accept any conditional logic that sometimes avoids the va_arg().
+  va_list va;
+  if (fst < 0)
+    return;
+  va_start(va, fst);
+  (void)va_arg(va, int); // no-warning
+  va_end(va);
+}
+
+
+void has_conditional_logic(int fst, ...) {
+  // As static analyzer only follows one execution path, it cannot see that
+  // va_arg() is always executed in this function. Theoretically it would be
+  // better to produce a report here, but implementing this would be a very
+  // ineffective use of development effort.
+  va_list va;
+  if (fst < 0)
+    fst = -fst;
+  va_start(va, fst);
+  (void)va_arg(va, int); // no-warning
+  va_end(va);
+}
+
+
+#define IS_FEATURE_ENABLED 1
+void has_constant_condition(int fst, ...) {
+  // This checker doesn't have special logic for recognizing and ignoring
+  // conditionals that can be resolved at compile time. This feature could be
+  // added in the future.
+  va_list va;
+  if (IS_FEATURE_ENABLED) {
+    va_start(va, fst);
+    (void)va_arg(va, int); // no-warning
+    va_end(va);
+  }
+}
+
+
+
+void caller_has_conditional_logic(int fst, ...) {
+  // This function is identical to never_called(), but it is used by a function
+  // that performs a state split before the call.
+  // This test validates that state splits _before_ the variadic call (which
+  // are irrelevant) do not influence the report creation. This is basically a
+  // workaround for the fact that not all functions are entrypoints and before
+  // reaching "our" variadic function the analyzer may go through arbitrary
+  // irrelevant code.
+  va_list va;
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'caller_has_conditional_logic' always reach 
this va_arg() expression, so calling 'caller_has_conditional_logic' with no 
variadic arguments would be undefined behavior}}
+  va_end(va);
+}
+
+void caller_with_conditional_logic(int flag) {
+  if (flag)
+    caller_has_conditional_logic(1, 2, 3);
+}
+
+
+int has_shortcircuiting_operator(int fst, ...) {
+  // In addition to 'if' and similar statements, ternary operators and
+  // shortcircuiting logical operators can also ensure that the use of va_arg()
+  // is not unconditional. (These all behave identically in the BranchCondition
+  // callback, so this simple testcase is mostly for documentation purposes.)
+  va_list va;
+  int x;
+  va_start(va, fst);
+  x = fst || va_arg(va, int); // no-warning
+  va_end(va);
+  return x;
+}
+
+
+void validate_argument(int fst) {
+  if (fst <= 0) {
+    printf("Negative first argument is illegal: %d!\n", fst);
+    abort();
+  }
+}
+
+void noreturn_in_function(int fst, ...) {
+  // This function calls a function which may call the noreturn function
+  // abort() before reaching the va_arg() call, so the checker doesn't produce
+  // a warning. This behavior is important e.g. for cases when a function uses
+  // assertions to mark preconditions.
+  va_list va;
+  validate_argument(fst);
+  va_start(va, fst);
+  (void)va_arg(va, int); // no-warning
+  va_end(va);
+}
+
+
+void print_negative(int fst) {
+  if (fst <= 0) {
+    printf("First argument is negative: %d!\n", fst);
+  }
+}
+
+void conditional_in_function(int fst, ...) {
+  // This function is very similar to noreturn_in_function(), but the called
+  // function always returns. However, the analyzer doesn't see this 
diff erence
+  // (because it follows a _single_ execution path and cannot see the
+  // alternative) so the checker behaves as in noreturn_in_function.
+  va_list va;
+  print_negative(fst);
+  va_start(va, fst);
+  (void)va_arg(va, int); // no-warning
+  va_end(va);
+}
+
+
+void defined_in_
diff erent_tu(int);
+
+void unknown_call(int fst, ...) {
+  // This function is very similar to noreturn_in_function() or
+  // conditional_in_function() but the definition of the called function is not
+  // visible for the analyzer. In theis situation the analyzer ignores the call
+  // and reports the va_arg() expression that unconditionally appears after it.
+  // This would be incorrect behavior only in a situation when the called
+  // function can return _and_ can be noreturn which should be fairly rare.
+  va_list va;
+  defined_in_
diff erent_tu(fst);
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'unknown_call' always reach this va_arg() 
expression, so calling 'unknown_call' with no variadic arguments would be 
undefined behavior}}
+  va_end(va);
+}
+
+
+void trivial_variadic(int fst, ...) {
+  // Do nothing.
+}
+
+void calls_other_variadic(int fst, ...) {
+  // As the 'HasUnconditionalPath' can "remember" only one variadic function,
+  // I would expect that the presence of 'trivial_variadic' would prevent the
+  // checker from reporting the unconditional use of va_arg() in this function.
+  // However, for some unclear reason the checker is still able to produce this
+  // (true positive) report.
+  va_list va;
+  trivial_variadic(fst, 2);
+  va_start(va, fst);
+  (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'calls_other_variadic' always reach this 
va_arg() expression, so calling 'calls_other_variadic' with no variadic 
arguments would be undefined behavior}}
+  va_end(va);
+}
+
+int get_int_from_va_list(va_list *va) {
+  // This checker can report va_arg() expressions that are unconditionally
+  // reached from a variadic function, even if they are located in a 
diff erent
+  // function. In this case the originating variadic function is marked with a
+  // note (to make it easier to find).
+  return va_arg(*va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'uses_wrapped_va_arg' always reach this 
va_arg() expression, so calling 'uses_wrapped_va_arg' with no variadic 
arguments would be undefined behavior}}
+}
+
+void uses_wrapped_va_arg(int fst, ...) {
+  // expected-note@-1 {{Variadic function 'uses_wrapped_va_arg' is defined 
here}}
+  va_list va;
+  va_start(va, fst);
+  (void)get_int_from_va_list(&va);
+  va_end(va);
+}
+
+int standalone_non_variadic(va_list *va) {
+  // A va_arg() expression is only reported if it is unconditionally reached
+  // from the beginning of a _variadic_ function. Functions that use va_arg()
+  // on a va_list obtained from other sources (e.g. as an argument) are
+  // presumed to be small helper subroutines of a complex variadic function, so
+  // we do not report cases where the va_arg() is unconditionally reached from
+  // the beginning of a _non-variadic_ function.
+  return va_arg(*va, int); //no-warning
+}
+
+int variadic_but_also_takes_va_list(va_list *va, ...) {
+  // The checker just looks for va_arg() calls without checking the origin of
+  // their argument, so in this (very artifical) example it produces a
+  // result that is arguably a false positive.
+  return va_arg(*va, int); // expected-warning{{Unconditional use of va_arg()}}
+  // expected-note@-1 {{Calls to 'variadic_but_also_takes_va_list' always 
reach this va_arg() expression, so calling 'variadic_but_also_takes_va_list' 
with no variadic arguments would be undefined behavior}}
+}

diff  --git 
a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn 
b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn
index 85905ad40a329..b3ac48975ee85 100644
--- a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn
@@ -123,6 +123,7 @@ static_library("Checkers") {
     "TraversalChecker.cpp",
     "TrustNonnullChecker.cpp",
     "TrustReturnsNonnullChecker.cpp",
+    "UnconditionalVAArgChecker.cpp",
     "UndefBranchChecker.cpp",
     "UndefCapturedBlockVarChecker.cpp",
     "UndefResultChecker.cpp",


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to