From e66f8fb480357255f5553252043a8f6c52566b0c Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Wed, 14 Sep 2022 20:40:18 +0200 Subject: [PATCH] Replace usage of NeverInline in jdbc-pgsql The previous implementation relied on a graal compiler optimization to never inline a method. By doing so it tricked native-image into not including XML processing code when it's not used. Instead, use a DomHelper-local bifunction field which stays null unless the XML processing code is actually reachable. If it is, the bifunction is being set by the reachability handler and, thus, achieves the same result. Closes #27791 --- .../postgresql/runtime/graal/DomHelper.java | 60 +++++++++++-------- .../runtime/graal/SQLXMLFeature.java | 22 +++---- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/DomHelper.java b/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/DomHelper.java index f3d4ee934d1a2..d6ad86a66d216 100644 --- a/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/DomHelper.java +++ b/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/DomHelper.java @@ -1,8 +1,8 @@ package io.quarkus.jdbc.postgresql.runtime.graal; import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; +import java.util.function.BiFunction; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; @@ -11,6 +11,7 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.graalvm.nativeimage.ImageSingletons; import org.postgresql.core.BaseConnection; import org.postgresql.util.GT; import org.postgresql.util.PSQLException; @@ -18,43 +19,44 @@ import org.postgresql.xml.DefaultPGXmlFactoryFactory; import org.postgresql.xml.PGXmlFactoryFactory; -import com.oracle.svm.core.annotate.NeverInline; - /** * Used by PgSQLXML: easier to keep the actual code separated from the substitutions. */ -public final class DomHelper { +final class DomHelper { - //This is the actual code reaching to the JDK XML types; no other code except - //the reflective call in the previous method should trigger this inclusion. - public static String processDomResult(DOMResult domResult, BaseConnection conn) throws SQLException { - return maybeProcessAsDomResult(domResult, conn); - } + // Stays null unless XML result processing becomes reachable. + private BiFunction processDomResult; // This is only ever invoked if field domResult got initialized; which in turn // is only possible if setResult(Class) is reachable. - public static String maybeProcessAsDomResult(DOMResult domResult, BaseConnection conn) throws SQLException { - try { - return (String) DomHelper.class.getMethod(obfuscatedMethodName(), DOMResult.class, BaseConnection.class) - .invoke(null, domResult, conn); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new RuntimeException("Unexpected failure in reflective call - please report", e); - } catch (InvocationTargetException e) { - final Throwable cause = e.getCause(); - if (cause instanceof SQLException) { - //Propagate normal SQLException(s): - throw (SQLException) cause; - } else { - throw new RuntimeException("Unexpected failure in reflective call - please report", e); + public static String processDomResult(DOMResult domResult, BaseConnection conn) throws SQLException { + BiFunction func = ImageSingletons.lookup(DomHelper.class).processDomResult; + if (func == null) { + return null; + } else { + try { + return func.apply(domResult, conn); + } catch (UncheckedSQLException e) { + throw (SQLException) e.getCause(); } } } - @NeverInline("Prevent GraalVM from figuring out the target method to be invoked reflectively, so that it can't automatically register it for reflection") - private static String obfuscatedMethodName() { - return "reallyProcessDomResult"; + // Called by SQLXMLFeature when setResult(Class) becomes reachable + static void enableXmlProcessing() { + ImageSingletons.lookup(DomHelper.class).processDomResult = DomHelper::doProcessDomResult; } + public static String doProcessDomResult(DOMResult domResult, BaseConnection conn) { + try { + return reallyProcessDomResult(domResult, conn); + } catch (SQLException e) { + throw new UncheckedSQLException(e); + } + } + + // This XML processing code should only be reachable (via processDomResult function) iff it's + // actually used. I.e. setResult(Class) is reachable. public static String reallyProcessDomResult(DOMResult domResult, BaseConnection conn) throws SQLException { TransformerFactory factory = getXmlFactoryFactory(conn).newTransformerFactory(); try { @@ -77,3 +79,11 @@ private static PGXmlFactoryFactory getXmlFactoryFactory(BaseConnection conn) thr return DefaultPGXmlFactoryFactory.INSTANCE; } } + +@SuppressWarnings("serial") +final class UncheckedSQLException extends RuntimeException { + + UncheckedSQLException(SQLException cause) { + super(cause); + } +} diff --git a/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/SQLXMLFeature.java b/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/SQLXMLFeature.java index f2e2fb777d880..2c88528487683 100644 --- a/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/SQLXMLFeature.java +++ b/extensions/jdbc/jdbc-postgresql/runtime/src/main/java/io/quarkus/jdbc/postgresql/runtime/graal/SQLXMLFeature.java @@ -3,11 +3,8 @@ import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; -import javax.xml.transform.dom.DOMResult; - +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeReflection; -import org.postgresql.core.BaseConnection; public final class SQLXMLFeature implements Feature { @@ -18,6 +15,11 @@ public final class SQLXMLFeature implements Feature { */ private static final boolean log = Boolean.getBoolean("io.quarkus.jdbc.postgresql.graalvm.diagnostics"); + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(DomHelper.class, new DomHelper()); + } + @Override public void beforeAnalysis(BeforeAnalysisAccess access) { Class pgSQLXMLClass = access.findClassByName("io.quarkus.jdbc.postgresql.runtime.graal.PgSQLXML"); @@ -39,17 +41,7 @@ private void identifiedXMLProcessingInDriver(DuringAnalysisAccess duringAnalysis System.out.println( "Quarkus' automatic feature for GraalVM native images: enabling support for XML processing in the PostgreSQL JDBC driver"); } - enableDomXMLProcessingInDriver(duringAnalysisAccess); - } - } - - private void enableDomXMLProcessingInDriver(DuringAnalysisAccess duringAnalysisAccess) { - final Class classByName = duringAnalysisAccess.findClassByName("io.quarkus.jdbc.postgresql.runtime.graal.DomHelper"); - try { - final Method method = classByName.getMethod("reallyProcessDomResult", DOMResult.class, BaseConnection.class); - RuntimeReflection.register(method); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + DomHelper.enableXmlProcessing(); } }