diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/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) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -582,6 +582,10 @@ WeakDependencies<[CallAndMessageChecker, NonNullParamChecker, StreamChecker]>, Documentation; +def UnixAPIPortabilityMinorChecker : Checker<"APIPortabilityMinor">, + HelpText<"Checks for minor portability issues in calls to various UNIX/Posix functions">, + Documentation; + } // end "alpha.unix" //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/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 diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIPortabilityMinorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIPortabilityMinorChecker.cpp new file mode 100644 --- /dev/null +++ b/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 { +public: + UnixAPIPortabilityMinorChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +private: + std::unique_ptr 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 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( + *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(); + + 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(); +} +bool ento::shouldRegisterUnixAPIPortabilityMinorChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/unix-api-portability-minor.cpp b/clang/test/Analysis/unix-api-portability-minor.cpp new file mode 100644 --- /dev/null +++ b/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}} +}