1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.bcel.util;
20
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24
25 import java.io.BufferedInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.File;
28 import java.io.OutputStream;
29 import java.io.PrintStream;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35
36 import org.apache.bcel.AbstractTest;
37 import org.apache.bcel.HelloWorldCreator;
38 import org.apache.bcel.classfile.JavaClass;
39 import org.apache.bcel.classfile.Utility;
40 import org.apache.bcel.generic.BinaryOpCreator;
41 import org.apache.commons.lang3.SystemProperties;
42 import org.apache.commons.lang3.SystemUtils;
43 import org.junit.jupiter.api.Test;
44 import org.junit.jupiter.api.condition.DisabledForJreRange;
45 import org.junit.jupiter.api.condition.JRE;
46 import org.junit.jupiter.params.ParameterizedTest;
47 import org.junit.jupiter.params.provider.ValueSource;
48
49 class BCELifierTest extends AbstractTest {
50
51 private static Pattern CANON1 = Pattern.compile("#\\d+");
52 private static Pattern CANON2 = Pattern.compile(" +");
53 private static Pattern CANON3 = Pattern.compile("//.+");
54
55 private static final String EOL = System.lineSeparator();
56 public static final String CLASSPATH = "." + File.pathSeparator + SystemProperties.getJavaClassPath();
57
58
59 private String canonHashRef(String input) {
60 input = CANON1.matcher(input).replaceAll("#n");
61 input = CANON2.matcher(input).replaceAll(" ");
62 return CANON3.matcher(input).replaceAll("");
63 }
64
65 private String exec(final File workDir, final String... args) throws Exception {
66
67
68
69 final ProcessBuilder pb = new ProcessBuilder(args);
70 pb.directory(workDir);
71 pb.redirectErrorStream(true);
72 final Process proc = pb.start();
73 try (BufferedInputStream is = new BufferedInputStream(proc.getInputStream())) {
74 final byte[] buff = new byte[2048];
75 int len;
76
77 final StringBuilder sb = new StringBuilder();
78 while ((len = is.read(buff)) != -1) {
79 sb.append(new String(buff, 0, len));
80 }
81 final String output = sb.toString();
82 assertEquals(0, proc.waitFor(), output);
83 return output;
84 }
85 }
86
87 private String getAppJava() {
88 return getJavaHomeApp("java");
89 }
90
91 private String getAppJavaC() {
92 return getJavaHomeApp("javac");
93 }
94
95 private String getAppJavaP() {
96 return getJavaHomeApp("javap");
97 }
98
99 private String getJavaHomeApp(final String app) {
100 final Path path = getJavaHomeBinPath().resolve(app);
101 if (Files.exists(path)) {
102 return path.toAbsolutePath().toString();
103 }
104 if (SystemUtils.IS_OS_WINDOWS && Files.exists(getJavaHomeBinPath().resolve(app + ".exe"))) {
105 return path.toAbsolutePath().toString();
106 }
107 return app;
108 }
109
110 private Path getJavaHomeBinPath() {
111 return SystemUtils.getJavaHomePath().resolve("bin");
112 }
113
114 @ParameterizedTest
115 @ValueSource(strings = {
116
117 "iadd 3 2 = 5",
118 "isub 3 2 = 1",
119 "imul 3 2 = 6",
120 "idiv 3 2 = 1",
121 "irem 3 2 = 1",
122 "iand 3 2 = 2",
123 "ior 3 2 = 3",
124 "ixor 3 2 = 1",
125 "ishl 4 1 = 8",
126 "ishr 4 1 = 2",
127 "iushr 4 1 = 2",
128 "ladd 3 2 = 5",
129 "lsub 3 2 = 1",
130 "lmul 3 2 = 6",
131 "ldiv 3 2 = 1",
132 "lrem 3 2 = 1",
133 "land 3 2 = 2",
134 "lor 3 2 = 3",
135 "lxor 3 2 = 1",
136 "lshl 4 1 = 8",
137 "lshr 4 1 = 2",
138 "lushr 4 1 = 2",
139 "fadd 3 2 = 5.0",
140 "fsub 3 2 = 1.0",
141 "fmul 3 2 = 6.0",
142 "fdiv 3 2 = 1.5",
143 "frem 3 2 = 1.0",
144 "dadd 3 2 = 5.0",
145 "dsub 3 2 = 1.0",
146 "dmul 3 2 = 6.0",
147 "ddiv 3 2 = 1.5",
148 "drem 3 2 = 1.0"
149
150 })
151 void testBinaryOp(final String exp) throws Exception {
152 BinaryOpCreator.main(new String[] {});
153 final File workDir = new File("target");
154 final Pattern pattern = Pattern.compile("([a-z]{3,5}) ([-+]?\\d*\\.?\\d+) ([-+]?\\d*\\.?\\d+) = ([-+]?\\d*\\.?\\d+)");
155 final Matcher matcher = pattern.matcher(exp);
156 assertTrue(matcher.matches());
157 final String op = matcher.group(1);
158 final String a = matcher.group(2);
159 final String b = matcher.group(3);
160 final String expected = matcher.group(4);
161 final String javaAgent = getJavaAgent();
162 if (javaAgent == null) {
163 assertEquals(expected + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, "org.apache.bcel.generic.BinaryOp", op, a, b));
164 } else {
165 final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_org.apache.bcel.generic.BinaryOp.exec");
166 assertEquals(expected + EOL, exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, "org.apache.bcel.generic.BinaryOp", op, a, b));
167 }
168 }
169
170 private void testClassOnPath(final String javaClassFileName) throws Exception {
171 final File workDir = new File("target", getClass().getSimpleName());
172 Files.createDirectories(workDir.getParentFile().toPath());
173 final File inFile = new File(javaClassFileName);
174 final JavaClass javaClass = BCELifier.getJavaClass(inFile.getName().replace(JavaClass.EXTENSION, ""));
175 assertNotNull(javaClass);
176
177
178 final String javaClassName = javaClass.getClassName();
179 final String javapOutInital = exec(null, getAppJavaP(), "-cp", CLASSPATH, "-p", "-c", javaClassName);
180 final String outCreatorFileName = javaClass.getSourceFilePath().replace(".java", "Creator.java");
181 final File outCreatorFile = new File(workDir, outCreatorFileName);
182 Files.createDirectories(outCreatorFile.getParentFile().toPath());
183 final String javaAgent = getJavaAgent();
184 final String bcelifierClassName = BCELifier.class.getName();
185 final String creatorJavaSource;
186
187 if (javaAgent == null) {
188 creatorJavaSource = exec(workDir, getAppJava(), "-cp", CLASSPATH, bcelifierClassName, javaClassName);
189 } else {
190 final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_" + inFile.getName() + ".exec");
191 creatorJavaSource = exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, bcelifierClassName, javaClassName);
192 }
193
194 Files.write(outCreatorFile.toPath(), creatorJavaSource.getBytes(StandardCharsets.UTF_8));
195
196 assertEquals("", exec(workDir, getAppJavaC(), "-cp", CLASSPATH, outCreatorFileName.toString()));
197 final String creatorClassName = javaClassName + "Creator";
198
199 if (javaAgent == null) {
200 assertEquals("", exec(workDir, getAppJava(), "-cp", CLASSPATH, creatorClassName));
201 } else {
202 final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_" + Utility.pathToPackage(outCreatorFileName) + ".exec");
203 assertEquals("", exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, creatorClassName));
204 }
205
206 final String javapOutput = exec(workDir, getAppJavaP(), "-p", "-c", inFile.getName());
207
208 assertEquals(canonHashRef(javapOutInital), canonHashRef(javapOutput));
209 }
210
211 @Test
212 void testHelloWorld() throws Exception {
213 HelloWorldCreator.main(new String[] {});
214 final File workDir = new File("target");
215 final String javaAgent = getJavaAgent();
216 if (javaAgent == null) {
217 assertEquals("Hello World!" + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, "org.apache.bcel.HelloWorld"));
218 } else {
219 final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_org.apache.bcel.HelloWorld.exec");
220 assertEquals("Hello World!" + EOL, exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, "org.apache.bcel.HelloWorld"));
221 }
222 }
223
224
225
226
227
228
229 @ParameterizedTest
230 @ValueSource(strings = {
231
232 "org.apache.commons.lang.math.Fraction.class",
233 "org.apache.commons.lang.exception.NestableDelegate.class",
234 "org.apache.commons.lang.builder.CompareToBuilder.class",
235 "org.apache.commons.lang.builder.ToStringBuilder.class",
236 "org.apache.commons.lang.SerializationUtils.class",
237 "org.apache.commons.lang.ArrayUtils.class",
238 "target/test-classes/Java4Example.class"
239
240 })
241 void testJavapCompare(final String pathToClass) throws Exception {
242 testClassOnPath(pathToClass);
243 }
244
245
246
247
248 @ParameterizedTest
249 @ValueSource(strings = {
250
251 "target/test-classes/Java8Example.class",
252 "target/test-classes/Java8Example2.class",
253
254 })
255 @DisabledForJreRange(min = JRE.JAVA_25)
256 void testJavapCompareJava25KnownBroken(final String pathToClass) throws Exception {
257 testClassOnPath(pathToClass);
258 }
259
260 @Test
261 void testMainNoArg() throws Exception {
262 final PrintStream sysout = System.out;
263 try {
264 final ByteArrayOutputStream out = new ByteArrayOutputStream();
265 System.setOut(new PrintStream(out));
266 BCELifier.main(new String[0]);
267 final String outputNoArgs = new String(out.toByteArray());
268 assertEquals("Usage: BCELifier className" + EOL + "\tThe class must exist on the classpath" + EOL, outputNoArgs);
269 } finally {
270 System.setOut(sysout);
271 }
272 }
273
274 @ParameterizedTest
275 @ValueSource(strings = { "StackMapExample", "StackMapExample2" })
276 void testStackMap(final String className) throws Exception {
277 testJavapCompare(className);
278 final File workDir = new File("target");
279 assertEquals("Hello World" + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, className, "Hello"));
280 }
281
282 @Test
283 void testStart() throws Exception {
284 final OutputStream os = new ByteArrayOutputStream();
285 final JavaClass javaClass = BCELifier.getJavaClass("Java8Example");
286 assertNotNull(javaClass);
287 final BCELifier bcelifier = new BCELifier(javaClass, os);
288 bcelifier.start();
289 }
290 }