CuriousGeorgiy updated this revision to Diff 554259.
CuriousGeorgiy marked 2 inline comments as done.
CuriousGeorgiy added a comment.
Herald added a subscriber: ormris.

[analyzer] Add check for null pointer passed to the %p of printf family

The result of passing a null pointer to the pointer conversion specifier of
printf family of functions is implementation defined — for instance, on
Windows zeros are printed, whilst on Linux '(nil)' is printed.

The problem described above is a minor portability issue, so we don't just
add a check for to the existing Unix API checker, since it would be
desirable to be able to disable it.

Instead, to collect such checks we add a new APIPortabilityMinor checker to
the alpha.unix package with a check for the problem described above.

The check assumes that a null pointer is only passed to a pointer
conversion specifier, otherwise it would be undefined behaviour. By making
this assumption we do not have to check the format string and match data
arguments to conversion specifiers.

Closes #43453

Differential Revision: https://reviews.llvm.org/D154838


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D154838/new/

https://reviews.llvm.org/D154838

Files:
  clang/docs/analyzer/checkers.rst
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/UnixAPIPortabilityMinorChecker.cpp
  clang/test/Analysis/unix-api-portability-minor.cpp

Index: clang/test/Analysis/unix-api-portability-minor.cpp
===================================================================
--- /dev/null
+++ clang/test/Analysis/unix-api-portability-minor.cpp
@@ -0,0 +1,50 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.APIPortabilityMinor -verify %s
+
+#define NULL ((void*)0)
+
+struct FILE_t;
+typedef struct FILE_t FILE;
+
+typedef __typeof(sizeof(int)) size_t;
+
+int printf(const char *, ... );
+int fprintf(FILE *, const char *, ...);
+int sprintf(char *, const char *, ...);
+int snprintf(char *, size_t, const char *, ...);
+
+// Test basic case for the whole print family.
+void test_printf_family_pointer_conversion_specifier_null(FILE *file, char *buf, size_t buf_size, char *format) {
+  printf(format, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  fprintf(file, format, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  sprintf(buf, format, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  snprintf(buf, buf_size, format, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+}
+
+// Test builtin null pointer type is handled correctly.
+void test_printf_pointer_conversion_specifier_null_nullptr(char *format) {
+  printf(format, nullptr); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+}
+
+// Test various argument indexes.
+void test_printf_pointer_conversion_specifier_null_various_arguments(char *format) {
+  printf(format, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  printf(format, 1, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  printf(format, 1, NULL, 2); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  printf(format, NULL, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  printf(format, NULL, 1, NULL); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+  printf(format, 0); // no-warning
+}
+
+// Test pointer constraints.
+void printf_pointer_conversion_specifier_null_pointer_constraints(char *format, int *pointer1, int *pointer2) {
+  // Unknown pointer should not rase warning.
+  printf(format, pointer1); // no-warning
+  // Pointer argument should not get constrained after the check.
+  *pointer1 = 777; // no-warning
+
+  if (pointer2 != NULL) {
+    printf(format, pointer1); // no-warning
+    return;
+  }
+  printf(format, pointer2); // expected-warning{{The result of passing a null pointer to the pointer conversion specifier of the printf family of functions is implementation defined}}
+}
Index: clang/lib/StaticAnalyzer/Checkers/UnixAPIPortabilityMinorChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/UnixAPIPortabilityMinorChecker.cpp
@@ -0,0 +1,177 @@
+//= UnixAPIPortabilityMinorChecker.cpp --------------------------*- C++ -*-===//
+//
+// 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 files defines the UnixAPIPortabilityMinorChecker, which is a collection
+// of checks for minor portability issues in calls to various UNIX/Posix
+// functions.
+//
+//===----------------------------------------------------------------------===//
+
+#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"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class UnixAPIPortabilityMinorChecker : public Checker<check::PreCall> {
+public:
+  UnixAPIPortabilityMinorChecker();
+
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+  std::unique_ptr<BugType> PrintfPointerConversionSpecifierNULLBugType;
+  void CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(
+      const CallEvent &Call, CheckerContext &C,
+      unsigned int data_args_index) const;
+  void CheckPrintfFamilyPointerConversionSpecifierNULLPrintf(
+      const CallEvent &Call, CheckerContext &C) const;
+  void CheckPrintfFamilyPointerConversionSpecifierNULLFprintf(
+      const CallEvent &Call, CheckerContext &C) const;
+  void CheckPrintfFamilyPointerConversionSpecifierNULLSprintf(
+      const CallEvent &Call, CheckerContext &C) const;
+  void CheckPrintfFamilyPointerConversionSpecifierNULLSnprintf(
+      const CallEvent &Call, CheckerContext &C) const;
+  void ReportPrintfPointerConversionSpecifierNULL(CheckerContext &C,
+                                                  ProgramStateRef nullState,
+                                                  const Expr *arg) const;
+  using CheckFn = void (UnixAPIPortabilityMinorChecker::*)(
+      const CallEvent &Call, CheckerContext &C) const;
+  const CallDescriptionMap<CheckFn> PrintfFamilyFunctions = {
+      {{{"printf"}},
+       &UnixAPIPortabilityMinorChecker::
+           CheckPrintfFamilyPointerConversionSpecifierNULLPrintf},
+      {{{"fprintf"}},
+       &UnixAPIPortabilityMinorChecker::
+           CheckPrintfFamilyPointerConversionSpecifierNULLFprintf},
+      {{{"sprintf"}},
+       &UnixAPIPortabilityMinorChecker::
+           CheckPrintfFamilyPointerConversionSpecifierNULLSprintf},
+      {{{"snprintf"}},
+       &UnixAPIPortabilityMinorChecker::
+           CheckPrintfFamilyPointerConversionSpecifierNULLSnprintf}};
+};
+} // end anonymous namespace
+
+UnixAPIPortabilityMinorChecker::UnixAPIPortabilityMinorChecker() {
+  PrintfPointerConversionSpecifierNULLBugType.reset(
+      new BugType(this,
+                  "Passing a null pointer to the pointer conversion "
+                  "specifier of ",
+                  categories::UnixAPI));
+}
+
+//===----------------------------------------------------------------------===//
+// printf family of functions with null pointer passed to pointer
+// conversion specifier
+//===----------------------------------------------------------------------===//
+
+// Generates an error report, indicating that the result of passing a null
+// pointer to pointer conversion specifier of printf family of functions is
+// implementation defined.
+void UnixAPIPortabilityMinorChecker::ReportPrintfPointerConversionSpecifierNULL(
+    CheckerContext &C, ProgramStateRef nullState, const Expr *arg) const {
+  ExplodedNode *N =
+      C.generateNonFatalErrorNode(nullState ? nullState : C.getState());
+  if (!N)
+    return;
+  auto report = std::make_unique<PathSensitiveBugReport>(
+      *PrintfPointerConversionSpecifierNULLBugType,
+      "The result of passing a null pointer to the pointer conversion "
+      "specifier of the printf family of functions is implementation defined",
+      N);
+  report->addRange(arg->getSourceRange());
+  bugreporter::trackExpressionValue(N, arg, *report);
+  C.emitReport(std::move(report));
+}
+
+// Checks data arguments of printf family of functions for a null pointer,
+// assuming it is passed to a pointer conversion specifier (%p), i.e. without
+// checking the format string.
+void UnixAPIPortabilityMinorChecker::
+    CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(
+        const CallEvent &Call, CheckerContext &C,
+        unsigned int data_args_index) const {
+  ProgramStateRef state = C.getState();
+  ConstraintManager &CM = state->getConstraintManager();
+
+  for (unsigned int i = data_args_index; i < Call.getNumArgs(); i++) {
+    const Expr *arg = Call.getArgExpr(i);
+    if (!arg)
+      continue;
+
+    const QualType type = arg->getType();
+    if (!type->isPointerType() && !type->isNullPtrType())
+      continue;
+
+    SVal argVal = Call.getArgSVal(i);
+    if (argVal.isUnknownOrUndef())
+      continue;
+
+    auto argDefinedVal = argVal.getAs<DefinedSVal>();
+
+    ProgramStateRef notNullState, nullState;
+    std::tie(notNullState, nullState) =
+        CM.assumeDual(C.getState(), *argDefinedVal);
+    if (!notNullState && nullState) {
+      ReportPrintfPointerConversionSpecifierNULL(C, nullState, arg);
+      return;
+    }
+  }
+}
+
+void UnixAPIPortabilityMinorChecker::
+    CheckPrintfFamilyPointerConversionSpecifierNULLPrintf(
+        const CallEvent &Call, CheckerContext &C) const {
+  CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(Call, C, 1);
+}
+
+void UnixAPIPortabilityMinorChecker::
+    CheckPrintfFamilyPointerConversionSpecifierNULLFprintf(
+        const CallEvent &Call, CheckerContext &C) const {
+  CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(Call, C, 2);
+}
+
+void UnixAPIPortabilityMinorChecker::
+    CheckPrintfFamilyPointerConversionSpecifierNULLSprintf(
+        const CallEvent &Call, CheckerContext &C) const {
+  CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(Call, C, 2);
+}
+
+void UnixAPIPortabilityMinorChecker::
+    CheckPrintfFamilyPointerConversionSpecifierNULLSnprintf(
+        const CallEvent &Call, CheckerContext &C) const {
+  CheckPrintfFamilyPointerConversionSpecifierNULLGeneric(Call, C, 3);
+}
+
+void UnixAPIPortabilityMinorChecker::checkPreCall(const CallEvent &Call,
+                                                  CheckerContext &C) const {
+  if (!Call.isGlobalCFunction())
+    return;
+
+  if (const auto *Checker = PrintfFamilyFunctions.lookup(Call))
+    (this->**Checker)(Call, C);
+}
+
+//===----------------------------------------------------------------------===//
+// Checker registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerUnixAPIPortabilityMinorChecker(CheckerManager &mgr) {
+  mgr.registerChecker<UnixAPIPortabilityMinorChecker>();
+}
+bool ento::shouldRegisterUnixAPIPortabilityMinorChecker(
+    const CheckerManager &mgr) {
+  return true;
+}
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -125,6 +125,7 @@
   UninitializedObject/UninitializedObjectChecker.cpp
   UninitializedObject/UninitializedPointee.cpp
   UnixAPIChecker.cpp
+  UnixAPIPortabilityMinorChecker.cpp
   UnreachableCodeChecker.cpp
   VforkChecker.cpp
   VLASizeChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -582,6 +582,10 @@
   WeakDependencies<[CallAndMessageChecker, NonNullParamChecker, StreamChecker]>,
   Documentation<HasDocumentation>;
 
+def UnixAPIPortabilityMinorChecker : Checker<"APIPortabilityMinor">,
+  HelpText<"Checks for minor portability issues in calls to various UNIX/Posix functions">,
+  Documentation<HasDocumentation>;
+
 } // end "alpha.unix"
 
 //===----------------------------------------------------------------------===//
Index: clang/docs/analyzer/checkers.rst
===================================================================
--- clang/docs/analyzer/checkers.rst
+++ clang/docs/analyzer/checkers.rst
@@ -949,6 +949,32 @@
    while(1);
  }
 
+unix.APIPortabilityMinor (C)
+""""""""""""""""""""""""""""
+Checks for minor portability issues in calls to various UNIX/Posix
+functions.
+
+These are the checks included:
+ -  Null pointer being passed to the pointer conversion specifier (%p) of the
+    printf family of functions. Passing null pointers to such functions is
+    implementation defined, thus non-portable. For instance, on Windows zeros
+    are printed, whilst on Linux '(nil)' is printed. The following functions are
+    considered: printf, fprintf, sprintf, snprintf.
+
+    .. code-block:: c
+
+     void test(void *p) {
+       if (p == NULL) {
+         printf("WARNING: null pointer passed");
+       }
+
+       printf("%p", p); // warn
+
+       if (p != NULL) {
+         printf("%p", p); // no warning
+       }
+     }
+
 .. _unix-cstring-BadSizeArg:
 
 unix.cstring.BadSizeArg (C)
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to