From 2537963e75dc487b74a0333e38abca6449020ee5 Mon Sep 17 00:00:00 2001 From: Aca-S Date: Thu, 24 Apr 2025 10:48:42 +0200 Subject: [PATCH] [GR-45250][GR-45734] Add framework for dataflow analysis on JVMCI provided method bytecode. --- .../graal/compiler/java/BytecodeParser.java | 5 + .../compiler/java/dataflow/AbstractFrame.java | 287 ++++ .../java/dataflow/AbstractInterpreter.java | 1314 +++++++++++++++++ .../dataflow/DataFlowAnalysisException.java | 46 + .../java/dataflow/DataFlowAnalyzer.java | 199 +++ .../GraphBuilderConfiguration.java | 19 + .../graphbuilderconf/GraphBuilderContext.java | 14 + .../graphbuilderconf/MethodParsingPlugin.java | 38 + .../replacements/IntrinsicGraphBuilder.java | 8 + .../compiler/replacements/PEGraphDecoder.java | 13 + .../com/oracle/svm/core/SubstrateOptions.java | 13 + .../reflect/ReflectionBytecodeAnalyzer.java | 426 ++++++ .../svm/hosted/reflect/ReflectionFeature.java | 8 +- .../reflect/StrictReflectionRegistry.java | 191 +++ .../hosted/snippets/ReflectionPlugins.java | 451 +++++- .../SubstrateGraphBuilderPlugins.java | 2 + .../AutomaticUnsafeTransformationSupport.java | 11 +- 17 files changed, 2965 insertions(+), 80 deletions(-) create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractFrame.java create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractInterpreter.java create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalysisException.java create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalyzer.java create mode 100644 compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/MethodParsingPlugin.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionBytecodeAnalyzer.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/StrictReflectionRegistry.java diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java index 2b851931a363..1295409b89c5 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/BytecodeParser.java @@ -269,6 +269,7 @@ import java.util.function.IntConsumer; import java.util.function.Supplier; +import jdk.graal.compiler.nodes.graphbuilderconf.MethodParsingPlugin; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Equivalence; import org.graalvm.word.LocationIdentity; @@ -1101,6 +1102,10 @@ protected void build(FixedWithNextNode startInstruction, FrameStateBuilder start graph.recordMethod(method); } + for (MethodParsingPlugin plugin : graphBuilderConfig.getPlugins().getMethodParsingPlugins()) { + plugin.execute(method, intrinsicContext); + } + // compute the block map, setup exception handlers and get the entrypoint(s) this.blockMap = generateBlockMap(); this.firstInstructionArray = new FixedWithNextNode[blockMap.getBlockCount()]; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractFrame.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractFrame.java new file mode 100644 index 000000000000..dd4737b8d50a --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractFrame.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.graal.compiler.java.dataflow; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Abstract representation of a bytecode execution frame. + * + * @param The abstract representation of values pushed and popped from the operand stack and stored in the local variable table. + */ +public class AbstractFrame { + + private final OperandStack operandStack; + private final LocalVariableTable localVariableTable; + + public AbstractFrame() { + this.operandStack = new OperandStack<>(); + this.localVariableTable = new LocalVariableTable<>(); + } + + public AbstractFrame(AbstractFrame state) { + this.operandStack = new OperandStack<>(state.operandStack); + this.localVariableTable = new LocalVariableTable<>(state.localVariableTable); + } + + public void mergeWith(AbstractFrame other, BiFunction mergeFunction) throws DataFlowAnalysisException { + operandStack.mergeWith(other.operandStack, mergeFunction); + localVariableTable.mergeWith(other.localVariableTable, mergeFunction); + } + + public void transform(Predicate filterFunction, Function transformFunction) { + operandStack.transform(filterFunction, transformFunction); + localVariableTable.transform(filterFunction, transformFunction); + } + + public OperandStack getOperandStack() { + return operandStack; + } + + public LocalVariableTable getLocalVariableTable() { + return localVariableTable; + } + + public T getOperand(int depth) { + try { + return operandStack.peek(depth).value; + } catch (DataFlowAnalysisException e) { + throw new RuntimeException(e); + } + } + + public T getVariable(int index) { + try { + return localVariableTable.get(index).value; + } catch (DataFlowAnalysisException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractFrame that = (AbstractFrame) o; + return Objects.equals(operandStack, that.operandStack) && Objects.equals(localVariableTable, that.localVariableTable); + } + + @Override + public int hashCode() { + return Objects.hash(operandStack, localVariableTable); + } + + @Override + public String toString() { + return operandStack + "" + localVariableTable; + } + + public static final class OperandStack { + + private final ArrayList> stack; + + public OperandStack() { + this.stack = new ArrayList<>(); + } + + public OperandStack(OperandStack stack) { + this.stack = new ArrayList<>(stack.stack); + } + + public void push(SizedValue value) { + stack.add(value); + } + + public SizedValue pop() throws DataFlowAnalysisException { + if (stack.isEmpty()) { + throw new DataFlowAnalysisException("Operand stack is empty"); + } + return stack.removeLast(); + } + + public SizedValue peek(int depth) throws DataFlowAnalysisException { + if (size() <= depth) { + throw new DataFlowAnalysisException("Operand stack doesn't contain enough values"); + } + return stack.get(stack.size() - depth - 1); + } + + public SizedValue peek() throws DataFlowAnalysisException { + return peek(0); + } + + public void clear() { + stack.clear(); + } + + public int size() { + return stack.size(); + } + + public void mergeWith(OperandStack other, BiFunction mergeFunction) throws DataFlowAnalysisException { + if (size() != other.size()) { + throw new DataFlowAnalysisException("Operand stack size mismatch upon merging"); + } + for (int i = 0; i < stack.size(); i++) { + SizedValue thisValue = stack.get(i); + SizedValue thatValue = other.stack.get(i); + if (thisValue.size() != thatValue.size()) { + throw new DataFlowAnalysisException("Operand stack value size mismatch upon merging"); + } + SizedValue mergedValue = new SizedValue<>(mergeFunction.apply(thisValue.value(), thatValue.value()), thisValue.size()); + stack.set(i, mergedValue); + } + } + + public void transform(Predicate filterFunction, Function transformFunction) { + for (int i = 0; i < stack.size(); i++) { + SizedValue value = stack.get(i); + if (filterFunction.test(value.value())) { + stack.set(i, new SizedValue<>(transformFunction.apply(value.value()), value.size())); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OperandStack that = (OperandStack) o; + return Objects.equals(stack, that.stack); + } + + @Override + public int hashCode() { + return Objects.hashCode(stack); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Operand stack:\n"); + for (SizedValue value : stack.reversed()) { + builder.append("[").append(value.value()).append("]").append("\n"); + } + return builder.toString(); + } + } + + public static final class LocalVariableTable { + + private final Map> variables; + + public LocalVariableTable() { + this.variables = new HashMap<>(); + } + + public LocalVariableTable(LocalVariableTable localVariableTable) { + this.variables = new HashMap<>(localVariableTable.variables); + } + + public void put(int index, SizedValue value) { + variables.put(index, value); + } + + public SizedValue get(int index) throws DataFlowAnalysisException { + if (!variables.containsKey(index)) { + throw new DataFlowAnalysisException("Variable table doesn't contain entry for index " + index); + } + return variables.get(index); + } + + public void mergeWith(LocalVariableTable other, BiFunction mergeFunction) { + for (Map.Entry> entry : variables.entrySet()) { + SizedValue thisValue = entry.getValue(); + SizedValue thatValue = other.variables.get(entry.getKey()); + if (thatValue != null) { + SizedValue mergedValue = new SizedValue<>(mergeFunction.apply(thisValue.value(), thatValue.value()), thisValue.size()); + entry.setValue(mergedValue); + } + } + } + + public void transform(Predicate filterFunction, Function transformFunction) { + for (Map.Entry> entry : variables.entrySet()) { + SizedValue value = entry.getValue(); + if (filterFunction.test(value.value())) { + entry.setValue(new SizedValue<>(transformFunction.apply(value.value()), value.size())); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LocalVariableTable that = (LocalVariableTable) o; + return Objects.equals(variables, that.variables); + } + + @Override + public int hashCode() { + return Objects.hashCode(variables); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Local variable table:\n"); + variables.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach( + e -> { + Integer varIndex = e.getKey(); + SizedValue value = e.getValue(); + builder.append(varIndex).append(": ").append(value.value()).append("\n"); + } + ); + return builder.toString(); + } + } + + public record SizedValue(T value, Slots size) { + public enum Slots {ONE_SLOT, TWO_SLOTS} + + public static SizedValue wrap(T value, Slots size) { + return new SizedValue<>(value, size); + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractInterpreter.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractInterpreter.java new file mode 100644 index 000000000000..a7fb6ae644c9 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/AbstractInterpreter.java @@ -0,0 +1,1314 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.graal.compiler.java.dataflow; + +import jdk.graal.compiler.bytecode.Bytecode; +import jdk.graal.compiler.bytecode.BytecodeLookupSwitch; +import jdk.graal.compiler.bytecode.BytecodeStream; +import jdk.graal.compiler.bytecode.BytecodeSwitch; +import jdk.graal.compiler.bytecode.BytecodeTableSwitch; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.Signature; + +import java.util.ArrayList; +import java.util.List; + +import static jdk.graal.compiler.bytecode.Bytecodes.AALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.AASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.ACONST_NULL; +import static jdk.graal.compiler.bytecode.Bytecodes.ALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.ALOAD_0; +import static jdk.graal.compiler.bytecode.Bytecodes.ALOAD_1; +import static jdk.graal.compiler.bytecode.Bytecodes.ALOAD_2; +import static jdk.graal.compiler.bytecode.Bytecodes.ALOAD_3; +import static jdk.graal.compiler.bytecode.Bytecodes.ANEWARRAY; +import static jdk.graal.compiler.bytecode.Bytecodes.ARETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.ARRAYLENGTH; +import static jdk.graal.compiler.bytecode.Bytecodes.ASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.ASTORE_0; +import static jdk.graal.compiler.bytecode.Bytecodes.ASTORE_1; +import static jdk.graal.compiler.bytecode.Bytecodes.ASTORE_2; +import static jdk.graal.compiler.bytecode.Bytecodes.ASTORE_3; +import static jdk.graal.compiler.bytecode.Bytecodes.ATHROW; +import static jdk.graal.compiler.bytecode.Bytecodes.BALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.BASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.BIPUSH; +import static jdk.graal.compiler.bytecode.Bytecodes.BREAKPOINT; +import static jdk.graal.compiler.bytecode.Bytecodes.CALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.CASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.CHECKCAST; +import static jdk.graal.compiler.bytecode.Bytecodes.D2F; +import static jdk.graal.compiler.bytecode.Bytecodes.D2I; +import static jdk.graal.compiler.bytecode.Bytecodes.D2L; +import static jdk.graal.compiler.bytecode.Bytecodes.DADD; +import static jdk.graal.compiler.bytecode.Bytecodes.DALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.DASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.DCMPG; +import static jdk.graal.compiler.bytecode.Bytecodes.DCMPL; +import static jdk.graal.compiler.bytecode.Bytecodes.DCONST_0; +import static jdk.graal.compiler.bytecode.Bytecodes.DCONST_1; +import static jdk.graal.compiler.bytecode.Bytecodes.DDIV; +import static jdk.graal.compiler.bytecode.Bytecodes.DLOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.DLOAD_0; +import static jdk.graal.compiler.bytecode.Bytecodes.DLOAD_1; +import static jdk.graal.compiler.bytecode.Bytecodes.DLOAD_2; +import static jdk.graal.compiler.bytecode.Bytecodes.DLOAD_3; +import static jdk.graal.compiler.bytecode.Bytecodes.DMUL; +import static jdk.graal.compiler.bytecode.Bytecodes.DNEG; +import static jdk.graal.compiler.bytecode.Bytecodes.DREM; +import static jdk.graal.compiler.bytecode.Bytecodes.DRETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.DSTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.DSTORE_0; +import static jdk.graal.compiler.bytecode.Bytecodes.DSTORE_1; +import static jdk.graal.compiler.bytecode.Bytecodes.DSTORE_2; +import static jdk.graal.compiler.bytecode.Bytecodes.DSTORE_3; +import static jdk.graal.compiler.bytecode.Bytecodes.DSUB; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP2; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP2_X1; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP2_X2; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP_X1; +import static jdk.graal.compiler.bytecode.Bytecodes.DUP_X2; +import static jdk.graal.compiler.bytecode.Bytecodes.F2D; +import static jdk.graal.compiler.bytecode.Bytecodes.F2I; +import static jdk.graal.compiler.bytecode.Bytecodes.F2L; +import static jdk.graal.compiler.bytecode.Bytecodes.FADD; +import static jdk.graal.compiler.bytecode.Bytecodes.FALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.FASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.FCMPG; +import static jdk.graal.compiler.bytecode.Bytecodes.FCMPL; +import static jdk.graal.compiler.bytecode.Bytecodes.FCONST_0; +import static jdk.graal.compiler.bytecode.Bytecodes.FCONST_1; +import static jdk.graal.compiler.bytecode.Bytecodes.FCONST_2; +import static jdk.graal.compiler.bytecode.Bytecodes.FDIV; +import static jdk.graal.compiler.bytecode.Bytecodes.FLOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.FLOAD_0; +import static jdk.graal.compiler.bytecode.Bytecodes.FLOAD_1; +import static jdk.graal.compiler.bytecode.Bytecodes.FLOAD_2; +import static jdk.graal.compiler.bytecode.Bytecodes.FLOAD_3; +import static jdk.graal.compiler.bytecode.Bytecodes.FMUL; +import static jdk.graal.compiler.bytecode.Bytecodes.FNEG; +import static jdk.graal.compiler.bytecode.Bytecodes.FREM; +import static jdk.graal.compiler.bytecode.Bytecodes.FRETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.FSTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.FSTORE_0; +import static jdk.graal.compiler.bytecode.Bytecodes.FSTORE_1; +import static jdk.graal.compiler.bytecode.Bytecodes.FSTORE_2; +import static jdk.graal.compiler.bytecode.Bytecodes.FSTORE_3; +import static jdk.graal.compiler.bytecode.Bytecodes.FSUB; +import static jdk.graal.compiler.bytecode.Bytecodes.GETFIELD; +import static jdk.graal.compiler.bytecode.Bytecodes.GETSTATIC; +import static jdk.graal.compiler.bytecode.Bytecodes.GOTO; +import static jdk.graal.compiler.bytecode.Bytecodes.GOTO_W; +import static jdk.graal.compiler.bytecode.Bytecodes.I2B; +import static jdk.graal.compiler.bytecode.Bytecodes.I2C; +import static jdk.graal.compiler.bytecode.Bytecodes.I2D; +import static jdk.graal.compiler.bytecode.Bytecodes.I2F; +import static jdk.graal.compiler.bytecode.Bytecodes.I2L; +import static jdk.graal.compiler.bytecode.Bytecodes.I2S; +import static jdk.graal.compiler.bytecode.Bytecodes.IADD; +import static jdk.graal.compiler.bytecode.Bytecodes.IALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.IAND; +import static jdk.graal.compiler.bytecode.Bytecodes.IASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_0; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_1; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_2; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_3; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_4; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_5; +import static jdk.graal.compiler.bytecode.Bytecodes.ICONST_M1; +import static jdk.graal.compiler.bytecode.Bytecodes.IDIV; +import static jdk.graal.compiler.bytecode.Bytecodes.IFEQ; +import static jdk.graal.compiler.bytecode.Bytecodes.IFGE; +import static jdk.graal.compiler.bytecode.Bytecodes.IFGT; +import static jdk.graal.compiler.bytecode.Bytecodes.IFLE; +import static jdk.graal.compiler.bytecode.Bytecodes.IFLT; +import static jdk.graal.compiler.bytecode.Bytecodes.IFNE; +import static jdk.graal.compiler.bytecode.Bytecodes.IFNONNULL; +import static jdk.graal.compiler.bytecode.Bytecodes.IFNULL; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ACMPEQ; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ACMPNE; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPEQ; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPGE; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPGT; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPLE; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPLT; +import static jdk.graal.compiler.bytecode.Bytecodes.IF_ICMPNE; +import static jdk.graal.compiler.bytecode.Bytecodes.IINC; +import static jdk.graal.compiler.bytecode.Bytecodes.ILOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.ILOAD_0; +import static jdk.graal.compiler.bytecode.Bytecodes.ILOAD_1; +import static jdk.graal.compiler.bytecode.Bytecodes.ILOAD_2; +import static jdk.graal.compiler.bytecode.Bytecodes.ILOAD_3; +import static jdk.graal.compiler.bytecode.Bytecodes.IMUL; +import static jdk.graal.compiler.bytecode.Bytecodes.INEG; +import static jdk.graal.compiler.bytecode.Bytecodes.INSTANCEOF; +import static jdk.graal.compiler.bytecode.Bytecodes.INVOKEDYNAMIC; +import static jdk.graal.compiler.bytecode.Bytecodes.INVOKEINTERFACE; +import static jdk.graal.compiler.bytecode.Bytecodes.INVOKESPECIAL; +import static jdk.graal.compiler.bytecode.Bytecodes.INVOKESTATIC; +import static jdk.graal.compiler.bytecode.Bytecodes.INVOKEVIRTUAL; +import static jdk.graal.compiler.bytecode.Bytecodes.IOR; +import static jdk.graal.compiler.bytecode.Bytecodes.IREM; +import static jdk.graal.compiler.bytecode.Bytecodes.IRETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.ISHL; +import static jdk.graal.compiler.bytecode.Bytecodes.ISHR; +import static jdk.graal.compiler.bytecode.Bytecodes.ISTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.ISTORE_0; +import static jdk.graal.compiler.bytecode.Bytecodes.ISTORE_1; +import static jdk.graal.compiler.bytecode.Bytecodes.ISTORE_2; +import static jdk.graal.compiler.bytecode.Bytecodes.ISTORE_3; +import static jdk.graal.compiler.bytecode.Bytecodes.ISUB; +import static jdk.graal.compiler.bytecode.Bytecodes.IUSHR; +import static jdk.graal.compiler.bytecode.Bytecodes.IXOR; +import static jdk.graal.compiler.bytecode.Bytecodes.JSR; +import static jdk.graal.compiler.bytecode.Bytecodes.JSR_W; +import static jdk.graal.compiler.bytecode.Bytecodes.L2D; +import static jdk.graal.compiler.bytecode.Bytecodes.L2F; +import static jdk.graal.compiler.bytecode.Bytecodes.L2I; +import static jdk.graal.compiler.bytecode.Bytecodes.LADD; +import static jdk.graal.compiler.bytecode.Bytecodes.LALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.LAND; +import static jdk.graal.compiler.bytecode.Bytecodes.LASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.LCMP; +import static jdk.graal.compiler.bytecode.Bytecodes.LCONST_0; +import static jdk.graal.compiler.bytecode.Bytecodes.LCONST_1; +import static jdk.graal.compiler.bytecode.Bytecodes.LDC; +import static jdk.graal.compiler.bytecode.Bytecodes.LDC2_W; +import static jdk.graal.compiler.bytecode.Bytecodes.LDC_W; +import static jdk.graal.compiler.bytecode.Bytecodes.LDIV; +import static jdk.graal.compiler.bytecode.Bytecodes.LLOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.LLOAD_0; +import static jdk.graal.compiler.bytecode.Bytecodes.LLOAD_1; +import static jdk.graal.compiler.bytecode.Bytecodes.LLOAD_2; +import static jdk.graal.compiler.bytecode.Bytecodes.LLOAD_3; +import static jdk.graal.compiler.bytecode.Bytecodes.LMUL; +import static jdk.graal.compiler.bytecode.Bytecodes.LNEG; +import static jdk.graal.compiler.bytecode.Bytecodes.LOOKUPSWITCH; +import static jdk.graal.compiler.bytecode.Bytecodes.LOR; +import static jdk.graal.compiler.bytecode.Bytecodes.LREM; +import static jdk.graal.compiler.bytecode.Bytecodes.LRETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.LSHL; +import static jdk.graal.compiler.bytecode.Bytecodes.LSHR; +import static jdk.graal.compiler.bytecode.Bytecodes.LSTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.LSTORE_0; +import static jdk.graal.compiler.bytecode.Bytecodes.LSTORE_1; +import static jdk.graal.compiler.bytecode.Bytecodes.LSTORE_2; +import static jdk.graal.compiler.bytecode.Bytecodes.LSTORE_3; +import static jdk.graal.compiler.bytecode.Bytecodes.LSUB; +import static jdk.graal.compiler.bytecode.Bytecodes.LUSHR; +import static jdk.graal.compiler.bytecode.Bytecodes.LXOR; +import static jdk.graal.compiler.bytecode.Bytecodes.MONITORENTER; +import static jdk.graal.compiler.bytecode.Bytecodes.MONITOREXIT; +import static jdk.graal.compiler.bytecode.Bytecodes.MULTIANEWARRAY; +import static jdk.graal.compiler.bytecode.Bytecodes.NEW; +import static jdk.graal.compiler.bytecode.Bytecodes.NEWARRAY; +import static jdk.graal.compiler.bytecode.Bytecodes.NOP; +import static jdk.graal.compiler.bytecode.Bytecodes.POP; +import static jdk.graal.compiler.bytecode.Bytecodes.POP2; +import static jdk.graal.compiler.bytecode.Bytecodes.PUTFIELD; +import static jdk.graal.compiler.bytecode.Bytecodes.PUTSTATIC; +import static jdk.graal.compiler.bytecode.Bytecodes.RET; +import static jdk.graal.compiler.bytecode.Bytecodes.RETURN; +import static jdk.graal.compiler.bytecode.Bytecodes.SALOAD; +import static jdk.graal.compiler.bytecode.Bytecodes.SASTORE; +import static jdk.graal.compiler.bytecode.Bytecodes.SIPUSH; +import static jdk.graal.compiler.bytecode.Bytecodes.SWAP; +import static jdk.graal.compiler.bytecode.Bytecodes.TABLESWITCH; +import static jdk.graal.compiler.java.dataflow.AbstractFrame.SizedValue.wrap; + +/** + * A data flow analyzer where the data flow state is represented by an abstract + * bytecode execution frame. + * + * @param The abstract representation of values pushed and popped from the operand stack and stored in the local variable table. + */ +public abstract class AbstractInterpreter extends DataFlowAnalyzer> { + + private final CoreProviders providers; + + public AbstractInterpreter(CoreProviders providers) { + this.providers = providers; + } + + public CoreProviders getProviders() { + return providers; + } + + @Override + protected AbstractFrame createInitialState(ResolvedJavaMethod method) { + AbstractFrame state = new AbstractFrame<>(); + + int variableIndex = 0; + if (!method.isStatic()) { + state.getLocalVariableTable().put(variableIndex, wrap(storeMethodArgument(method, -1, variableIndex), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + variableIndex++; + } + + Signature signature = method.getSignature(); + int numParameters = signature.getParameterCount(false); + for (int parameterIndex = 0; parameterIndex < numParameters; parameterIndex++) { + JavaKind kind = signature.getParameterKind(parameterIndex); + AbstractFrame.SizedValue.Slots size = getSizeForKind(kind); + state.getLocalVariableTable().put(variableIndex, wrap(storeMethodArgument(method, parameterIndex, variableIndex), size)); + variableIndex += kind.needsTwoSlots() ? 2 : 1; + } + + return state; + } + + @Override + protected AbstractFrame createExceptionState(AbstractFrame inState, List exceptionTypes) { + /* + * The initial frame state when entering an exception handler is created + * by clearing the operand stack and placing only one object, representing the + * caught exception, on it. + */ + AbstractFrame exceptionPathState = new AbstractFrame<>(inState); + exceptionPathState.getOperandStack().clear(); + AbstractFrame.SizedValue exceptionObject = wrap(pushExceptionObject(exceptionTypes), AbstractFrame.SizedValue.Slots.ONE_SLOT); + exceptionPathState.getOperandStack().push(exceptionObject); + return exceptionPathState; + } + + @Override + protected AbstractFrame copyState(AbstractFrame state) { + return new AbstractFrame<>(state); + } + + @Override + protected AbstractFrame mergeStates(AbstractFrame left, AbstractFrame right) throws DataFlowAnalysisException { + AbstractFrame merged = copyState(left); + merged.mergeWith(right, this::merge); + return merged; + } + + @Override + protected AbstractFrame processInstruction(AbstractFrame inState, BytecodeStream stream, Bytecode code) throws DataFlowAnalysisException { + AbstractFrame outState = copyState(inState); + + int bci = stream.currentBCI(); + int opcode = stream.currentBC(); + + // @formatter:off + // Checkstyle: stop + switch (opcode) { + case NOP : break; + case ACONST_NULL : handleConstant(opcode, bci, outState, JavaConstant.NULL_POINTER); break; + case ICONST_M1 : handleConstant(opcode, bci, outState, JavaConstant.forInt(-1)); break; + case ICONST_0 : // fall through + case ICONST_1 : // fall through + case ICONST_2 : // fall through + case ICONST_3 : // fall through + case ICONST_4 : // fall through + case ICONST_5 : handleConstant(opcode, bci, outState, JavaConstant.forInt(opcode - ICONST_0)); break; + case LCONST_0 : // fall through + case LCONST_1 : handleConstant(opcode, bci, outState, JavaConstant.forLong((long) opcode - LCONST_0)); break; + case FCONST_0 : // fall through + case FCONST_1 : // fall through + case FCONST_2 : handleConstant(opcode, bci, outState, JavaConstant.forFloat((float) opcode - FCONST_0)); break; + case DCONST_0 : // fall through + case DCONST_1 : handleConstant(opcode, bci, outState, JavaConstant.forDouble((double) opcode - DCONST_0)); break; + case BIPUSH : handleConstant(opcode, bci, outState, JavaConstant.forByte(stream.readByte())); break; + case SIPUSH : handleConstant(opcode, bci, outState, JavaConstant.forShort(stream.readShort())); break; + case LDC : // fall through + case LDC_W : // fall through + case LDC2_W : handleConstant(opcode, bci, outState, getConstant(code, stream)); break; + case ILOAD : // fall through + case LLOAD : // fall through + case FLOAD : // fall through + case DLOAD : // fall through + case ALOAD : handleVariableLoad(opcode, bci, outState, stream.readLocalIndex()); break; + case ILOAD_0 : // fall through + case ILOAD_1 : // fall through + case ILOAD_2 : // fall through + case ILOAD_3 : handleVariableLoad(opcode, bci, outState, opcode - ILOAD_0); break; + case LLOAD_0 : // fall through + case LLOAD_1 : // fall through + case LLOAD_2 : // fall through + case LLOAD_3 : handleVariableLoad(opcode, bci, outState, opcode - LLOAD_0); break; + case FLOAD_0 : // fall through + case FLOAD_1 : // fall through + case FLOAD_2 : // fall through + case FLOAD_3 : handleVariableLoad(opcode, bci, outState, opcode - FLOAD_0); break; + case DLOAD_0 : // fall through + case DLOAD_1 : // fall through + case DLOAD_2 : // fall through + case DLOAD_3 : handleVariableLoad(opcode, bci, outState, opcode - DLOAD_0); break; + case ALOAD_0 : // fall through + case ALOAD_1 : // fall through + case ALOAD_2 : // fall through + case ALOAD_3 : handleVariableLoad(opcode, bci, outState, opcode - ALOAD_0); break; + case IALOAD : // fall through + case LALOAD : // fall through + case FALOAD : // fall through + case DALOAD : // fall through + case AALOAD : // fall through + case BALOAD : // fall through + case CALOAD : // fall through + case SALOAD : handleArrayElementLoad(opcode, bci, outState); break; + case ISTORE : // fall through + case LSTORE : // fall through + case FSTORE : // fall through + case DSTORE : // fall through + case ASTORE : handleVariableStore(opcode, bci, outState, stream.readLocalIndex()); break; + case ISTORE_0 : // fall through + case ISTORE_1 : // fall through + case ISTORE_2 : // fall through + case ISTORE_3 : handleVariableStore(opcode, bci, outState, opcode - ISTORE_0); break; + case LSTORE_0 : // fall through + case LSTORE_1 : // fall through + case LSTORE_2 : // fall through + case LSTORE_3 : handleVariableStore(opcode, bci, outState, opcode - LSTORE_0); break; + case FSTORE_0 : // fall through + case FSTORE_1 : // fall through + case FSTORE_2 : // fall through + case FSTORE_3 : handleVariableStore(opcode, bci, outState, opcode - FSTORE_0); break; + case DSTORE_0 : // fall through + case DSTORE_1 : // fall through + case DSTORE_2 : // fall through + case DSTORE_3 : handleVariableStore(opcode, bci, outState, opcode - DSTORE_0); break; + case ASTORE_0 : // fall through + case ASTORE_1 : // fall through + case ASTORE_2 : // fall through+ + case ASTORE_3 : handleVariableStore(opcode, bci, outState, opcode - ASTORE_0); break; + case IASTORE : // fall through + case LASTORE : // fall through + case FASTORE : // fall through + case DASTORE : // fall through + case AASTORE : // fall through + case BASTORE : // fall through + case CASTORE : // fall through + case SASTORE : handleArrayElementStore(opcode, bci, outState); break; + case POP : handlePop(outState); break; + case POP2 : handlePop2(outState); break; + case DUP : handleDup(outState); break; + case DUP_X1 : handleDupX1(outState); break; + case DUP_X2 : handleDupX2(outState); break; + case DUP2 : handleDup2(outState); break; + case DUP2_X1 : handleDup2X1(outState); break; + case DUP2_X2 : handleDup2X2(outState); break; + case SWAP : handleSwap(outState); break; + case IADD : // fall through + case ISUB : // fall through + case IMUL : // fall through + case IDIV : // fall through + case IREM : // fall through + case LADD : // fall through + case LSUB : // fall through + case LMUL : // fall through + case LDIV : // fall through + case LREM : // fall through + case FADD : // fall through + case FSUB : // fall through + case FMUL : // fall through + case FDIV : // fall through + case FREM : // fall through + case DADD : // fall through + case DSUB : // fall through + case DMUL : // fall through + case DDIV : // fall through + case DREM : handleBinaryOperation(opcode, bci, outState); break; + case INEG : // fall through + case LNEG : // fall through + case FNEG : // fall through + case DNEG : handleUnaryOperation(opcode, bci, outState); break; + case ISHL : // fall through + case ISHR : // fall through + case IUSHR : // fall through + case IAND : // fall through + case IOR : // fall through + case IXOR : // fall through + case LSHL : // fall through + case LSHR : // fall through + case LUSHR : // fall through + case LAND : // fall through + case LOR : // fall through + case LXOR : handleBinaryOperation(opcode, bci, outState); break; + case IINC : handleIncrement(opcode, bci, outState, stream.readLocalIndex(), stream.readIncrement()); break; + case I2F : // fall through + case I2D : // fall through + case L2F : // fall through + case L2D : // fall through + case F2I : // fall through + case F2L : // fall through + case F2D : // fall through + case D2I : // fall through + case D2L : // fall through + case D2F : // fall through + case L2I : // fall through + case I2L : // fall through + case I2B : // fall through + case I2S : // fall through + case I2C : handleCast(opcode, bci, outState); break; + case LCMP : // fall through + case FCMPL : // fall through + case FCMPG : // fall through + case DCMPL : // fall through + case DCMPG : handleCompare(opcode, bci, outState); break; + case IFEQ : // fall through + case IFNE : // fall through + case IFLT : // fall through + case IFGE : // fall through + case IFGT : // fall through + case IFLE : handleUnaryConditionalJump(opcode, bci, outState, stream.readBranchDest(), stream.nextBCI()); break; + case IF_ICMPEQ : // fall through + case IF_ICMPNE : // fall through + case IF_ICMPLT : // fall through + case IF_ICMPGE : // fall through + case IF_ICMPGT : // fall through + case IF_ICMPLE : // fall through + case IF_ACMPEQ : // fall through + case IF_ACMPNE : handleBinaryConditionalJump(opcode, bci, outState, stream.readBranchDest(), stream.nextBCI()); break; + case GOTO : unconditionalJump(opcode, bci, outState, stream.readBranchDest()); break; + case JSR : // fall through + case RET : throw new DataFlowAnalysisException("Unsupported opcode " + opcode); + case TABLESWITCH : handleSwitch(opcode, bci, outState, new BytecodeTableSwitch(stream, bci)); break; + case LOOKUPSWITCH : handleSwitch(opcode, bci, outState, new BytecodeLookupSwitch(stream, bci)); break; + case IRETURN : // fall through + case LRETURN : // fall through + case FRETURN : // fall through + case DRETURN : // fall through + case ARETURN : returnValue(opcode, bci, outState, outState.getOperandStack().pop().value()); break; + case RETURN : returnVoid(opcode, bci, outState); break; + case GETSTATIC : handleStaticFieldLoad(opcode, bci, outState, getJavaField(code, stream)); break; + case PUTSTATIC : handleStaticFieldStore(opcode, bci, outState, getJavaField(code, stream)); break; + case GETFIELD : handleFieldLoad(opcode, bci, outState, getJavaField(code, stream)); break; + case PUTFIELD : handleFieldStore(opcode, bci, outState, getJavaField(code, stream)); break; + case INVOKEVIRTUAL : handleInvoke(opcode, bci, outState, getJavaMethod(code, stream), getAppendix(code, stream)); break; + case INVOKESPECIAL : // fall through + case INVOKESTATIC : // fall through + case INVOKEINTERFACE: handleInvoke(opcode, bci, outState, getJavaMethod(code, stream), null); break; + case INVOKEDYNAMIC : handleInvoke(opcode, bci, outState, getJavaMethod(code, stream), getAppendix(code, stream)); break; + case NEW : handleNew(opcode, bci, outState, getJavaType(code, stream)); break; + case NEWARRAY : handleNewArray(opcode, bci, outState, getJavaTypeFromPrimitiveArrayCode(stream.readLocalIndex()), 1); break; + case ANEWARRAY : handleNewArray(opcode, bci, outState, getJavaType(code, stream), 1); break; + case ARRAYLENGTH : handleArrayLength(opcode, bci, outState); break; + case ATHROW : doThrow(opcode, bci, outState, outState.getOperandStack().pop().value()); break; + case CHECKCAST : // fall through + case INSTANCEOF : handleCastCheck(opcode, bci, outState, getJavaType(code, stream)); break; + case MONITORENTER : // fall through + case MONITOREXIT : monitorOperation(opcode, bci, outState, outState.getOperandStack().pop().value()); break; + case MULTIANEWARRAY : handleNewArray(opcode, bci, outState, getJavaType(code, stream), stream.readUByte(bci + 3)); break; + case IFNULL : // fall through + case IFNONNULL : handleUnaryConditionalJump(opcode, bci, outState, stream.readBranchDest(), stream.nextBCI()); break; + case GOTO_W : unconditionalJump(opcode, bci, outState, stream.readBranchDest()); break; + case JSR_W : // fall through + case BREAKPOINT : // fall through + default : throw new DataFlowAnalysisException("Unsupported opcode " + opcode); + } + // @formatter:on + // Checkstyle: resume + + return outState; + } + + protected Object lookupConstant(Bytecode code, int cpi, int opcode) { + tryToResolve(code.getConstantPool(), cpi, opcode); + return code.getConstantPool().lookupConstant(cpi); + } + + protected JavaType lookupType(Bytecode code, int cpi, int opcode) { + tryToResolve(code.getConstantPool(), cpi, opcode); + return code.getConstantPool().lookupType(cpi, opcode); + } + + protected JavaField lookupField(Bytecode code, int cpi, int opcode) { + tryToResolve(code.getConstantPool(), cpi, opcode); + return code.getConstantPool().lookupField(cpi, code.getMethod(), opcode); + } + + protected JavaMethod lookupMethod(Bytecode code, int cpi, int opcode) { + tryToResolve(code.getConstantPool(), cpi, opcode); + return code.getConstantPool().lookupMethod(cpi, opcode, code.getMethod()); + } + + protected static void tryToResolve(ConstantPool constantPool, int cpi, int opcode) { + try { + constantPool.loadReferencedType(cpi, opcode, false); + } catch (Throwable t) { + // Ignore and leave the type unresolved. + } + } + + private Object getConstant(Bytecode code, BytecodeStream stream) { + return lookupConstant(code, stream.readCPI(), stream.currentBC()); + } + + private JavaType getJavaType(Bytecode code, BytecodeStream stream) { + return lookupType(code, stream.readCPI(), stream.currentBC()); + } + + private JavaField getJavaField(Bytecode code, BytecodeStream stream) { + return lookupField(code, stream.readCPI(), stream.currentBC()); + } + + private JavaMethod getJavaMethod(Bytecode code, BytecodeStream stream) { + int opcode = stream.currentBC(); + int cpi = opcode == INVOKEDYNAMIC ? stream.readCPI4() : stream.readCPI(); + return lookupMethod(code, cpi, opcode); + } + + private static JavaConstant getAppendix(Bytecode code, BytecodeStream stream) { + int opcode = stream.currentBC(); + int cpi = opcode == INVOKEDYNAMIC ? stream.readCPI4() : stream.readCPI(); + return code.getConstantPool().lookupAppendix(cpi, stream.currentBC()); + } + + private void handleConstant(int opcode, int bci, AbstractFrame state, Object value) { + AbstractFrame.SizedValue.Slots size = value instanceof JavaConstant constant + ? getSizeForKind(constant.getJavaKind()) + : AbstractFrame.SizedValue.Slots.ONE_SLOT; + if (value instanceof Constant constant) { + state.getOperandStack().push(wrap(pushConstant(opcode, bci, state, constant), size)); + } else if (value instanceof JavaType type) { + state.getOperandStack().push(wrap(pushType(opcode, bci, state, type), size)); + } + } + + private void handleVariableLoad(int opcode, int bci, AbstractFrame state, int variableIndex) throws DataFlowAnalysisException { + AbstractFrame.SizedValue value = state.getLocalVariableTable().get(variableIndex); + state.getOperandStack().push(wrap(loadVariable(opcode, bci, state, variableIndex, value.value()), value.size())); + } + + private void handleArrayElementLoad(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + T index = state.getOperandStack().pop().value(); + T array = state.getOperandStack().pop().value(); + AbstractFrame.SizedValue.Slots size = opcode == LALOAD || opcode == DALOAD + ? AbstractFrame.SizedValue.Slots.TWO_SLOTS + : AbstractFrame.SizedValue.Slots.ONE_SLOT; + state.getOperandStack().push(wrap(loadArrayElement(opcode, bci, state, array, index), size)); + } + + private void handleVariableStore(int opcode, int bci, AbstractFrame state, int variableIndex) throws DataFlowAnalysisException { + AbstractFrame.SizedValue value = state.getOperandStack().pop(); + state.getLocalVariableTable().put(variableIndex, wrap(storeVariable(opcode, bci, state, variableIndex, value.value()), value.size())); + } + + private void handleArrayElementStore(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + T index = state.getOperandStack().pop().value(); + T array = state.getOperandStack().pop().value(); + storeArrayElement(opcode, bci, state, array, index, value); + } + + private void handlePop(AbstractFrame state) throws DataFlowAnalysisException { + state.getOperandStack().pop(); + } + + private void handlePop2(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue value = state.getOperandStack().pop(); + if (value.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + state.getOperandStack().pop(); + } + } + + private void handleDup(AbstractFrame state) throws DataFlowAnalysisException { + state.getOperandStack().push(state.getOperandStack().peek()); + } + + private void handleDupX1(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + AbstractFrame.SizedValue second = state.getOperandStack().pop(); + state.getOperandStack().push(first); + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } + + private void handleDupX2(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + AbstractFrame.SizedValue second = state.getOperandStack().pop(); + if (second.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue third = state.getOperandStack().pop(); + state.getOperandStack().push(first); + state.getOperandStack().push(third); + } else { + state.getOperandStack().push(first); + } + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } + + private void handleDup2(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + if (first.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue second = state.getOperandStack().peek(); + state.getOperandStack().push(first); + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } else { + state.getOperandStack().push(first); + state.getOperandStack().push(first); + } + } + + private void handleDup2X1(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + AbstractFrame.SizedValue second = state.getOperandStack().pop(); + if (first.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue third = state.getOperandStack().pop(); + state.getOperandStack().push(second); + state.getOperandStack().push(first); + state.getOperandStack().push(third); + } else { + state.getOperandStack().push(first); + } + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } + + private void handleDup2X2(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + AbstractFrame.SizedValue second = state.getOperandStack().pop(); + if (first.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue third = state.getOperandStack().pop(); + if (third.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue fourth = state.getOperandStack().pop(); + state.getOperandStack().push(second); + state.getOperandStack().push(first); + state.getOperandStack().push(fourth); + } else { + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } + state.getOperandStack().push(third); + } else { + if (second.size() == AbstractFrame.SizedValue.Slots.ONE_SLOT) { + AbstractFrame.SizedValue third = state.getOperandStack().pop(); + state.getOperandStack().push(first); + state.getOperandStack().push(third); + } else { + state.getOperandStack().push(first); + } + } + state.getOperandStack().push(second); + state.getOperandStack().push(first); + } + + private void handleSwap(AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue first = state.getOperandStack().pop(); + AbstractFrame.SizedValue second = state.getOperandStack().pop(); + state.getOperandStack().push(first); + state.getOperandStack().push(second); + } + + private void handleBinaryOperation(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue right = state.getOperandStack().pop(); + AbstractFrame.SizedValue left = state.getOperandStack().pop(); + state.getOperandStack().push(wrap(binaryOperation(opcode, bci, state, left.value(), right.value()), left.size())); + } + + private void handleUnaryOperation(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + AbstractFrame.SizedValue value = state.getOperandStack().pop(); + state.getOperandStack().push(wrap(unaryOperation(opcode, bci, state, value.value()), value.size())); + } + + private void handleIncrement(int opcode, int bci, AbstractFrame state, int variableIndex, int incrementBy) throws DataFlowAnalysisException { + AbstractFrame.SizedValue value = state.getLocalVariableTable().get(variableIndex); + state.getLocalVariableTable().put(variableIndex, wrap(incrementVariable(opcode, bci, state, variableIndex, incrementBy, value.value()), value.size())); + } + + private void handleCast(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + AbstractFrame.SizedValue.Slots size = List.of(I2D, L2D, F2L, F2D, D2L, I2L).contains(opcode) + ? AbstractFrame.SizedValue.Slots.TWO_SLOTS + : AbstractFrame.SizedValue.Slots.ONE_SLOT; + state.getOperandStack().push(wrap(castOperation(opcode, bci, state, value), size)); + } + + private void handleCompare(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + T right = state.getOperandStack().pop().value(); + T left = state.getOperandStack().pop().value(); + state.getOperandStack().push(wrap(comparisonOperation(opcode, bci, state, left, right), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + private void handleUnaryConditionalJump(int opcode, int bci, AbstractFrame state, int ifDestinationBci, int elseDestinationBci) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + unaryConditionalJump(opcode, bci, state, ifDestinationBci, elseDestinationBci, value); + } + + private void handleBinaryConditionalJump(int opcode, int bci, AbstractFrame state, int ifDestinationBci, int elseDestinationBci) throws DataFlowAnalysisException { + T right = state.getOperandStack().pop().value(); + T left = state.getOperandStack().pop().value(); + binaryConditionalJump(opcode, bci, state, ifDestinationBci, elseDestinationBci, left, right); + } + + private void handleSwitch(int opcode, int bci, AbstractFrame state, BytecodeSwitch bcSwitch) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + switchJump(opcode, bci, state, bcSwitch, value); + } + + private void handleStaticFieldLoad(int opcode, int bci, AbstractFrame state, JavaField field) { + JavaKind fieldKind = field.getJavaKind(); + AbstractFrame.SizedValue.Slots size = getSizeForKind(fieldKind); + state.getOperandStack().push(wrap(loadStaticField(opcode, bci, state, field), size)); + } + + private void handleStaticFieldStore(int opcode, int bci, AbstractFrame state, JavaField field) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + storeStaticField(opcode, bci, state, field, value); + } + + private void handleFieldLoad(int opcode, int bci, AbstractFrame state, JavaField field) throws DataFlowAnalysisException { + JavaKind fieldKind = field.getJavaKind(); + AbstractFrame.SizedValue.Slots size = getSizeForKind(fieldKind); + T object = state.getOperandStack().pop().value(); + state.getOperandStack().push(wrap(loadField(opcode, bci, state, field, object), size)); + } + + private void handleFieldStore(int opcode, int bci, AbstractFrame state, JavaField field) throws DataFlowAnalysisException { + T value = state.getOperandStack().pop().value(); + T object = state.getOperandStack().pop().value(); + storeField(opcode, bci, state, field, object, value); + } + + private void handleInvoke(int opcode, int bci, AbstractFrame state, JavaMethod method, JavaConstant appendix) throws DataFlowAnalysisException { + if (appendix != null) { + state.getOperandStack().push(wrap(pushAppendix(method, appendix), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + Signature signature = method.getSignature(); + + /* + * HotSpot can rewrite some (method handle related) invocations, which can + * potentially lead to an INVOKEVIRTUAL instruction actually invoking a static method. + * This means that we cannot rely on the opcode to determine if the call has a receiver. + */ + boolean hasReceiver; + if (opcode == INVOKEVIRTUAL && method instanceof ResolvedJavaMethod resolved) { + hasReceiver = resolved.hasReceiver(); + } else { + hasReceiver = opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC; + } + + int operandCount = signature.getParameterCount(hasReceiver); + List operands = new ArrayList<>(operandCount); + for (int i = 0; i < operandCount; i++) { + operands.add(state.getOperandStack().pop().value()); + } + operands = operands.reversed(); + + JavaKind returnKind = signature.getReturnKind(); + if (returnKind.equals(JavaKind.Void)) { + invokeVoidMethod(opcode, bci, state, method, operands); + } else { + AbstractFrame.SizedValue.Slots size = getSizeForKind(returnKind); + state.getOperandStack().push(wrap(invokeMethod(opcode, bci, state, method, operands), size)); + } + } + + private void handleNew(int opcode, int bci, AbstractFrame state, JavaType type) { + state.getOperandStack().push(wrap(newObject(opcode, bci, state, type), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + private void handleNewArray(int opcode, int bci, AbstractFrame state, JavaType type, int dimensions) throws DataFlowAnalysisException { + List counts = new ArrayList<>(dimensions); + for (int i = 0; i < dimensions; i++) { + counts.add(state.getOperandStack().pop().value()); + } + counts = counts.reversed(); + state.getOperandStack().push(wrap(newArray(opcode, bci, state, type, counts), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + private JavaType getJavaTypeFromPrimitiveArrayCode(int typeCode) { + Class clazz = switch (typeCode) { + case 4 -> boolean.class; + case 5 -> char.class; + case 6 -> float.class; + case 7 -> double.class; + case 8 -> byte.class; + case 9 -> short.class; + case 10 -> int.class; + case 11 -> long.class; + default -> throw GraalError.shouldNotReachHere("Unexpected primitive type code: " + typeCode); + }; + return providers.getMetaAccess().lookupJavaType(clazz); + } + + private void handleArrayLength(int opcode, int bci, AbstractFrame state) throws DataFlowAnalysisException { + T array = state.getOperandStack().pop().value(); + state.getOperandStack().push(wrap(arrayLength(opcode, bci, state, array), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + private void handleCastCheck(int opcode, int bci, AbstractFrame state, JavaType type) throws DataFlowAnalysisException { + T object = state.getOperandStack().pop().value(); + state.getOperandStack().push(wrap(castCheckOperation(opcode, bci, state, type, object), AbstractFrame.SizedValue.Slots.ONE_SLOT)); + } + + private static AbstractFrame.SizedValue.Slots getSizeForKind(JavaKind kind) { + return kind.needsTwoSlots() ? AbstractFrame.SizedValue.Slots.TWO_SLOTS : AbstractFrame.SizedValue.Slots.ONE_SLOT; + } + + /** + * @return The default abstract value. This value usually represents an over saturated value from which no useful information can be inferred. + */ + protected abstract T top(); + + /** + * Merge two matching operand stack or local variable table values from divergent control flow paths. + * + * @return The merged value. + */ + protected abstract T merge(T left, T right); + + /** + * Put a variable corresponding to the analyzed method's arguments in the local variable table. This happens when + * constructing the initial abstract execution frame at the method's entry point. + * + * @param method The method being analyzed. + * @param argumentIndex The index of the argument being stored. If the method is non-static, the argument index for the receiver is set to -1. + * @param variableIndex The index of the local variable table entry the value is being stored to. + * @return The value to store in the local variable table. + */ + protected T storeMethodArgument(ResolvedJavaMethod method, int argumentIndex, int variableIndex) { + return top(); + } + + /** + * Push an exception object on the operand stack upon entering an exception handler. + * + * @param exceptionTypes The possible types of the exception object. + * @return The value representing the exception object pushed on the operand stack. + */ + protected T pushExceptionObject(List exceptionTypes) { + return top(); + } + + /** + * Create a constant value to be pushed on the operand stack. This handler is called for the ACONST_NULL, ICONST_M1, ICONST_0, ..., ICONST_5, + * LCONST_0, LCONST_1, FCONST_0, ..., FCONST_2, DCONST_0, DCONST_1, BIPUSH, SIPUSH, LDC, LDC_W and LDC2_W instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param constant The constant being pushed onto the operand stack. + * @return The abstract value to be pushed on the operand stack. + */ + protected T pushConstant(int opcode, int bci, AbstractFrame state, Constant constant) { + return top(); + } + + /** + * Create a constant type reference to be pushed on the operand stack. This handler is called for the LDC, LDC_W and LDC2_W instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param type The type being pushed onto the operand stack. + * @return The abstract value to be pushed on the operand stack. + */ + protected T pushType(int opcode, int bci, AbstractFrame state, JavaType type) { + return top(); + } + + /** + * Load a variable from the local variable table and push its value on the operand stack. This handler is called for the + * various LOAD instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param variableIndex The local variable table index of the variable being accessed. + * @param value The abstract value currently stored in the accessed local variable. + * @return The abstract value to be pushed on the operand stack. + */ + protected T loadVariable(int opcode, int bci, AbstractFrame state, int variableIndex, T value) { + return value; + } + + /** + * Push an element value stored in the target array on the operand stack. This handler is called for the + * IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD and SALOAD instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param array The abstract value representing the accessed array. + * @param index The abstract value representing the array index. + * @return The abstract value representing the loaded element to be pushed on the operand stack. + */ + protected T loadArrayElement(int opcode, int bci, AbstractFrame state, T array, T index) { + return top(); + } + + /** + * Store a value in the local variable table. This handler is called for the various STORE instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param variableIndex The local variable table index of the variable being accessed. + * @param value The abstract value from the operand stack. + * @return The abstract value to be stored in the local variable table. + */ + protected T storeVariable(int opcode, int bci, AbstractFrame state, int variableIndex, T value) { + return value; + } + + /** + * Store a value in an array. This handler is called for the IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, + * CASTORE and SASTORE instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param array The abstract value representing the accessed array. + * @param index The abstract value representing the array index. + * @param value The abstract value to be stored in the array. + */ + protected void storeArrayElement(int opcode, int bci, AbstractFrame state, T array, T index, T value) { + + } + + /** + * Handler for various binary operations. It is invoked for the ADD, SUB, MUL, DIV, REM, SHL, SHR, USHR, AND, OR and XOR instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param left The abstract value representing the left operand of the instruction. + * @param right The abstract value representing the right operand of the instruction. + * @return The abstract value representing the result of the binary operation. + */ + protected T binaryOperation(int opcode, int bci, AbstractFrame state, T left, T right) { + return top(); + } + + /** + * Handler for unary negation operations. It is invoked for the INEG, LNEG, FNEG and DNEG instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param value The abstract value representing the operand of the instruction. + * @return The abstract value representing the result of the unary operation. + */ + protected T unaryOperation(int opcode, int bci, AbstractFrame state, T value) { + return top(); + } + + /** + * Increment an integral variable in the local variable table. THis handler is called for the IINC instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param variableIndex The local variable table index of the variable being accessed. + * @param incrementBy The integer value to increment the variable by. + * @param value The abstract value currently stored in the accessed local variable. + * @return The abstract value of the accessed local variable after incrementing. + */ + protected T incrementVariable(int opcode, int bci, AbstractFrame state, int variableIndex, int incrementBy, T value) { + return top(); + } + + /** + * Cast a value into another type. This handler is invoked for the I2F, I2D, L2F, L2D, F2I, F2L, F2D, D2I, D2L, + * D2F, L2I, I2L, I2B, I2S and I2C instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param value The abstract value from the operand stack to be cast into another type. + * @return The abstract value after casting. + */ + protected T castOperation(int opcode, int bci, AbstractFrame state, T value) { + return top(); + } + + /** + * Compare two values. This handler is called for the LCMP, FCMPL, FCMPG, DCMPL and DCMPG instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param left The abstract value representing the left operand of the instruction. + * @param right The abstract value representing the right operand of the instruction. + * @return The result of the comparison operation. + */ + protected T comparisonOperation(int opcode, int bci, AbstractFrame state, T left, T right) { + return top(); + } + + /** + * Handler for unary conditional jumps. It is called for the IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL and IFNONNULL instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param ifDestinationBci The BCI of the if-then destination instruction. + * @param elseDestinationBci The BCI of the else destination instruction. + * @param value The abstract value representing the comparison operand. + */ + protected void unaryConditionalJump(int opcode, int bci, AbstractFrame state, int ifDestinationBci, int elseDestinationBci, T value) { + + } + + /** + * Handler for binary conditional jumps. It is called for the IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ and IF_ACMPNE instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param ifDestinationBci The BCI of the if-then destination instruction. + * @param elseDestinationBci The BCI of the else destination instruction. + * @param left The abstract value representing the left comparison operand. + * @param right The abstract value representing the right comparison operand. + */ + protected void binaryConditionalJump(int opcode, int bci, AbstractFrame state, int ifDestinationBci, int elseDestinationBci, T left, T right) { + + } + + /** + * Handler for GOTO and GOTO_W instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param destinationBci The BCI of the destination instruction. + */ + protected void unconditionalJump(int opcode, int bci, AbstractFrame state, int destinationBci) { + + } + + /** + * Handler for the TABLESWITCH and LOOKUPSWITCH instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param bcSwitch The switch instruction. + * @param value The abstract value representing the operand of the switch instruction. + */ + protected void switchJump(int opcode, int bci, AbstractFrame state, BytecodeSwitch bcSwitch, T value) { + + } + + /** + * Handler for the IRETURN, LRETURN, FRETURN, DRETURN and ARETURN instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param value The abstract value being returned. + */ + protected void returnValue(int opcode, int bci, AbstractFrame state, Object value) { + + } + + /** + * Handler for the RETURN instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + */ + protected void returnVoid(int opcode, int bci, AbstractFrame state) { + + } + + /** + * Load value from a static field and push it on the operand stack. This handler is called for the GETSTATIC instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param field The field which is being accessed. + * @return The abstract representation of the accessed field's value. + */ + protected T loadStaticField(int opcode, int bci, AbstractFrame state, JavaField field) { + return top(); + } + + /** + * Store an abstract value into a static field. This handler is called for the PUTSTATIC instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param field The field which is being accessed. + * @param value The abstract representation of the value to be stored in the accessed field. + */ + protected void storeStaticField(int opcode, int bci, AbstractFrame state, JavaField field, T value) { + + } + + /** + * Load value from a non-static field and push it on the operand stack. This handler is called for the GETFIELD instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param field The field which is being accessed. + * @param object The abstract representation of the object the accessed field belongs to. + * @return The abstract representation of the accessed field's value. + */ + protected T loadField(int opcode, int bci, AbstractFrame state, JavaField field, T object) { + return top(); + } + + /** + * Store an abstract value into a non-static field. This handler is called for the PUTFIELD instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param field The field which is being accessed. + * @param object The abstract representation of the object the accessed field belongs to. + * @param value The abstract representation of the value to be stored in the accessed field. + */ + protected void storeField(int opcode, int bci, AbstractFrame state, JavaField field, T object, T value) { + + } + + /** + * Invoke a non-void method. This handler is called for the INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE + * and INVOKEDYNAMIC instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param method The method which is being invoked. + * @param operands The abstract representation of the operands. If the method has a receiver, its abstract value is stored as the first element of the list. + * @return The abstract representation of the result of the invocation. + */ + protected T invokeMethod(int opcode, int bci, AbstractFrame state, JavaMethod method, List operands) { + return top(); + } + + /** + * Invoke a void method. This handler is called for the INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE + * and INVOKEDYNAMIC instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param method The method which is being invoked. + * @param operands The abstract representation of the operands. If the method has a receiver, its abstract value is stored as the first element of the list. + */ + protected void invokeVoidMethod(int opcode, int bci, AbstractFrame state, JavaMethod method, List operands) { + + } + + /** + * Handler for loading a method's appendix. + * This is used in INVOKEDYNAMIC instructions + * + * @param method The invoked method. + * @param appendix The invoked method's appendix. + * @return Abstract representation of the appendix. + */ + protected T pushAppendix(JavaMethod method, JavaConstant appendix) { + return top(); + } + + /** + * Create a new object and push its reference on the operand stack. This handler is called for the NEW instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param type The type of the object whose reference is being pushed to the stack. + * @return The abstract representation of the object. + */ + protected T newObject(int opcode, int bci, AbstractFrame state, JavaType type) { + return top(); + } + + /** + * Create a new array and push its reference on the operand stack. This handler is called for the NEWARRAY, ANEWARRAY and MULTIANEWARRAY instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param type The type of the array elements. + * @param counts List of capacities for each of the array's dimensions. + * @return The abstract representation of the array. + */ + protected T newArray(int opcode, int bci, AbstractFrame state, JavaType type, List counts) { + return top(); + } + + /** + * Handler for the ARRAYLENGTH instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param array The abstract representation of the array. + * @return The abstract representation of the array's length. + */ + protected T arrayLength(int opcode, int bci, AbstractFrame state, T array) { + return top(); + } + + /** + * Handler for the ATHROW instruction. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param throwable The abstract representation of the object being thrown. + */ + protected void doThrow(int opcode, int bci, AbstractFrame state, T throwable) { + + } + + /** + * Handler for the CHECKCAST and INSTANCEOF instructions. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param type The type being checked against. + * @param object The abstract representation of the instruction operand. + * @return The abstract representation of the instruction's result. + */ + protected T castCheckOperation(int opcode, int bci, AbstractFrame state, JavaType type, T object) { + return top(); + } + + /** + * Handler for the MONITORENTER and MONITOREXIT operations. + * + * @param opcode The instruction opcode. + * @param bci The instruction BCI. + * @param state The abstract frame being modified by this instruction. + * @param object The abstract representation of the instruction operand. + */ + protected void monitorOperation(int opcode, int bci, AbstractFrame state, T object) { + + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalysisException.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalysisException.java new file mode 100644 index 000000000000..b256b394f1ba --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalysisException.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.graal.compiler.java.dataflow; + +import java.io.Serial; + +/** + * Exceptions thrown during bytecode data flow analysis. These should only be thrown + * in case the bytecode of the parsed method is invalid, or contains JSR/RET + * bytecodes which are currently not supported by the data flow analyzer. + */ +public class DataFlowAnalysisException extends Exception { + + @Serial + private static final long serialVersionUID = 1L; + + public DataFlowAnalysisException() { + super(); + } + + public DataFlowAnalysisException(String message) { + super(message); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalyzer.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalyzer.java new file mode 100644 index 000000000000..1e017105d7ab --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/java/dataflow/DataFlowAnalyzer.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.graal.compiler.java.dataflow; + +import jdk.graal.compiler.bytecode.Bytecode; +import jdk.graal.compiler.bytecode.BytecodeStream; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.java.BciBlockMapping; +import jdk.graal.compiler.java.BciBlockMapping.BciBlock; +import jdk.vm.ci.meta.ExceptionHandler; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import org.graalvm.collections.Pair; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A small bytecode forward data flow analyzer. + * + * @param The abstract program state type to be propagated during the data flow analysis. + */ +public abstract class DataFlowAnalyzer { + + /** + * Executes the data flow analysis on method bytecode. + * + * @param bytecode The bytecode to analyze. + * @return A mapping from bytecode instruction BCI to the inferred abstract state of the program before the execution of that instruction. + */ + public Map analyze(Bytecode bytecode) throws DataFlowAnalysisException { + BciBlockMapping controlFlowGraph = BciBlockMapping.create(new BytecodeStream(bytecode.getCode()), bytecode, null, DebugContext.disabled(null), false); + + ExceptionHandler[] exceptionHandlers = controlFlowGraph.code.getExceptionHandlers(); + + Map states = new HashMap<>(); + Set workList = new HashSet<>(); + + states.put(controlFlowGraph.getStartBlock().getStartBci(), createInitialState(bytecode.getMethod())); + workList.add(controlFlowGraph.getStartBlock()); + + while (!workList.isEmpty()) { + BciBlock currentBlock = workList.iterator().next(); + workList.remove(currentBlock); + + Pair outState = processBlock(currentBlock, controlFlowGraph.code, states); + /* + * We don't have to process the basic block's successors if we reach + * a fixed point within that block. + */ + if (outState == null) { + continue; + } + + /* Go through all non-exception handler successors. */ + for (BciBlock successor : currentBlock.getSuccessors()) { + if (!successor.isInstructionBlock()) { + continue; + } + + S successorState = states.get(successor.getStartBci()); + if (successorState == null) { + states.put(successor.getStartBci(), copyState(outState.getRight())); + workList.add(successor); + } else { + S mergedState = mergeStates(successorState, outState.getRight()); + if (!mergedState.equals(successorState)) { + states.put(successor.getStartBci(), mergedState); + workList.add(successor); + } + } + } + + BitSet handlers = exceptionHandlers.length > 0 + ? controlFlowGraph.getBciExceptionHandlerIDs(outState.getLeft()) + : new BitSet(); + + /* Go through all the exception handler successors. */ + for (int i = handlers.nextSetBit(0); i >= 0;) { + BciBlock handlerBlock = controlFlowGraph.getHandlerBlock(i); + + /* Gather all the exception types caught by this handler. */ + List exceptionTypes = new ArrayList<>(); + while (i >= 0 && controlFlowGraph.getHandlerBlock(i) == handlerBlock) { + exceptionTypes.add(exceptionHandlers[i].getCatchType()); + i = handlers.nextSetBit(i + 1); + } + S handlerState = createExceptionState(outState.getRight(), exceptionTypes); + + S successorState = states.get(handlerBlock.getStartBci()); + if (successorState == null) { + states.put(handlerBlock.getStartBci(), handlerState); + workList.add(handlerBlock); + } else { + S mergedState = mergeStates(successorState, handlerState); + if (!mergedState.equals(successorState)) { + states.put(handlerBlock.getStartBci(), mergedState); + workList.add(handlerBlock); + } + } + } + } + + return states; + } + + private Pair processBlock(BciBlock block, Bytecode code, Map states) throws DataFlowAnalysisException { + BytecodeStream stream = new BytecodeStream(code.getCode()); + stream.setBCI(block.getStartBci()); + + S outState = processInstruction(states.get(stream.currentBCI()), stream, code); + while (stream.nextBCI() <= block.getEndBci()) { + S successorState = states.get(stream.nextBCI()); + if (!outState.equals(successorState)) { + states.put(stream.nextBCI(), outState); + } else { + return null; + } + stream.next(); + outState = processInstruction(states.get(stream.currentBCI()), stream, code); + } + + /* + * There seems to sometimes be a mismatch between the actual end BCI of a BciBlock + * and the BCI obtained from BciBlock::getEndBci. Because of that, we return the BCI + * of the last processed instruction in the block together with the state. + */ + return Pair.create(stream.currentBCI(), outState); + } + + /** + * Create the initial state for the analysis. + * + * @param method The JVMCI method being analyzed. + * @return The abstract program state at the entry point of the method. + */ + protected abstract S createInitialState(ResolvedJavaMethod method); + + /** + * Create the state at the entry of an exception handler. + * + * @param inState The abstract program state before the entry into the exception handler. + * @param exceptionTypes The exception types of the handler which is being entered. + * @return The abstract program state at the entry point of the exception handler. + */ + protected abstract S createExceptionState(S inState, List exceptionTypes); + + /** + * Create a deep copy of a state. + * + * @param state State to be copied. + * @return Deep copy of the input state. + */ + protected abstract S copyState(S state); + + /** + * Merge two states from divergent control flow paths. + * + * @return The merged state of the left and right input states. + */ + protected abstract S mergeStates(S left, S right) throws DataFlowAnalysisException; + + /** + * The data flow transfer function. + * + * @param inState The input state. + * @param stream A bytecode stream of which the position is set to the instruction currently being processed. The stream should not be modified from this method. + * @param code The bytecode of the method currently being analyzed. + * @return The abstract program state after the instruction being processed. + */ + protected abstract S processInstruction(S inState, BytecodeStream stream, Bytecode code) throws DataFlowAnalysisException; +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderConfiguration.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderConfiguration.java index 0b22dd6ca44c..d1dd32a402aa 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderConfiguration.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderConfiguration.java @@ -42,6 +42,7 @@ public static class Plugins { private TypePlugin[] typePlugins; private InlineInvokePlugin[] inlineInvokePlugins; private ClassInitializationPlugin classInitializationPlugin; + private MethodParsingPlugin[] methodPlugins; /** * Creates a copy of a given set of plugins. The {@link InvocationPlugins} in @@ -55,6 +56,7 @@ public Plugins(Plugins copyFrom, InvocationPlugins invocationPlugins) { this.typePlugins = copyFrom.typePlugins; this.inlineInvokePlugins = copyFrom.inlineInvokePlugins; this.classInitializationPlugin = copyFrom.classInitializationPlugin; + this.methodPlugins = copyFrom.methodPlugins; } public Plugins(Plugins copyFrom) { @@ -73,6 +75,7 @@ public Plugins(InvocationPlugins invocationPlugins) { this.parameterPlugins = new ParameterPlugin[0]; this.typePlugins = new TypePlugin[0]; this.inlineInvokePlugins = new InlineInvokePlugin[0]; + this.methodPlugins = new MethodParsingPlugin[0]; } public InvocationPlugins getInvocationPlugins() { @@ -157,6 +160,22 @@ public StampPair getOverridingStamp(GraphBuilderTool b, JavaType type, boolean n } return null; } + + public void appendMethodParsingPlugin(MethodParsingPlugin plugin) { + methodPlugins = Arrays.copyOf(methodPlugins, methodPlugins.length + 1); + methodPlugins[methodPlugins.length - 1] = plugin; + } + + public void prependMethodParsingPlugin(MethodParsingPlugin plugin) { + MethodParsingPlugin[] newPlugins = new MethodParsingPlugin[methodPlugins.length + 1]; + System.arraycopy(methodPlugins, 0, newPlugins, 1, methodPlugins.length); + newPlugins[0] = plugin; + methodPlugins = newPlugins; + } + + public MethodParsingPlugin[] getMethodParsingPlugins() { + return methodPlugins; + } } private final boolean eagerResolving; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java index fc96098c427d..8d3d2646326c 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/GraphBuilderContext.java @@ -82,6 +82,8 @@ import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; +import java.util.ArrayList; + /** * Used by a {@link GraphBuilderPlugin} to interface with an object that parses the bytecode of a * single {@linkplain #getMethod() method} as part of building a {@linkplain #getGraph() graph} . @@ -281,6 +283,18 @@ default int getDepth() { return result; } + /** + * Gets the inlined call stack for this context. A list with only one element implies that no + * inlining has taken place. + */ + default List getCallStack() { + List callStack = new ArrayList<>(); + for (GraphBuilderContext cur = this; cur != null; cur = cur.getParent()) { + callStack.add(cur.getMethod().asStackTraceElement(cur.bci())); + } + return callStack; + } + /** * Computes the recursive inlining depth of the provided method, i.e., counts how often the * provided method is already in the {@link #getParent()} chain starting at this context. diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/MethodParsingPlugin.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/MethodParsingPlugin.java new file mode 100644 index 000000000000..84bab2bee19e --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/graphbuilderconf/MethodParsingPlugin.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.graal.compiler.nodes.graphbuilderconf; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * Plugin for accessing the method currently being parsed. These plugins are executed + * before invocation and other types of bytecode parser plugins and can be used for + * collecting certain information from the parsed method that the other plugins may + * rely on. + */ +public interface MethodParsingPlugin extends GraphBuilderPlugin { + + void execute(ResolvedJavaMethod method, IntrinsicContext intrinsicContext); +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java index 27a18d59d57d..da753bae3f46 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/IntrinsicGraphBuilder.java @@ -76,6 +76,9 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; +import java.util.ArrayList; +import java.util.List; + /** * Implementation of {@link GraphBuilderContext} used to produce a graph for a method based on an * {@link InvocationPlugin} for the method. @@ -325,6 +328,11 @@ public int getDepth() { return 0; } + @Override + public List getCallStack() { + return new ArrayList<>(); + } + @Override public boolean parsingIntrinsic() { return false; diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java index ba0af5322903..bae8b64de96e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/PEGraphDecoder.java @@ -412,6 +412,19 @@ public int getDepth() { return methodScope.inliningDepth; } + @Override + public List getCallStack() { + List callStack = new ArrayList<>(Arrays.asList(methodScope.getCallStack())); + /* + * If we're processing an invocation plugin, we want the top stack element to be the + * callee of the method targeted by the plugin, and not the target itself. + */ + if (isParsingInvocationPlugin()) { + callStack.removeFirst(); + } + return callStack; + } + @Override public int recursiveInliningDepth(ResolvedJavaMethod method) { int result = 0; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 9ab1223742bb..561ba5cff290 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -1484,4 +1484,17 @@ public static boolean hasDumpRuntimeCompiledMethodsSupport() { return !Platform.includedIn(Platform.WINDOWS.class) && ConcealedOptions.DumpRuntimeCompiledMethods.getValue(); } + public enum StrictReflectionMode { + Disable, + Warn, + Enforce + } + + @Option(help = """ + Select the mode for the strict, build-time analysis of reflective calls. + Possible values are:", + "Disable" (default): Disable the strict reflection mode and fall back to the optimization dependent analysis for inferrable reflective calls; + "Warn": Fold both the reflective calls inferred with the strict mode analysis and the optimization dependant analysis, but print a warning for non-strict call folding; + "Enforce": Fold only the reflective calls inferred by the strict analysis mode.""")// + public static final HostedOptionKey StrictReflection = new HostedOptionKey<>(StrictReflectionMode.Disable); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionBytecodeAnalyzer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionBytecodeAnalyzer.java new file mode 100644 index 000000000000..52e51a67b494 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionBytecodeAnalyzer.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2025, 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 com.oracle.svm.hosted.reflect; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider; +import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.util.TypeResult; +import jdk.graal.compiler.bytecode.Bytecode; +import jdk.graal.compiler.java.dataflow.AbstractFrame; +import jdk.graal.compiler.java.dataflow.AbstractInterpreter; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaField; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static jdk.graal.compiler.bytecode.Bytecodes.ACONST_NULL; +import static jdk.graal.compiler.bytecode.Bytecodes.ANEWARRAY; +import static jdk.graal.compiler.bytecode.Bytecodes.CHECKCAST; + +public class ReflectionBytecodeAnalyzer extends AbstractInterpreter { + + private static final NotACompileTimeConstant NOT_A_COMPILE_TIME_CONSTANT = new NotACompileTimeConstant(); + + private final ImageClassLoader classLoader; + + public ReflectionBytecodeAnalyzer(CoreProviders providers, ImageClassLoader classLoader) { + super(providers); + this.classLoader = classLoader; + } + + @Override + protected ReflectionAnalysisValue top() { + return NOT_A_COMPILE_TIME_CONSTANT; + } + + @Override + protected ReflectionAnalysisValue merge(ReflectionAnalysisValue left, ReflectionAnalysisValue right) { + return left.equals(right) ? left : top(); + } + + @Override + protected ReflectionAnalysisValue pushConstant(int opcode, int bci, AbstractFrame state, Constant constant) { + if (opcode == ACONST_NULL) { + return new CompileTimeValueConstant<>(bci, null); + } + if (constant instanceof JavaConstant javaConstant) { + Object javaValue = switch (javaConstant.getJavaKind().getStackKind()) { + case Int -> javaConstant.asInt(); + case Long -> javaConstant.asLong(); + case Float -> javaConstant.asFloat(); + case Double -> javaConstant.asDouble(); + case Object -> getProviders().getSnippetReflection().asObject(Object.class, javaConstant); + default -> null; + }; + if (javaValue != null) { + return new CompileTimeValueConstant<>(bci, javaValue); + } + } + return top(); + } + + @Override + protected ReflectionAnalysisValue pushType(int opcode, int bci, AbstractFrame state, JavaType type) { + if (type instanceof ResolvedJavaType resolvedType) { + return new CompileTimeValueConstant<>(bci, OriginalClassProvider.getJavaClass(resolvedType)); + } else { + return top(); + } + } + + @Override + protected ReflectionAnalysisValue loadVariable(int opcode, int bci, AbstractFrame state, int variableIndex, ReflectionAnalysisValue value) { + if (value instanceof CompileTimeValueConstant constant) { + return new CompileTimeValueConstant<>(bci, constant.getValue()); + } else { + return top(); + } + } + + @Override + protected ReflectionAnalysisValue storeVariable(int opcode, int bci, AbstractFrame state, int variableIndex, ReflectionAnalysisValue value) { + if (value instanceof CompileTimeValueConstant constant) { + return new CompileTimeValueConstant<>(bci, constant.getValue()); + } else if (value instanceof CompileTimeArrayConstant constantArray) { + state.transform(v -> v.equals(constantArray), v -> top()); + return top(); + } else { + return top(); + } + } + + @Override + protected void storeArrayElement(int opcode, int bci, AbstractFrame state, ReflectionAnalysisValue array, ReflectionAnalysisValue index, ReflectionAnalysisValue value) { + if (array instanceof CompileTimeArrayConstant constantArray) { + if (index instanceof CompileTimeValueConstant constantIndex && value instanceof CompileTimeValueConstant constantValue) { + CompileTimeArrayConstant newConstantArray = new CompileTimeArrayConstant<>(bci, constantArray); + try { + int realIndex = ((Number) constantIndex.getValue()).intValue(); + newConstantArray.setElement(realIndex, constantValue.getValue()); + state.transform(v -> v.equals(constantArray), v -> newConstantArray); + } catch (Exception e) { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } else { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } + } + + @Override + protected ReflectionAnalysisValue loadStaticField(int opcode, int bci, AbstractFrame state, JavaField field) { + if (field.getName().equals("TYPE")) { + Class primitiveClass = switch (field.getDeclaringClass().toJavaName()) { + case "java.lang.Boolean" -> boolean.class; + case "java.lang.Character" -> char.class; + case "java.lang.Float" -> float.class; + case "java.lang.Double" -> double.class; + case "java.lang.Byte" -> byte.class; + case "java.lang.Short" -> short.class; + case "java.lang.Integer" -> int.class; + case "java.lang.Long" -> long.class; + case "java.lang.Void" -> void.class; + default -> null; + }; + if (primitiveClass != null) { + return new CompileTimeValueConstant<>(bci, primitiveClass); + } + } + return top(); + } + + @Override + protected void storeStaticField(int opcode, int bci, AbstractFrame state, JavaField field, ReflectionAnalysisValue value) { + if (value instanceof CompileTimeArrayConstant constantArray) { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } + + @Override + protected void storeField(int opcode, int bci, AbstractFrame state, JavaField field, ReflectionAnalysisValue object, ReflectionAnalysisValue value) { + if (value instanceof CompileTimeArrayConstant constantArray) { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } + + @Override + protected ReflectionAnalysisValue invokeMethod(int opcode, int bci, AbstractFrame state, JavaMethod method, List operands) { + for (ReflectionAnalysisValue operand : operands) { + if (operand instanceof CompileTimeArrayConstant constantArray) { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } + if (methodsMatch(method, getMethod(Class.class, "forName", String.class))) { + if (operands.getFirst() instanceof CompileTimeValueConstant c && c.getValue() instanceof String className) { + return findClass(bci, className); + } + } else if (methodsMatch(method, getMethod(Class.class, "forName", String.class, boolean.class, ClassLoader.class))) { + if (operands.getFirst() instanceof CompileTimeValueConstant c && c.getValue() instanceof String className && operands.get(1) instanceof CompileTimeValueConstant) { + return findClass(bci, className); + } + } + return top(); + } + + private static boolean methodsMatch(JavaMethod methodOne, JavaMethod methodTwo) { + if (methodOne == null || methodTwo == null) { + return false; + } + boolean ownerMatches = methodOne.getDeclaringClass().getName().equals(methodTwo.getDeclaringClass().getName()); + boolean nameMatches = methodOne.getName().equals(methodTwo.getName()); + boolean signatureMatches = methodOne.getSignature().toMethodDescriptor().equals(methodTwo.getSignature().toMethodDescriptor()); + return ownerMatches && nameMatches && signatureMatches; + } + + private ReflectionAnalysisValue findClass(int bci, String className) { + TypeResult> clazz = classLoader.findClass(className, false); + if (clazz.isPresent()) { + return new CompileTimeValueConstant<>(bci, clazz.get()); + } else { + return top(); + } + } + + @Override + protected void invokeVoidMethod(int opcode, int bci, AbstractFrame state, JavaMethod method, List operands) { + for (ReflectionAnalysisValue operand : operands) { + if (operand instanceof CompileTimeArrayConstant constantArray) { + state.transform(v -> v.equals(constantArray), v -> top()); + } + } + } + + @Override + protected ReflectionAnalysisValue newArray(int opcode, int bci, AbstractFrame state, JavaType type, List counts) { + if (opcode == ANEWARRAY && counts.getFirst() instanceof CompileTimeValueConstant size && type instanceof ResolvedJavaType) { + int realSize = ((Number) size.getValue()).intValue(); + return new CompileTimeArrayConstant<>(bci, realSize, OriginalClassProvider.getJavaClass(type)); + } else { + return top(); + } + } + + @Override + protected ReflectionAnalysisValue castCheckOperation(int opcode, int bci, AbstractFrame state, JavaType type, ReflectionAnalysisValue object) { + if (opcode == CHECKCAST && object instanceof CompileTimeConstant constant && constant.getValue() == null) { + return new CompileTimeValueConstant<>(bci, null); + } else { + return top(); + } + } + + public ResolvedJavaMethod getMethod(Class owner, String name, Class... parameterTypes) { + try { + Method method = owner.getMethod(name, parameterTypes); + return getProviders().getMetaAccess().lookupJavaMethod(method); + } catch (NoSuchMethodException e) { + return null; + } + } + + /* + * Looking up types/fields/methods through a WrappedConstantPool can result in + * UnsupportedFeatureException(s) being thrown. To avoid these exceptions being thrown, we + * essentially simulate the behavior of the WrappedConstantPool when looking up the underlying + * JVMCI type/field/method, but skip the analysis (or hosted) universe lookup. + */ + + @Override + protected JavaType lookupType(Bytecode code, int cpi, int opcode) { + if (code.getMethod() instanceof WrappedJavaMethod wrapper) { + ConstantPool constantPool = wrapper.getWrapped().getConstantPool(); + tryToResolve(constantPool, cpi, opcode); + return constantPool.lookupType(cpi, opcode); + } else { + return super.lookupType(code, cpi, opcode); + } + } + + @Override + protected JavaField lookupField(Bytecode code, int cpi, int opcode) { + if (code.getMethod() instanceof WrappedJavaMethod wrapper) { + ConstantPool constantPool = wrapper.getWrapped().getConstantPool(); + tryToResolve(constantPool, cpi, opcode); + return constantPool.lookupField(cpi, OriginalMethodProvider.getOriginalMethod(code.getMethod()), opcode); + } else { + return super.lookupField(code, cpi, opcode); + } + } + + @Override + protected JavaMethod lookupMethod(Bytecode code, int cpi, int opcode) { + if (code.getMethod() instanceof WrappedJavaMethod wrapper) { + ConstantPool constantPool = wrapper.getWrapped().getConstantPool(); + tryToResolve(constantPool, cpi, opcode); + return constantPool.lookupMethod(cpi, opcode, OriginalMethodProvider.getOriginalMethod(code.getMethod())); + } else { + return super.lookupMethod(code, cpi, opcode); + } + } + + /** + * Marker interface for abstract values obtained during bytecode-level constant reflection + * analysis. + */ + public interface ReflectionAnalysisValue { + + } + + public static class NotACompileTimeConstant implements ReflectionAnalysisValue { + + @Override + public String toString() { + return "Not a compile time constant"; + } + } + + public abstract static class CompileTimeConstant implements ReflectionAnalysisValue { + + private final int sourceBci; + + public CompileTimeConstant(int bci) { + this.sourceBci = bci; + } + + public int getSourceBci() { + return sourceBci; + } + + public abstract Object getValue(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CompileTimeConstant that = (CompileTimeConstant) o; + /* + * The source BCI (BCI of the instruction that placed the value onto the operand stack + * or in the local variable table) is the source of truth when comparing two compile + * time constant values (an equal source BCI implies an equal value). + */ + return sourceBci == that.sourceBci; + } + + @Override + public int hashCode() { + return Objects.hashCode(sourceBci); + } + } + + public static class CompileTimeValueConstant extends CompileTimeConstant { + + private final T value; + + public CompileTimeValueConstant(int bci, T value) { + super(bci); + this.value = value; + } + + @Override + public T getValue() { + return value; + } + + @Override + public String toString() { + return "(" + getSourceBci() + ", " + getValue() + ")"; + } + } + + public static class CompileTimeArrayConstant extends CompileTimeConstant { + + /* + * Sparse array representation to avoid possible large memory overheads when analyzing an + * array initialization with a large size. + */ + private final Map value; + private final int size; + private final Class elementType; + + public CompileTimeArrayConstant(int bci, int size, Class elementType) { + super(bci); + this.value = new HashMap<>(); + this.size = size; + this.elementType = elementType; + } + + public CompileTimeArrayConstant(int bci, CompileTimeArrayConstant arrayConstant) { + super(bci); + this.value = new HashMap<>(arrayConstant.value); + this.size = arrayConstant.size; + this.elementType = arrayConstant.elementType; + } + + public void setElement(int index, Object element) throws ArrayIndexOutOfBoundsException, ClassCastException { + if (index < 0 || index >= size) { + throw new ArrayIndexOutOfBoundsException(index); + } + if (!elementType.isAssignableFrom(element.getClass())) { + throw new ClassCastException(element.toString()); + } + @SuppressWarnings("unchecked") + T typedElement = (T) element; + value.put(index, typedElement); + } + + @Override + public T[] getValue() { + @SuppressWarnings("unchecked") + T[] arrayValue = (T[]) Array.newInstance(elementType, size); + for (Map.Entry entry : value.entrySet()) { + arrayValue[entry.getKey()] = entry.getValue(); + } + return arrayValue; + } + + @Override + public String toString() { + if (size >= 32) { + return "(" + getSourceBci() + ", Array[" + size + "])"; + } else { + return "(" + getSourceBci() + ", " + Arrays.toString(getValue()) + ")"; + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 55f5b5086272..81829ff98e8a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -410,9 +410,15 @@ public String getDeletionReason(Field reflectionField) { @Override public void registerInvocationPlugins(Providers providers, Plugins plugins, ParsingReason reason) { + StrictReflectionRegistry reflectionRegistry = null; + SubstrateOptions.StrictReflectionMode strictReflectionMode = SubstrateOptions.StrictReflection.getValue(); + if (strictReflectionMode == SubstrateOptions.StrictReflectionMode.Warn || strictReflectionMode == SubstrateOptions.StrictReflectionMode.Enforce) { + reflectionRegistry = new StrictReflectionRegistry(providers, loader); + plugins.appendMethodParsingPlugin(reflectionRegistry::analyzeMethod); + } FallbackFeature fallbackFeature = ImageSingletons.contains(FallbackFeature.class) ? ImageSingletons.lookup(FallbackFeature.class) : null; ReflectionPlugins.registerInvocationPlugins(loader, annotationSubstitutions, - plugins.getClassInitializationPlugin(), plugins.getInvocationPlugins(), aUniverse, reason, fallbackFeature); + plugins.getClassInitializationPlugin(), plugins.getInvocationPlugins(), aUniverse, reason, fallbackFeature, reflectionRegistry); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/StrictReflectionRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/StrictReflectionRegistry.java new file mode 100644 index 000000000000..4532c3757b34 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/StrictReflectionRegistry.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 com.oracle.svm.hosted.reflect; + +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.reflect.ReflectionBytecodeAnalyzer.ReflectionAnalysisValue; +import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.bytecode.Bytecode; +import jdk.graal.compiler.bytecode.BytecodeProvider; +import jdk.graal.compiler.bytecode.ResolvedJavaMethodBytecodeProvider; +import jdk.graal.compiler.java.dataflow.AbstractFrame; +import jdk.graal.compiler.java.dataflow.DataFlowAnalysisException; +import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext; +import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaMethod; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.Signature; +import org.graalvm.collections.Pair; + +import java.util.IllegalFormatException; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class StrictReflectionRegistry { + + private static final Object NULL_MARKER = new Object(); + + private final Map, AbstractFrame> registry; + private final ReflectionBytecodeAnalyzer analyzer; + private final Set reflectionTargets; + + public StrictReflectionRegistry(Providers providers, ImageClassLoader loader) { + this.registry = new ConcurrentHashMap<>(); + this.analyzer = new ReflectionBytecodeAnalyzer(providers, loader); + this.reflectionTargets = Set.of( + HashableJavaMethod.make(analyzer.getMethod(Class.class, "forName", String.class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "forName", String.class, boolean.class, ClassLoader.class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getField", String.class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredField", String.class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getConstructor", Class[].class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredConstructor", Class[].class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getMethod", String.class, Class[].class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredMethod", String.class, Class[].class)), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getFields")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredFields")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getConstructors")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredConstructors")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getMethods")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredMethods")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getClasses")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getDeclaredClasses")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getNestMembers")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getPermittedSubclasses")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getRecordComponents")), + HashableJavaMethod.make(analyzer.getMethod(Class.class, "getSigners"))); + } + + public void analyzeMethod(ResolvedJavaMethod method, IntrinsicContext intrinsicContext) { + Bytecode bytecode = getBytecode(method, intrinsicContext); + try { + Map> abstractFrames = analyzer.analyze(bytecode); + abstractFrames.forEach((key, value) -> registry.put(Pair.create(method, key), value)); + } catch (DataFlowAnalysisException e) { + LogUtils.warning("Constant reflection analysis failed for " + method.format("%H.%n(%p)") + ": " + e.getMessage()); + } + } + + private static Bytecode getBytecode(ResolvedJavaMethod method, IntrinsicContext intrinsicContext) { + BytecodeProvider bytecodeProvider = intrinsicContext == null + ? ResolvedJavaMethodBytecodeProvider.INSTANCE + : intrinsicContext.getBytecodeProvider(); + return bytecodeProvider.getBytecode(method); + } + + public Optional getConstantOperand(ResolvedJavaMethod callerMethod, int bci, ResolvedJavaMethod targetMethod, int index) { + AbstractFrame frame = registry.get(Pair.create(callerMethod, bci)); + if (frame == null) { + return Optional.empty(); + } + int numOfOperands = targetMethod.getSignature().getParameterCount(targetMethod.hasReceiver()); + ReflectionAnalysisValue operand = frame.getOperand(numOfOperands - index - 1); + if (operand instanceof ReflectionBytecodeAnalyzer.CompileTimeConstant constant) { + Object value = constant.getValue(); + if (value == null) { + value = NULL_MARKER; + } + /* + * Since the analyzer doesn't differentiate between boolean, byte, short, char and int + * types, we have to check what the expected type is based on the signature of the + * target method and cast the value appropriately. + */ + if (!targetMethod.hasReceiver() || index != 0) { + int parameterIndex = targetMethod.hasReceiver() ? index - 1 : index; + JavaKind parameterKind = targetMethod.getSignature().getParameterKind(parameterIndex); + if (value instanceof Integer n) { + value = switch (parameterKind) { + case JavaKind.Boolean -> n != 0; + case JavaKind.Byte -> n.byteValue(); + case JavaKind.Short -> n.shortValue(); + case JavaKind.Char -> (char) ('0' + n); + default -> value; + }; + } + } + return Optional.of(value); + } else { + return Optional.empty(); + } + } + + public static boolean isNull(Object object) { + return object == NULL_MARKER; + } + + public boolean isStrictReflectionTarget(JavaMethod method) { + return reflectionTargets.contains(HashableJavaMethod.make(method)); + } + + private record HashableJavaMethod(JavaMethod method) implements JavaMethod { + + public static HashableJavaMethod make(JavaMethod method) { + return new HashableJavaMethod(method); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public JavaType getDeclaringClass() { + return method.getDeclaringClass(); + } + + @Override + public Signature getSignature() { + return method.getSignature(); + } + + @Override + public String format(String format) throws IllegalFormatException { + return method.format(format); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HashableJavaMethod that = (HashableJavaMethod) o; + return Objects.equals(getDeclaringClass().getName(), that.getDeclaringClass().getName()) && + Objects.equals(getName(), that.getName()) && + Objects.equals(getSignature().toMethodDescriptor(), that.getSignature().toMethodDescriptor()); + } + + @Override + public int hashCode() { + return Objects.hash(getDeclaringClass().getName(), getName(), getSignature().toMethodDescriptor()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java index cfc0ee9a8af0..d31f9cfa14b2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java @@ -24,6 +24,11 @@ */ package com.oracle.svm.hosted.snippets; +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -36,18 +41,31 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteOrder; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.reflect.StrictReflectionRegistry; +import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.util.json.JsonBuilder; +import jdk.graal.compiler.util.json.JsonPrettyWriter; +import jdk.graal.compiler.util.json.JsonWriter; +import jdk.vm.ci.meta.JavaMethod; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -119,23 +137,27 @@ static class Options { private final AnalysisUniverse aUniverse; private final ParsingReason reason; private final FallbackFeature fallbackFeature; + private final StrictReflectionRegistry reflectionRegistry; private final ClassInitializationSupport classInitializationSupport; private ReflectionPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitutionProcessor annotationSubstitutions, - ClassInitializationPlugin classInitializationPlugin, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature) { + ClassInitializationPlugin classInitializationPlugin, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature, + StrictReflectionRegistry reflectionRegistry) { this.imageClassLoader = imageClassLoader; this.annotationSubstitutions = annotationSubstitutions; this.classInitializationPlugin = classInitializationPlugin; this.aUniverse = aUniverse; this.reason = reason; this.fallbackFeature = fallbackFeature; + this.reflectionRegistry = reflectionRegistry; this.classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); } public static void registerInvocationPlugins(ImageClassLoader imageClassLoader, AnnotationSubstitutionProcessor annotationSubstitutions, - ClassInitializationPlugin classInitializationPlugin, InvocationPlugins plugins, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature) { - ReflectionPlugins rp = new ReflectionPlugins(imageClassLoader, annotationSubstitutions, classInitializationPlugin, aUniverse, reason, fallbackFeature); + ClassInitializationPlugin classInitializationPlugin, InvocationPlugins plugins, AnalysisUniverse aUniverse, ParsingReason reason, FallbackFeature fallbackFeature, + StrictReflectionRegistry reflectionRegistry) { + ReflectionPlugins rp = new ReflectionPlugins(imageClassLoader, annotationSubstitutions, classInitializationPlugin, aUniverse, reason, fallbackFeature, reflectionRegistry); rp.registerMethodHandlesPlugins(plugins); rp.registerClassPlugins(plugins); } @@ -315,7 +337,16 @@ private void registerClassPlugins(InvocationPlugins plugins) { r.register(new RequiredInvocationPlugin("forName", String.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode nameNode) { - return processClassForName(b, targetMethod, nameNode, ConstantNode.forBoolean(true)); + if (strictReflectionEnabled(targetMethod)) { + if (processClassForName(b, targetMethod, getArgumentFromRegistry(b, targetMethod, 0), true, false)) { + return true; + } else if (shouldWarnForNonStrictFolding()) { + return processClassForName(b, targetMethod, unbox(b, nameNode, JavaKind.Object), true, true); + } else { + return false; + } + } + return processClassForName(b, targetMethod, unbox(b, nameNode, JavaKind.Object), true, false); } }); r.register(new RequiredInvocationPlugin("forName", String.class, boolean.class, ClassLoader.class) { @@ -327,7 +358,16 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec * time. We assume that every class loader used at run time delegates to the * application class loader. */ - return processClassForName(b, targetMethod, nameNode, initializeNode); + if (strictReflectionEnabled(targetMethod)) { + if (processClassForName(b, targetMethod, getArgumentFromRegistry(b, targetMethod, 0), getArgumentFromRegistry(b, targetMethod, 1), false)) { + return true; + } else if (shouldWarnForNonStrictFolding()) { + return processClassForName(b, targetMethod, unbox(b, nameNode, JavaKind.Object), unbox(b, initializeNode, JavaKind.Boolean), true); + } else { + return false; + } + } + return processClassForName(b, targetMethod, unbox(b, nameNode, JavaKind.Object), unbox(b, initializeNode, JavaKind.Boolean), false); } }); r.register(new RequiredInvocationPlugin("getClassLoader", Receiver.class) { @@ -348,8 +388,6 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec * the constructor parameter. */ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMethod targetMethod) { - Supplier targetParameters = () -> ""; - if (StackTraceUtils.ignoredBySecurityStackWalk(b.getMetaAccess(), b.getMethod())) { /* * If our immediate caller (which is the only method available at the time the @@ -364,9 +402,9 @@ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMe /* The constructor of Lookup is not public, so we need to invoke it via reflection. */ lookup = LOOKUP_CONSTRUCTOR.newInstance(callerClass); } catch (Throwable ex) { - return throwException(b, targetMethod, targetParameters, ex.getClass(), ex.getMessage()); + return throwException(b, targetMethod, null, new Object[]{}, ex.getClass(), ex.getMessage(), false); } - return pushConstant(b, targetMethod, targetParameters, JavaKind.Object, lookup, false) != null; + return pushConstant(b, targetMethod, null, new Object[]{}, JavaKind.Object, lookup, false, false) != null; } /** @@ -374,28 +412,29 @@ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMe * {@link ImageClassLoader} to look up the class name, not the class loader that loaded the * native image generator. */ - private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod targetMethod, ValueNode nameNode, ValueNode initializeNode) { - Object classNameValue = unbox(b, nameNode, JavaKind.Object); - Object initializeValue = unbox(b, initializeNode, JavaKind.Boolean); - + private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object classNameValue, Object initializeValue, boolean printWarning) { if (!(classNameValue instanceof String) || !(initializeValue instanceof Boolean)) { return false; } String className = (String) classNameValue; boolean initialize = (Boolean) initializeValue; - Supplier targetParameters = () -> className + ", " + initialize; + /* + * Check which variant of Class.forName was called in order to avoid logging the initialize + * argument value for the single parameter version of the call. + */ + Object[] argValues = targetMethod.getParameters().length == 1 ? new Object[]{className} : new Object[]{className, initialize}; TypeResult> typeResult = imageClassLoader.findClass(className, false); if (!typeResult.isPresent()) { Throwable e = typeResult.getException(); - return throwException(b, targetMethod, targetParameters, e.getClass(), e.getMessage()); + return throwException(b, targetMethod, null, argValues, e.getClass(), e.getMessage(), printWarning); } Class clazz = typeResult.get(); if (PredefinedClassesSupport.isPredefined(clazz)) { return false; } - JavaConstant classConstant = pushConstant(b, targetMethod, targetParameters, JavaKind.Object, clazz, false); + JavaConstant classConstant = pushConstant(b, targetMethod, null, argValues, JavaKind.Object, clazz, false, printWarning); if (classConstant == null) { return false; } @@ -433,7 +472,7 @@ private boolean processClassGetClassLoader(GraphBuilderContext b, ResolvedJavaMe if (result != null) { b.addPush(JavaKind.Object, ConstantNode.forConstant(result, b.getMetaAccess())); - traceConstant(b, targetMethod, clazz::getName, result); + traceConstant(b, targetMethod, clazz, new Object[]{}, result, false); return true; } @@ -476,61 +515,100 @@ private void registerFoldInvocationPlugin(InvocationPlugins plugins, Method refl plugins.register(reflectionMethod.getDeclaringClass(), new RequiredInvocationPlugin(reflectionMethod.getName(), parameterTypes.toArray(new Class[0])) { @Override public boolean defaultHandler(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode... args) { - return foldInvocationUsingReflection(b, targetMethod, reflectionMethod, receiver, args, allowConstantFolding); + boolean hasReceiver = targetMethod.hasReceiver(); + if (strictReflectionEnabled(targetMethod)) { + if (foldInvocationUsingReflection(b, targetMethod, reflectionMethod, hasReceiver ? getArgumentFromRegistry(b, targetMethod, 0) : null, getArgumentsFromRegistry(b, targetMethod), + allowConstantFolding, false)) { + return true; + } else if (shouldWarnForNonStrictFolding()) { + return foldInvocationUsingReflection(b, targetMethod, reflectionMethod, hasReceiver ? unboxReceiver(b, receiver) : null, unboxNodeArguments(b, targetMethod, args), + allowConstantFolding, true); + } else { + return false; + } + } + return foldInvocationUsingReflection(b, targetMethod, reflectionMethod, hasReceiver ? unboxReceiver(b, receiver) : null, unboxNodeArguments(b, targetMethod, args), + allowConstantFolding, false); } }); } - private static boolean isAllowedReturnType(Class returnType) { - return ALLOWED_CONSTANT_CLASSES.contains(returnType) || returnType.isPrimitive(); + private Object unboxReceiver(GraphBuilderContext b, Receiver receiver) { + /* + * Calling receiver.get(true) can add a null check guard, i.e., modifying the graph in the + * process. It is an error for invocation plugins that do not replace the call to modify the + * graph. + */ + return unbox(b, receiver.get(false), JavaKind.Object); } - private boolean foldInvocationUsingReflection(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Method reflectionMethod, Receiver receiver, ValueNode[] args, - Predicate allowConstantFolding) { - assert b.getMetaAccess().lookupJavaMethod(reflectionMethod).equals(targetMethod) : "Fold method mismatch: " + reflectionMethod + " != " + targetMethod; - - Object receiverValue; - if (targetMethod.isStatic()) { - receiverValue = null; - } else { - /* - * Calling receiver.get(true) can add a null check guard, i.e., modifying the graph in - * the process. It is an error for invocation plugins that do not replace the call to - * modify the graph. - */ - receiverValue = unbox(b, receiver.get(false), JavaKind.Object); - if (receiverValue == null || receiverValue == NULL_MARKER) { - return false; + private Object[] unboxNodeArguments(GraphBuilderContext b, ResolvedJavaMethod targetMethod, ValueNode... args) { + Object[] argValues = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + argValues[i] = unbox(b, args[i], targetMethod.getSignature().getParameterKind(i)); + if (argValues[i] == null) { + return null; + } else if (argValues[i] == NULL_MARKER) { + argValues[i] = null; } } + return argValues; + } - Object[] argValues = new Object[args.length]; - for (int i = 0; i < args.length; i++) { - Object argValue = unbox(b, args[i], targetMethod.getSignature().getParameterKind(i)); - if (argValue == null) { - return false; - } else if (argValue == NULL_MARKER) { + private Object[] getArgumentsFromRegistry(GraphBuilderContext b, ResolvedJavaMethod targetMethod) { + Object[] argValues = new Object[targetMethod.getSignature().getParameterCount(false)]; + for (int i = 0; i < argValues.length; i++) { + argValues[i] = getArgumentFromRegistry(b, targetMethod, targetMethod.hasReceiver() ? i + 1 : i); + if (argValues[i] == null) { + return null; + } else if (argValues[i] == NULL_MARKER) { argValues[i] = null; - } else { - argValues[i] = argValue; } } + return argValues; + } + + private Object getArgumentFromRegistry(GraphBuilderContext b, ResolvedJavaMethod targetMethod, int argumentIdx) { + Optional maybeValue = reflectionRegistry.getConstantOperand(b.getMethod(), b.bci(), targetMethod, argumentIdx); + if (maybeValue.isPresent()) { + Object value = maybeValue.get(); + return StrictReflectionRegistry.isNull(value) ? NULL_MARKER : value; + } else { + return null; + } + } + + private boolean strictReflectionEnabled(JavaMethod method) { + return reflectionRegistry != null && reflectionRegistry.isStrictReflectionTarget(method); + } - if (!allowConstantFolding.test(argValues)) { + static boolean shouldWarnForNonStrictFolding() { + return SubstrateOptions.StrictReflection.getValue() == SubstrateOptions.StrictReflectionMode.Warn; + } + + private static boolean isAllowedReturnType(Class returnType) { + return ALLOWED_CONSTANT_CLASSES.contains(returnType) || returnType.isPrimitive(); + } + + private boolean foldInvocationUsingReflection(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Method reflectionMethod, Object receiverValue, Object[] argValues, + Predicate allowConstantFolding, boolean printWarning) { + assert b.getMetaAccess().lookupJavaMethod(reflectionMethod).equals(targetMethod) : "Fold method mismatch: " + reflectionMethod + " != " + targetMethod; + + if (targetMethod.hasReceiver() && (receiverValue == null || receiverValue == NULL_MARKER)) { return false; } - /* String representation of the parameters for debug printing. */ - Supplier targetParameters = () -> (receiverValue == null ? "" : receiverValue + "; ") + - Stream.of(argValues).map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", ")); + if (argValues == null || !allowConstantFolding.test(argValues)) { + return false; + } Object returnValue; try { returnValue = reflectionMethod.invoke(receiverValue, argValues); } catch (InvocationTargetException ex) { - return throwException(b, targetMethod, targetParameters, ex.getTargetException().getClass(), ex.getTargetException().getMessage()); + return throwException(b, targetMethod, receiverValue, argValues, ex.getTargetException().getClass(), ex.getTargetException().getMessage(), printWarning); } catch (Throwable ex) { - return throwException(b, targetMethod, targetParameters, ex.getClass(), ex.getMessage()); + return throwException(b, targetMethod, receiverValue, argValues, ex.getClass(), ex.getMessage(), printWarning); } JavaKind returnKind = targetMethod.getSignature().getReturnKind(); @@ -538,11 +616,11 @@ private boolean foldInvocationUsingReflection(GraphBuilderContext b, ResolvedJav /* * The target method is a side-effect free void method that did not throw an exception. */ - traceConstant(b, targetMethod, targetParameters, JavaKind.Void); + traceConstant(b, targetMethod, receiverValue, argValues, JavaKind.Void, printWarning); return true; } - return pushConstant(b, targetMethod, targetParameters, returnKind, returnValue, false) != null; + return pushConstant(b, targetMethod, receiverValue, argValues, returnKind, returnValue, false, printWarning) != null; } private void registerBulkInvocationPlugin(InvocationPlugins plugins, Class declaringClass, String methodName, Consumer registrationCallback) { @@ -555,24 +633,28 @@ public boolean isDecorator() { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) { VMError.guarantee(!targetMethod.isStatic(), "Bulk reflection queries are not static"); - return registerConstantBulkReflectionQuery(b, receiver, registrationCallback); + if (strictReflectionEnabled(targetMethod)) { + if (registerConstantBulkReflectionQuery(b, targetMethod, getArgumentFromRegistry(b, targetMethod, 0), registrationCallback, false)) { + return true; + } else if (shouldWarnForNonStrictFolding()) { + return registerConstantBulkReflectionQuery(b, targetMethod, unboxReceiver(b, receiver), registrationCallback, true); + } else { + return false; + } + } + return registerConstantBulkReflectionQuery(b, targetMethod, unboxReceiver(b, receiver), registrationCallback, false); } }); } @SuppressWarnings("unchecked") - private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, Receiver receiver, Consumer registrationCallback) { - /* - * Calling receiver.get(true) can add a null check guard, i.e., modifying the graph in the - * process. It is an error for invocation plugins that do not replace the call to modify the - * graph. - */ - Object receiverValue = unbox(b, receiver.get(false), JavaKind.Object); + private boolean registerConstantBulkReflectionQuery(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object receiverValue, Consumer registrationCallback, boolean printWarning) { if (receiverValue == null || receiverValue == NULL_MARKER) { return false; } b.add(ReachabilityRegistrationNode.create(() -> registerForRuntimeReflection((T) receiverValue, registrationCallback), reason)); + traceConstant(b, targetMethod, receiverValue, new Object[]{}, null, printWarning); return true; } @@ -725,8 +807,8 @@ private static boolean isDeleted(T element, MetaAccessProvider metaAccess) { return annotated != null && annotated.isAnnotationPresent(Delete.class); } - private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, JavaKind returnKind, Object returnValue, - boolean allowNullReturnValue) { + private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments, JavaKind returnKind, Object returnValue, + boolean allowNullReturnValue, boolean printWarning) { Object intrinsicValue = getIntrinsic(b, returnValue == null && allowNullReturnValue ? NULL_MARKER : returnValue); if (intrinsicValue == null) { return null; @@ -742,11 +824,12 @@ private JavaConstant pushConstant(GraphBuilderContext b, ResolvedJavaMethod targ } b.addPush(returnKind, ConstantNode.forConstant(intrinsicConstant, b.getMetaAccess())); - traceConstant(b, targetMethod, targetParameters, intrinsicValue); + traceConstant(b, targetMethod, targetReceiver, targetArguments, intrinsicValue, printWarning); return intrinsicConstant; } - private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Class exceptionClass, String originalMessage) { + private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass, + String originalMessage, boolean printWarning) { /* Get the exception throwing method that has a message parameter. */ Method exceptionMethod = ExceptionSynthesizer.throwExceptionMethodOrNull(exceptionClass, String.class); if (exceptionMethod == null) { @@ -757,28 +840,240 @@ private boolean throwException(GraphBuilderContext b, ResolvedJavaMethod targetM return false; } + /* + * Because tracing adds a ReachabilityRegistrationNode to the graph, it has to happen before + * exception synthesis. + */ + traceException(b, targetMethod, targetCaller, targetArguments, exceptionClass, printWarning); + String message = originalMessage + ". This exception was synthesized during native image building from a call to " + targetMethod.format("%H.%n(%p)") + " with constant arguments."; ExceptionSynthesizer.throwException(b, exceptionMethod, message); - traceException(b, targetMethod, targetParameters, exceptionClass); return true; } - private static void traceConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Object value) { - if (Options.ReflectionPluginTracing.getValue()) { - System.out.println("Call to " + targetMethod.format("%H.%n(%p)") + - " reached in " + b.getMethod().format("%H.%n(%p)") + - " with parameters (" + targetParameters.get() + ")" + - " was reduced to the constant " + value); + private void traceConstant(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments, Object value, boolean printWarning) { + if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && (ReflectionPluginsTracingFeature.isEnabled() || shouldWarnForNonStrictFolding())) { + /* + * We're capturing the call stack here in order to avoid late binding in the + * reachability node callback. + */ + List callStack = b.getCallStack(); + b.add(ReachabilityRegistrationNode.create(() -> ReflectionPluginsTracingFeature.traceConstant(callStack, targetMethod, targetReceiver, targetArguments, value, printWarning), reason)); + } + } + + private void traceException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Object targetReceiver, Object[] targetArguments, Class exceptionClass, + boolean printWarning) { + if (reason.duringAnalysis() && reason != ParsingReason.JITCompilation && (ReflectionPluginsTracingFeature.isEnabled() || shouldWarnForNonStrictFolding())) { + /* + * We're capturing the call stack here in order to avoid late binding in the + * reachability node callback. + */ + List callStack = b.getCallStack(); + b.add(ReachabilityRegistrationNode.create(() -> ReflectionPluginsTracingFeature.traceException(callStack, targetMethod, targetReceiver, targetArguments, exceptionClass, printWarning), + reason)); + } + } +} + +@AutomaticallyRegisteredFeature +final class ReflectionPluginsTracingFeature implements InternalFeature { + + static class Options { + @Option(help = "Specify the trace logging location for reflection plugins.")// + static final HostedOptionKey ReflectionPluginTraceLocation = new HostedOptionKey<>(null); + + @Option(help = "Specify the trace logging format for reflection plugins.")// + static final HostedOptionKey ReflectionPluginTraceFormat = new HostedOptionKey<>("json", key -> { + if (!key.getValue().equals("json") && !key.getValue().equals("plain")) { + throw UserError.invalidOptionValue(key, key.getValue(), "Value must be either \"json\" or \"plain\"."); + } + }); + } + + private static final Queue log = new ConcurrentLinkedQueue<>(); + private ReflectionPluginLogSupport logger; + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + String dumpLocation = Options.ReflectionPluginTraceLocation.getValue(); + if (dumpLocation == null) { + return; + } + String logFormat = Options.ReflectionPluginTraceFormat.getValue(); + logger = logFormat.equals("json") ? new ReflectionPluginJsonLogSupport(dumpLocation) : new ReflectionPluginPlainLogSupport(dumpLocation); + } + + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + if (isEnabled()) { + logger.dump(log); + } + if (ReflectionPlugins.shouldWarnForNonStrictFolding()) { + warnForNonStrictFolding(); + } + } + + private void warnForNonStrictFolding() { + List unsafeFoldingEntries = log.stream().filter(e -> e.printWarning).toList(); + if (!unsafeFoldingEntries.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("The following reflective calls have been folded outside of the strict constant reflection mode:").append(System.lineSeparator()); + for (int i = 0; i < unsafeFoldingEntries.size(); i++) { + sb.append((i + 1)).append(". ").append(unsafeFoldingEntries.get(i)).append(System.lineSeparator()); + } + LogUtils.warning(sb.toString()); + } + } + + public static boolean isEnabled() { + return Options.ReflectionPluginTraceLocation.getValue() != null; + } + + public static void traceConstant(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Object value, boolean printWarning) { + log.add(new ConstantTraceEntry(callStack, targetMethod, targetCaller, targetArguments, printWarning, value)); + } + + public static void traceException(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, Class exceptionClass, + boolean printWarning) { + log.add(new ExceptionTraceEntry(callStack, targetMethod, targetCaller, targetArguments, printWarning, exceptionClass)); + } + + private abstract static class TraceEntry { + + private final List callStack; + private final ResolvedJavaMethod targetMethod; + private final Object targetCaller; + private final Object[] targetArguments; + private final boolean printWarning; + + TraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, boolean printWarning) { + this.callStack = callStack; + this.targetMethod = targetMethod; + this.targetCaller = targetCaller; + this.targetArguments = targetArguments; + this.printWarning = printWarning; + } + + @Override + public String toString() { + String targetArgumentsString = Stream.of(targetArguments) + .map(arg -> arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)).collect(Collectors.joining(", ")); + + return "Call to " + targetMethod.format("%H.%n(%p)") + + " reached in " + callStack.getFirst() + + (targetCaller != null ? " with caller " + targetCaller + " and" : "") + + " with arguments (" + targetArgumentsString + ") was reduced"; + } + + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + try (JsonBuilder.ArrayBuilder foldContextBuilder = builder.append("foldContext").array()) { + for (StackTraceElement element : callStack) { + foldContextBuilder.append(element); + } + } + builder.append("targetMethod", targetMethod.format("%H.%n(%p)")); + if (targetCaller != null) { + builder.append("targetCaller", targetCaller); + } + try (JsonBuilder.ArrayBuilder argsBuilder = builder.append("targetArguments").array()) { + for (Object arg : targetArguments) { + argsBuilder.append(arg instanceof Object[] ? Arrays.toString((Object[]) arg) : Objects.toString(arg)); + } + } } } - private static void traceException(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Supplier targetParameters, Class exceptionClass) { - if (Options.ReflectionPluginTracing.getValue()) { - System.out.println("Call to " + targetMethod.format("%H.%n(%p)") + - " reached in " + b.getMethod().format("%H.%n(%p)") + - " with parameters (" + targetParameters.get() + ")" + - " was reduced to a \"throw new " + exceptionClass.getName() + "(...)\""); + private static class ConstantTraceEntry extends TraceEntry { + + private final Object value; + + ConstantTraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, boolean printWarning, Object value) { + super(callStack, targetMethod, targetCaller, targetArguments, printWarning); + this.value = value; + } + + @Override + public String toString() { + return super.toString() + " to the constant " + value; + } + + @Override + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + super.toJson(builder); + builder.append("constantValue", value); + } + } + + private static class ExceptionTraceEntry extends TraceEntry { + + private final Class exceptionClass; + + ExceptionTraceEntry(List callStack, ResolvedJavaMethod targetMethod, Object targetCaller, Object[] targetArguments, boolean printWarning, + Class exceptionClass) { + super(callStack, targetMethod, targetCaller, targetArguments, printWarning); + this.exceptionClass = exceptionClass; + } + + @Override + public String toString() { + return super.toString() + " to a \"throw new " + exceptionClass.getName() + "(...)\""; + } + + @Override + public void toJson(JsonBuilder.ObjectBuilder builder) throws IOException { + super.toJson(builder); + builder.append("exception", exceptionClass.getName()); + } + } + + private abstract static class ReflectionPluginLogSupport { + + protected final String location; + + ReflectionPluginLogSupport(String location) { + this.location = location; + } + + public abstract void dump(Iterable constantReflectionLog); + } + + private static final class ReflectionPluginPlainLogSupport extends ReflectionPluginLogSupport { + + ReflectionPluginPlainLogSupport(String location) { + super(location); + } + + @Override + public void dump(Iterable constantReflectionLog) { + try (PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(location)))) { + constantReflectionLog.forEach(out::println); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } + + private static final class ReflectionPluginJsonLogSupport extends ReflectionPluginLogSupport { + + ReflectionPluginJsonLogSupport(String location) { + super(location); + } + + @Override + public void dump(Iterable constantReflectionLog) { + try (JsonWriter out = new JsonPrettyWriter(Path.of(location))) { + try (JsonBuilder.ArrayBuilder arrayBuilder = out.arrayBuilder()) { + for (TraceEntry entry : constantReflectionLog) { + try (JsonBuilder.ObjectBuilder objectBuilder = arrayBuilder.nextEntry().object()) { + entry.toJson(objectBuilder); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index 9a27cdd9e53e..d59da4f23104 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -686,6 +686,8 @@ private static FixedNode unwrapNode(FixedNode node) { } else if (successor instanceof AbstractBeginNode) { /* Useless block begins can occur during parsing or graph decoding. */ successor = ((AbstractBeginNode) successor).next(); + } else if (successor instanceof ReachabilityRegistrationNode) { + successor = ((ReachabilityRegistrationNode) successor).next(); } else { return successor; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java index eb7b6d3f2406..ce4a01ba9953 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AutomaticUnsafeTransformationSupport.java @@ -41,6 +41,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.hosted.reflect.StrictReflectionRegistry; import jdk.graal.compiler.nodes.calc.NarrowNode; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.FieldValueTransformer; @@ -251,9 +253,16 @@ public AutomaticUnsafeTransformationSupport(OptionValues options, AnnotationSubs NoClassInitializationPlugin classInitializationPlugin = new NoClassInitializationPlugin(); plugins.setClassInitializationPlugin(classInitializationPlugin); + StrictReflectionRegistry reflectionRegistry = null; + SubstrateOptions.StrictReflectionMode strictReflectionMode = SubstrateOptions.StrictReflection.getValue(); + if (strictReflectionMode == SubstrateOptions.StrictReflectionMode.Warn || strictReflectionMode == SubstrateOptions.StrictReflectionMode.Enforce) { + reflectionRegistry = new StrictReflectionRegistry(GraalAccess.getOriginalProviders(), loader); + plugins.appendMethodParsingPlugin(reflectionRegistry::analyzeMethod); + } + FallbackFeature fallbackFeature = ImageSingletons.contains(FallbackFeature.class) ? ImageSingletons.lookup(FallbackFeature.class) : null; ReflectionPlugins.registerInvocationPlugins(loader, annotationSubstitutions, classInitializationPlugin, plugins.getInvocationPlugins(), null, - ParsingReason.AutomaticUnsafeTransformation, fallbackFeature); + ParsingReason.AutomaticUnsafeTransformation, fallbackFeature, reflectionRegistry); /* * Note: ConstantFoldLoadFieldPlugin should not be installed because it will disrupt