Commit e781f932 authored by Eric Bruneton's avatar Eric Bruneton
Browse files

Use the arrange-act-asset pattern for the ASM util tests.

parent f4858361
Pipeline #4183 passed with stage
in 11 minutes and 52 seconds
......@@ -28,6 +28,7 @@
package org.objectweb.asm.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
......@@ -47,6 +48,11 @@ import org.objectweb.asm.TypePath;
// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
public class ASMifier extends Printer {
/** The help message shown when command line arguments are incorrect. */
private static final String USAGE =
"Prints the ASM code to generate the given class.\n"
+ "Usage: ASMifier [-debug] <fully qualified class name or class file name>";
/** A pseudo access flag used to distinguish class access flags. */
private static final int ACCESS_CLASS = 0x40000;
......@@ -131,10 +137,22 @@ public class ASMifier extends Printer {
* @throws IOException if the class cannot be found, or if an IOException occurs.
*/
public static void main(final String[] args) throws IOException {
String usage =
"Prints the ASM code to generate the given class.\n"
+ "Usage: ASMifier [-debug] <fully qualified class name or class file name>";
main(usage, new ASMifier(), args);
main(args, new PrintWriter(System.out, true), new PrintWriter(System.err, true));
}
/**
* Prints the ASM source code to generate the given class to the given output.
*
* <p>Usage: ASMifier [-debug] &lt;binary class name or class file name&gt;
*
* @param args the command line arguments.
* @param output where to print the result.
* @param logger where to log errors.
* @throws IOException if the class cannot be found, or if an IOException occurs.
*/
static void main(final String[] args, final PrintWriter output, final PrintWriter logger)
throws IOException {
main(args, USAGE, new ASMifier(), output, logger);
}
// -----------------------------------------------------------------------------------------------
......
......@@ -109,6 +109,11 @@ import org.objectweb.asm.tree.analysis.SimpleVerifier;
*/
public class CheckClassAdapter extends ClassVisitor {
/** The help message shown when command line arguments are incorrect. */
private static final String USAGE =
"Verifies the given class.\n"
+ "Usage: CheckClassAdapter <fully qualified class name or class file name>";
private static final String ERROR_AT = ": error at index ";
/** Whether the bytecode must be checked with a BasicVerifier. */
......@@ -948,10 +953,19 @@ public class CheckClassAdapter extends ClassVisitor {
* @throws IOException if the class cannot be found, or if an IO exception occurs.
*/
public static void main(final String[] args) throws IOException {
main(args, new PrintWriter(System.err, true));
}
/**
* Checks the given class.
*
* @param args the command line arguments.
* @param logger where to log errors.
* @throws IOException if the class cannot be found, or if an IO exception occurs.
*/
static void main(final String[] args, final PrintWriter logger) throws IOException {
if (args.length != 1) {
System.err.println(
"Verifies the given class.\n"
+ "Usage: CheckClassAdapter <fully qualified class name or class file name>");
logger.println(USAGE);
return;
}
......@@ -964,7 +978,7 @@ public class CheckClassAdapter extends ClassVisitor {
classReader = new ClassReader(args[0]);
}
verify(classReader, false, new PrintWriter(System.err));
verify(classReader, false, logger);
}
/**
......
......@@ -205,7 +205,7 @@ public class CheckModuleAdapter extends ModuleVisitor {
void checkNameNotAlreadyDeclared(final String name) {
if (!names.add(name)) {
throw new IllegalArgumentException(type + " " + name + " already declared");
throw new IllegalArgumentException(type + " '" + name + "' already declared");
}
}
}
......
......@@ -171,7 +171,7 @@ public class CheckSignatureAdapter extends SignatureVisitor {
@Override
public SignatureVisitor visitInterfaceBound() {
if (type == TYPE_SIGNATURE || !VISIT_INTERFACE_BOUND_STATES.contains(state)) {
throw new IllegalArgumentException();
throw new IllegalStateException();
}
return new CheckSignatureAdapter(
TYPE_SIGNATURE, signatureVisitor == null ? null : signatureVisitor.visitInterfaceBound());
......@@ -182,7 +182,7 @@ public class CheckSignatureAdapter extends SignatureVisitor {
@Override
public SignatureVisitor visitSuperclass() {
if (type != CLASS_SIGNATURE || !VISIT_SUPER_CLASS_STATES.contains(state)) {
throw new IllegalArgumentException();
throw new IllegalStateException();
}
state = State.SUPER;
return new CheckSignatureAdapter(
......@@ -203,7 +203,7 @@ public class CheckSignatureAdapter extends SignatureVisitor {
@Override
public SignatureVisitor visitParameterType() {
if (type != METHOD_SIGNATURE || !VISIT_PARAMETER_TYPE_STATES.contains(state)) {
throw new IllegalArgumentException();
throw new IllegalStateException();
}
state = State.PARAM;
return new CheckSignatureAdapter(
......@@ -213,7 +213,7 @@ public class CheckSignatureAdapter extends SignatureVisitor {
@Override
public SignatureVisitor visitReturnType() {
if (type != METHOD_SIGNATURE || !VISIT_RETURN_TYPE_STATES.contains(state)) {
throw new IllegalArgumentException();
throw new IllegalStateException();
}
state = State.RETURN;
CheckSignatureAdapter checkSignatureAdapter =
......
......@@ -1213,24 +1213,30 @@ public abstract class Printer {
}
/**
* Prints a the given class to the standard output.
* Prints a the given class to the given output.
*
* <p>Command line arguments: [-debug] &lt;binary class name or class file name &gt;
*
* @param args the command line arguments.
* @param usage the help message to show when command line arguments are incorrect.
* @param printer the printer to convert the class into text.
* @param args the command line arguments.
* @param output where to print the result.
* @param logger where to log errors.
* @throws IOException if the class cannot be found, or if an IOException occurs.
*/
static void main(final String usage, final Printer printer, final String[] args)
static void main(
final String[] args,
final String usage,
final Printer printer,
final PrintWriter output,
final PrintWriter logger)
throws IOException {
if (args.length < 1 || args.length > 2 || (args[0].equals("-debug") && args.length != 2)) {
System.err.println(usage);
logger.println(usage);
return;
}
TraceClassVisitor traceClassVisitor =
new TraceClassVisitor(null, printer, new PrintWriter(System.out));
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, output);
String className;
int parsingOptions;
......
......@@ -28,6 +28,7 @@
package org.objectweb.asm.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.Attribute;
......@@ -46,6 +47,11 @@ import org.objectweb.asm.signature.SignatureReader;
*/
public class Textifier extends Printer {
/** The help message shown when command line arguments are incorrect. */
private static final String USAGE =
"Prints a disassembled view of the given class.\n"
+ "Usage: Textifier [-debug] <fully qualified class name or class file name>";
/** The type of internal names. See {@link #appendDescriptor}. */
public static final int INTERNAL_NAME = 0;
......@@ -145,10 +151,22 @@ public class Textifier extends Printer {
* @throws IOException if the class cannot be found, or if an IOException occurs.
*/
public static void main(final String[] args) throws IOException {
String usage =
"Prints a disassembled view of the given class.\n"
+ "Usage: Textifier [-debug] <fully qualified class name or class file name>";
main(usage, new Textifier(), args);
main(args, new PrintWriter(System.out, true), new PrintWriter(System.err, true));
}
/**
* Prints a disassembled view of the given class to the given output.
*
* <p>Usage: Textifier [-debug] &lt;binary class name or class file name &gt;
*
* @param args the command line arguments.
* @param output where to print the result.
* @param logger where to log errors.
* @throws IOException if the class cannot be found, or if an IOException occurs.
*/
public static void main(final String[] args, final PrintWriter output, final PrintWriter logger)
throws IOException {
main(args, USAGE, new Textifier(), output, logger);
}
// -----------------------------------------------------------------------------------------------
......
......@@ -27,16 +27,17 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.util;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import org.codehaus.commons.compiler.CompileException;
......@@ -46,6 +47,7 @@ import org.codehaus.janino.Parser;
import org.codehaus.janino.Scanner;
import org.codehaus.janino.UnitCompiler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.objectweb.asm.Attribute;
......@@ -55,7 +57,7 @@ import org.objectweb.asm.test.AsmTest;
import org.objectweb.asm.test.ClassFile;
/**
* ASMifier tests.
* Unit tests for {@link ASMifier}.
*
* @author Eugene Kuleshov
* @author Eric Bruneton
......@@ -63,44 +65,26 @@ import org.objectweb.asm.test.ClassFile;
// DontCheck(AbbreviationAsWordInName)
public class ASMifierTest extends AsmTest {
private static final String EXPECTED_USAGE =
"Prints the ASM code to generate the given class.\n"
+ "Usage: ASMifier [-debug] <fully qualified class name or class file name>\n";
private static final IClassLoader ICLASS_LOADER =
new ClassLoaderIClassLoader(new URLClassLoader(new URL[0]));
@Test
public void testConstructor() {
assertDoesNotThrow(() -> new ASMifier());
assertThrows(IllegalStateException.class, () -> new ASMifier() {});
}
@Test
public void testMain() throws IOException {
PrintStream err = System.err;
PrintStream out = System.out;
System.setErr(new PrintStream(new ByteArrayOutputStream()));
System.setOut(new PrintStream(new ByteArrayOutputStream()));
try {
String thisClassName = getClass().getName();
String thisClassFilePath =
ClassLoader.getSystemResource(thisClassName.replace('.', '/') + ".class").getPath();
ASMifier.main(new String[0]);
ASMifier.main(new String[] {"-debug"});
ASMifier.main(new String[] {thisClassName});
ASMifier.main(new String[] {thisClassFilePath});
ASMifier.main(new String[] {"-debug", thisClassName});
ASMifier.main(new String[] {"java.lang.Object"});
ASMifier.main(new String[] {"-debug", thisClassName, "extraArgument"});
assertThrows(IOException.class, () -> ASMifier.main(new String[] {"DoNotExist.class"}));
assertThrows(IOException.class, () -> ASMifier.main(new String[] {"do\\not\\exist"}));
} finally {
System.setErr(err);
System.setOut(out);
}
}
@Test
@SuppressWarnings("deprecation")
public void testDeprecatedVisitMethodInsn() {
ASMifier asmifier = new ASMifier();
asmifier.visitMethodInsn(Opcodes.INVOKESPECIAL, "owner", "name", "()V");
assertEquals(
"classWriter.visitMethodInsn(INVOKESPECIAL, \"owner\", \"name\", \"()V\", false);\n",
asmifier.getText().get(0));
......@@ -108,65 +92,53 @@ public class ASMifierTest extends AsmTest {
@Test
@SuppressWarnings("deprecation")
public void testDeprecatedVisitMethodInsnAsm4() {
public void testDeprecatedVisitMethodInsn_asm4() {
ASMifier asmifier = new ASMifier(Opcodes.ASM4, "classWriter", 0) {};
asmifier.visitMethodInsn(Opcodes.INVOKESPECIAL, "owner", "name", "()V");
String expectedText =
"classWriter.visitMethodInsn(INVOKESPECIAL, \"owner\", \"name\", \"()V\", false);\n";
assertEquals(expectedText, asmifier.getText().get(0));
assertEquals(
"classWriter.visitMethodInsn(INVOKESPECIAL, \"owner\", \"name\", \"()V\", false);\n",
asmifier.getText().get(0));
}
@Test
public void testVisitMethodInsnAsm4() {
public void testVisitMethodInsn_asm4() {
ASMifier asmifier = new ASMifier(Opcodes.ASM4, "classWriter", 0) {};
asmifier.visitMethodInsn(Opcodes.INVOKESPECIAL, "owner", "name", "()V", false);
String expectedText =
"classWriter.visitMethodInsn(INVOKESPECIAL, \"owner\", \"name\", \"()V\", false);\n";
assertEquals(expectedText, asmifier.getText().get(0));
assertEquals(
"classWriter.visitMethodInsn(INVOKESPECIAL, \"owner\", \"name\", \"()V\", false);\n",
asmifier.getText().get(0));
}
/**
* Tests that the code produced with an ASMifier compiles and generates the original class.
*
* @throws IOException if the ASMified class can't be read.
* @throws CompileException if the ASMified class can't be compiled.
* @throws ReflectiveOperationException if the ASMified class can't be run.
* @throws Exception if something goes wrong.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
public void testAsmifyCompileAndExecute(
final PrecompiledClass classParameter, final Api apiParameter)
throws IOException, CompileException, ReflectiveOperationException {
public void testAsmify_precompiledClass(
final PrecompiledClass classParameter, final Api apiParameter) throws Exception {
byte[] classFile = classParameter.getBytes();
if (classFile.length > Short.MAX_VALUE) {
return;
}
// Produce the ASMified Java source code corresponding to classParameter.
StringWriter stringWriter = new StringWriter();
TraceClassVisitor classVisitor =
assumeTrue(classFile.length < Short.MAX_VALUE);
StringWriter output = new StringWriter();
TraceClassVisitor asmifier =
new TraceClassVisitor(
null,
new ASMifier(apiParameter.value(), "classWriter", 0) {},
new PrintWriter(stringWriter));
new PrintWriter(output, true));
new ClassReader(classFile)
.accept(classVisitor, new Attribute[] {new Comment(), new CodeComment()}, 0);
String asmifiedSource = stringWriter.toString();
// Compile and execute this Java source code (skip JDK9 modules, Janino can't compile them).
if (classParameter == PrecompiledClass.JDK9_MODULE) {
return;
}
byte[] asmifiedClassFile = compile(classParameter.getName(), asmifiedSource);
StringBuilder asmifiedClassName = new StringBuilder(classParameter.getName()).append("Dump");
if (classParameter.getName().indexOf('.') != -1) {
asmifiedClassName.insert(0, "asm.");
}
Class<?> asmifiedClass =
new TestClassLoader().defineClass(asmifiedClassName.toString(), asmifiedClassFile);
Method dumpMethod = asmifiedClass.getMethod("dump");
byte[] dumpClassFile = (byte[]) dumpMethod.invoke(null);
.accept(asmifier, new Attribute[] {new Comment(), new CodeComment()}, 0);
// Janino can't compile JDK9 modules.
assumeTrue(classParameter != PrecompiledClass.JDK9_MODULE);
byte[] asmifiedClassFile = compile(classParameter.getName(), output.toString());
Class<?> asmifiedClass = new ClassFile(asmifiedClassFile).newInstance().getClass();
byte[] dumpClassFile = (byte[]) asmifiedClass.getMethod("dump").invoke(null);
assertEquals(new ClassFile(classFile), new ClassFile(dumpClassFile));
}
......@@ -177,12 +149,109 @@ public class ASMifierTest extends AsmTest {
return unitCompiler.compileUnit(true, true, true)[0].toByteArray();
}
private static class TestClassLoader extends ClassLoader {
@Test
public void testMain_missingClassName() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = new String[0];
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertEquals("", output.toString());
assertEquals(EXPECTED_USAGE, logger.toString());
}
@Test
public void testMain_missingClassName_withDebug() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {"-debug"};
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertEquals("", output.toString());
assertEquals(EXPECTED_USAGE, logger.toString());
}
@Test
public void testMain_tooManyArguments() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {"-debug", getClass().getName(), "extraArgument"};
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertEquals("", output.toString());
assertEquals(EXPECTED_USAGE, logger.toString());
}
@Test
public void testMain_classFileNotFound() {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {"DoNotExist.class"};
Executable main =
() -> ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertThrows(IOException.class, main);
assertEquals("", output.toString());
assertEquals("", logger.toString());
}
@Test
public void testMain_classNotFound() {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {"do\\not\\exist"};
Executable main =
() -> ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertThrows(IOException.class, main);
assertEquals("", output.toString());
assertEquals("", logger.toString());
}
@Test
public void testMain_className() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {getClass().getName()};
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertTrue(output.toString().contains("public class ASMifierTestDump implements Opcodes"));
assertTrue(output.toString().contains("\nmethodVisitor.visitLineNumber("));
assertTrue(output.toString().contains("\nmethodVisitor.visitLocalVariable("));
assertEquals("", logger.toString());
}
@Test
public void testMain_className_withDebug() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {"-debug", getClass().getName()};
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
assertTrue(output.toString().contains("public class ASMifierTestDump implements Opcodes"));
assertFalse(output.toString().contains("\nmethodVisitor.visitLineNumber("));
assertFalse(output.toString().contains("\nmethodVisitor.visitLocalVariable("));
assertEquals("", logger.toString());
}
@Test
public void testMain_classFile() throws IOException {
StringWriter output = new StringWriter();
StringWriter logger = new StringWriter();
String[] args = {
ClassLoader.getSystemResource(getClass().getName().replace('.', '/') + ".class").getPath()
};
TestClassLoader() {}
ASMifier.main(args, new PrintWriter(output, true), new PrintWriter(logger, true));
public Class<?> defineClass(final String name, final byte[] classFile) {
return defineClass(name, classFile, 0, classFile.length);
}
assertTrue(output.toString().contains("public class ASMifierTestDump implements Opcodes"));
assertEquals("", logger.toString());
}
}
......@@ -35,35 +35,47 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.test.AsmTest;
/**
* CheckAnnotationAdapter tests.
* Unit tests for {@link CheckAnnotationAdapter}.
*
* @author Eric Bruneton
*/
public class CheckAnnotationAdapterTest extends AsmTest implements Opcodes {
private CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
@Test
public void testIllegalAnnotationName() {
public void testVisit_illegalAnnotationName() {
CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
assertThrows(Exception.class, () -> checkAnnotationAdapter.visit(null, Integer.valueOf(0)));
}
@Test
public void testIllegalAnnotationValue() {
public void testVisit_illegalAnnotationValue1() {
CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
assertThrows(Exception.class, () -> checkAnnotationAdapter.visit("name", new Object()));
assertThrows(
Exception.class, () -> checkAnnotationAdapter.visit("name", Type.getMethodType("()V")));
}
@Test
public void testIllegalAnnotationEnumValue() {
public void testVisit_illegalAnnotationValue2() {
CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
assertThrows(
Exception.class, () -> checkAnnotationAdapter.visitEnum("name", "Lpkg/Enum;", null));
Exception.class, () -> checkAnnotationAdapter.visit("name", Type.getMethodType("()V")));
}
@Test
public void testIllegalAnnotationValueAfterEnd() {
public void testVisit_afterEnd() {
CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
checkAnnotationAdapter.visitEnd();
assertThrows(Exception.class, () -> checkAnnotationAdapter.visit("name", Integer.valueOf(0)));
}
@Test
public void testVisitEnum_illegalAnnotationEnumValue() {
CheckAnnotationAdapter checkAnnotationAdapter = new CheckAnnotationAdapter(null);
assertThrows(
Exception.class, () -> checkAnnotationAdapter.visitEnum("name", "Lpkg/Enum;", null));
}
}