# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
#
# Subject to the condition set forth below, permission is hereby granted to any
# person obtaining a copy of this software, associated documentation and/or
# data (collectively the "Software"), free of charge and under any and all
# copyright rights in the Software, and any and all patent rights owned or
# freely licensable by each licensor hereunder covering either (i) the
# unmodified Software as contributed to or provided by such licensor, or (ii)
# the Larger Works (as defined below), to deal in both
#
# (a) the Software, and
#
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
# one is included with the Software each a "Larger Work" to which the Software
# is contributed by such licensors),
#
# without restriction, including without limitation the rights to copy, create
# derivative works of, display, perform, and distribute the Software and make,
# use, sell, offer for sale, import, export, have made, and have sold the
# Software and the Larger Work(s), and to sublicense the foregoing rights on
# either these or other terms.
#
# This license is subject to the following condition:
#
# The above copyright notice and either this complete permission notice or at a
# minimum a reference to the UPL must be included in all copies or substantial
# portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import pytest
from hpy.debug.leakdetector import LeakDetector
from hpy.test.support import SUPPORTS_SYS_EXECUTABLE, IS_PYTHON_DEBUG_BUILD, GRAALPYTHON, HPyTest
from hpy.test.conftest import IS_VALGRIND_RUN

pytestmark = pytest.mark.skipif(not HPyTest.supports_debug_mode(), reason="debug mode not supported")

@pytest.fixture
def hpy_abi():
    with LeakDetector():
        yield "debug"


def test_no_invalid_handle(compiler, hpy_debug_capture):
    # Basic sanity check that valid code does not trigger any error reports
    mod = compiler.make_module("""
        HPyDef_METH(f, "f", f_impl, HPyFunc_O)
        static HPy f_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            HPy x = HPyLong_FromLong(ctx, 42);
            HPy y = HPyLong_FromLong(ctx, 2);
            HPy arg_dup = HPy_Dup(ctx, arg);
            HPy_Close(ctx, y);
            HPy b = HPy_Dup(ctx, x);
            HPy_Close(ctx, x);
            HPy_Close(ctx, arg_dup);
            return b;
        }

        @EXPORT(f)
        @INIT
    """)
    assert mod.f("hello") == 42
    assert mod.f("world") == 42
    assert hpy_debug_capture.invalid_handles_count == 0


def test_cant_use_closed_handle(compiler, hpy_debug_capture):
    mod = compiler.make_module("""
        HPyDef_METH(f, "f", f_impl, HPyFunc_O, .doc="double close")
        static HPy f_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            HPy h = HPy_Dup(ctx, arg);
            HPy_Close(ctx, h);
            HPy_Close(ctx, h); // double close
            return HPy_Dup(ctx, ctx->h_None);
        }

        HPyDef_METH(g, "g", g_impl, HPyFunc_O, .doc="use after close")
        static HPy g_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            HPy h = HPy_Dup(ctx, arg);
            HPy_Close(ctx, h);
            return HPy_Repr(ctx, h);
        }

        HPyDef_METH(h, "h", h_impl, HPyFunc_O, .doc="closing argument")
        static HPy h_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            // Argument is implicitly closed by the caller
            HPy_Close(ctx, arg);
            return HPy_Dup(ctx, ctx->h_None);
        }

        HPyDef_METH(f_noargs, "f_noargs", f_noargs_impl, HPyFunc_NOARGS, .doc="returns arg w/o dupping it")
        static HPy f_noargs_impl(HPyContext *ctx, HPy self)
        {
            // should be: return HPy_Dup(ctx, self);
            return self;
        }

        HPyDef_METH(f0, "f0", f0_impl, HPyFunc_O, .doc="returns arg w/o dupping it")
        static HPy f0_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            // should be: return HPy_Dup(ctx, arg);
            return arg;
        }

        HPyDef_METH(f_varargs, "f_varargs", f_varargs_impl, HPyFunc_VARARGS, .doc="returns arg w/o dupping it")
        static HPy f_varargs_impl(HPyContext *ctx, HPy self, HPy *args, HPy_ssize_t nargs)
        {
            // should be: return HPy_Dup(ctx, args[0]);
            return args[0];
        }

        @EXPORT(f)
        @EXPORT(g)
        @EXPORT(f0)
        @EXPORT(f_noargs)
        @EXPORT(f_varargs)
        @EXPORT(h)
        @INIT
    """)
    mod.f('foo')   # double close
    assert hpy_debug_capture.invalid_handles_count == 1
    mod.g('bar')   # use-after-close
    assert hpy_debug_capture.invalid_handles_count == 2
    if not IS_PYTHON_DEBUG_BUILD and not IS_VALGRIND_RUN:
        # CPython debug build can also catch these errors, so we cannot trigger
        # them when running on debug builds
        mod.f0('foo')
        assert hpy_debug_capture.invalid_handles_count == 3
        mod.f_noargs()
        assert hpy_debug_capture.invalid_handles_count == 4
        mod.f_varargs('foo', 'bar')
        assert hpy_debug_capture.invalid_handles_count == 5
        if not GRAALPYTHON:
            # GraalPython does not support this test because of the strict
            # separation between universal adn debug context. The debug context
            # still correctly detects the invalid handle access but later, the
            # runtime will still try to close the argument handle and there is
            # no means to propagate that information to the handle owner.
            # Hence, an assertion will fail.
            mod.h('baz')
            assert hpy_debug_capture.invalid_handles_count == 6


@pytest.mark.xfail(reason="graalpython does not prevent reuse of leaked handles for other handles and thus cannot always catch this")
def test_keeping_and_reusing_argument_handle(compiler, hpy_debug_capture):
    mod = compiler.make_module("""
        HPy keep;

        HPyDef_METH(f, "f", f_impl, HPyFunc_O)
        static HPy f_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            keep = arg;
            return HPy_Dup(ctx, ctx->h_None);
        }

        HPyDef_METH(g, "g", g_impl, HPyFunc_NOARGS)
        static HPy g_impl(HPyContext *ctx, HPy self)
        {
            HPy_ssize_t len = HPy_Length(ctx, keep);
            return HPyLong_FromSsize_t(ctx, len);
        }

        @EXPORT(f)
        @EXPORT(g)
        @INIT
    """)
    s = "hello leaks!"
    mod.f(s)
    assert hpy_debug_capture.invalid_handles_count == 0
    assert mod.g() == len(s)
    assert hpy_debug_capture.invalid_handles_count == 1


@pytest.mark.xfail(reason="set_handle_stack_trace_limit not implemented yet")
def test_invalid_handle_crashes_python_if_no_hook(compiler, python_subprocess, fatal_exit_code):
    if not SUPPORTS_SYS_EXECUTABLE:
        pytest.skip("no sys.executable")

    mod = compiler.compile_module("""
        HPyDef_METH(f, "f", f_impl, HPyFunc_O, .doc="double close")
        static HPy f_impl(HPyContext *ctx, HPy self, HPy arg)
        {
            HPy h = HPy_Dup(ctx, arg);
            HPy_Close(ctx, h);
            HPy_Close(ctx, h); // double close
            return HPy_Dup(ctx, ctx->h_None);
        }

        @EXPORT(f)
        @INIT
    """)
    result = python_subprocess.run(mod, "mod.f(42);")
    assert result.returncode == fatal_exit_code
