package com.oracle.svm.hosted.classinitialization;
import static org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createStandardInlineInfo;
import java.util.function.Supplier;
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.core.common.GraalBailoutException;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DebugContext.Builder;
import org.graalvm.compiler.graph.Graph;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.java.BytecodeParser;
import org.graalvm.compiler.java.GraphBuilderPhase;
import org.graalvm.compiler.nodes.FrameState;
import org.graalvm.compiler.nodes.Invoke;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.GuardsStage;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.extended.UnsafeAccessNode;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
import org.graalvm.compiler.nodes.java.AccessFieldNode;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.phases.OptimisticOptimizations;
import org.graalvm.compiler.phases.tiers.HighTierContext;
import org.graalvm.compiler.phases.util.Providers;
import com.oracle.svm.core.option.HostedOptionValues;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.c.GraalAccess;
import com.oracle.svm.hosted.phases.EarlyConstantFoldLoadFieldPlugin;
import com.oracle.svm.hosted.phases.NoClassInitializationPlugin;
import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
class EarlyClassInitializerAnalysis {
private final Providers originalProviders;
private final GraphBuilderConfiguration graphBuilderConfig;
private final HighTierContext context;
EarlyClassInitializerAnalysis() {
originalProviders = GraalAccess.getOriginalProviders();
SnippetReflectionProvider originalSnippetReflection = GraalAccess.getOriginalSnippetReflection();
InvocationPlugins invocationPlugins = new InvocationPlugins();
SubstrateGraphBuilderPlugins.registerClassDesiredAssertionStatusPlugin(invocationPlugins, originalSnippetReflection);
Plugins plugins = new Plugins(invocationPlugins);
plugins.appendInlineInvokePlugin(new AbortOnRecursiveInliningPlugin());
plugins.setClassInitializationPlugin(new AbortOnUnitializedClassPlugin());
plugins.appendNodePlugin(new EarlyConstantFoldLoadFieldPlugin(originalProviders.getMetaAccess(), originalProviders.getSnippetReflection()));
graphBuilderConfig = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
context = new HighTierContext(originalProviders, null, OptimisticOptimizations.NONE);
}
@SuppressWarnings("try")
boolean canInitializeWithoutSideEffects(Class<?> clazz) {
ResolvedJavaType type = originalProviders.getMetaAccess().lookupJavaType(clazz);
assert type.getSuperclass() == null || type.getSuperclass().isInitialized() : "This analysis assumes that the superclass was successfully analyzed and initialized beforehand: " +
type.toJavaName(true);
ResolvedJavaMethod clinit = type.getClassInitializer();
if (clinit == null) {
return true;
} else if (clinit.getCode() == null) {
return false;
}
OptionValues options = HostedOptionValues.singleton();
DebugContext debug = new Builder(options).build();
try (DebugContext.Scope s = debug.scope("EarlyClassInitializerAnalysis", clinit)) {
return canInitializeWithoutSideEffects(clinit, options, debug);
} catch (Throwable ex) {
throw debug.handle(ex);
}
}
@SuppressWarnings("try")
private boolean canInitializeWithoutSideEffects(ResolvedJavaMethod clinit, OptionValues options, DebugContext debug) {
StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(clinit).build();
graph.setGuardsStage(GuardsStage.FIXED_DEOPTS);
GraphBuilderPhase.Instance builderPhase = new ClassInitializerGraphBuilderPhase(context, graphBuilderConfig, context.getOptimisticOptimizations());
try (Graph.NodeEventScope nes = graph.trackNodeEvents(new AbortOnDisallowedNode())) {
builderPhase.apply(graph, context);
return true;
} catch (ClassInitalizerHasSideEffectsException ex) {
return false;
} catch (BytecodeParser.BytecodeParserError ex) {
if (ex.getCause() instanceof ClassInitalizerHasSideEffectsException) {
return false;
}
throw ex;
}
}
}
class ClassInitalizerHasSideEffectsException extends GraalBailoutException {
private static final long serialVersionUID = 1L;
ClassInitalizerHasSideEffectsException(String message) {
super(message);
}
}
class AbortOnRecursiveInliningPlugin implements InlineInvokePlugin {
@Override
public InlineInfo shouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod original, ValueNode[] arguments) {
for (GraphBuilderContext parent = b.getParent(); parent != null; parent = parent.getParent()) {
if (parent.getMethod().equals(original)) {
return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION;
}
}
if (original.getCode() == null) {
return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION;
}
return createStandardInlineInfo(original);
}
}
class AbortOnUnitializedClassPlugin extends NoClassInitializationPlugin {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaType type, Supplier<FrameState> frameState, ValueNode[] classInit) {
ResolvedJavaMethod clinitMethod = b.getGraph().method();
if (!type.isInitialized() && !type.isArray() && !type.equals(clinitMethod.getDeclaringClass())) {
throw new ClassInitalizerHasSideEffectsException("Reference of class that is not initialized: " + type.toJavaName(true));
}
return false;
}
}
class AbortOnDisallowedNode extends Graph.NodeEventListener {
@Override
public void nodeAdded(Node node) {
if (node instanceof Invoke) {
throw new ClassInitalizerHasSideEffectsException("Non-inlined invoke of method: " + ((Invoke) node).getTargetMethod().format("%H.%n(%p)"));
} else if (node instanceof AccessFieldNode) {
ResolvedJavaField field = ((AccessFieldNode) node).field();
ResolvedJavaMethod clinit = ((StructuredGraph) node.graph()).method();
if (field.isStatic() && !field.getDeclaringClass().equals(clinit.getDeclaringClass())) {
throw new ClassInitalizerHasSideEffectsException("Access of static field from a different class: " + field.format("%H.%n"));
}
} else if (node instanceof UnsafeAccessNode) {
throw VMError.shouldNotReachHere("Intrinsification of Unsafe methods is not enabled during bytecode parsing");
}
}
}