Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -380,6 +380,10 @@ HelpText<"Check for proper usage of vfork">, DescFile<"VforkChecker.cpp">; +def LibraryFunctionsChecker : Checker<"LibraryFunctions">, + HelpText<"Improve modeling of common library functions">, + DescFile<"LibraryFunctionsChecker.cpp">; + } // end "unix" let ParentPackage = UnixAlpha in { Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -34,6 +34,7 @@ GenericTaintChecker.cpp IdenticalExprChecker.cpp IvarInvalidationChecker.cpp + LibraryFunctionsChecker.cpp LLVMConventionsChecker.cpp LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp Index: lib/StaticAnalyzer/Checkers/LibraryFunctionsChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/LibraryFunctionsChecker.cpp @@ -0,0 +1,644 @@ +//=== LibraryFunctionsChecker.cpp - integer overflows checker ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker improves modeling of a few simple library functions. +// It does not throw warnings. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace clang::ento; + +namespace { +class LibraryFunctionsChecker : public Checker { + static const uint32_t Ret = std::numeric_limits::max(); + enum ValueRangeKindTy { Outside, Inside, ComparesToArgument }; + enum InvalidationKindTy { Normal, Pure }; + typedef std::vector> IntRangeVector; + + class ValueRange { + private: + // ArgNo in CallExpr and CallEvent is defined is Unsigned, but + // obviously uint32_t should be enough for most practical purposes. + uint32_t ArgNo; // Argument to which we apply the range. + ValueRangeKindTy Kind; // Kind of range definition. + IntRangeVector Args; // Polymorphic arguments. + + public: + ValueRange(uint32_t ArgNo, ValueRangeKindTy Kind, + const IntRangeVector &Args) + : ArgNo(ArgNo), Kind(Kind), Args(Args) {} + + uint32_t getArgNo() const { return ArgNo; } + ValueRangeKindTy getKind() const { return Kind; } + + BinaryOperator::Opcode getOpcode() const { + assert(Kind == ComparesToArgument); + assert(Args.size() == 1); + BinaryOperator::Opcode Op = + static_cast(Args[0].first); + assert(BinaryOperator::isComparisonOp(Op) && + "Only comparison ops are supported for ComparesToArgument"); + return Op; + } + + uint32_t getOtherArgNo() const { + assert(Kind == ComparesToArgument); + assert(Args.size() == 1); + return static_cast(Args[0].second); + } + + const IntRangeVector &getRanges() const { + assert(Kind != ComparesToArgument); + return Args; + } + }; + + typedef std::vector ValueRangeSet; + + // This spec is a bit more specific than CallDescription. + struct FunctionSpec { + const std::vector ArgTypes; + const QualType RetType; + const InvalidationKindTy InvalidationKind; + const std::vector Ranges; + }; + + typedef std::map FunctionSpecMapTy; + mutable FunctionSpecMapTy FunctionSpecMap; + + static QualType getArgType(const FunctionSpec &Spec, uint32_t ArgNo) { + return ArgNo == Ret ? Spec.RetType : Spec.ArgTypes[ArgNo]; + } + static QualType getArgType(const CallEvent &Call, uint32_t ArgNo) { + return ArgNo == Ret ? Call.getResultType().getCanonicalType() + : Call.getArgExpr(ArgNo)->getType().getCanonicalType(); + } + static QualType getArgType(const CallExpr *CE, uint32_t ArgNo) { + return ArgNo == Ret ? CE->getType().getCanonicalType() + : CE->getArg(ArgNo)->getType().getCanonicalType(); + } + static SVal getArgSVal(const CallEvent &Call, uint32_t ArgNo) { + return ArgNo == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgNo); + } + +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + bool evalCall(const CallExpr *CE, CheckerContext &C) const; + +private: + Optional findFunctionSpec(const FunctionDecl *FD, + const CallExpr *CE, + CheckerContext &C) const; + + void initFunctionSpecs(BasicValueFactory &BVF) const; +}; +} // end of anonymous namespace + +void LibraryFunctionsChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) + return; + + const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + Optional FoundSpec = findFunctionSpec(FD, CE, C); + if (!FoundSpec) + return; + + const FunctionSpec &Spec = *FoundSpec; + + // Now apply ranges. + + SValBuilder &SVB = C.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + ConstraintManager &CM = C.getConstraintManager(); + ProgramStateRef State = C.getState(); + QualType CondT = SVB.getConditionType(); + + for (const auto &VRS: Spec.Ranges) { + + ProgramStateRef NewState = State; + + for (const auto &VR: VRS) { + QualType T = getArgType(Spec, VR.getArgNo()); + SVal V = getArgSVal(Call, VR.getArgNo()); + + // In case we encounter unknown or undefined values, + // we would not be able to add ranges on them anyway, + // but other ranges are still worth adding. + switch (VR.getKind()) { + case ComparesToArgument: { + BinaryOperator::Opcode Op = VR.getOpcode(); + uint32_t OtherArg = VR.getOtherArgNo(); + SVal OtherV = getArgSVal(Call, OtherArg); + QualType OtherT = getArgType(Call, OtherArg); + // Note: we avoid integral promotion for comparison. + OtherV = SVB.evalCast(OtherV, T, OtherT); + if (auto CompV = SVB.evalBinOp(NewState, Op, V, OtherV, CondT) + .getAs()) + NewState = NewState->assume(*CompV, true); + break; + } + case Outside: + if (auto N = V.getAs()) { + const IntRangeVector &R = VR.getRanges(); + size_t E = R.size(); + for (size_t I = 0; I != E; ++I) { + const llvm::APSInt &Min = BVF.getValue(R[I].first, T); + const llvm::APSInt &Max = BVF.getValue(R[I].second, T); + assert(Min <= Max); + NewState = + CM.assumeWithinInclusiveRange(NewState, *N, Min, Max, false); + if (!NewState) + break; + } + } + break; + case Inside: + // "Inside R" is treated as "outside [T_MIN, T_MAX] \ R". + // We cut off [T_MIN, min(R) - 1] and [max(R) + 1, T_MAX] if necessary, + // and then cut away all holes in R one by one. + if (auto N = V.getAs()) { + const IntRangeVector &R = VR.getRanges(); + size_t E = R.size(); + + const llvm::APSInt &MinusInf = BVF.getMinValue(T); + const llvm::APSInt &PlusInf = BVF.getMaxValue(T); + + const llvm::APSInt &Left = BVF.getValue(R[0].first - 1, T); + if (Left != PlusInf) { + assert(MinusInf <= Left); + NewState = CM.assumeWithinInclusiveRange(NewState, *N, + MinusInf, Left, false); + if (!NewState) + break; + } + + const llvm::APSInt &Right = BVF.getValue(R[E - 1].second + 1, T); + if (Right != MinusInf) { + assert(Right <= PlusInf); + NewState = CM.assumeWithinInclusiveRange(NewState, *N, + Right, PlusInf, false); + if (!NewState) + break; + } + + for (size_t I = 1; I != E; ++I) { + const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1, T); + const llvm::APSInt &Max = BVF.getValue(R[I].first - 1, T); + assert(Min <= Max); + NewState = CM.assumeWithinInclusiveRange(NewState, *N, Min, Max, + false); + if (!NewState) + break; + } + } + break; + } + + if (!NewState) + break; + } + + if (NewState) + C.addTransition(NewState); + } +} + +bool LibraryFunctionsChecker::evalCall(const CallExpr *CE, + CheckerContext &C) const { + const FunctionDecl *FD = dyn_cast_or_null(CE->getCalleeDecl()); + if (!FD) + return false; + + Optional FoundSpec = findFunctionSpec(FD, CE, C); + if (!FoundSpec) + return false; + + const FunctionSpec &Spec = *FoundSpec; + switch (Spec.InvalidationKind) { + case Pure: { + ProgramStateRef State = C.getState(); + const LocationContext *LC = C.getLocationContext(); + SVal V = C.getSValBuilder().conjureSymbolVal( + CE, LC, CE->getType().getCanonicalType(), C.blockCount()); + State = State->BindExpr(CE, LC, V); + C.addTransition(State); + return true; + } + case Normal: + return false; + } + llvm_unreachable("Unknown invalidation kind!"); +} + +Optional +LibraryFunctionsChecker::findFunctionSpec(const FunctionDecl *FD, + const CallExpr *CE, + CheckerContext &C) const { + // Note: we cannot always obtain FD from CE (eg. virtual call). + assert(CE); + + if (!FD) + return None; + + SValBuilder &SVB = C.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + initFunctionSpecs(BVF); + + std::string Name = FD->getQualifiedNameAsString(); + if (Name.empty() || !C.isCLibraryFunction(FD, Name)) + return None; + + auto FSMI = FunctionSpecMap.find(Name); + if (FSMI == FunctionSpecMap.end()) + return None; + + // Verify that function signature matches the spec in advance, + // so that we didn't have to roll back if anything goes wrong. + + // Check number of arguments: + const FunctionSpec &Spec = FSMI->second; + if (CE->getNumArgs() != Spec.ArgTypes.size()) + return None; + + // Check return type if relevant: + if (!Spec.RetType.isNull() && + Spec.RetType != CE->getType().getCanonicalType()) + return None; + + // Check argument types when relevant: + for (size_t I = 0, E = Spec.ArgTypes.size(); I != E; ++I) { + QualType FormalT = Spec.ArgTypes[I]; + // Null type marks irrelevant arguments. + if (FormalT.isNull()) + continue; + assert(!FormalT->isVoidType() && + "We should have had no significant void types in the spec"); + assert(FormalT.isCanonical() && + "We should only have canonical types in the spec"); + // FIXME: lift this assert (but not the ones above!) + assert(FormalT->isIntegralOrEnumerationType() && + "We only support integral ranges in the spec"); + QualType ActualT = getArgType(CE, I); + assert(ActualT.isCanonical()); + if (ActualT != FormalT) + return None; + } + return FSMI->second; +} + +void LibraryFunctionsChecker::initFunctionSpecs(BasicValueFactory &BVF) const { + if (!FunctionSpecMap.empty()) + return; + + ASTContext &ACtx = BVF.getContext(); + + QualType Irrelevant; + QualType IntTy = ACtx.IntTy; + QualType SizeTy = ACtx.getSizeType(); + QualType SSizeTy = ACtx.getIntTypeForBitwidth(ACtx.getTypeSize(SizeTy), true); + + // Don't worry about truncation here, it'd be cast back to SIZE_MAX when used. + int64_t SizeMax = BVF.getMaxValue(SizeTy).getLimitedValue(); + int64_t SSizeMax = BVF.getMaxValue(SSizeTy).getLimitedValue(); + + // NOTE: The signature needs to have the correct number of arguments. + // NOTE: However, insert Irrelevant when the type is insignificant. + // NOTE: Argument ranges should always cover all variants. If return value + // is completely unknown, omit it from the respective range set. + // NOTE: Upon comparing to another argument, the other argument is casted + // to the current argument's type. This avoids proper promotion but + // seems useful. + // NOTE: All types in the spec need to be canonical. + // + // The format is as follows: + // + //{ "function name", + // { spec: + // { argument types list, ... }, return type, purity, { range sets list: + // { ranges list: + // { argument index, inside or outside, {{from, to}, ...} }, + // { argument index, compares to argument, {{how, which}} }, + // ... + // } + // } + // } + //} + FunctionSpecMap = { + // The isascii() family of functions. + { "isalnum", + { + { IntTy }, IntTy, Pure, { + { // isupper() or islower() or isdigit() + { 0U, Inside, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}} }, + { Ret, Outside, {{0, 0}} } + }, + { // The locale-specific range. + { 0U, Inside, {{128, 255}} } + }, + { // Other. + { 0U, Outside, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, 255}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isalpha", + { + { IntTy }, IntTy, Pure, { + { // isupper() or islower(). Note that 'Z' is less than 'a'. + { 0U, Inside, {{'A', 'Z'}, {'a', 'z'}} }, + { Ret, Outside, {{0, 0}} } + }, + { // The locale-specific range. + { 0U, Inside, {{128, 255}} }, + }, + { // Other. + { 0U, Outside, {{'A', 'Z'}, {'a', 'z'}, {128, 255}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isascii", + { + { IntTy }, IntTy, Pure, { + { // Is ASCII. + { 0U, Inside, {{0, 127}} }, + { Ret, Outside, {{0, 0}} } + }, + { // Is not ASCII. + { 0U, Outside, {{0, 127}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isblank", + { + { IntTy }, IntTy, Pure, { + { // Is tab or space. + { 0U, Inside, {{'\t', '\t'}, {' ', ' '}} }, + { Ret, Outside, {{0, 0}} } + }, + { // Other. + { 0U, Outside, {{'\t', '\t'}, {' ', ' '}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "iscntrl", + { + { IntTy }, IntTy, Pure, { + { // 0..31 or 127 + { 0U, Inside, {{0, 32}, {127, 127}} }, + { Ret, Outside, {{0, 0}} }, + }, + { + { 0U, Outside, {{0, 32}, {127, 127}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isdigit", + { + { IntTy }, IntTy, Pure, { + { // Is a digit. + { 0U, Inside, {{'0', '9'}} }, + { Ret, Outside, {{0, 0}} }, + }, + { + { 0U, Outside, {{'0', '9'}} }, + { Ret, Inside, {{0, 0}} }, + } + } + } + }, + { "isgraph", + { + { IntTy }, IntTy, Pure, { + { + { 0U, Inside, {{33, 126}} }, + { Ret, Outside, {{0, 0}} } + }, + { + { 0U, Outside, {{33, 126}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "islower", + { + { IntTy }, IntTy, Pure, { + { // Is certainly uppercase. + { 0U, Inside, {{'a', 'z'}} }, + { Ret, Outside, {{0, 0}} } + }, + { // Is ascii but not uppercase. + { 0U, Inside, {{0, 127}} }, + { 0U, Outside, {{'a', 'z'}} }, + { Ret, Inside, {{0, 0}} } + }, + { // The locale-specific range. + { 0U, Inside, {{128, 255}} } + }, + { // Is not an unsigned char. + { 0U, Outside, {{0, 255}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isprint", + { + { IntTy }, IntTy, Pure, { + { + { 0U, Inside, {{32, 126}} }, + { Ret, Outside, {{0, 0}} } + }, + { + { 0U, Outside, {{32, 126}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "ispunct", + { + { IntTy }, IntTy, Pure, { + { + { 0U, Inside, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}} }, + { Ret, Outside, {{0, 0}} } + }, + { + { 0U, Outside, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isspace", + { + { IntTy }, IntTy, Pure, { + { // Space, '\f', '\n', '\r', '\t', '\v'. + { 0U, Inside, {{9, 13}, {' ', ' '}} }, + { Ret, Outside, {{0, 0}} } + }, + { // The locale-specific range. + { 0U, Inside, {{128, 255}} } + }, + { + { 0U, Outside, {{9, 13}, {' ', ' '}, {128, 255}} }, + { Ret, Inside, {{0, 0}} } + }, + } + } + }, + { "isupper", + { + { IntTy }, IntTy, Pure, { + { // Is certainly uppercase. + { 0U, Inside, {{'A', 'Z'}} }, + { Ret, Outside, {{0, 0}} } + }, + { // The locale-specific range. + { 0U, Inside, {{128, 255}} } + }, + { // Other. + { 0U, Outside, {{'A', 'Z'}, {128, 255}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + { "isxdigit", + { + { IntTy }, IntTy, Pure, { + { + { 0U, Inside, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}} }, + { Ret, Outside, {{0, 0}} } + }, + { + { 0U, Outside, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}} }, + { Ret, Inside, {{0, 0}} } + } + } + } + }, + + // The getc() family of functions that returns either a char or an EOF. + { "getc", + { + { Irrelevant }, IntTy, Normal, { + { // FIXME: EOF is assumed to be defined as -1. + { Ret, Inside, {{-1, 255}} } + } + } + } + }, + { "fgetc", + { + { Irrelevant }, IntTy, Normal, { + { // FIXME: EOF is assumed to be defined as -1. + { Ret, Inside, {{-1, 255}} } + } + } + } + }, + { "getchar", + { + { }, IntTy, Normal, { + { // FIXME: EOF is assumed to be defined as -1. + { Ret, Inside, {{-1, 255}} } + } + } + } + }, + + // read()-like functions that never return more than buffer size. + { "read", + { + { Irrelevant, Irrelevant, SizeTy }, SSizeTy, Normal, { + { + { Ret, ComparesToArgument, {{BO_LE, 2U}} }, + { Ret, Inside, {{-1, SSizeMax}} } + }, + } + } + }, + { "write", + { + { Irrelevant, Irrelevant, SizeTy }, SSizeTy, Normal, { + { + { Ret, ComparesToArgument, {{BO_LE, 2U}} }, + { Ret, Inside, {{-1, SSizeMax}} } + }, + } + } + }, + { "fread", + { + { Irrelevant, Irrelevant, SizeTy, Irrelevant }, SizeTy, Normal, { + { + { Ret, Inside, {{0, SizeMax}} }, + { Ret, ComparesToArgument, {{BO_LE, 2U}} } + } + } + } + }, + { "fwrite", + { + { Irrelevant, Irrelevant, SizeTy, Irrelevant }, SizeTy, Normal, { + { + { Ret, Inside, {{0, SizeMax}} }, + { Ret, ComparesToArgument, {{BO_LE, 2U}} } + } + } + } + }, + + // getline()-like functions either fail or read at least the delimiter. + { "getline", + { + { Irrelevant, Irrelevant, Irrelevant }, SSizeTy, Normal, { + { + { Ret, Inside, {{-1, -1}, {1, SSizeMax}} } + }, + } + } + }, + { "getdelim", + { + { Irrelevant, Irrelevant, Irrelevant, Irrelevant }, SSizeTy, Normal, { + { + { Ret, Inside, {{-1, -1}, {1, SSizeMax}} } + }, + } + } + } + }; +} + +void ento::registerLibraryFunctionsChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/library-functions.c =================================================================== --- /dev/null +++ test/Analysis/library-functions.c @@ -0,0 +1,109 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=unix.LibraryFunctions,debug.ExprInspection -verify %s + +void clang_analyzer_eval(int); +int glob; + +typedef struct FILE FILE; +int getc(FILE *); +#define EOF -1 +void test_getc(FILE *fp) { + int x; + while ((x = getc(fp)) != EOF) { + clang_analyzer_eval(x > 255); // expected-warning{{FALSE}} + clang_analyzer_eval(x >= 0); // expected-warning{{TRUE}} + } +} + +typedef unsigned long size_t; +typedef signed long ssize_t; +ssize_t write(int, const void *, size_t); +void test_write(int fd, char *buf) { + glob = 1; + ssize_t x = write(fd, buf, 10); + clang_analyzer_eval(glob); // expected-warning{{UNKNOWN}} + if (x >= 0) + clang_analyzer_eval(x <= 10); // expected-warning{{TRUE}} + else + clang_analyzer_eval(x == -1); // expected-warning{{TRUE}} +} + +size_t fread(void *, size_t, size_t, FILE *); +void test_fread(FILE *fp, int *buf) { + size_t x = fread(buf, sizeof(int), 10, fp); + clang_analyzer_eval(x <= 10); // expected-warning{{TRUE}} +} + +ssize_t getline(char **, size_t *, FILE *); +void test_getline(FILE *fp) { + char *line = 0; + size_t n = 0; + ssize_t len; + while ((len = getline(&line, &n, fp)) != -1) { + clang_analyzer_eval(len == 0); // expected-warning{{FALSE}} + } +} + +int isascii(int); +void test_isascii(int x) { + clang_analyzer_eval(isascii(123)); // expected-warning{{TRUE}} + clang_analyzer_eval(isascii(-1)); // expected-warning{{FALSE}} + if (isascii(x)) { + clang_analyzer_eval(x < 128); // expected-warning{{TRUE}} + clang_analyzer_eval(x >= 0); // expected-warning{{TRUE}} + } else { + if (x > 42) + clang_analyzer_eval(x >= 128); // expected-warning{{TRUE}} + else + clang_analyzer_eval(x < 0); // expected-warning{{TRUE}} + } + glob = 1; + isascii('a'); + clang_analyzer_eval(glob); // expected-warning{{TRUE}} +} + +int islower(int); +void test_islower(int x) { + clang_analyzer_eval(islower('x')); // expected-warning{{TRUE}} + clang_analyzer_eval(islower('X')); // expected-warning{{FALSE}} + if (islower(x)) + clang_analyzer_eval(x < 'a'); // expected-warning{{FALSE}} +} + +int getchar(void); +void test_getchar() { + int x = getchar(); + if (x == EOF) + return; + clang_analyzer_eval(x < 0); // expected-warning{{FALSE}} + clang_analyzer_eval(x < 256); // expected-warning{{TRUE}} +} + +int isalpha(int); +void test_isalpha() { + clang_analyzer_eval(isalpha(']')); // expected-warning{{FALSE}} + clang_analyzer_eval(isalpha('Q')); // expected-warning{{TRUE}} + clang_analyzer_eval(isalpha(128)); // expected-warning{{UNKNOWN}} +} + +int isalnum(int); +void test_alnum() { + clang_analyzer_eval(isalnum('1')); // expected-warning{{TRUE}} + clang_analyzer_eval(isalnum(')')); // expected-warning{{FALSE}} +} + +int isblank(int); +void test_isblank() { + clang_analyzer_eval(isblank('\t')); // expected-warning{{TRUE}} + clang_analyzer_eval(isblank(' ')); // expected-warning{{TRUE}} + clang_analyzer_eval(isblank('\n')); // expected-warning{{FALSE}} +} + +int ispunct(int); +void test_ispunct(int x) { + clang_analyzer_eval(ispunct(' ')); // expected-warning{{FALSE}} + clang_analyzer_eval(ispunct(-1)); // expected-warning{{FALSE}} + clang_analyzer_eval(ispunct('#')); // expected-warning{{TRUE}} + clang_analyzer_eval(ispunct('_')); // expected-warning{{TRUE}} + if (ispunct(x)) + clang_analyzer_eval(x < 127); // expected-warning{{TRUE}} +}