package org.graalvm.compiler.java;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.graphbuilderconf.ClassInitializationPlugin;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
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 jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
public final class LambdaUtils {
private static final Pattern LAMBDA_PATTERN = Pattern.compile("\\$\\$Lambda\\$\\d+[/\\.][^/]+;");
private static final char[] HEX = "0123456789abcdef".toCharArray();
private static GraphBuilderConfiguration buildLambdaParserConfig(ClassInitializationPlugin cip) {
GraphBuilderConfiguration.Plugins plugins = new GraphBuilderConfiguration.Plugins(new InvocationPlugins());
plugins.setClassInitializationPlugin(cip);
return GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true);
}
private LambdaUtils() {
}
@SuppressWarnings("try")
public static String findStableLambdaName(ClassInitializationPlugin cip, Providers providers, ResolvedJavaType lambdaType, OptionValues options, DebugContext debug, Object ctx)
throws RuntimeException {
ResolvedJavaMethod[] lambdaProxyMethods = Arrays.stream(lambdaType.getDeclaredMethods()).filter(m -> !m.isBridge() && m.isPublic()).toArray(ResolvedJavaMethod[]::new);
assert lambdaProxyMethods.length == 1 : "There must be only one method calling the target.";
StructuredGraph graph = new StructuredGraph.Builder(options, debug).method(lambdaProxyMethods[0]).build();
try (DebugContext.Scope ignored = debug.scope("Lambda target method analysis", graph, lambdaType, ctx)) {
GraphBuilderPhase lambdaParserPhase = new GraphBuilderPhase(buildLambdaParserConfig(cip));
HighTierContext context = new HighTierContext(providers, null, OptimisticOptimizations.NONE);
lambdaParserPhase.apply(graph, context);
} catch (Throwable e) {
throw debug.handle(e);
}
List<ResolvedJavaMethod> invokedMethods = StreamSupport.stream(graph.getInvokes().spliterator(), false).map((inv) -> inv.getTargetMethod()).collect(Collectors.toList());
if (invokedMethods.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("Lambda without a target invoke: ").append(lambdaType.toClassName());
for (ResolvedJavaMethod m : lambdaType.getDeclaredMethods()) {
sb.append("\n Method: ").append(m);
}
throw new JVMCIError(sb.toString());
}
String lambdaTargetName = createStableLambdaName(lambdaType, invokedMethods);
return lambdaTargetName;
}
public static boolean isLambdaType(ResolvedJavaType type) {
String typeName = type.getName();
return type.isFinalFlagSet() && typeName.contains("/") && typeName.contains("$$Lambda$") && lambdaMatcher(type.getName()).find();
}
private static String createStableLambdaName(ResolvedJavaType lambdaType, List<ResolvedJavaMethod> targetMethods) {
final String lambdaName = lambdaType.getName();
assert lambdaMatcher(lambdaName).find() : "Stable name should be created only for lambda types: " + lambdaName;
Matcher m = lambdaMatcher(lambdaName);
StringBuilder sb = new StringBuilder();
targetMethods.forEach((targetMethod) -> {
sb.append(targetMethod.format("%H.%n(%P)%R"));
});
return m.replaceFirst(Matcher.quoteReplacement("$$Lambda$" + digest(sb.toString()) + ";"));
}
private static Matcher lambdaMatcher(String value) {
return LAMBDA_PATTERN.matcher(value);
}
public static String toHex(byte[] data) {
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(HEX[(b >> 4) & 0xf]);
r.append(HEX[b & 0xf]);
}
return r.toString();
}
public static String digest(String value) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(value.getBytes("UTF-8"));
return toHex(md.digest());
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new JVMCIError(ex);
}
}
}