From b59b27040efbc1ac03427a5ff0eceec527202cc0 Mon Sep 17 00:00:00 2001 From: Gabor Horvath Date: Mon, 22 Aug 2016 11:21:30 +0000 Subject: [PATCH] Reapply "[analyzer] Added valist related checkers." Differential Revision: https://reviews.llvm.org/D15227 llvm-svn: 279427 --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 24 ++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../lib/StaticAnalyzer/Checkers/ValistChecker.cpp | 373 +++++++++++++++++++++ .../Inputs/system-header-simulator-for-valist.h | 30 ++ clang/test/Analysis/valist-uninitialized.c | 178 ++++++++++ clang/test/Analysis/valist-unterminated.c | 133 ++++++++ 6 files changed, 739 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp create mode 100644 clang/test/Analysis/Inputs/system-header-simulator-for-valist.h create mode 100644 clang/test/Analysis/valist-uninitialized.c create mode 100644 clang/test/Analysis/valist-unterminated.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 629a688..a7a6623 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -43,6 +43,9 @@ def Nullability : Package<"nullability">; def Cplusplus : Package<"cplusplus">; def CplusplusAlpha : Package<"cplusplus">, InPackage, Hidden; +def Valist : Package<"valist">; +def ValistAlpha : Package<"valist">, InPackage, Hidden; + def DeadCode : Package<"deadcode">; def DeadCodeAlpha : Package<"deadcode">, InPackage, Hidden; @@ -267,6 +270,27 @@ def VirtualCallChecker : Checker<"VirtualCall">, } // end: "alpha.cplusplus" + +//===----------------------------------------------------------------------===// +// Valist checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = ValistAlpha in { + +def UninitializedChecker : Checker<"Uninitialized">, + HelpText<"Check for usages of uninitialized (or already released) va_lists.">, + DescFile<"ValistChecker.cpp">; + +def UnterminatedChecker : Checker<"Unterminated">, + HelpText<"Check for va_lists which are not released by a va_end call.">, + DescFile<"ValistChecker.cpp">; + +def CopyToSelfChecker : Checker<"CopyToSelf">, + HelpText<"Check for va_lists which are copied onto itself.">, + DescFile<"ValistChecker.cpp">; + +} // end : "alpha.valist" + //===----------------------------------------------------------------------===// // Deadcode checkers. //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index a024249..75636c6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -81,6 +81,7 @@ add_clang_library(clangStaticAnalyzerCheckers UnreachableCodeChecker.cpp VforkChecker.cpp VLASizeChecker.cpp + ValistChecker.cpp VirtualCallChecker.cpp DEPENDS diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp new file mode 100644 index 0000000..b4bfa0c --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -0,0 +1,373 @@ +//== ValistChecker.cpp - stdarg.h macro usage checker -----------*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines checkers which detect usage of uninitialized va_list values +// and va_start calls with no matching va_end. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.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/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +REGISTER_SET_WITH_PROGRAMSTATE(InitializedVALists, const MemRegion *) + +namespace { +typedef SmallVector RegionVector; + +class ValistChecker : public Checker, + check::DeadSymbols> { + mutable std::unique_ptr BT_leakedvalist, BT_uninitaccess; + + struct VAListAccepter { + CallDescription Func; + int VAListPos; + }; + static const SmallVector VAListAccepters; + static const CallDescription VaStart, VaEnd, VaCopy; + +public: + enum CheckKind { + CK_Uninitialized, + CK_Unterminated, + CK_CopyToSelf, + CK_NumCheckKinds + }; + + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckName CheckNames[CK_NumCheckKinds]; + + void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + +private: + const MemRegion *getVAListAsRegion(SVal SV, CheckerContext &C) const; + StringRef getVariableNameFromRegion(const MemRegion *Reg) const; + const ExplodedNode *getStartCallSite(const ExplodedNode *N, + const MemRegion *Reg, + CheckerContext &C) const; + + void reportUninitializedAccess(const MemRegion *VAList, StringRef Msg, + CheckerContext &C) const; + void reportLeakedVALists(const RegionVector &LeakedVALists, StringRef Msg1, + StringRef Msg2, CheckerContext &C, ExplodedNode *N, + bool ForceReport = false) const; + + void checkVAListStartCall(const CallEvent &Call, CheckerContext &C, + bool IsCopy) const; + void checkVAListEndCall(const CallEvent &Call, CheckerContext &C) const; + + class ValistBugVisitor : public BugReporterVisitorImpl { + public: + ValistBugVisitor(const MemRegion *Reg, bool IsLeak = false) + : Reg(Reg), IsLeak(IsLeak) {} + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Reg); + } + std::unique_ptr + getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, + BugReport &BR) override { + if (!IsLeak) + return nullptr; + + PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( + EndPathNode, BRC.getSourceManager()); + // Do not add the statement itself as a range in case of leak. + return llvm::make_unique(L, BR.getDescription(), + false); + } + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + const MemRegion *Reg; + bool IsLeak; + }; +}; + +const SmallVector + ValistChecker::VAListAccepters = { + {{"vfprintf", 3}, 2}, + {{"vfscanf", 3}, 2}, + {{"vprintf", 2}, 1}, + {{"vscanf", 2}, 1}, + {{"vsnprintf", 4}, 3}, + {{"vsprintf", 3}, 2}, + {{"vsscanf", 3}, 2}, + {{"vfwprintf", 3}, 2}, + {{"vfwscanf", 3}, 2}, + {{"vwprintf", 2}, 1}, + {{"vwscanf", 2}, 1}, + {{"vswprintf", 4}, 3}, + // vswprintf is the wide version of vsnprintf, + // vsprintf has no wide version + {{"vswscanf", 3}, 2}}; +const CallDescription ValistChecker::VaStart("__builtin_va_start", 2), + ValistChecker::VaCopy("__builtin_va_copy", 2), + ValistChecker::VaEnd("__builtin_va_end", 1); +} // end anonymous namespace + +void ValistChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isGlobalCFunction()) + return; + if (Call.isCalled(VaStart)) + checkVAListStartCall(Call, C, false); + else if (Call.isCalled(VaCopy)) + checkVAListStartCall(Call, C, true); + else if (Call.isCalled(VaEnd)) + checkVAListEndCall(Call, C); + else { + for (auto FuncInfo : VAListAccepters) { + if (!Call.isCalled(FuncInfo.Func)) + continue; + const MemRegion *VAList = + getVAListAsRegion(Call.getArgSVal(FuncInfo.VAListPos), C); + if (!VAList) + return; + + if (C.getState()->contains(VAList)) + return; + + SmallString<80> Errmsg("Function '"); + Errmsg += FuncInfo.Func.getFunctionName(); + Errmsg += "' is called with an uninitialized va_list argument"; + reportUninitializedAccess(VAList, Errmsg.c_str(), C); + break; + } + } +} + +void ValistChecker::checkPreStmt(const VAArgExpr *VAA, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal VAListSVal = State->getSVal(VAA->getSubExpr(), C.getLocationContext()); + const MemRegion *VAList = getVAListAsRegion(VAListSVal, C); + if (!VAList) + return; + if (!State->contains(VAList)) + reportUninitializedAccess( + VAList, "va_arg() is called on an uninitialized va_list", C); +} + +void ValistChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + InitializedVAListsTy TrackedVALists = State->get(); + RegionVector LeakedVALists; + for (auto Reg : TrackedVALists) { + if (SR.isLiveRegion(Reg)) + continue; + LeakedVALists.push_back(Reg); + State = State->remove(Reg); + } + if (ExplodedNode *N = C.addTransition(State)) + reportLeakedVALists(LeakedVALists, "Initialized va_list", " is leaked", C, + N); +} + +const MemRegion *ValistChecker::getVAListAsRegion(SVal SV, + CheckerContext &C) const { + const MemRegion *Reg = SV.getAsRegion(); + const auto *TReg = dyn_cast_or_null(Reg); + // Some VarRegion based VLAs reach here as ElementRegions. + const auto *EReg = dyn_cast_or_null(TReg); + return EReg ? EReg->getSuperRegion() : TReg; +} + +// This function traverses the exploded graph backwards and finds the node where +// the va_list is initialized. That node is used for uniquing the bug paths. +// It is not likely that there are several different va_lists that belongs to +// different stack frames, so that case is not yet handled. +const ExplodedNode *ValistChecker::getStartCallSite(const ExplodedNode *N, + const MemRegion *Reg, + CheckerContext &C) const { + const LocationContext *LeakContext = N->getLocationContext(); + const ExplodedNode *StartCallNode = N; + + bool FoundInitializedState = false; + + while (N) { + ProgramStateRef State = N->getState(); + if (!State->contains(Reg)) { + if (FoundInitializedState) + break; + } else { + FoundInitializedState = true; + } + const LocationContext *NContext = N->getLocationContext(); + if (NContext == LeakContext || NContext->isParentOf(LeakContext)) + StartCallNode = N; + N = N->pred_empty() ? nullptr : *(N->pred_begin()); + } + + return StartCallNode; +} + +void ValistChecker::reportUninitializedAccess(const MemRegion *VAList, + StringRef Msg, + CheckerContext &C) const { + if (!ChecksEnabled[CK_Uninitialized]) + return; + if (ExplodedNode *N = C.generateErrorNode()) { + if (!BT_uninitaccess) + BT_uninitaccess.reset(new BugType(CheckNames[CK_Uninitialized], + "Uninitialized va_list", + "Memory Error")); + auto R = llvm::make_unique(*BT_uninitaccess, Msg, N); + R->markInteresting(VAList); + R->addVisitor(llvm::make_unique(VAList)); + C.emitReport(std::move(R)); + } +} + +void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, + StringRef Msg1, StringRef Msg2, + CheckerContext &C, ExplodedNode *N, + bool ForceReport) const { + if (!(ChecksEnabled[CK_Unterminated] || + (ChecksEnabled[CK_Uninitialized] && ForceReport))) + return; + for (auto Reg : LeakedVALists) { + if (!BT_leakedvalist) { + BT_leakedvalist.reset(new BugType(CheckNames[CK_Unterminated], + "Leaked va_list", "Memory Error")); + BT_leakedvalist->setSuppressOnSink(true); + } + + const ExplodedNode *StartNode = getStartCallSite(N, Reg, C); + PathDiagnosticLocation LocUsedForUniqueing; + + if (const Stmt *StartCallStmt = PathDiagnosticLocation::getStmt(StartNode)) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + StartCallStmt, C.getSourceManager(), StartNode->getLocationContext()); + + SmallString<100> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << Msg1; + std::string VariableName = Reg->getDescriptiveName(); + if (!VariableName.empty()) + OS << " " << VariableName; + OS << Msg2; + + auto R = llvm::make_unique( + *BT_leakedvalist, OS.str(), N, LocUsedForUniqueing, + StartNode->getLocationContext()->getDecl()); + R->markInteresting(Reg); + R->addVisitor(llvm::make_unique(Reg, true)); + C.emitReport(std::move(R)); + } +} + +void ValistChecker::checkVAListStartCall(const CallEvent &Call, + CheckerContext &C, bool IsCopy) const { + const MemRegion *VAList = getVAListAsRegion(Call.getArgSVal(0), C); + ProgramStateRef State = C.getState(); + if (!VAList) + return; + + if (IsCopy) { + const MemRegion *Arg2 = getVAListAsRegion(Call.getArgSVal(1), C); + if (Arg2) { + if (ChecksEnabled[CK_CopyToSelf] && VAList == Arg2) { + RegionVector LeakedVALists{VAList}; + if (ExplodedNode *N = C.addTransition(State)) + reportLeakedVALists(LeakedVALists, "va_list", + " is copied onto itself", C, N, true); + return; + } else if (!State->contains(Arg2)) { + if (State->contains(VAList)) { + State = State->remove(VAList); + RegionVector LeakedVALists{VAList}; + if (ExplodedNode *N = C.addTransition(State)) + reportLeakedVALists(LeakedVALists, "Initialized va_list", + " is overwritten by an uninitialized one", C, N, + true); + } else { + reportUninitializedAccess(Arg2, "Uninitialized va_list is copied", C); + } + return; + } + } + } + if (State->contains(VAList)) { + RegionVector LeakedVALists{VAList}; + if (ExplodedNode *N = C.addTransition(State)) + reportLeakedVALists(LeakedVALists, "Initialized va_list", + " is initialized again", C, N); + return; + } + + State = State->add(VAList); + C.addTransition(State); +} + +void ValistChecker::checkVAListEndCall(const CallEvent &Call, + CheckerContext &C) const { + const MemRegion *VAList = getVAListAsRegion(Call.getArgSVal(0), C); + if (!VAList) + return; + + if (!C.getState()->contains(VAList)) { + reportUninitializedAccess( + VAList, "va_end() is called on an uninitialized va_list", C); + return; + } + ProgramStateRef State = C.getState(); + State = State->remove(VAList); + C.addTransition(State); +} + +PathDiagnosticPiece *ValistChecker::ValistBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef State = N->getState(); + ProgramStateRef StatePrev = PrevN->getState(); + + const Stmt *S = PathDiagnosticLocation::getStmt(N); + if (!S) + return nullptr; + + StringRef Msg; + if (State->contains(Reg) && + !StatePrev->contains(Reg)) + Msg = "Initialized va_list"; + else if (!State->contains(Reg) && + StatePrev->contains(Reg)) + Msg = "Ended va_list"; + + if (Msg.empty()) + return nullptr; + + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, Msg, true); +} + +#define REGISTER_CHECKER(name) \ + void ento::register##name##Checker(CheckerManager &mgr) { \ + ValistChecker *checker = mgr.registerChecker(); \ + checker->ChecksEnabled[ValistChecker::CK_##name] = true; \ + checker->CheckNames[ValistChecker::CK_##name] = mgr.getCurrentCheckName(); \ + } + +REGISTER_CHECKER(Uninitialized) +REGISTER_CHECKER(Unterminated) +REGISTER_CHECKER(CopyToSelf) diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-valist.h b/clang/test/Analysis/Inputs/system-header-simulator-for-valist.h new file mode 100644 index 0000000..7299b61 --- /dev/null +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-valist.h @@ -0,0 +1,30 @@ +// Like the compiler, the static analyzer treats some functions differently if +// they come from a system header -- for example, it is assumed that system +// functions do not arbitrarily free() their parameters, and that some bugs +// found in system headers cannot be fixed by the user and should be +// suppressed. + +#pragma clang system_header + +#ifdef __cplusplus +#define restrict /*restrict*/ +#endif + +typedef __builtin_va_list va_list; + +#define va_start(ap, param) __builtin_va_start(ap, param) +#define va_end(ap) __builtin_va_end(ap) +#define va_arg(ap, type) __builtin_va_arg(ap, type) +#define va_copy(dst, src) __builtin_va_copy(dst, src) + +int vprintf (const char *restrict format, va_list arg); + +int vsprintf (char *restrict s, const char *restrict format, va_list arg); + +int some_library_function(int n, va_list arg); + +// No warning from system header. +inline void __impl_detail(int fst, ...) { + va_list va; + (void)va_arg(va, int); +} diff --git a/clang/test/Analysis/valist-uninitialized.c b/clang/test/Analysis/valist-uninitialized.c new file mode 100644 index 0000000..b1f11d1 --- /dev/null +++ b/clang/test/Analysis/valist-uninitialized.c @@ -0,0 +1,178 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -analyze -analyzer-checker=core,alpha.valist.Uninitialized,alpha.valist.CopyToSelf -analyzer-output=text -analyzer-store=region -verify %s + +#include "Inputs/system-header-simulator-for-valist.h" + +void f1(int fst, ...) { + va_list va; + (void)va_arg(va, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} +} + +int f2(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_end(va); // expected-note{{Ended va_list}} + return va_arg(va, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} +} + +void f3(int fst, ...) { + va_list va, va2; + va_start(va, fst); + va_copy(va2, va); + va_end(va); + (void)va_arg(va2, int); + va_end(va2); +} //no-warning + +void f4(int cond, ...) { + va_list va; + if (cond) { // expected-note{{Assuming 'cond' is 0}} expected-note{{Taking false branch}} + va_start(va, cond); + (void)va_arg(va,int); + } + va_end(va); //expected-warning{{va_end() is called on an uninitialized va_list}} expected-note{{va_end() is called on an uninitialized va_list}} +} + +void f5(va_list fst, ...) { + va_start(fst, fst); + (void)va_arg(fst, int); + va_end(fst); +} // no-warning + +//FIXME: this should not cause a warning +void f6(va_list *fst, ...) { + va_start(*fst, fst); + (void)va_arg(*fst, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} + va_end(*fst); +} + +void f7(int *fst, ...) { + va_list x; + va_list *y = &x; + va_start(*y,fst); + (void)va_arg(x, int); + va_end(x); +} // no-warning + +void f8(int *fst, ...) { + va_list x; + va_list *y = &x; + va_start(*y,fst); // expected-note{{Initialized va_list}} + va_end(x); // expected-note{{Ended va_list}} + (void)va_arg(*y, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} +} // no-warning + +// This only contains problems which are handled by varargs.Unterminated. +void reinit(int *fst, ...) { + va_list va; + va_start(va, fst); + va_start(va, fst); + (void)va_arg(va, int); +} // no-warning + +void reinitOk(int *fst, ...) { + va_list va; + va_start(va, fst); + (void)va_arg(va, int); + va_end(va); + va_start(va, fst); + (void)va_arg(va, int); + va_end(va); +} // no-warning + +void reinit3(int *fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + (void)va_arg(va, int); + va_end(va); // expected-note{{Ended va_list}} + va_start(va, fst); // expected-note{{Initialized va_list}} + (void)va_arg(va, int); + va_end(va); // expected-note{{Ended va_list}} + (void)va_arg(va, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} +} + +void copyself(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_copy(va, va); // expected-warning{{va_list 'va' is copied onto itself}} expected-note{{va_list 'va' is copied onto itself}} + va_end(va); +} // no-warning + +void copyselfUninit(int fst, ...) { + va_list va; + va_copy(va, va); // expected-warning{{va_list 'va' is copied onto itself}} expected-note{{va_list 'va' is copied onto itself}} +} // no-warning + +void copyOverwrite(int fst, ...) { + va_list va, va2; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_copy(va, va2); // expected-warning{{Initialized va_list 'va' is overwritten by an uninitialized one}} expected-note{{Initialized va_list 'va' is overwritten by an uninitialized one}} +} // no-warning + +void copyUnint(int fst, ...) { + va_list va, va2; + va_copy(va, va2); // expected-warning{{Uninitialized va_list is copied}} expected-note{{Uninitialized va_list is copied}} +} + +void g1(int fst, ...) { + va_list va; + va_end(va); // expected-warning{{va_end() is called on an uninitialized va_list}} expected-note{{va_end() is called on an uninitialized va_list}} +} + +void g2(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_end(va); // expected-note{{Ended va_list}} + va_end(va); // expected-warning{{va_end() is called on an uninitialized va_list}} expected-note{{va_end() is called on an uninitialized va_list}} +} + +void is_sink(int fst, ...) { + va_list va; + va_end(va); // expected-warning{{va_end() is called on an uninitialized va_list}} expected-note{{va_end() is called on an uninitialized va_list}} + *((volatile int *)0) = 1; //no-warning +} + +// NOTE: this is invalid, as the man page of va_end requires that "Each invocation of va_start() +// must be matched by a corresponding invocation of va_end() in the same function." +void ends_arg(va_list arg) { + va_end(arg); +} //no-warning + +void uses_arg(va_list arg) { + (void)va_arg(arg, int); +} //no-warning + +// This is the same function as the previous one, but it is called in call_uses_arg2(), +// and the warning is generated during the analysis of call_uses_arg2(). +void inlined_uses_arg(va_list arg) { + (void)va_arg(arg, int); //expected-warning{{va_arg() is called on an uninitialized va_list}} expected-note{{va_arg() is called on an uninitialized va_list}} +} + +void call_inlined_uses_arg(int fst, ...) { + va_list va; + inlined_uses_arg(va); // expected-note{{Calling 'inlined_uses_arg'}} +} + +void call_vprintf_ok(int isstring, ...) { + va_list va; + va_start(va, isstring); + vprintf(isstring ? "%s" : "%d", va); + va_end(va); +} //no-warning + +void call_vprintf_bad(int isstring, ...) { + va_list va; + vprintf(isstring ? "%s" : "%d", va); //expected-warning{{Function 'vprintf' is called with an uninitialized va_list argument}} expected-note{{Function 'vprintf' is called with an uninitialized va_list argument}} expected-note{{Assuming 'isstring' is 0}} expected-note{{'?' condition is false}} +} + +void call_vsprintf_bad(char *buffer, ...) { + va_list va; + va_start(va, buffer); // expected-note{{Initialized va_list}} + va_end(va); // expected-note{{Ended va_list}} + vsprintf(buffer, "%s %d %d %lf %03d", va); //expected-warning{{Function 'vsprintf' is called with an uninitialized va_list argument}} expected-note{{Function 'vsprintf' is called with an uninitialized va_list argument}} +} + +void call_some_other_func(int n, ...) { + va_list va; + some_library_function(n, va); +} //no-warning + diff --git a/clang/test/Analysis/valist-unterminated.c b/clang/test/Analysis/valist-unterminated.c new file mode 100644 index 0000000..63ee992 --- /dev/null +++ b/clang/test/Analysis/valist-unterminated.c @@ -0,0 +1,133 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -analyze -analyzer-checker=core,alpha.valist.Unterminated,alpha.valist.CopyToSelf -analyzer-output=text -analyzer-store=region -verify %s + +#include "Inputs/system-header-simulator-for-valist.h" + +void f1(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + return; // expected-warning{{Initialized va_list 'va' is leaked}} expected-note{{Initialized va_list 'va' is leaked}} +} + +void f2(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_end(va); // expected-note{{Ended va_list}} + va_start(va, fst); // expected-note{{Initialized va_list}} +} // expected-warning{{Initialized va_list 'va' is leaked}} expected-note{{Initialized va_list 'va' is leaked}}} + +void f3(int fst, ...) { + va_list va, va2; + va_start(va, fst); + va_copy(va2, va); // expected-note{{Initialized va_list}} + va_end(va); // expected-warning{{Initialized va_list 'va2' is leaked}} expected-note{{Initialized va_list 'va2' is leaked}} +} + +void f4(va_list *fst, ...) { + va_start(*fst, fst); // expected-note{{Initialized va_list}} + return; // expected-warning{{Initialized va_list is leaked}} expected-note{{Initialized va_list is leaked}} +} + +void f5(va_list fst, ...) { + va_start(fst, fst); + //FIXME: this should cause a warning +} // no-warning + +void f6(va_list *fst, ...) { + va_start(*fst, fst); // expected-note{{Initialized va_list}} + (void)va_arg(*fst, int); + //FIXME: this should NOT cause a warning + va_end(*fst); // expected-warning{{Initialized va_list is leaked}} expected-note{{Initialized va_list is leaked}} +} + +void f7(int *fst, ...) { + va_list x; + va_list *y = &x; + va_start(*y,fst); // expected-note{{Initialized va_list}} +} // expected-warning{{Initialized va_list 'x' is leaked}} expected-note{{Initialized va_list 'x' is leaked}} + +void f8(int *fst, ...) { + va_list x; + va_list *y = &x; + va_start(*y,fst); + va_end(x); +} // no-warning + +void reinit(int *fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} expected-note{{Initialized va_list}} + va_start(va, fst); // expected-warning{{Initialized va_list 'va' is initialized again}} expected-note{{Initialized va_list 'va' is initialized again}} +} // expected-warning{{Initialized va_list 'va' is leaked}} expected-note{{Initialized va_list 'va' is leaked}} + +void reinitOk(int *fst, ...) { + va_list va; + va_start(va, fst); + va_end(va); + va_start(va, fst); + va_end(va); +} // no-warning + +void copyself(int fst, ...) { + va_list va; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_copy(va, va); // expected-warning{{va_list 'va' is copied onto itself}} expected-note{{va_list 'va' is copied onto itself}} + va_end(va); +} // no-warning + +void copyselfUninit(int fst, ...) { + va_list va; + va_copy(va, va); // expected-warning{{va_list 'va' is copied onto itself}} expected-note{{va_list 'va' is copied onto itself}} +} // no-warning + +void copyOverwrite(int fst, ...) { + va_list va, va2; + va_start(va, fst); // expected-note{{Initialized va_list}} + va_copy(va, va2); // expected-warning{{Initialized va_list 'va' is overwritten by an uninitialized one}} expected-note{{Initialized va_list 'va' is overwritten by an uninitialized one}} +} // no-warning + +//This only generates a warning for the valist.Uninitialized checker +void copyUnint(int fst, ...) { + va_list va, va2; + va_copy(va, va2); +} // no-warning + +void recopy(int fst, ...) { + va_list va, va2; + va_start(va, fst); + va_copy(va2, va); // expected-note{{Initialized va_list}} + va_copy(va2, va); // expected-warning{{Initialized va_list 'va2' is initialized again}} expected-note{{Initialized va_list 'va2' is initialized again}} + va_end(va); + va_end(va2); +} //no-warning + +void doublemsg(int fst, ...) { + va_list va, va2; + va_start(va, fst), va_start(va2, fst); // expected-warning{{Initialized va_list 'va' is leaked}} expected-warning{{Initialized va_list 'va2' is leaked}} expected-note{{Initialized va_list}} expected-note{{Initialized va_list}} expected-note{{Initialized va_list}} expected-note{{Initialized va_list 'va' is leaked}} +} + +void in_array(int fst, ...) { + va_list va_array[8]; + va_start(va_array[3], fst); // expected-note{{Initialized va_list}} +} // expected-warning{{Initialized va_list 'va_array[3]' is leaked}} expected-note{{Initialized va_list 'va_array[3]' is leaked}} + +struct containing_a_valist { + va_list vafield; + int foobar; +}; + +void in_struct(int fst, ...) { + struct containing_a_valist s; + va_start(s.vafield, fst); // expected-note{{Initialized va_list}} +} // expected-warning{{Initialized va_list 's.vafield' is leaked}} expected-note{{Initialized va_list 's.vafield' is leaked}} + +void casting(int fst, ...) { + char mem[sizeof(va_list)]; + va_start(*(va_list *) mem, fst); // expected-note{{Initialized va_list}} +} // expected-warning{{Initialized va_list 'mem[0]' is leaked}} expected-note{{Initialized va_list 'mem[0]' is leaked}} + + +void castingOk(int fst, ...) { + char mem[sizeof(va_list)]; + va_start(*(va_list *) mem, fst); + va_end(*(va_list *) mem); +} // no-warning + -- 2.7.4