//===- unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp ---===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>

namespace {

using namespace clang;
using namespace dataflow;

class DataflowAnalysisContextTest : public ::testing::Test {
protected:
  DataflowAnalysisContextTest()
      : Context(std::make_unique<WatchedLiteralsSolver>()), A(Context.arena()) {
  }

  DataflowAnalysisContext Context;
  Arena &A;
};

TEST_F(DataflowAnalysisContextTest, DistinctTopsNotEquivalent) {
  auto &X = A.makeTopValue();
  auto &Y = A.makeTopValue();
  EXPECT_FALSE(Context.equivalentFormulas(X.formula(), Y.formula()));
}

TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionImplies) {
  Atom FC = A.makeFlowConditionToken();
  EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
  EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
  EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
}

TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionAllows) {
  Atom FC = A.makeFlowConditionToken();
  EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
  EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
  EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
}

TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionImpliesAnything) {
  Atom FC = A.makeFlowConditionToken();
  Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
  EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
  EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
  EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
}

TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionAllowsNothing) {
  Atom FC = A.makeFlowConditionToken();
  Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
  EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
  EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
  EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
}

TEST_F(DataflowAnalysisContextTest, AddFlowConditionConstraint) {
  Atom FC = A.makeFlowConditionToken();
  auto &C = A.makeAtomRef(A.makeAtom());
  Context.addFlowConditionConstraint(FC, C);
  EXPECT_TRUE(Context.flowConditionImplies(FC, C));
}

TEST_F(DataflowAnalysisContextTest, AddInvariant) {
  Atom FC = A.makeFlowConditionToken();
  auto &C = A.makeAtomRef(A.makeAtom());
  Context.addInvariant(C);
  EXPECT_TRUE(Context.flowConditionImplies(FC, C));
}

TEST_F(DataflowAnalysisContextTest, InvariantAndFCConstraintInteract) {
  Atom FC = A.makeFlowConditionToken();
  auto &C = A.makeAtomRef(A.makeAtom());
  auto &D = A.makeAtomRef(A.makeAtom());
  Context.addInvariant(A.makeImplies(C, D));
  Context.addFlowConditionConstraint(FC, C);
  EXPECT_TRUE(Context.flowConditionImplies(FC, D));
}

TEST_F(DataflowAnalysisContextTest, ForkFlowCondition) {
  Atom FC1 = A.makeFlowConditionToken();
  auto &C1 = A.makeAtomRef(A.makeAtom());
  Context.addFlowConditionConstraint(FC1, C1);

  // Forked flow condition inherits the constraints of its parent flow
  // condition.
  Atom FC2 = Context.forkFlowCondition(FC1);
  EXPECT_TRUE(Context.flowConditionImplies(FC2, C1));

  // Adding a new constraint to the forked flow condition does not affect its
  // parent flow condition.
  auto &C2 = A.makeAtomRef(A.makeAtom());
  Context.addFlowConditionConstraint(FC2, C2);
  EXPECT_TRUE(Context.flowConditionImplies(FC2, C2));
  EXPECT_FALSE(Context.flowConditionImplies(FC1, C2));
}

TEST_F(DataflowAnalysisContextTest, JoinFlowConditions) {
  auto &C1 = A.makeAtomRef(A.makeAtom());
  auto &C2 = A.makeAtomRef(A.makeAtom());
  auto &C3 = A.makeAtomRef(A.makeAtom());

  Atom FC1 = A.makeFlowConditionToken();
  Context.addFlowConditionConstraint(FC1, C1);
  Context.addFlowConditionConstraint(FC1, C3);

  Atom FC2 = A.makeFlowConditionToken();
  Context.addFlowConditionConstraint(FC2, C2);
  Context.addFlowConditionConstraint(FC2, C3);

  Atom FC3 = Context.joinFlowConditions(FC1, FC2);
  EXPECT_FALSE(Context.flowConditionImplies(FC3, C1));
  EXPECT_FALSE(Context.flowConditionImplies(FC3, C2));
  EXPECT_TRUE(Context.flowConditionImplies(FC3, C3));
}

TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
  auto &X = A.makeAtomRef(A.makeAtom());
  auto &Y = A.makeAtomRef(A.makeAtom());
  auto &Z = A.makeAtomRef(A.makeAtom());
  auto &True = A.makeLiteral(true);
  auto &False = A.makeLiteral(false);

  // X == X
  EXPECT_TRUE(Context.equivalentFormulas(X, X));
  // X != Y
  EXPECT_FALSE(Context.equivalentFormulas(X, Y));

  // !X != X
  EXPECT_FALSE(Context.equivalentFormulas(A.makeNot(X), X));
  // !(!X) = X
  EXPECT_TRUE(Context.equivalentFormulas(A.makeNot(A.makeNot(X)), X));

  // (X || X) == X
  EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, X), X));
  // (X || Y) != X
  EXPECT_FALSE(Context.equivalentFormulas(A.makeOr(X, Y), X));
  // (X || True) == True
  EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, True), True));
  // (X || False) == X
  EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, False), X));

  // (X && X) == X
  EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, X), X));
  // (X && Y) != X
  EXPECT_FALSE(Context.equivalentFormulas(A.makeAnd(X, Y), X));
  // (X && True) == X
  EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, True), X));
  // (X && False) == False
  EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, False), False));

  // (X || Y) == (Y || X)
  EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, Y), A.makeOr(Y, X)));
  // (X && Y) == (Y && X)
  EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, Y), A.makeAnd(Y, X)));

  // ((X || Y) || Z) == (X || (Y || Z))
  EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(A.makeOr(X, Y), Z),
                                         A.makeOr(X, A.makeOr(Y, Z))));
  // ((X && Y) && Z) == (X && (Y && Z))
  EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(A.makeAnd(X, Y), Z),
                                         A.makeAnd(X, A.makeAnd(Y, Z))));
}

} // namespace
