/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * 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 "ecmascript/ecma_vm.h"
#include "ecmascript/js_runtime_options.h"
#include "ecmascript/tests/test_helper.h"
#if defined(PANDA_TARGET_OHOS) && !defined(STANDALONE_MODE)
#include "parameters.h"
#endif

#include <csetjmp>
#include <csignal>
using namespace panda::ecmascript;
using namespace common;

namespace panda::test {
class EcmaVMTest : public BaseTestWithOutScope {
};

static thread_local sigjmp_buf g_env;
static thread_local bool g_abortFlag = false;
static std::mutex g_signalMutex;

static void ProcessHandleAllocAbort(int sig)
{
    ForceResetAssertData();
    g_abortFlag = true;
    siglongjmp(g_env, sig);
}

static int RegisterSignal(struct sigaction* oldAct = nullptr)
{
    std::lock_guard<std::mutex> lock(g_signalMutex);
    
    struct sigaction act;
    act.sa_handler = ProcessHandleAllocAbort;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGABRT);
    act.sa_flags = 0;
    
    return sigaction(SIGABRT, &act, oldAct);
}

static void RestoreSignal(const struct sigaction* oldAct)
{
    std::lock_guard<std::mutex> lock(g_signalMutex);
    sigaction(SIGABRT, oldAct, nullptr);
}

static void ResetAbortFlag()
{
    g_abortFlag = false;
}

class EcmaVmIterator : public ecmascript::RootVisitor {
public:
    EcmaVmIterator() = default;
    ~EcmaVmIterator() = default;

    void VisitRoot([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot slot) override {}

    void VisitRangeRoot([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot start,
        [[maybe_unused]] ObjectSlot end) override
    {
        CHECK_NO_HANDLE_ALLOC;
    }

    void VisitBaseAndDerivedRoot([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot base,
        [[maybe_unused]] ObjectSlot derived, [[maybe_unused]] uintptr_t baseOldObject) override {}
};

/*
 * @tc.name: CreateEcmaVMInTwoWays
 * @tc.desc: Create EcmaVM in 2 ways, check the Options state
 * @tc.type: FUNC
 * @tc.require:
 */
HWTEST_F_L0(EcmaVMTest, CreateEcmaVMInTwoWays)
{
    RuntimeOption options;
    options.SetLogLevel(common::LOG_LEVEL::ERROR);
    EcmaVM::SetMultiThreadCheck(true);
    EcmaVM *ecmaVm1 = JSNApi::CreateJSVM(options);
    EXPECT_TRUE(ecmaVm1->GetMultiThreadCheck());
    auto jsthread1 = ecmaVm1->GetJSThread();
    EXPECT_TRUE(jsthread1 != nullptr);

    std::thread t1([&]() {
        JSRuntimeOptions options2;
        options2.SetEnableArkTools(false);
        options2.SetEnableForceGC(false);
        options2.SetForceFullGC(false);
        options2.SetArkProperties(ArkProperties::GC_STATS_PRINT);
        options2.SetMemConfigProperty("jsHeap500");
        // A non-production gc strategy. Prohibit stw-gc 10 times.
        EcmaVM::SetMultiThreadCheck(false);
        EcmaVM *ecmaVm2 = JSNApi::CreateEcmaVM(options2);
        auto jsthread2 = ecmaVm2->GetJSThread();
        EXPECT_FALSE(ecmaVm2->GetMultiThreadCheck());
        EXPECT_TRUE(jsthread2 != nullptr);
        EXPECT_TRUE(ecmaVm1 != ecmaVm2);

        JSRuntimeOptions options1Out = ecmaVm1->GetJSOptions();
        JSRuntimeOptions options2Out = ecmaVm2->GetJSOptions();

        EXPECT_TRUE(&options1Out != &options2Out);

        EXPECT_TRUE(options1Out.EnableArkTools() != options2Out.EnableArkTools());
        EXPECT_TRUE(options1Out.EnableForceGC() != options2Out.EnableForceGC());
        EXPECT_TRUE(options1Out.ForceFullGC() != options2Out.ForceFullGC());
        EXPECT_TRUE(options1Out.GetArkProperties() != options2Out.GetArkProperties());
        EXPECT_TRUE(options2Out.GetHeapSize() == 500_MB);

        options2.SetAsmOpcodeDisableRange("1,10");
        options2.ParseAsmInterOption();
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleStart == 1); // 1 targer start
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleEnd == 10); // 10 targer end

        options2.SetAsmOpcodeDisableRange("0x1,0xa");
        options2.ParseAsmInterOption();
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleStart == 1); // 1 targer start
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleEnd == 10); // 10 targer end

        options2.SetAsmOpcodeDisableRange(",");
        options2.ParseAsmInterOption();
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleStart == 0);
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleEnd == kungfu::BYTECODE_STUB_END_ID);

        options2.SetAsmOpcodeDisableRange("@,@");
        options2.ParseAsmInterOption();
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleStart == 0);
        EXPECT_TRUE(options2.GetAsmInterParsedOption().handleEnd == kungfu::BYTECODE_STUB_END_ID);

        JSNApi::DestroyJSVM(ecmaVm2);
    });
    t1.join();
    JSNApi::DestroyJSVM(ecmaVm1);
}

HWTEST_F_L0(EcmaVMTest, DumpExceptionObject)
{
    RuntimeOption option;
    option.SetLogLevel(common::LOG_LEVEL::ERROR);
    EcmaVM *ecmaVm = JSNApi::CreateJSVM(option);
    auto thread = ecmaVm->GetJSThread();
    int arkProperties = thread->GetEcmaVM()->GetJSOptions().GetArkProperties();
    ecmaVm->GetJSOptions().SetArkProperties(arkProperties | ArkProperties::EXCEPTION_BACKTRACE);
    EXPECT_TRUE(ecmaVm->GetJSOptions().EnableExceptionBacktrace());
    JSNApi::DestroyJSVM(ecmaVm);
}

HWTEST_F_L0(EcmaVMTest, LargeHeap)
{
    RuntimeOption option;
    option.SetLargeHeap(true);
    EcmaVM *ecmaVm = JSNApi::CreateJSVM(option);
    EXPECT_TRUE(ecmaVm->GetJSOptions().GetLargeHeap());
    JSNApi::DestroyJSVM(ecmaVm);
}

HWTEST_F_L0(EcmaVMTest, TestHandleAlocate)
{
    RuntimeOption option;
    option.SetLogLevel(RuntimeOption::LOG_LEVEL::ERROR);
    EcmaVM *ecmaVm = JSNApi::CreateJSVM(option);
    [[maybe_unused]]HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(ecmaVm);

    ObjectFactory *factory = ecmaVm->GetFactory();
    // JS_ARRAY_BUFFER
    factory->NewJSArrayBuffer(10);
    // JS_SHARED_ARRAY_BUFFER
    factory->NewJSSharedArrayBuffer(10);
    // PROMISE_REACTIONS
    factory->NewPromiseReaction();
    // PROMISE_CAPABILITY
    factory->NewPromiseCapability();
    // PROMISE_RECORD
    factory->NewPromiseRecord();
    // RESOLVING_FUNCTIONS_RECORD
    factory->NewResolvingFunctionsRecord();

    struct sigaction oldAct;
    ASSERT_TRUE(RegisterSignal(&oldAct) != -1);
    
    {
        EcmaVmIterator ecmaVmIterator;
        auto ret = sigsetjmp(g_env, 1);
        if (ret != SIGABRT) {
            ecmaVm->Iterate(ecmaVmIterator);
        } else {
            EXPECT_TRUE(g_abortFlag);
        }
    }

    RestoreSignal(&oldAct);
    ResetAbortFlag();
    
    ASSERT_TRUE(RegisterSignal(&oldAct) != -1);
    {
        EcmaVmIterator ecmaVmIterator;
        auto ret = sigsetjmp(g_env, 1);
        if (ret != SIGABRT) {
            ecmaVm->IterateHandle(ecmaVmIterator);
        } else {
            EXPECT_TRUE(g_abortFlag);
        }
    }
    
    RestoreSignal(&oldAct);
    ResetAbortFlag();
    JSNApi::DestroyJSVM(ecmaVm);
}

HWTEST_F_L0(EcmaVMTest, TestInitConfigurableParam)
{
#if defined(PANDA_TARGET_OHOS) && !defined(STANDALONE_MODE)
    OHOS::system::SetParameter("persist.ark.sheap.growfactor", "1");
    OHOS::system::SetParameter("persist.ark.sheap.growstep", "2");
    OHOS::system::SetParameter("persist.ark.sensitive.threshold", "3");
    OHOS::system::SetParameter("persist.ark.native.stepsize", "4");
    OHOS::system::SetParameter("persist.ark.global.alloclimit", "4");

    EcmaParamConfiguration cfg = EcmaParamConfiguration(EcmaParamConfiguration::HeapType::WORKER_HEAP,
                                                        MAX_HEAP_SIZE, MAX_HEAP_SIZE);
    EcmaVM::InitConfigurableParam(cfg);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingFactor() != 1);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingStep() != 2_MB);
    EXPECT_TRUE(cfg.GetIncObjSizeThresholdInSensitive() != 3_MB);
    EXPECT_TRUE(cfg.GetStepNativeSizeInc() != 4_MB);
    EXPECT_TRUE(cfg.GetDefaultGlobalAllocLimit() != 4_MB);

    OHOS::system::SetParameter("persist.ark.sheap.growfactor", "4");
    OHOS::system::SetParameter("persist.ark.sheap.growstep", "10");
    OHOS::system::SetParameter("persist.ark.sensitive.threshold", "10");
    OHOS::system::SetParameter("persist.ark.native.stepsize", "128");
    OHOS::system::SetParameter("persist.ark.global.alloclimit", "10");
    EcmaVM::InitConfigurableParam(cfg);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingFactor() == 4);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingStep() == 10_MB);
    EXPECT_TRUE(cfg.GetIncObjSizeThresholdInSensitive() == 10_MB);
    EXPECT_TRUE(cfg.GetStepNativeSizeInc() == 128_MB);
    EXPECT_TRUE(cfg.GetDefaultGlobalAllocLimit() == 10_MB);

    OHOS::system::SetParameter("persist.ark.sheap.growfactor", "10");
    OHOS::system::SetParameter("persist.ark.sheap.growstep", "640");
    OHOS::system::SetParameter("persist.ark.sensitive.threshold", "640");
    OHOS::system::SetParameter("persist.ark.native.stepsize", "2048");
    OHOS::system::SetParameter("persist.ark.global.alloclimit", "256");
    EcmaVM::InitConfigurableParam(cfg);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingFactor() != 10);
    EXPECT_TRUE(cfg.GetSharedHeapLimitGrowingStep() != 640_MB);
    EXPECT_TRUE(cfg.GetIncObjSizeThresholdInSensitive() != 640_MB);
    EXPECT_TRUE(cfg.GetStepNativeSizeInc() != 2048_MB);
    EXPECT_TRUE(cfg.GetDefaultGlobalAllocLimit() != 256_MB);
#endif
}

}  // namespace panda::ecmascript