Extend checker 'ErrnoModeling' with a state of 'errno' to indicate the importance of the 'errno' value and how it should be used. Add a new checker 'ErrnoChecker' that observes use of 'errno' and finds possible wrong uses, based on the "errno state". The "errno state" should be set (together with value of 'errno') by other checkers (that perform modeling of the given function) in the future. Currently only a test function can set this value. The new checker has no user-observable effect yet. Reviewed By: martong, steakhal Differential Revision: https://reviews.llvm.org/D122150
185 lines
8.0 KiB
C++
185 lines
8.0 KiB
C++
//=== 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"
|
|
|
|
using namespace clang;
|
|
using namespace ento;
|
|
using namespace errno_modeling;
|
|
|
|
namespace {
|
|
|
|
class ErrnoTesterChecker : public Checker<eval::Call> {
|
|
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<void(CheckerContext &, const CallEvent &)>;
|
|
const CallDescriptionMap<EvalFn> 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();
|
|
|
|
Optional<SVal> 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<ErrnoTesterChecker>();
|
|
}
|
|
|
|
bool ento::shouldRegisterErrnoTesterChecker(const CheckerManager &Mgr) {
|
|
return true;
|
|
}
|