//=== 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 <optional>

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{
      {{CDM::SimpleFunc, {"ErrnoTesterChecker_setErrno"}, 1},
       &ErrnoTesterChecker::evalSetErrno},
      {{CDM::SimpleFunc, {"ErrnoTesterChecker_getErrno"}, 0},
       &ErrnoTesterChecker::evalGetErrno},
      {{CDM::SimpleFunc, {"ErrnoTesterChecker_setErrnoIfError"}, 0},
       &ErrnoTesterChecker::evalSetErrnoIfError},
      {{CDM::SimpleFunc, {"ErrnoTesterChecker_setErrnoIfErrorRange"}, 0},
       &ErrnoTesterChecker::evalSetErrnoIfErrorRange},
      {{CDM::SimpleFunc, {"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<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(Call, 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;
}
