Skip to content

Commit 42bc369

Browse files
Add ClassLoader support for ASM ClassWriter
For bigger and more complicated classes org.objectweb.asm.ClassWriter requires access to classloader where can found classes used in transformed class. Also add a warning when a class cannot be transformed.
1 parent f4e8634 commit 42bc369

File tree

8 files changed

+181
-27
lines changed

8 files changed

+181
-27
lines changed

src/it/projects/mexec-gh-389-block-exit-non-zero/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
def buildLogLines = new File( basedir, "build.log" ).readLines()
1818

1919
// Find "System::exit was called" line index
20-
def infoMessageLineNumber = buildLogLines.indexOf("[INFO] System::exit was called with return code 123")
20+
def infoMessageLineNumber = buildLogLines.indexOf("[ERROR] System::exit was called with return code 123")
2121
assert infoMessageLineNumber > 0
2222
// Verify that preceding line is program output
2323
assert buildLogLines[infoMessageLineNumber - 1].contains("[one, two, three]")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
4+
targetNamespace="http://www.liquibase.org/xml/ns/dbchangelog"
5+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
6+
elementFormDefault="qualified">
7+
8+
</xsd:schema>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
invoker.goals = package
2+
invoker.debug = false
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>org.codehaus.mojo.exec.it</groupId>
8+
<artifactId>parent</artifactId>
9+
<version>0.1</version>
10+
</parent>
11+
12+
<artifactId>mexec-gh-389</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
15+
<description>
16+
For bigger and more complicated classes org.objectweb.asm.ClassWriter
17+
requires access to classloader where can found classes used in transformed class.
18+
</description>
19+
20+
<properties>
21+
<xsd-file>dbchangelog-4.31</xsd-file>
22+
</properties>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>net.java.dev.msv</groupId>
27+
<artifactId>msv-rngconverter</artifactId>
28+
<version>2022.7</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.relaxng</groupId>
32+
<artifactId>trang</artifactId>
33+
<version>20241231</version>
34+
</dependency>
35+
</dependencies>
36+
37+
<build>
38+
<plugins>
39+
<plugin>
40+
<groupId>org.codehaus.mojo</groupId>
41+
<artifactId>exec-maven-plugin</artifactId>
42+
<version>@project.version@</version>
43+
<executions>
44+
<execution>
45+
<id>convert-xsd-to-rng</id>
46+
<phase>package</phase>
47+
<goals>
48+
<goal>java</goal>
49+
</goals>
50+
<configuration>
51+
<mainClass>com.sun.msv.writer.relaxng.Driver</mainClass>
52+
<commandlineArgs>
53+
${project.basedir}/${xsd-file}.xsd
54+
${project.build.directory}/${xsd-file}.rng
55+
</commandlineArgs>
56+
</configuration>
57+
</execution>
58+
<execution>
59+
<id>convert-liquibase-rng-to-rnc</id>
60+
<phase>package</phase>
61+
<goals>
62+
<goal>java</goal>
63+
</goals>
64+
<configuration>
65+
<mainClass>com.thaiopensource.relaxng.translate.Driver</mainClass>
66+
<blockSystemExit>true</blockSystemExit>
67+
<arguments>
68+
<argument>${project.build.directory}/${xsd-file}.rng</argument>
69+
<argument>${project.build.directory}/${xsd-file}.rnc</argument>
70+
</arguments>
71+
</configuration>
72+
</execution>
73+
</executions>
74+
</plugin>
75+
</plugins>
76+
</build>
77+
78+
</project>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright MojoHaus and Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
def buildLogLines = new File(basedir, "build.log").text
18+
19+
assert buildLogLines.contains("[INFO] System::exit was called with return code 0")
20+
21+
assert new File(basedir,'target/dbchangelog-4.31.rnc').exists()
22+
assert new File(basedir,'target/dbchangelog-4.31.rng').exists()

src/main/java/org/codehaus/mojo/exec/BlockExitTransformer.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
* under the License.
2020
*/
2121

22+
import java.io.IOException;
2223
import java.lang.instrument.ClassFileTransformer;
24+
import java.lang.instrument.IllegalClassFormatException;
25+
import java.net.URLClassLoader;
2326
import java.security.ProtectionDomain;
2427

28+
import org.apache.maven.plugin.logging.Log;
2529
import org.objectweb.asm.ClassReader;
2630
import org.objectweb.asm.ClassVisitor;
2731
import org.objectweb.asm.ClassWriter;
@@ -33,24 +37,57 @@
3337
import static org.objectweb.asm.Opcodes.ASM9;
3438

3539
public class BlockExitTransformer implements ClassFileTransformer {
40+
41+
private final URLClassLoader classLoader;
42+
43+
private final Log logger;
44+
45+
BlockExitTransformer(URLClassLoader classLoader, Log logger) {
46+
this.classLoader = classLoader;
47+
this.logger = logger;
48+
}
49+
3650
@Override
3751
public byte[] transform(
3852
final ClassLoader loader,
3953
final String className,
4054
final Class<?> classBeingRedefined,
4155
final ProtectionDomain protectionDomain,
42-
final byte[] classfileBuffer) {
56+
final byte[] classfileBuffer)
57+
throws IllegalClassFormatException {
4358
try {
4459
final ClassReader reader = new ClassReader(classfileBuffer);
45-
final ClassWriter writer = new ClassWriter(COMPUTE_FRAMES);
60+
final ClassWriter writer = createClassWriter();
4661
final SystemExitOverrideVisitor visitor = new SystemExitOverrideVisitor(writer);
4762
reader.accept(visitor, EXPAND_FRAMES);
4863
return writer.toByteArray();
4964
} catch (final RuntimeException re) { // too old asm for ex, ignore these classes to not block the rest
65+
logger.warn("Unable to transform class " + className + " : " + re.getMessage());
5066
return null;
5167
}
5268
}
5369

70+
/**
71+
* Creates a new {@link ClassWriter} that uses the dedicated {@link ClassLoader} of this transformer.
72+
* <p>
73+
* For bigger and more complicated classes {@link ClassWriter}
74+
* requires access to classloader where can found classes used in transformed class.
75+
*
76+
* @return a new {@link ClassWriter}
77+
*/
78+
private ClassWriter createClassWriter() {
79+
return new ClassWriter(COMPUTE_FRAMES) {
80+
@Override
81+
protected ClassLoader getClassLoader() {
82+
return classLoader;
83+
}
84+
};
85+
}
86+
87+
public void close() throws IOException {
88+
classLoader.close();
89+
}
90+
5491
private static class SystemExitOverrideVisitor extends ClassVisitor {
5592
private static final String SYSTEM_REPLACEMENT =
5693
SystemExitManager.class.getName().replace('.', '/');

src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,6 @@ public void execute() throws MojoExecutionException, MojoFailureException {
264264
}
265265

266266
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(mainClass /* name */);
267-
// TODO:
268-
// Adjust implementation for future JDKs after removal of SecurityManager.
269-
// See https://openjdk.org/jeps/411 for basic information.
270-
// See https://bugs.openjdk.org/browse/JDK-8199704 for details about how users might be able to
271-
// block
272-
// System::exit in post-removal JDKs (still undecided at the time of writing this comment).
273267
Thread bootstrapThread = new Thread( // TODO: drop this useless thread 99% of the time
274268
threadGroup,
275269
() -> {
@@ -299,9 +293,11 @@ public void execute() throws MojoExecutionException, MojoFailureException {
299293
.getThreadGroup()
300294
.uncaughtException(Thread.currentThread(), exceptionToReport);
301295
} catch (SystemExitException systemExitException) {
302-
getLog().info(systemExitException.getMessage());
303296
if (systemExitException.getExitCode() != 0) {
297+
getLog().error(systemExitException.getMessage());
304298
throw systemExitException;
299+
} else {
300+
getLog().info(systemExitException.getMessage());
305301
}
306302
} catch (Throwable e) { // just pass it on
307303
Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), e);
@@ -696,16 +692,14 @@ private URLClassLoader getClassLoader() throws MojoExecutionException {
696692
this.addRelevantProjectDependenciesToClasspath(classpathURLs);
697693
this.addAdditionalClasspathElements(classpathURLs);
698694
try {
699-
final URLClassLoaderBuilder builder = URLClassLoaderBuilder.builder()
695+
return URLClassLoaderBuilder.builder()
700696
.setLogger(getLog())
701697
.setPaths(classpathURLs)
702698
.setExclusions(classpathFilenameExclusions)
703699
.setForcedJvmPackages(forcedJvmPackages)
704-
.setExcludedJvmPackages(excludedJvmPackages);
705-
if (blockSystemExit) {
706-
builder.setTransformer(new BlockExitTransformer());
707-
}
708-
return builder.build();
700+
.setExcludedJvmPackages(excludedJvmPackages)
701+
.withTransformers(blockSystemExit)
702+
.build();
709703
} catch (NullPointerException | IOException e) {
710704
throw new MojoExecutionException(e.getMessage(), e);
711705
}

src/main/java/org/codehaus/mojo/exec/URLClassLoaderBuilder.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.io.File;
2424
import java.io.IOException;
2525
import java.io.InputStream;
26-
import java.lang.instrument.ClassFileTransformer;
2726
import java.lang.instrument.IllegalClassFormatException;
2827
import java.net.MalformedURLException;
2928
import java.net.URL;
@@ -51,7 +50,7 @@ class URLClassLoaderBuilder {
5150
private List<String> excludedJvmPackages;
5251
private Collection<Path> paths;
5352
private Collection<String> exclusions;
54-
private ClassFileTransformer transformer;
53+
private boolean withTransformers;
5554

5655
private URLClassLoaderBuilder() {}
5756

@@ -69,11 +68,6 @@ URLClassLoaderBuilder setForcedJvmPackages(List<String> forcedJvmPackages) {
6968
return this;
7069
}
7170

72-
public URLClassLoaderBuilder setTransformer(final ClassFileTransformer transformer) {
73-
this.transformer = transformer;
74-
return this;
75-
}
76-
7771
URLClassLoaderBuilder setLogger(Log logger) {
7872
this.logger = logger;
7973
return this;
@@ -89,6 +83,11 @@ URLClassLoaderBuilder setPaths(Collection<Path> paths) {
8983
return this;
9084
}
9185

86+
URLClassLoaderBuilder withTransformers(boolean wiTransformers) {
87+
this.withTransformers = wiTransformers;
88+
return this;
89+
}
90+
9291
URLClassLoader build() throws IOException {
9392
List<URL> urls = new ArrayList<>(paths.size());
9493

@@ -107,7 +106,13 @@ URLClassLoader build() throws IOException {
107106
}
108107
}
109108

110-
return new ExecJavaClassLoader(urls.toArray(new URL[0]), transformer, forcedJvmPackages, excludedJvmPackages);
109+
URL[] urlsArray = urls.toArray(new URL[0]);
110+
BlockExitTransformer transformer = null;
111+
if (withTransformers) {
112+
transformer = new BlockExitTransformer(new URLClassLoader(urlsArray), logger);
113+
}
114+
115+
return new ExecJavaClassLoader(urlsArray, transformer, forcedJvmPackages, excludedJvmPackages);
111116
}
112117

113118
// child first strategy
@@ -121,13 +126,13 @@ private static class ExecJavaClassLoader extends URLClassLoader {
121126
}
122127

123128
private final String jre;
124-
private final ClassFileTransformer transformer;
129+
private final BlockExitTransformer transformer;
125130
private final List<String> forcedJvmPackages;
126131
private final List<String> excludedJvmPackages;
127132

128133
public ExecJavaClassLoader(
129134
URL[] urls,
130-
ClassFileTransformer transformer,
135+
BlockExitTransformer transformer,
131136
List<String> forcedJvmPackages,
132137
List<String> excludedJvmPackages) {
133138
super(urls);
@@ -137,6 +142,14 @@ public ExecJavaClassLoader(
137142
this.excludedJvmPackages = excludedJvmPackages;
138143
}
139144

145+
@Override
146+
public void close() throws IOException {
147+
super.close();
148+
if (transformer != null) {
149+
transformer.close();
150+
}
151+
}
152+
140153
@Override
141154
public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
142155
if (name == null) {
@@ -208,7 +221,7 @@ private Class<?> doFindClass(final String name) throws ClassNotFoundException {
208221

209222
try (final InputStream inputStream = url.openStream()) {
210223
final byte[] raw = IOUtil.toByteArray(inputStream);
211-
final byte[] res = transformer.transform(this, name, null, null, raw);
224+
final byte[] res = transformer.transform(null, name, null, null, raw);
212225
final byte[] bin = res == null ? raw : res;
213226
return super.defineClass(name, bin, 0, bin.length);
214227
} catch (final ClassFormatError | IOException | IllegalClassFormatException var4) {

0 commit comments

Comments
 (0)