/*
 * Copyright (c) 2015, 2020, 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 static org.graalvm.compiler.debug.DebugOptions.DumpOnError;
import static org.graalvm.compiler.lir.LIRInstruction.OperandFlag.REG;
import static org.graalvm.compiler.lir.LIRInstruction.OperandFlag.STACK;
import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_IGNORED;
import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_IGNORED;

import java.util.function.Consumer;

import org.graalvm.compiler.code.CompilationResult;
import org.graalvm.compiler.core.common.LIRKind;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.core.test.GraalCompilerTest;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DebugContext.Builder;
import org.graalvm.compiler.debug.DebugContext.Scope;
import org.graalvm.compiler.graph.NodeClass;
import org.graalvm.compiler.hotspot.HotSpotCompiledCodeBuilder;
import org.graalvm.compiler.lir.FullInfopointOp;
import org.graalvm.compiler.lir.LIRFrameState;
import org.graalvm.compiler.lir.LIRInstruction;
import org.graalvm.compiler.lir.LIRInstructionClass;
import org.graalvm.compiler.lir.Variable;
import org.graalvm.compiler.lir.asm.CompilationResultBuilder;
import org.graalvm.compiler.lir.gen.LIRGeneratorTool;
import org.graalvm.compiler.nodeinfo.NodeInfo;
import org.graalvm.compiler.nodes.DeoptimizingFixedWithNextNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.spi.LIRLowerable;
import org.graalvm.compiler.nodes.spi.NodeLIRBuilderTool;
import org.graalvm.compiler.options.OptionValues;
import org.junit.Test;

import jdk.vm.ci.code.BytecodeFrame;
import jdk.vm.ci.code.CodeCacheProvider;
import jdk.vm.ci.code.VirtualObject;
import jdk.vm.ci.code.site.InfopointReason;
import jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.hotspot.HotSpotCompiledCode;
import jdk.vm.ci.meta.AllocatableValue;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.JavaValue;
import jdk.vm.ci.meta.PlatformKind;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.meta.Value;

public class JVMCIInfopointErrorTest extends GraalCompilerTest {

    private static class ValueDef extends LIRInstruction {
        private static final LIRInstructionClass<ValueDef> TYPE = LIRInstructionClass.create(ValueDef.class);

        @Def({REG, STACK}) AllocatableValue value;

        ValueDef(AllocatableValue value) {
            super(TYPE);
            this.value = value;
        }

        @Override
        public void emitCode(CompilationResultBuilder crb) {
        }
    }

    private static class ValueUse extends LIRInstruction {
        private static final LIRInstructionClass<ValueUse> TYPE = LIRInstructionClass.create(ValueUse.class);

        @Use({REG, STACK}) AllocatableValue value;

        ValueUse(AllocatableValue value) {
            super(TYPE);
            this.value = value;
        }

        @Override
        public void emitCode(CompilationResultBuilder crb) {
        }
    }

    @NodeInfo(cycles = CYCLES_IGNORED, size = SIZE_IGNORED)
    private static class TestNode extends DeoptimizingFixedWithNextNode implements LIRLowerable {
        private static final NodeClass<TestNode> TYPE = NodeClass.create(TestNode.class);

        private final TestSpec spec;

        protected TestNode(TestSpec spec) {
            super(TYPE, StampFactory.forVoid());
            this.spec = spec;
        }

        @Override
        public boolean canDeoptimize() {
            return true;
        }

        @Override
        public void generate(NodeLIRBuilderTool gen) {
            LIRGeneratorTool tool = gen.getLIRGeneratorTool();
            LIRFrameState state = gen.state(this);
            spec.spec(tool, state, st -> {
                tool.append(new FullInfopointOp(st, InfopointReason.SAFEPOINT));
            });
        }
    }

    @FunctionalInterface
    private interface TestSpec {
        void spec(LIRGeneratorTool tool, LIRFrameState state, Consumer<LIRFrameState> safepoint);
    }

    public static void testMethod() {
    }

    private void test(TestSpec spec) {
        test(getDebugContext(), spec);
    }

    
Avoids dumping during tests which are expected to fail.
/** * Avoids dumping during tests which are expected to fail. */
private void testNoDump(TestSpec spec) { OptionValues options = new OptionValues(getInitialOptions(), DumpOnError, false); test(getDebugContext(options, null, null), spec); } private void test(DebugContext debug, TestSpec spec) { ResolvedJavaMethod method = getResolvedJavaMethod("testMethod"); StructuredGraph graph = parseForCompile(method, debug); TestNode test = graph.add(new TestNode(spec)); graph.addAfterFixed(graph.start(), test); CompilationResult compResult = compile(method, graph); CodeCacheProvider codeCache = getCodeCache(); HotSpotCompiledCode compiledCode = HotSpotCompiledCodeBuilder.createCompiledCode(codeCache, method, null, compResult, getInitialOptions()); codeCache.addCode(method, compiledCode, null, null); } @Test(expected = Error.class) public void testInvalidShortOop() { testNoDump((tool, state, safepoint) -> { PlatformKind kind = tool.target().arch.getPlatformKind(JavaKind.Short); LIRKind lirKind = LIRKind.reference(kind); Variable var = tool.newVariable(lirKind); tool.append(new ValueDef(var)); safepoint.accept(state); tool.append(new ValueUse(var)); }); } @Test(expected = Error.class) public void testInvalidShortDerivedOop() { testNoDump((tool, state, safepoint) -> { Variable baseOop = tool.newVariable(LIRKind.fromJavaKind(tool.target().arch, JavaKind.Object)); tool.append(new ValueDef(baseOop)); PlatformKind kind = tool.target().arch.getPlatformKind(JavaKind.Short); LIRKind lirKind = LIRKind.derivedReference(kind, baseOop, false); Variable var = tool.newVariable(lirKind); tool.append(new ValueDef(var)); safepoint.accept(state); tool.append(new ValueUse(var)); }); } private static LIRFrameState modifyTopFrame(LIRFrameState state, JavaValue[] values, JavaKind[] slotKinds, int locals, int stack, int locks) { return modifyTopFrame(state, null, values, slotKinds, locals, stack, locks); } private static LIRFrameState modifyTopFrame(LIRFrameState state, VirtualObject[] vobj, JavaValue[] values, JavaKind[] slotKinds, int locals, int stack, int locks) { BytecodeFrame top = state.topFrame; top = new BytecodeFrame(top.caller(), top.getMethod(), top.getBCI(), top.rethrowException, top.duringCall, values, slotKinds, locals, stack, locks); return new LIRFrameState(top, vobj, state.exceptionEdge); } @Test(expected = JVMCIError.class) public void testUnexpectedScopeValuesLength() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.FALSE}, new JavaKind[0], 0, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnexpectedScopeSlotKindsLength() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[0], new JavaKind[]{JavaKind.Boolean}, 0, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testWrongMonitorType() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.INT_0}, new JavaKind[]{}, 0, 0, 1); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnexpectedIllegalValue() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{Value.ILLEGAL}, new JavaKind[]{JavaKind.Int}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnexpectedTypeInRegister() { test((tool, state, safepoint) -> { Variable var = tool.newVariable(LIRKind.fromJavaKind(tool.target().arch, JavaKind.Int)); tool.append(new ValueDef(var)); LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{var}, new JavaKind[]{JavaKind.Illegal}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testWrongConstantType() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.INT_0}, new JavaKind[]{JavaKind.Object}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnsupportedConstantType() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.forShort((short) 0)}, new JavaKind[]{JavaKind.Short}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnexpectedNull() { test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.NULL_POINTER}, new JavaKind[]{JavaKind.Int}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUnexpectedObject() { JavaValue wrapped = getSnippetReflection().forObject(this); test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{wrapped}, new JavaKind[]{JavaKind.Int}, 1, 0, 0); safepoint.accept(newState); }); } private static class UnknownJavaValue implements JavaValue { } @SuppressWarnings("try") @Test(expected = Error.class) public void testUnknownJavaValue() { DebugContext debug = new Builder(getInitialOptions()).build(); try (Scope s = debug.disable()) { /* * Expected: either AssertionError or GraalError, depending on whether the unit test run * is with assertions enabled or disabled. */ test(debug, (tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{new UnknownJavaValue()}, new JavaKind[]{JavaKind.Int}, 1, 0, 0); safepoint.accept(newState); }); } } @Test(expected = Error.class) public void testMissingIllegalAfterDouble() { /* * Expected: either AssertionError or GraalError, depending on whether the unit test run is * with assertions enabled or disabled. */ test((tool, state, safepoint) -> { LIRFrameState newState = modifyTopFrame(state, new JavaValue[]{JavaConstant.DOUBLE_0, JavaConstant.INT_0}, new JavaKind[]{JavaKind.Double, JavaKind.Int}, 2, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testInvalidVirtualObjectId() { ResolvedJavaType obj = getMetaAccess().lookupJavaType(Object.class); test((tool, state, safepoint) -> { VirtualObject o = VirtualObject.get(obj, 5); o.setValues(new JavaValue[0], new JavaKind[0]); safepoint.accept(new LIRFrameState(state.topFrame, new VirtualObject[]{o}, state.exceptionEdge)); }); } @Test(expected = JVMCIError.class) public void testDuplicateVirtualObject() { ResolvedJavaType obj = getMetaAccess().lookupJavaType(Object.class); test((tool, state, safepoint) -> { VirtualObject o1 = VirtualObject.get(obj, 0); o1.setValues(new JavaValue[0], new JavaKind[0]); VirtualObject o2 = VirtualObject.get(obj, 0); o2.setValues(new JavaValue[0], new JavaKind[0]); safepoint.accept(new LIRFrameState(state.topFrame, new VirtualObject[]{o1, o2}, state.exceptionEdge)); }); } @Test(expected = JVMCIError.class) public void testUnexpectedVirtualObject() { ResolvedJavaType obj = getMetaAccess().lookupJavaType(Object.class); test((tool, state, safepoint) -> { VirtualObject o = VirtualObject.get(obj, 0); o.setValues(new JavaValue[0], new JavaKind[0]); LIRFrameState newState = modifyTopFrame(state, new VirtualObject[]{o}, new JavaValue[]{o}, new JavaKind[]{JavaKind.Int}, 1, 0, 0); safepoint.accept(newState); }); } @Test(expected = JVMCIError.class) public void testUndefinedVirtualObject() { ResolvedJavaType obj = getMetaAccess().lookupJavaType(Object.class); test((tool, state, safepoint) -> { VirtualObject o0 = VirtualObject.get(obj, 0); o0.setValues(new JavaValue[0], new JavaKind[0]); VirtualObject o1 = VirtualObject.get(obj, 1); o1.setValues(new JavaValue[0], new JavaKind[0]); LIRFrameState newState = modifyTopFrame(state, new VirtualObject[]{o0}, new JavaValue[]{o1}, new JavaKind[]{JavaKind.Object}, 1, 0, 0); safepoint.accept(newState); }); } }