/*
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package org.graalvm.compiler.hotspot.test;

import java.io.IOException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.List;

import org.graalvm.compiler.test.SubprocessUtil;
import org.graalvm.compiler.test.SubprocessUtil.Subprocess;

import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

public class ReservedStackAccessTest extends HotSpotGraalCompilerTest {
    @Before
    public void check() {
        Assume.assumeTrue(runtime().getVMConfig().enableStackReservedZoneAddress != 0);
    }

    public void stackAccessTest() {
        Assume.assumeTrue(runtime().getVMConfig().enableStackReservedZoneAddress != 0);

        int passed = 0;
        for (int i = 0; i < 1000; i++) {
            // Each iteration has to be executed by a new thread. The test
            // relies on the random size area pushed by the VM at the beginning
            // of the stack of each Java thread it creates.
            RunWithSOEContext r = new RunWithSOEContext(new ReentrantLockTest(), 256);
            Thread thread = new Thread(r);
            thread.start();
            try {
                thread.join();
                assertTrue(r.result.equals("PASSED"), r.result);
                ++passed;
            } catch (InterruptedException ex) {
            }
        }
        System.out.println("RESULT: " + (passed == 1000 ? "PASSED" : "FAILED"));
    }

    public static void main(String[] args) {
        new ReservedStackAccessTest().stackAccessTest();
    }

    @Test
    public void run() throws IOException, InterruptedException {
        Assume.assumeTrue(runtime().getVMConfig().enableStackReservedZoneAddress != 0);
        List<String> vmArgs = SubprocessUtil.withoutDebuggerArguments(SubprocessUtil.getVMCommandLine());
        vmArgs.add("-XX:+UseJVMCICompiler");
        vmArgs.add("-Dgraal.Inline=false");
        vmArgs.add("-XX:CompileCommand=exclude,java/util/concurrent/locks/AbstractOwnableSynchronizer.setExclusiveOwnerThread");

        // Avoid SOE in HotSpotJVMCIRuntime.adjustCompilationLevel
        vmArgs.add("-Dgraal.CompileGraalWithC1Only=false");

        Subprocess proc = SubprocessUtil.java(vmArgs, ReservedStackAccessTest.class.getName());
        boolean passed = false;
        for (String line : proc.output) {
            if (line.equals("RESULT: PASSED")) {
                passed = true;
            }
        }
        if (!passed) {
            System.err.println(proc);
        }
        assertTrue(passed);
    }

    static class ReentrantLockTest {

        private ReentrantLock[] lockArray;
        // Frame sizes vary a lot between interpreted code and compiled code
        // so the lock array has to be big enough to cover all cases.
        // If test fails with message "Not conclusive test", try to increase
        // LOCK_ARRAY_SIZE value
        private static final int LOCK_ARRAY_SIZE = 8192;
        private boolean stackOverflowErrorReceived;
        StackOverflowError soe = null;
        int index = -1;

        public void initialize() {
            lockArray = new ReentrantLock[LOCK_ARRAY_SIZE];
            for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
                lockArray[i] = new ReentrantLock();
            }
            stackOverflowErrorReceived = false;
        }

        public String getResult() {
            if (!stackOverflowErrorReceived) {
                return "ERROR: Not conclusive test: no StackOverflowError received";
            }
            for (int i = 0; i < LOCK_ARRAY_SIZE; i++) {
                if (lockArray[i].isLocked()) {
                    if (!lockArray[i].isHeldByCurrentThread()) {
                        StringBuilder s = new StringBuilder();
                        s.append("FAILED: ReentrantLock ");
                        s.append(i);
                        s.append(" looks corrupted");
                        return s.toString();
                    }
                }
            }
            return "PASSED";
        }

        public void run() {
            try {
                lockAndCall(0);
            } catch (StackOverflowError e) {
                soe = e;
                stackOverflowErrorReceived = true;
            }
        }

        private void lockAndCall(int i) {
            index = i;
            if (i < LOCK_ARRAY_SIZE) {
                lockArray[i].lock();
                lockAndCall(i + 1);
            }
        }
    }

    static class RunWithSOEContext implements Runnable {

        int counter;
        int deframe;
        int decounter;
        int setupSOEFrame;
        int testStartFrame;
        ReentrantLockTest test;
        String result = "FAILED: no result";

        RunWithSOEContext(ReentrantLockTest test, int deframe) {
            this.test = test;
            this.deframe = deframe;
        }

        @Override
        public void run() {
            counter = 0;
            decounter = deframe;
            test.initialize();
            recursiveCall();
            System.out.println("Framework got StackOverflowError at frame = " + counter);
            System.out.println("Test started execution at frame = " + (counter - deframe));
            result = test.getResult();
        }

        @SuppressWarnings("unused")
        void recursiveCall() {
            // Unused local variables to increase the frame size
            long l1;
            long l2;
            long l3;
            long l4;
            long l5;
            long l6;
            long l7;
            long l8;
            long l9;
            long l10;
            long l11;
            long l12;
            long l13;
            long l14;
            long l15;
            long l16;
            long l17;
            long l18;
            long l19;
            long l20;
            long l21;
            long l22;
            long l23;
            long l24;
            long l25;
            long l26;
            long l27;
            long l28;
            long l30;
            long l31;
            long l32;
            long l33;
            long l34;
            long l35;
            long l36;
            long l37;
            counter++;
            try {
                recursiveCall();
            } catch (StackOverflowError e) {
            }
            decounter--;
            if (decounter == 0) {
                setupSOEFrame = counter;
                testStartFrame = counter - deframe;
                test.run();
            }
        }
    }

}