//=== ErrnoTesterChecker.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 defines ErrnoTesterChecker, which is used to test functionality of the // errno_check API. // //===----------------------------------------------------------------------===// #include "ErrnoModeling.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerContext.h" #include using namespace clang; using namespace ento; using namespace errno_modeling; namespace { class ErrnoTesterChecker : public Checker { public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; private: /// Evaluate function \code void ErrnoTesterChecker_setErrno(int) \endcode. /// Set value of \c errno to the argument. static void evalSetErrno(CheckerContext &C, const CallEvent &Call); /// Evaluate function \code int ErrnoTesterChecker_getErrno() \endcode. /// Return the value of \c errno. static void evalGetErrno(CheckerContext &C, const CallEvent &Call); /// Evaluate function \code int ErrnoTesterChecker_setErrnoIfError() \endcode. /// Simulate a standard library function tha returns 0 on success and 1 on /// failure. On the success case \c errno is not allowed to be used (may be /// undefined). On the failure case \c errno is set to a fixed value 11 and /// is not needed to be checked. static void evalSetErrnoIfError(CheckerContext &C, const CallEvent &Call); /// Evaluate function \code int ErrnoTesterChecker_setErrnoIfErrorRange() /// \endcode. Same as \c ErrnoTesterChecker_setErrnoIfError but \c errno is /// set to a range (to be nonzero) at the failure case. static void evalSetErrnoIfErrorRange(CheckerContext &C, const CallEvent &Call); /// Evaluate function \code int ErrnoTesterChecker_setErrnoCheckState() /// \endcode. This function simulates the following: /// - Return 0 and leave \c errno with undefined value. /// This is the case of a successful standard function call. /// For example if \c ftell returns not -1. /// - Return 1 and sets \c errno to a specific error code (1). /// This is the case of a failed standard function call. /// The function indicates the failure by a special return value /// that is returned only at failure. /// \c errno can be checked but it is not required. /// For example if \c ftell returns -1. /// - Return 2 and may set errno to a value (actually it does not set it). /// This is the case of a standard function call where the failure can only /// be checked by reading from \c errno. The value of \c errno is changed by /// the function only at failure, the user should set \c errno to 0 before /// the call (\c ErrnoChecker does not check for this rule). /// \c strtol is an example of this case, if it returns \c LONG_MIN (or /// \c LONG_MAX). This case applies only if \c LONG_MIN or \c LONG_MAX is /// returned, otherwise the first case in this list applies. static void evalSetErrnoCheckState(CheckerContext &C, const CallEvent &Call); using EvalFn = std::function; const CallDescriptionMap TestCalls{ {{{"ErrnoTesterChecker_setErrno"}, 1}, &ErrnoTesterChecker::evalSetErrno}, {{{"ErrnoTesterChecker_getErrno"}, 0}, &ErrnoTesterChecker::evalGetErrno}, {{{"ErrnoTesterChecker_setErrnoIfError"}, 0}, &ErrnoTesterChecker::evalSetErrnoIfError}, {{{"ErrnoTesterChecker_setErrnoIfErrorRange"}, 0}, &ErrnoTesterChecker::evalSetErrnoIfErrorRange}, {{{"ErrnoTesterChecker_setErrnoCheckState"}, 0}, &ErrnoTesterChecker::evalSetErrnoCheckState}}; }; } // namespace void ErrnoTesterChecker::evalSetErrno(CheckerContext &C, const CallEvent &Call) { C.addTransition(setErrnoValue(C.getState(), C.getLocationContext(), Call.getArgSVal(0), Irrelevant)); } void ErrnoTesterChecker::evalGetErrno(CheckerContext &C, const CallEvent &Call) { ProgramStateRef State = C.getState(); std::optional ErrnoVal = getErrnoValue(State); assert(ErrnoVal && "Errno value should be available."); State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), *ErrnoVal); C.addTransition(State); } void ErrnoTesterChecker::evalSetErrnoIfError(CheckerContext &C, const CallEvent &Call) { ProgramStateRef State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); ProgramStateRef StateSuccess = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); ProgramStateRef StateFailure = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); StateFailure = setErrnoValue(StateFailure, C, 11, Irrelevant); C.addTransition(StateSuccess); C.addTransition(StateFailure); } void ErrnoTesterChecker::evalSetErrnoIfErrorRange(CheckerContext &C, const CallEvent &Call) { ProgramStateRef State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); ProgramStateRef StateSuccess = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); ProgramStateRef StateFailure = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); DefinedOrUnknownSVal ErrnoVal = SVB.conjureSymbolVal( nullptr, Call.getOriginExpr(), C.getLocationContext(), C.blockCount()); StateFailure = StateFailure->assume(ErrnoVal, true); assert(StateFailure && "Failed to assume on an initial value."); StateFailure = setErrnoValue(StateFailure, C.getLocationContext(), ErrnoVal, Irrelevant); C.addTransition(StateSuccess); C.addTransition(StateFailure); } void ErrnoTesterChecker::evalSetErrnoCheckState(CheckerContext &C, const CallEvent &Call) { ProgramStateRef State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); ProgramStateRef StateSuccess = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); ProgramStateRef StateFailure1 = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); StateFailure1 = setErrnoValue(StateFailure1, C, 1, Irrelevant); ProgramStateRef StateFailure2 = State->BindExpr( Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(2, true)); StateFailure2 = setErrnoValue(StateFailure2, C, 2, MustBeChecked); C.addTransition(StateSuccess, getErrnoNoteTag(C, "Assuming that this function succeeds but " "sets 'errno' to an unspecified value.")); C.addTransition(StateFailure1); C.addTransition( StateFailure2, getErrnoNoteTag(C, "Assuming that this function returns 2. 'errno' " "should be checked to test for failure.")); } bool ErrnoTesterChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { const EvalFn *Fn = TestCalls.lookup(Call); if (Fn) { (*Fn)(C, Call); return C.isDifferent(); } return false; } void ento::registerErrnoTesterChecker(CheckerManager &Mgr) { Mgr.registerChecker(); } bool ento::shouldRegisterErrnoTesterChecker(const CheckerManager &Mgr) { return true; }