| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.bcel.util; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.bcel.AbstractTest; |
| import org.apache.bcel.HelloWorldCreator; |
| import org.apache.bcel.classfile.JavaClass; |
| import org.apache.bcel.classfile.Utility; |
| import org.apache.bcel.generic.BinaryOpCreator; |
| import org.apache.commons.lang3.SystemProperties; |
| import org.apache.commons.lang3.SystemUtils; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.api.condition.DisabledForJreRange; |
| import org.junit.jupiter.api.condition.JRE; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.ValueSource; |
| |
| class BCELifierTest extends AbstractTest { |
| |
| private static Pattern CANON1 = Pattern.compile("#\\d+"); // numbers may vary in length |
| private static Pattern CANON2 = Pattern.compile(" +"); // collapse spaces |
| private static Pattern CANON3 = Pattern.compile("//.+"); |
| |
| private static final String EOL = System.lineSeparator(); |
| public static final String CLASSPATH = "." + File.pathSeparator + SystemProperties.getJavaClassPath(); |
| |
| // Canonicalise the javap output so it compares better |
| private String canonHashRef(String input) { |
| input = CANON1.matcher(input).replaceAll("#n"); |
| input = CANON2.matcher(input).replaceAll(" "); |
| return CANON3.matcher(input).replaceAll(""); |
| } |
| |
| private String exec(final File workDir, final String... args) throws Exception { |
| // System.out.print(workDir + ": "); |
| // Stream.of(args).forEach(e -> System.out.print(e + " ")); |
| // System.out.println(); |
| final ProcessBuilder pb = new ProcessBuilder(args); |
| pb.directory(workDir); |
| pb.redirectErrorStream(true); |
| final Process proc = pb.start(); |
| try (BufferedInputStream is = new BufferedInputStream(proc.getInputStream())) { |
| final byte[] buff = new byte[2048]; |
| int len; |
| |
| final StringBuilder sb = new StringBuilder(); |
| while ((len = is.read(buff)) != -1) { |
| sb.append(new String(buff, 0, len)); |
| } |
| final String output = sb.toString(); |
| assertEquals(0, proc.waitFor(), output); |
| return output; |
| } |
| } |
| |
| private String getAppJava() { |
| return getJavaHomeApp("java"); |
| } |
| |
| private String getAppJavaC() { |
| return getJavaHomeApp("javac"); |
| } |
| |
| private String getAppJavaP() { |
| return getJavaHomeApp("javap"); |
| } |
| |
| private String getJavaHomeApp(final String app) { |
| final Path path = getJavaHomeBinPath().resolve(app); |
| if (Files.exists(path)) { |
| return path.toAbsolutePath().toString(); |
| } |
| if (SystemUtils.IS_OS_WINDOWS && Files.exists(getJavaHomeBinPath().resolve(app + ".exe"))) { |
| return path.toAbsolutePath().toString(); |
| } |
| return app; |
| } |
| |
| private Path getJavaHomeBinPath() { |
| return SystemUtils.getJavaHomePath().resolve("bin"); |
| } |
| |
| @ParameterizedTest |
| @ValueSource(strings = { |
| // @formatter:off |
| "iadd 3 2 = 5", |
| "isub 3 2 = 1", |
| "imul 3 2 = 6", |
| "idiv 3 2 = 1", |
| "irem 3 2 = 1", |
| "iand 3 2 = 2", |
| "ior 3 2 = 3", |
| "ixor 3 2 = 1", |
| "ishl 4 1 = 8", |
| "ishr 4 1 = 2", |
| "iushr 4 1 = 2", |
| "ladd 3 2 = 5", |
| "lsub 3 2 = 1", |
| "lmul 3 2 = 6", |
| "ldiv 3 2 = 1", |
| "lrem 3 2 = 1", |
| "land 3 2 = 2", |
| "lor 3 2 = 3", |
| "lxor 3 2 = 1", |
| "lshl 4 1 = 8", |
| "lshr 4 1 = 2", |
| "lushr 4 1 = 2", |
| "fadd 3 2 = 5.0", |
| "fsub 3 2 = 1.0", |
| "fmul 3 2 = 6.0", |
| "fdiv 3 2 = 1.5", |
| "frem 3 2 = 1.0", |
| "dadd 3 2 = 5.0", |
| "dsub 3 2 = 1.0", |
| "dmul 3 2 = 6.0", |
| "ddiv 3 2 = 1.5", |
| "drem 3 2 = 1.0" |
| // @formatter:on |
| }) |
| void testBinaryOp(final String exp) throws Exception { |
| BinaryOpCreator.main(new String[] {}); |
| final File workDir = new File("target"); |
| final Pattern pattern = Pattern.compile("([a-z]{3,5}) ([-+]?\\d*\\.?\\d+) ([-+]?\\d*\\.?\\d+) = ([-+]?\\d*\\.?\\d+)"); |
| final Matcher matcher = pattern.matcher(exp); |
| assertTrue(matcher.matches()); |
| final String op = matcher.group(1); |
| final String a = matcher.group(2); |
| final String b = matcher.group(3); |
| final String expected = matcher.group(4); |
| final String javaAgent = getJavaAgent(); |
| if (javaAgent == null) { |
| assertEquals(expected + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, "org.apache.bcel.generic.BinaryOp", op, a, b)); |
| } else { |
| final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_org.apache.bcel.generic.BinaryOp.exec"); |
| assertEquals(expected + EOL, exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, "org.apache.bcel.generic.BinaryOp", op, a, b)); |
| } |
| } |
| |
| private void testClassOnPath(final String javaClassFileName) throws Exception { |
| final File workDir = new File("target", getClass().getSimpleName()); |
| Files.createDirectories(workDir.getParentFile().toPath()); |
| final File inFile = new File(javaClassFileName); |
| final JavaClass javaClass = BCELifier.getJavaClass(inFile.getName().replace(JavaClass.EXTENSION, "")); |
| assertNotNull(javaClass); |
| // Gets javap output of the input class |
| // System.out.println(exec(null, getJavaP(), "-version")); |
| final String javaClassName = javaClass.getClassName(); |
| final String javapOutInital = exec(null, getAppJavaP(), "-cp", CLASSPATH, "-p", "-c", javaClassName); |
| final String outCreatorFileName = javaClass.getSourceFilePath().replace(".java", "Creator.java"); |
| final File outCreatorFile = new File(workDir, outCreatorFileName); |
| Files.createDirectories(outCreatorFile.getParentFile().toPath()); |
| final String javaAgent = getJavaAgent(); |
| final String bcelifierClassName = BCELifier.class.getName(); |
| final String creatorJavaSource; |
| // Creates the BCEL Java app source in creatorJavaSource |
| if (javaAgent == null) { |
| creatorJavaSource = exec(workDir, getAppJava(), "-cp", CLASSPATH, bcelifierClassName, javaClassName); |
| } else { |
| final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_" + inFile.getName() + ".exec"); |
| creatorJavaSource = exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, bcelifierClassName, javaClassName); |
| } |
| // Write the BCEL Java app source to a file |
| Files.write(outCreatorFile.toPath(), creatorJavaSource.getBytes(StandardCharsets.UTF_8)); |
| // Compiles the BCEL Java app |
| assertEquals("", exec(workDir, getAppJavaC(), "-cp", CLASSPATH, outCreatorFileName.toString())); |
| final String creatorClassName = javaClassName + "Creator"; |
| // Runs the BCEL Java app to create a class file |
| if (javaAgent == null) { |
| assertEquals("", exec(workDir, getAppJava(), "-cp", CLASSPATH, creatorClassName)); |
| } else { |
| final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_" + Utility.pathToPackage(outCreatorFileName) + ".exec"); |
| assertEquals("", exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, creatorClassName)); |
| } |
| // Runs javap on the BCEL generated class file |
| final String javapOutput = exec(workDir, getAppJavaP(), "-p", "-c", inFile.getName()); |
| // Finally compares the output of the roundtrip |
| assertEquals(canonHashRef(javapOutInital), canonHashRef(javapOutput)); |
| } |
| |
| @Test |
| void testHelloWorld() throws Exception { |
| HelloWorldCreator.main(new String[] {}); |
| final File workDir = new File("target"); |
| final String javaAgent = getJavaAgent(); |
| if (javaAgent == null) { |
| assertEquals("Hello World!" + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, "org.apache.bcel.HelloWorld")); |
| } else { |
| final String runtimeExecJavaAgent = javaAgent.replace("jacoco.exec", "jacoco_org.apache.bcel.HelloWorld.exec"); |
| assertEquals("Hello World!" + EOL, exec(workDir, getAppJava(), runtimeExecJavaAgent, "-cp", CLASSPATH, "org.apache.bcel.HelloWorld")); |
| } |
| } |
| |
| /* |
| * Dumps a class using "javap" and compare with the same class recreated using BCELifier, "javac", "java" and dumped with "javap". |
| * |
| * TODO: detect if JDK present and skip test if not |
| */ |
| @ParameterizedTest |
| @ValueSource(strings = { |
| // @formatter:off |
| "org.apache.commons.lang.math.Fraction.class", |
| "org.apache.commons.lang.exception.NestableDelegate.class", |
| "org.apache.commons.lang.builder.CompareToBuilder.class", |
| "org.apache.commons.lang.builder.ToStringBuilder.class", |
| "org.apache.commons.lang.SerializationUtils.class", |
| "org.apache.commons.lang.ArrayUtils.class", |
| "target/test-classes/Java4Example.class" |
| // @formatter:on |
| }) |
| void testJavapCompare(final String pathToClass) throws Exception { |
| testClassOnPath(pathToClass); |
| } |
| |
| /* |
| * See https://issues.apache.org/jira/browse/BCEL-378 |
| */ |
| @ParameterizedTest |
| @ValueSource(strings = { |
| // @formatter:off |
| "target/test-classes/Java8Example.class", |
| "target/test-classes/Java8Example2.class", |
| // @formatter:on |
| }) |
| @DisabledForJreRange(min = JRE.JAVA_25) |
| void testJavapCompareJava25KnownBroken(final String pathToClass) throws Exception { |
| testClassOnPath(pathToClass); |
| } |
| |
| @Test |
| void testMainNoArg() throws Exception { |
| final PrintStream sysout = System.out; |
| try { |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| System.setOut(new PrintStream(out)); |
| BCELifier.main(new String[0]); |
| final String outputNoArgs = new String(out.toByteArray()); |
| assertEquals("Usage: BCELifier className" + EOL + "\tThe class must exist on the classpath" + EOL, outputNoArgs); |
| } finally { |
| System.setOut(sysout); |
| } |
| } |
| |
| @ParameterizedTest |
| @ValueSource(strings = { "StackMapExample", "StackMapExample2" }) |
| void testStackMap(final String className) throws Exception { |
| testJavapCompare(className); |
| final File workDir = new File("target"); |
| assertEquals("Hello World" + EOL, exec(workDir, getAppJava(), "-cp", CLASSPATH, className, "Hello")); |
| } |
| |
| @Test |
| void testStart() throws Exception { |
| final OutputStream os = new ByteArrayOutputStream(); |
| final JavaClass javaClass = BCELifier.getJavaClass("Java8Example"); |
| assertNotNull(javaClass); |
| final BCELifier bcelifier = new BCELifier(javaClass, os); |
| bcelifier.start(); |
| } |
| } |