Skip to content

Commit e852ffe

Browse files
authored
Replace SecurityManager use with Java agent (#143)
Starting in Java 24 the SecurityManager will be removed from the JDK. Load tests rely on the SecurityManager to intercept calls to System.exit and replace them with an exception to avoid shutting down before all the tests have run. I replaced this with a Java agent that uses ASM to rewrite System.exit calls to throw an exception instead. This change also works for previous Java versions. This change also updates the ASM library version from 9.0 to 9.7.1 to include Java 24 support. It also adds the asm-commons library as a dependency which is needed to use the org.objectweb.asm.commons.LocalVariablesSorter library. Signed-off-by: Theresa Mammarella <[email protected]>
1 parent 5b80d94 commit e852ffe

File tree

11 files changed

+154
-45
lines changed

11 files changed

+154
-45
lines changed

stf.build/docs/build.md

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ These prereqs must be installed before attempting to build STF
3434
| perl 5.6.1 or later | http://perldoc.perl.org/index-licence.html | stf.core | Windows - tests can be executed using Strawberry perl. Other perl implementations may be OK too. | Add to PATH | No |
3535
| Windows Sysinternals | https://technet.microsoft.com/en-us/sysinternals/bb469936.aspx| stf.core | Download from https://download.sysinternals.com/files/SysinternalsSuite.zip | Unzip to PREREQS_ROOT/windows_sysinternals | Yes |
3636
| wget | https://www.gnu.org/copyleft/gpl.html | stf.build | Windows - download from https://sourceforge.net/projects/gnuwin32/files/wget/1.11.4-1/wget-1.11.4-1-bin.zip | Add to PATH | No |
37+
| asm 9.7.1 | https://asm.ow2.io/ | stf.load | Download from https://repository.ow2.org/nexus/content/repositories/releases/org/ow2/asm/asm/9.7.1/asm-9.7.1.jar | Copy to PREREQS_ROOT/asm/asm.jar | Yes |
38+
| asm-commons 9.7.1 | https://asm.ow2.io/ | stf.load | Download from https://repository.ow2.org/nexus/content/repositories/releases/org/ow2/asm/asm-commons/9.7.1/asm-commons-9.7.1.jar | Copy to PREREQS_ROOT/asm/asm-commons.jar | Yes |
3739

3840
## Building from a command line
3941
1. `git clone https://github.com/adoptium/STF.git stf`

stf.build/include/top.xml

+20-4
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ limitations under the License.
4949
</condition>
5050

5151
<!--Following are the systemtest prereq jar versions currently in use-->
52-
<property name="asm-version" value="9.0"/>
52+
<property name="asm-version" value="9.7.1"/>
5353
<property name="ant-version" value="1.10.2"/>
5454
<property name="log4j-version" value="2.16.0"/>
5555
<property name="junit-version" value="4.12"/>
@@ -136,6 +136,7 @@ limitations under the License.
136136
</path>
137137
<path id="asm.class.path">
138138
<pathelement location="${first_prereqs_root}/asm/asm.jar"/>
139+
<pathelement location="${first_prereqs_root}/asm/asm-commons.jar"/>
139140
</path>
140141
<path id="stf.class.path">
141142
<pathelement location="${stf_root}/stf.core/bin/stf.core.jar"/>
@@ -343,16 +344,30 @@ limitations under the License.
343344
<target name="check-for-asm">
344345
<property name="asm_dir" location="${first_prereqs_root}/asm"/>
345346
<property name="asm_file" value="asm.jar"/>
347+
<property name="asm_commons_file" value="asm-commons.jar"/>
346348
<property name="asm" value="${asm_dir}/${asm_file}"/>
347-
<available file="${asm_dir}/${asm_file}" property="asm_available"/>
349+
<property name="asm_commons" value="${asm_dir}/${asm_commons_file}"/>
350+
<condition property="asm_available">
351+
<and>
352+
<available file="${asm}"/>
353+
<available file="${asm_commons}"/>
354+
</and>
355+
</condition>
348356
</target>
349357

350358
<target name="configure-asm" unless="asm_available">
351359
<!-- Fetch asm from https://repository.ow2.org/etc. -->
352360
<delete dir="${first_prereqs_root}/asm" failonerror="false"/>
353361
<download-file destdir="${asm_dir}" destfile="${asm_file}" srcurl="https://repository.ow2.org/nexus/content/repositories/releases/org/ow2/asm/asm/${asm-version}/asm-${asm-version}.jar"/>
362+
<download-file destdir="${asm_dir}" destfile="${asm_commons_file}" srcurl="https://repository.ow2.org/nexus/content/repositories/releases/org/ow2/asm/asm-commons/${asm-version}/asm-commons-${asm-version}.jar"/>
354363
<property name="asm" value="${asm_dir}/${asm_file}"/>
355-
<available file="${asm}" property="asm_available"/>
364+
<property name="asm-commons" value="${asm_dir}/${asm_commons_file}"/>
365+
<condition property="asm_available">
366+
<and>
367+
<available file="${asm}"/>
368+
<available file="${asm_commons}"/>
369+
</and>
370+
</condition>
356371
</target>
357372

358373
<condition property="isZOS" value="true">
@@ -416,6 +431,7 @@ limitations under the License.
416431

417432
<target name="print-asm-location" if="asm_available">
418433
<echo message="Using ${asm_file} from ${asm}"/>
434+
<echo message="Using ${asm_commons_file} from ${asm_commons}"/>
419435
</target>
420436

421437
<target name="print-ant-error" unless="ant_available">
@@ -439,7 +455,7 @@ limitations under the License.
439455
</target>
440456

441457
<target name="print-asm-error" unless="asm_available">
442-
<echo message="ERROR: Cannot find ${asm_file} at ${asm}"/>
458+
<echo message="ERROR: Cannot find ${asm_file} at ${asm} and / or ${asm_commons_file} at ${asm_commons}"/>
443459
<property name="fail_run" value="true"/>
444460
</target>
445461

stf.core/config/stf.core.properties

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ junit-jar = /systemtest-prereqs/junit/junit.jar
1717
hamcrest-core-jar = /systemtest-prereqs/junit/hamcrest-core.jar
1818
log4j-api-jar = /systemtest-prereqs/log4j/log4j-api.jar
1919
log4j-core-jar = /systemtest-prereqs/log4j/log4j-core.jar
20+
asm-jar = /systemtest-prereqs/asm/asm.jar
21+
asm-commons-jar = /systemtest-prereqs/asm/asm-commons.jar
2022
#
2123
# Set the default location for apps-root.
2224
# Should no longer be required since systemtest-prereqs can now take multiple paths.

stf.core/scripts/stf.pl

+4-1
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,14 @@
428428
my $sep = stf::stfUtility->getPathSeparator;
429429
my $log4j_core_dir = findElement($prereqs_root, "/log4j/log4j-core.jar");
430430
my $log4j_api_dir = findElement($prereqs_root, "/log4j/log4j-api.jar");
431+
my $asm_jar = findElement($prereqs_root, "/asm/asm.jar");
432+
my $asm_commons_jar = findElement($prereqs_root, "/asm/asm-commons.jar");
431433
my $cmd = "$javahome_generation/bin/java " .
432434
"$java_debug_settings" .
433435
" -Dlog4j.skipJansi=true" . # Suppress warning on Windows
434436
" -Djava.system.class.loader=net.adoptopenjdk.stf.runner.StfClassLoader" .
435-
" -classpath $log4j_api_dir" . $sep . "$log4j_core_dir" . $sep . "$Bin/../bin" .
437+
" -Dload.agent.path=$Bin/../../stf.load/bin/stf.load.jar" .
438+
" -classpath $asm_jar" . $sep . "$asm_commons_jar" . $sep . "$log4j_api_dir" . $sep . "$log4j_core_dir" . $sep . "$Bin/../bin" .
436439
" net.adoptopenjdk.stf.runner.StfRunner" .
437440
" -properties \"$stf_parameters, $stf_personal_properties, $stf_defaults\"" .
438441
" -testDir \"$test_dir\"";

stf.core/src/stf.core/net/adoptopenjdk/stf/extensions/core/StfCoreExtension.java

+14
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public class StfCoreExtension implements StfExtension {
7777
public static Argument ARG_HAMCREST_CORE_JAR = new Argument("stfCore", "hamcrest-core-jar", false, Required.MANDATORY);
7878
public static Argument ARG_LOG4J_API_JAR = new Argument("stfCore", "log4j-api-jar", false, Required.MANDATORY);
7979
public static Argument ARG_LOG4J_CORE_JAR = new Argument("stfCore", "log4j-core-jar", false, Required.MANDATORY);
80+
public static Argument ARG_ASM_JAR = new Argument("stfCore", "asm-jar", false, Required.MANDATORY);
81+
public static Argument ARG_ASM_COMMONS_JAR = new Argument("stfCore", "asm-commons-jar", false, Required.MANDATORY);
8082
public static Argument ARG_APPS_ROOT = new Argument("stfCore", "apps-root", false, Required.MANDATORY);
8183
public static Argument ARG_MODE = new Argument("stfCore", "mode", false, Required.MANDATORY);
8284

@@ -96,6 +98,8 @@ public Argument[] getSupportedArguments() {
9698
ARG_HAMCREST_CORE_JAR,
9799
ARG_LOG4J_API_JAR,
98100
ARG_LOG4J_CORE_JAR,
101+
ARG_ASM_JAR,
102+
ARG_ASM_COMMONS_JAR,
99103
ARG_APPS_ROOT,
100104
ARG_MODE,
101105
};
@@ -116,6 +120,12 @@ public void help(HelpTextGenerator help) {
116120

117121
help.outputArgName("-" + ARG_LOG4J_CORE_JAR.getName(), "FILE");
118122
help.outputArgDesc("Points to the location of the log4j core jar file.");
123+
124+
help.outputArgName("-" + ARG_ASM_JAR.getName(), "FILE");
125+
help.outputArgDesc("Points to the location of the asm jar file.");
126+
127+
help.outputArgName("-" + ARG_ASM_COMMONS_JAR.getName(), "FILE");
128+
help.outputArgDesc("Points to the location of the asm-commons jar file.");
119129
}
120130

121131
public void initialise(StfEnvironmentCore environmentCore, StfExtensionBase extensionBase, PerlCodeGenerator generator) throws StfException {
@@ -1055,11 +1065,15 @@ public LoadTestProcessDefinition createLoadTestSpecification() throws StfExcepti
10551065
* @throws StfException if an internal error is detected.
10561066
*/
10571067
public LoadTestProcessDefinition createLoadTestSpecification(JavaVersion jvm) throws StfException {
1068+
String agentPath = System.getProperty("load.agent.path");
10581069
LoadTestProcessDefinition loadTestInvocation = new LoadTestProcessDefinition(environmentCore, jvm)
1070+
.addJvmOption("-javaagent:" + agentPath)
10591071
.addProjectToClasspath("stf.load") // stf.load goes first to make sure we pick up the correct log4j config file
10601072
.addProjectToClasspath("stf.core")
10611073
.addPrereqJarToClasspath(JavaProcessDefinition.JarId.LOG4J_API)
10621074
.addPrereqJarToClasspath(JavaProcessDefinition.JarId.LOG4J_CORE)
1075+
.addPrereqJarToClasspath(JavaProcessDefinition.JarId.ASM)
1076+
.addPrereqJarToClasspath(JavaProcessDefinition.JarId.ASM_COMMONS)
10631077
.runClass("net.adoptopenjdk.loadTest.LoadTest")
10641078
.setResultsDir(environmentCore.getResultsDir());
10651079

stf.core/src/stf.core/net/adoptopenjdk/stf/processes/definitions/JavaProcessDefinition.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,9 @@ public enum JarId {
529529
JUNIT(StfCoreExtension.ARG_JUNIT_JAR),
530530
HAMCREST(StfCoreExtension.ARG_HAMCREST_CORE_JAR),
531531
LOG4J_API(StfCoreExtension.ARG_LOG4J_API_JAR),
532-
LOG4J_CORE(StfCoreExtension.ARG_LOG4J_CORE_JAR);
532+
LOG4J_CORE(StfCoreExtension.ARG_LOG4J_CORE_JAR),
533+
ASM(StfCoreExtension.ARG_ASM_JAR),
534+
ASM_COMMONS(StfCoreExtension.ARG_ASM_COMMONS_JAR);
533535

534536
private Argument jarLocation;
535537
private JarId(Argument jarLocation) { this.jarLocation = jarLocation; }

stf.load/build.xml

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ limitations under the License.
2727
<path id="project.class.path">
2828
<path refid="junit.class.path" />
2929
<path refid="log4j.class.path" />
30+
<path refid="asm.class.path" />
3031
<pathelement location="${stf_root}/stf.core/bin/stf.core.jar"/>
3132
</path>
3233

@@ -58,6 +59,10 @@ limitations under the License.
5859
<target name="build-jar" depends="build-java, create-bin-dir">
5960
<jar destfile="${stf_load_jar_file}">
6061
<fileset dir="${stf_load_bin_dir}" includes="**/*.class" />
62+
<manifest>
63+
<attribute name="Premain-Class" value="net.adoptopenjdk.blockedexitagent.BlockedExitAgent"/>
64+
<attribute name="Can-Retransform-Classes" value="true"/>
65+
</manifest>
6166
</jar>
6267
</target>
6368

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*******************************************************************************
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*******************************************************************************/
14+
15+
package net.adoptopenjdk.blockedexitagent;
16+
17+
import java.lang.instrument.*;
18+
import java.security.ProtectionDomain;
19+
import org.objectweb.asm.*;
20+
import org.objectweb.asm.commons.LocalVariablesSorter;
21+
22+
import static org.objectweb.asm.Opcodes.*;
23+
24+
/*
25+
* Replace all calls to System.exit for load tests with BlockedExitException.
26+
* This ensures the test framework will not shut down before all tests
27+
* have completed.
28+
*/
29+
class BlockedExitAgent {
30+
public static void premain(String args, Instrumentation instrumentation) {
31+
instrumentation.addTransformer(new BlockedExitTransformer(), true);
32+
}
33+
static class BlockedExitTransformer implements ClassFileTransformer {
34+
@Override
35+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) {
36+
/* System.exit calls in LoadTest class should not be overwritten. */
37+
if ((null != loader) && (!className.contains("net/adoptopenjdk/loadTest/LoadTest"))) {
38+
ClassReader cr = new ClassReader(classBytes);
39+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
40+
cr.accept(new BlockedExitClassVisitor(cw), ClassReader.EXPAND_FRAMES);
41+
return cw.toByteArray();
42+
} else {
43+
return null;
44+
}
45+
}
46+
}
47+
48+
public static class BlockedExitClassVisitor extends ClassVisitor {
49+
public BlockedExitClassVisitor(ClassVisitor cv) {
50+
super(ASM9, cv);
51+
}
52+
53+
@Override
54+
public MethodVisitor visitMethod(int methodAccess, String methodName, String methodDesc, String signature, String[] exceptions) {
55+
MethodVisitor methodVisitor = cv.visitMethod(methodAccess, methodName, methodDesc, signature, exceptions);
56+
return new BlockedExitMethodVisitor(methodAccess, methodDesc, methodVisitor);
57+
}
58+
}
59+
60+
static class BlockedExitMethodVisitor extends LocalVariablesSorter {
61+
public BlockedExitMethodVisitor(int access, String descriptor, MethodVisitor methodVisitor) {
62+
super(ASM9, access, descriptor, methodVisitor);
63+
}
64+
65+
@Override
66+
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
67+
if (isSystemExitInsn(opcode, owner, name, descriptor)) {
68+
String blockedExitException = "net/adoptopenjdk/loadTest/BlockedExitException";
69+
/* The bytecode just before this will have loaded the exit code
70+
* onto the stack. Store it in a new local variable so it can
71+
* be passed into the new exception.
72+
*/
73+
int localId = super.newLocal(Type.INT_TYPE);
74+
75+
super.visitVarInsn(ISTORE, localId);
76+
super.visitTypeInsn(NEW, blockedExitException);
77+
super.visitInsn(DUP);
78+
super.visitVarInsn(ILOAD, localId);
79+
super.visitMethodInsn(INVOKESPECIAL, blockedExitException, "<init>", "(I)V");
80+
super.visitInsn(ATHROW);
81+
} else {
82+
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
83+
}
84+
}
85+
86+
private boolean isSystemExitInsn(int opcode, String owner, String name, String descriptor) {
87+
return (opcode == INVOKESTATIC)
88+
&& "java/lang/System".equals(owner)
89+
&& "exit".equals(name)
90+
&& "(I)V".equals(descriptor);
91+
}
92+
}
93+
}

stf.load/src/stf.load/net/adoptopenjdk/loadTest/LoadTest.java

+1-28
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package net.adoptopenjdk.loadTest;
1616

1717
import java.io.File;
18-
import java.security.Permission;
1918
import java.util.ArrayList;
2019
import java.util.Arrays;
2120
import java.util.HashMap;
@@ -108,48 +107,22 @@ public static void main(String[] args) {
108107
logger.fatal("Failed to initialise LoadTest", e);
109108
System.exit(2);
110109
}
111-
112-
// Setup a security manager to block System.exit attempts
113-
SecurityManager defaultSecurityManager = System.getSecurityManager();
114-
overrideSecurityManager();
115110

116111
// Run the tests
117112
long numberFailingTests = -1;
118113
try {
119114
numberFailingTests = loadTest.runLoadTest();
120115
} catch (Exception e) {
121116
logger.fatal("Failed during LoadTest execution", e);
122-
System.exit(3);
117+
throw new BlockedExitException(3);
123118
}
124119

125-
// Restore original security manager
126-
System.setSecurityManager(defaultSecurityManager);
127-
128120
// Exit with a non-zero value if a test has failed
129121
int exitCode = numberFailingTests == 0 ? 0 : 1;
130122
System.exit(exitCode);
131123
}
132124

133125

134-
private static void overrideSecurityManager() {
135-
System.setSecurityManager(new SecurityManager() {
136-
@Override
137-
public void checkExit(int status) {
138-
// Don't allow the test to exit the process
139-
super.checkExit(status);
140-
throw new BlockedExitException(status);
141-
}
142-
143-
public void checkPermission(Permission perm) {
144-
}
145-
146-
// Don't block permission check, so that log4j will work
147-
public void checkPermission(Permission perm, Object context) {
148-
}
149-
});
150-
}
151-
152-
153126
private long runLoadTest() throws Exception {
154127
// Create now, so that it's ready to go to work when a failure happens
155128
FirstFailureDumper.createInstance();

stf.load/src/stf.load/net/adoptopenjdk/loadTest/LoadTestRunner.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,16 @@ public void run() {
180180
ResultStatus testResult = null;
181181
try {
182182
testResult = test.executeTest();
183-
} catch (InvocationTargetException e) {
184-
if (e.getCause() instanceof BlockedExitException) {
185-
// The test has attempted to call System.exit(). Keep running.
186-
BlockedExitException exitException = (BlockedExitException) e.getCause();
187-
if (exitException.getExitValue() == 0) {
188-
testResult = ResultStatus.BLOCKED_EXIT_PASS;
189-
} else {
190-
testResult = ResultStatus.BLOCKED_EXIT_FAIL;
191-
}
183+
} catch(BlockedExitException exitException) {
184+
// The test has attempted to call System.exit(). Keep running.
185+
if (exitException.getExitValue() == 0) {
186+
testResult = ResultStatus.BLOCKED_EXIT_PASS;
192187
} else {
193-
// Some other invocation exception. Rethrow to log as test failure.
194-
throw e;
188+
testResult = ResultStatus.BLOCKED_EXIT_FAIL;
195189
}
190+
} catch (InvocationTargetException e) {
191+
// Some other exception. Rethrow to log as test failure.
192+
throw e;
196193
}
197194

198195
// Test completed. Record pass/fail result to file

stf.samples/.classpath

+2
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
<classpathentry kind="lib" path="/systemtest_prereqs/junit/junit.jar"/>
88
<classpathentry kind="lib" path="/systemtest_prereqs/log4j/log4j-api.jar"/>
99
<classpathentry kind="lib" path="/systemtest_prereqs/log4j/log4j-core.jar"/>
10+
<classpathentry kind="lib" path="/systemtest_prereqs/asm/asm.jar"/>
11+
<classpathentry kind="lib" path="/systemtest_prereqs/asm/asm-commons.jar"/>
1012
<classpathentry kind="output" path="bin"/>
1113
</classpath>

0 commit comments

Comments
 (0)