/*
 * Copyright (c) 2018 SAP SE. 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.
 */

/**
 * @test
 * @bug 8212928
 * @summary With NeedsDeoptSuspend == true the assertion in compiledVFrame::update_deferred_value() fails, because the frame is not deoptimized.
 * @author Richard Reingruber richard DOT reingruber AT sap DOT com
 *
 * @library /test/lib
 *
 * @run build TestScaffold VMConnection TargetListener TargetAdapter
 * @run main jdk.test.lib.FileInstaller compilerDirectives.json compilerDirectives.json
 * @run compile -g SetLocalWhileThreadInNative.java
 * @run driver SetLocalWhileThreadInNative -Xbatch -XX:-TieredCompilation -XX:CICompilerCount=1 -XX:+UnlockDiagnosticVMOptions -XX:CompilerDirectivesFile=compilerDirectives.json
 */

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import com.sun.jdi.*;
import com.sun.jdi.event.*;

import jdk.test.lib.Asserts;

/********** target program **********/

class SetLocalWhileThreadInNativeTarget {

    public static final String name = SetLocalWhileThreadInNativeTarget.class.getName();
    public static FileInputStream fis;
    public static int count;
    public static int bytesRead;


    // Let D (debugger) be a JVMTI agent that updates a local of a compiled frame F owned by
    // thread T. In this test F corresponds to dontinline_testMethod.
    //
    // Issue: The VM thread executes an VM op on behalf of D to set the local in F. Doing so it
    // requests the deoptimization of F and then calls compiledVFrame::update_deferred_value(),
    // where frame::is_deoptimized_frame() returns false causing an assertion failure on SPARC where
    // NeedsDeoptSuspend is true.
    //
    // Analysis: The deoptimization of F is requested, while T is in the native method
    // java.io.FileInputStream::read0() and F is the direct caller of read0(). This is a special
    // case with NeedsDeoptSuspend in frame::deoptimize(). Effectively the deoptimization is not
    // done synchronously, instead T deoptimizes F at a later point upon return from the native
    // method.
    public static int dontinline_testMethod() {
        int zero = 0;
        int val = 0;
        try {
            val = fis.read(); // Will be inlined. Calls native method java.io.FileInputStream::read0()
            count++;
        } catch (IOException e) { /* ignored */ }
        return val + zero;
    }

    public static void main(String[] args) {
        System.out.println(name + " is up and running.");
        fis = new FileInputStream(FileDescriptor.in);
        bytesRead=0;
        while (true) {
            int val = dontinline_testMethod();
            if (val == SetLocalWhileThreadInNative.STOP) {
                System.out.println("Debuggee: received STOP message");
                System.exit(0);
            }
            bytesRead++;
            if ((bytesRead & ((1L << 14)-1)) == 0) {
                System.out.println("Called test method " + bytesRead + " times");
            }
        }
    }
}

 /********** test program **********/

public class SetLocalWhileThreadInNative extends TestScaffold {
    public static final int MESSAGE_COUNT = 10000;
    public static final String MESSAGE    = "0123456789";
    public static final int MESSAGE_SIZE  = MESSAGE.length();
    public static final int TOTAL_BYTES   = MESSAGE_COUNT * MESSAGE_SIZE;
    public static final int STOP = 255;

    ReferenceType mainClass;
    ThreadReference mainThread;

    SetLocalWhileThreadInNative (String args[]) {
        super(args);
    }

    public static void main(String[] args)
        throws Exception
    {
        new SetLocalWhileThreadInNative (args).startTests();
    }

    /********** test core **********/

    protected void runTests()
        throws Exception
    {
        String targetProgName = SetLocalWhileThreadInNativeTarget.class.getName();
        String testName = getClass().getSimpleName();

        // Start debuggee and obtain reference to main thread an main class
        BreakpointEvent bpe = startToMain(targetProgName);
        mainClass = bpe.location().declaringType();
        mainThread = bpe.thread();

        // Resume debuggee send some bytes
        vm().resume();
        OutputStream os = vm().process().getOutputStream();
        byte[] ba = MESSAGE.getBytes();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            os.write(ba);
        }
        os.flush();

        // Wait for the debugee to read all the bytes.
        int bytesRead = 0;
        Field bytesReadField = mainClass.fieldByName("bytesRead");
        do {
            bytesRead = ((PrimitiveValue)mainClass.getValue(bytesReadField)).intValue();
            System.out.println("debugee has read " + bytesRead + " of " + TOTAL_BYTES);
            Thread.sleep(500);
        } while (bytesRead < TOTAL_BYTES);

        // By now  dontinline_testMethod() will be compiled. The debugee will be blocked in java.io.FileInputStream::read0().
        // Now set local variable in dontinline_testMethod().
        vm().suspend();
        System.out.println("Debuggee Stack:");
        List<StackFrame> stack_frames = mainThread.frames();
        int i = 0;
        for (StackFrame ff : stack_frames) {
            System.out.println("frame[" + i++ +"]: " + ff.location().method());
        }
        StackFrame frame = mainThread.frame(2);
        Asserts.assertEQ(frame.location().method().toString(), "SetLocalWhileThreadInNativeTarget.dontinline_testMethod()");
        List<LocalVariable> localVars = frame.visibleVariables();
        boolean changedLocal = false;
        boolean caughtOFE = false;
        for (LocalVariable lv : localVars) {
            if (lv.name().equals("zero")) {
                try {
                    frame.setValue(lv, vm().mirrorOf(0)); // triggers deoptimization!
                    changedLocal = true;
                } catch (OpaqueFrameException e) {
                    caughtOFE = true;
                }
            }
        }
        boolean isVirtualThread = DebuggeeWrapper.isVirtual();
        Asserts.assertTrue(caughtOFE == isVirtualThread);
        Asserts.assertTrue(changedLocal == !isVirtualThread);

        // signal stop
        os.write(STOP);
        os.flush();


        // resume the target listening for events
        listenUntilVMDisconnect();


        // deal with results of test if anything has called failure("foo")
        // testFailed will be true
        if (!testFailed) {
            println(testName + ": passed");
        } else {
            throw new Exception(testName + ": failed");
        }
    }
}
