/*
 * Copyright 2023 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <set>
#include <string>
#include <vector>

#include "shared-constants.h"
#include "support/string.h"
#include "wasm-binary.h"
#include "wasm-builder.h"
#include "wasm-features.h"
#include "wasm-validator.h"
#include "gtest/gtest.h"

using namespace wasm;

// Check that the validator does not crash on missing tags in catch statements
// (invalid parse, so cannot be reproduced with Python test suite)
TEST(ValidatorTest, MissingCatchTag) {
  WasmValidator validator;
  Module module;
  module.features.enable(FeatureSet::ExceptionHandling);
  auto tryExp = module.allocator.alloc<Try>();
  tryExp->name = "tst1";
  auto bodyExp = module.allocator.alloc<Const>();
  bodyExp->value = Literal(1);
  tryExp->body = bodyExp;
  tryExp->catchTags.push_back(wasm::Name("foo"));
  auto catchBodyExp = module.allocator.alloc<Const>();
  catchBodyExp->value = Literal(2);
  tryExp->catchBodies.push_back(catchBodyExp);
  Function function = Function();
  function.body = tryExp;
  WasmValidator::Flags flags =
    WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet;
  EXPECT_FALSE(validator.validate(&function, module, flags));
}

TEST(ValidatorTest, ReturnUnreachable) {
  Module module;
  Builder builder(module);

  // (return (unreachable)) should be invalid if a function has no return type.
  auto func =
    builder.makeFunction("func",
                         {},
                         Signature(Type::none, Type::none),
                         {},
                         builder.makeReturn(builder.makeUnreachable()));

  auto flags =
    WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet;
  EXPECT_FALSE(WasmValidator{}.validate(func.get(), module, flags));
}

TEST(ValidatorTest, UnreachableCastDescEq) {
  // The parser will error trying to parse a ref.cast_desc_eq with a
  // non-matching descriptor type, so we must construct the IR directly to test
  // the validator.
  Module module;
  module.features = FeatureSet::All;
  Builder builder(module);

  auto func = builder.makeFunction(
    "func",
    {},
    Signature(Type::none, Type::none),
    {},
    builder.makeDrop(builder.makeRefCast(builder.makeUnreachable(),
                                         builder.makeStructNew(Struct{}, {}),
                                         Type::unreachable)));

  ASSERT_EQ(func->body->type, Type(Type::unreachable));

  auto flags =
    WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet;
  EXPECT_FALSE(WasmValidator{}.validate(func.get(), module, flags));
}

TEST(ValidatorTest, ContNewUnreachable) {
  Module module;
  module.features = FeatureSet::All;
  Builder builder(module);

  auto sig = Signature(Type::none, Type::none);
  module.addFunction(builder.makeFunction(
    "f", {}, Signature(Type::none, Type::none), {}, builder.makeUnreachable()));

  auto contType = HeapType(Continuation(sig));
  auto contRefType = Type(contType, NonNullable);

  // Create a cont.new with a concrete type despite an unreachable child. This
  // is not valid, but we should not crash while validating it.
  auto* contNew = builder.makeContNew(contType, builder.makeUnreachable());
  contNew->type = contRefType;

  auto testFunc = builder.makeFunction(
    "test", {}, Signature(Type::none, contRefType), {}, contNew);

  auto flags =
    WasmValidator::FlagValues::Globally | WasmValidator::FlagValues::Quiet;
  EXPECT_FALSE(WasmValidator{}.validate(testFunc.get(), module, flags));
}
