Background
GraalVM native executables do not allow loading classes at runtime due to the closed world assumption. To make XSLT work with them, we generate XSLT Translet classes at build time and let native-image compile them into the native executable. For that to work reliably, we need to be able to set the name of the generated class so that we are then able to find the class and pass it to the native compiler.
Steps to reproduce
To generate a Translet class for a given XSL file, we perform steps similar to the following:
```
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
public class Main {
public static void main(String[] args) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute("generate-translet", true);
tf.setAttribute("translet-name", "MyTranslet");
tf.setAttribute("package-name", "org.acme");
tf.setAttribute("destination-directory", "test");
Path test = Path.of("test");
if (Files.exists(test)) {
Files.walk(test).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
File xslFile = new File(args[0]);
tf.newTemplates(new StreamSource(Files.newInputStream(xslFile.toPath())));
Files.list(Path.of("test/org/acme")).forEach(System.out::println);
}
}
```
When this program is compiled through javac Main.java and run via java Main test.xsl, where test.xsl is any simple XSL file, such as
```
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:template match="/request">
<xsl:variable name="name" select="//name/text()"/>
<response>
<message>
<xsl:value-of select="concat('Hello, ', $name)"/>
</message>
</response>
</xsl:template>
</xsl:stylesheet>
```
then, we expect to find the generated translet file under test/org/acme/MyTranslet.class. In reality, the generated translet is under test/org/acme/die_verwandlung.class.
Analysis
The execution flow goes via com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(Source), where a new com.sun.org.apache.xalan.internal.xsltc.trax.XSLTC is created and its setClassName(String) and setPackageName(String) are called.
XSLTC.setClassName(String) does some sanitization of the passed className and if the _packageName field is set, it sets the _className field to _packageName + '.' + name.
Because XSLTC._packageName is initialized to "die.verwandlung", then, after the first call of setClassName("MyTranslet"), the value of _packageName is "die.verwandlung.MyTranslet".
The XSLTC.setPackageName("org.acme") called afterwards, first sets the _packageName field to the passed value and then, if _className != null, it calls setClassName(_className).
In our situation, it effectively means calling setClassName("die.verwandlung.MyTranslet").
The sanitization of the passed value done within this second setClassName() call transforms "die.verwandlung.MyTranslet" into "die_verwandlung".
Afterwards, the _className field is set to _packageName + '.' + name which is "org.acme" + '.' + "die_verwandlung" in our case.
Observation: the ASF Xalan does not initialize XSLTC._packageName to "die.verwandlung" and therefore the reproducer code works as expected there.
Possible solutions
A. In TransformerFactoryImpl.newTemplates(Source), call XSLTC.setPackageName(String) before XSLTC.setClassName(String).
B. In XSLTC.setPackageName(String), instead of calling setClassName(_className), pass only the simple class name extracted from the _className field to setClassName(String).
C. Make XSLTC.setClassName(String) throw an exception when it is called with a className containig ., / or \; document that it expects a simple name, document that it sets _className to a fully qualified name when _packageName is set; move all sanitization to callers of XSLTC.setClassName(String). (This implies solution B).
GraalVM native executables do not allow loading classes at runtime due to the closed world assumption. To make XSLT work with them, we generate XSLT Translet classes at build time and let native-image compile them into the native executable. For that to work reliably, we need to be able to set the name of the generated class so that we are then able to find the class and pass it to the native compiler.
Steps to reproduce
To generate a Translet class for a given XSL file, we perform steps similar to the following:
```
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
public class Main {
public static void main(String[] args) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute("generate-translet", true);
tf.setAttribute("translet-name", "MyTranslet");
tf.setAttribute("package-name", "org.acme");
tf.setAttribute("destination-directory", "test");
Path test = Path.of("test");
if (Files.exists(test)) {
Files.walk(test).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
File xslFile = new File(args[0]);
tf.newTemplates(new StreamSource(Files.newInputStream(xslFile.toPath())));
Files.list(Path.of("test/org/acme")).forEach(System.out::println);
}
}
```
When this program is compiled through javac Main.java and run via java Main test.xsl, where test.xsl is any simple XSL file, such as
```
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"/>
<xsl:template match="/request">
<xsl:variable name="name" select="//name/text()"/>
<response>
<message>
<xsl:value-of select="concat('Hello, ', $name)"/>
</message>
</response>
</xsl:template>
</xsl:stylesheet>
```
then, we expect to find the generated translet file under test/org/acme/MyTranslet.class. In reality, the generated translet is under test/org/acme/die_verwandlung.class.
Analysis
The execution flow goes via com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(Source), where a new com.sun.org.apache.xalan.internal.xsltc.trax.XSLTC is created and its setClassName(String) and setPackageName(String) are called.
XSLTC.setClassName(String) does some sanitization of the passed className and if the _packageName field is set, it sets the _className field to _packageName + '.' + name.
Because XSLTC._packageName is initialized to "die.verwandlung", then, after the first call of setClassName("MyTranslet"), the value of _packageName is "die.verwandlung.MyTranslet".
The XSLTC.setPackageName("org.acme") called afterwards, first sets the _packageName field to the passed value and then, if _className != null, it calls setClassName(_className).
In our situation, it effectively means calling setClassName("die.verwandlung.MyTranslet").
The sanitization of the passed value done within this second setClassName() call transforms "die.verwandlung.MyTranslet" into "die_verwandlung".
Afterwards, the _className field is set to _packageName + '.' + name which is "org.acme" + '.' + "die_verwandlung" in our case.
Observation: the ASF Xalan does not initialize XSLTC._packageName to "die.verwandlung" and therefore the reproducer code works as expected there.
Possible solutions
A. In TransformerFactoryImpl.newTemplates(Source), call XSLTC.setPackageName(String) before XSLTC.setClassName(String).
B. In XSLTC.setPackageName(String), instead of calling setClassName(_className), pass only the simple class name extracted from the _className field to setClassName(String).
C. Make XSLTC.setClassName(String) throw an exception when it is called with a className containig ., / or \; document that it expects a simple name, document that it sets _className to a fully qualified name when _packageName is set; move all sanitization to callers of XSLTC.setClassName(String). (This implies solution B).