All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.asm.util.CheckClassAdapter Maven / Gradle / Ivy

The newest version!
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package scala.tools.asm.util;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import scala.tools.asm.AnnotationVisitor;
import scala.tools.asm.Attribute;
import scala.tools.asm.ClassReader;
import scala.tools.asm.ClassVisitor;
import scala.tools.asm.ClassWriter;
import scala.tools.asm.FieldVisitor;
import scala.tools.asm.Label;
import scala.tools.asm.MethodVisitor;
import scala.tools.asm.ModuleVisitor;
import scala.tools.asm.Opcodes;
import scala.tools.asm.RecordComponentVisitor;
import scala.tools.asm.Type;
import scala.tools.asm.TypePath;
import scala.tools.asm.TypeReference;
import scala.tools.asm.tree.ClassNode;
import scala.tools.asm.tree.MethodNode;
import scala.tools.asm.tree.TryCatchBlockNode;
import scala.tools.asm.tree.analysis.Analyzer;
import scala.tools.asm.tree.analysis.AnalyzerException;
import scala.tools.asm.tree.analysis.BasicValue;
import scala.tools.asm.tree.analysis.Frame;
import scala.tools.asm.tree.analysis.SimpleVerifier;

/**
 * A {@link ClassVisitor} that checks that its methods are properly used. More precisely this class
 * adapter checks each method call individually, based only on its arguments, but does
 * not check the sequence of method calls. For example, the invalid sequence {@code
 * visitField(ACC_PUBLIC, "i", "I", null)} {@code visitField(ACC_PUBLIC, "i", "D", null)} will
 * not be detected by this class adapter.
 *
 * 

CheckClassAdapter can be also used to verify bytecode transformations in order to * make sure that the transformed bytecode is sane. For example: * *

 * InputStream inputStream = ...; // get bytes for the source class
 * ClassReader classReader = new ClassReader(inputStream);
 * ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
 * ClassVisitor classVisitor = new MyClassAdapter(new CheckClassAdapter(classWriter, true));
 * classReader.accept(classVisitor, 0);
 *
 * StringWriter stringWriter = new StringWriter();
 * PrintWriter printWriter = new PrintWriter(stringWriter);
 * CheckClassAdapter.verify(new ClassReader(classWriter.toByteArray()), false, printWriter);
 * assertTrue(stringWriter.toString().isEmpty());
 * 
* *

The above code pass the transformed bytecode through a CheckClassAdapter, with * data flow checks enabled. These checks are not exactly the same as the JVM verification, but * provide some basic type checking for each method instruction. If the bytecode has errors, the * output text shows the erroneous instruction number, and a dump of the failed method with * information about the type of the local variables and of the operand stack slots for each * instruction. For example (format is - insnNumber locals : stack): * *

 * scala.tools.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found .
 *   at scala.tools.asm.tree.analysis.Analyzer.analyze(Analyzer.java:...)
 *   at scala.tools.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:...)
 * ...
 * remove()V
 * 00000 LinkedBlockingQueue$Itr . . . . . . . .  : ICONST_0
 * 00001 LinkedBlockingQueue$Itr . . . . . . . .  : I ISTORE 2
 * 00001 LinkedBlockingQueue$Itr . I . . . . . .  :
 * ...
 * 00071 LinkedBlockingQueue$Itr . I . . . . . .  : ILOAD 1
 * 00072 ? INVOKESPECIAL java/lang/Integer.<init> (I)V
 * ...
 * 
* *

The above output shows that the local variable 1, loaded by the ILOAD 1 * instruction at position 00071 is not initialized, whereas the local variable 2 is * initialized and contains an int value. * * @author Eric Bruneton */ 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 "; private static final String ERROR_AT = ": error at index "; /** Whether the bytecode must be checked with a BasicVerifier. */ private boolean checkDataFlow; /** The class version number. */ private int version; /** Whether the {@link #visit} method has been called. */ private boolean visitCalled; /** Whether the {@link #visitModule} method has been called. */ private boolean visitModuleCalled; /** Whether the {@link #visitSource} method has been called. */ private boolean visitSourceCalled; /** Whether the {@link #visitOuterClass} method has been called. */ private boolean visitOuterClassCalled; /** Whether the {@link #visitNestHost} method has been called. */ private boolean visitNestHostCalled; /** * The common package of all the nest members. Not {@literal null} if the visitNestMember method * has been called. */ private String nestMemberPackageName; /** Whether the {@link #visitEnd} method has been called. */ private boolean visitEndCalled; /** The index of the instruction designated by each visited label so far. */ private Map labelInsnIndices; // ----------------------------------------------------------------------------------------------- // Constructors // ----------------------------------------------------------------------------------------------- /** * Constructs a new {@link CheckClassAdapter}. Subclasses must not use this constructor. * Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. * * @param classVisitor the class visitor to which this adapter must delegate calls. */ public CheckClassAdapter(final ClassVisitor classVisitor) { this(classVisitor, /* checkDataFlow= */ true); } /** * Constructs a new {@link CheckClassAdapter}. Subclasses must not use this constructor. * Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. * * @param classVisitor the class visitor to which this adapter must delegate calls. * @param checkDataFlow whether to perform basic data flow checks. * @throws IllegalStateException If a subclass calls this constructor. */ public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDataFlow) { this(/* latest api = */ Opcodes.ASM9, classVisitor, checkDataFlow); if (getClass() != CheckClassAdapter.class) { throw new IllegalStateException(); } } /** * Constructs a new {@link CheckClassAdapter}. * * @param api the ASM API version implemented by this visitor. Must be one of the {@code * ASM}x values in {@link Opcodes}. * @param classVisitor the class visitor to which this adapter must delegate calls. * @param checkDataFlow {@literal true} to perform basic data flow checks, or {@literal false} to * not perform any data flow check (see {@link CheckMethodAdapter}). */ protected CheckClassAdapter( final int api, final ClassVisitor classVisitor, final boolean checkDataFlow) { super(api, classVisitor); this.labelInsnIndices = new HashMap<>(); this.checkDataFlow = checkDataFlow; } // ----------------------------------------------------------------------------------------------- // Implementation of the ClassVisitor interface // ----------------------------------------------------------------------------------------------- @Override public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { if (visitCalled) { throw new IllegalStateException("visit must be called only once"); } visitCalled = true; checkState(); checkAccess( access, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ANNOTATION | Opcodes.ACC_ENUM | Opcodes.ACC_DEPRECATED | Opcodes.ACC_RECORD | Opcodes.ACC_MODULE); if (name == null) { throw new IllegalArgumentException("Illegal class name (null)"); } if (!name.endsWith("package-info") && !name.endsWith("module-info")) { CheckMethodAdapter.checkInternalName(version, name, "class name"); } if ("java/lang/Object".equals(name)) { if (superName != null) { throw new IllegalArgumentException( "The super class name of the Object class must be 'null'"); } } else if (name.endsWith("module-info")) { if (superName != null) { throw new IllegalArgumentException( "The super class name of a module-info class must be 'null'"); } } else { CheckMethodAdapter.checkInternalName(version, superName, "super class name"); } if (signature != null) { checkClassSignature(signature); } if ((access & Opcodes.ACC_INTERFACE) != 0 && !"java/lang/Object".equals(superName)) { throw new IllegalArgumentException( "The super class name of interfaces must be 'java/lang/Object'"); } if (interfaces != null) { for (int i = 0; i < interfaces.length; ++i) { CheckMethodAdapter.checkInternalName( version, interfaces[i], "interface name at index " + i); } } this.version = version; super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitSource(final String file, final String debug) { checkState(); if (visitSourceCalled) { throw new IllegalStateException("visitSource can be called only once."); } visitSourceCalled = true; super.visitSource(file, debug); } @Override public ModuleVisitor visitModule(final String name, final int access, final String version) { checkState(); if (visitModuleCalled) { throw new IllegalStateException("visitModule can be called only once."); } visitModuleCalled = true; checkFullyQualifiedName(this.version, name, "module name"); checkAccess(access, Opcodes.ACC_OPEN | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED); CheckModuleAdapter checkModuleAdapter = new CheckModuleAdapter( api, super.visitModule(name, access, version), (access & Opcodes.ACC_OPEN) != 0); checkModuleAdapter.classVersion = this.version; return checkModuleAdapter; } @Override public void visitNestHost(final String nestHost) { checkState(); CheckMethodAdapter.checkInternalName(version, nestHost, "nestHost"); if (visitNestHostCalled) { throw new IllegalStateException("visitNestHost can be called only once."); } if (nestMemberPackageName != null) { throw new IllegalStateException("visitNestHost and visitNestMember are mutually exclusive."); } visitNestHostCalled = true; super.visitNestHost(nestHost); } @Override public void visitNestMember(final String nestMember) { checkState(); CheckMethodAdapter.checkInternalName(version, nestMember, "nestMember"); if (visitNestHostCalled) { throw new IllegalStateException( "visitMemberOfNest and visitNestHost are mutually exclusive."); } String packageName = packageName(nestMember); if (nestMemberPackageName == null) { nestMemberPackageName = packageName; } else if (!nestMemberPackageName.equals(packageName)) { throw new IllegalStateException( "nest member " + nestMember + " should be in the package " + nestMemberPackageName); } super.visitNestMember(nestMember); } @Override public void visitPermittedSubclass(final String permittedSubclass) { checkState(); CheckMethodAdapter.checkInternalName(version, permittedSubclass, "permittedSubclass"); super.visitPermittedSubclass(permittedSubclass); } @Override public void visitOuterClass(final String owner, final String name, final String descriptor) { checkState(); if (visitOuterClassCalled) { throw new IllegalStateException("visitOuterClass can be called only once."); } visitOuterClassCalled = true; if (owner == null) { throw new IllegalArgumentException("Illegal outer class owner"); } if (descriptor != null) { CheckMethodAdapter.checkMethodDescriptor(version, descriptor); } super.visitOuterClass(owner, name, descriptor); } @Override public void visitInnerClass( final String name, final String outerName, final String innerName, final int access) { checkState(); CheckMethodAdapter.checkInternalName(version, name, "class name"); if (outerName != null) { CheckMethodAdapter.checkInternalName(version, outerName, "outer class name"); } if (innerName != null) { int startIndex = 0; while (startIndex < innerName.length() && Character.isDigit(innerName.charAt(startIndex))) { startIndex++; } if (startIndex == 0 || startIndex < innerName.length()) { CheckMethodAdapter.checkIdentifier(version, innerName, startIndex, -1, "inner class name"); } } checkAccess( access, Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ANNOTATION | Opcodes.ACC_ENUM); super.visitInnerClass(name, outerName, innerName, access); } @Override public RecordComponentVisitor visitRecordComponent( final String name, final String descriptor, final String signature) { checkState(); CheckMethodAdapter.checkUnqualifiedName(version, name, "record component name"); CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid= */ false); if (signature != null) { checkFieldSignature(signature); } return new CheckRecordComponentAdapter( api, super.visitRecordComponent(name, descriptor, signature)); } @Override public FieldVisitor visitField( final int access, final String name, final String descriptor, final String signature, final Object value) { checkState(); checkAccess( access, Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ENUM | Opcodes.ACC_MANDATED | Opcodes.ACC_DEPRECATED); CheckMethodAdapter.checkUnqualifiedName(version, name, "field name"); CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid= */ false); if (signature != null) { checkFieldSignature(signature); } if (value != null) { CheckMethodAdapter.checkConstant(value); } return new CheckFieldAdapter(api, super.visitField(access, name, descriptor, signature, value)); } @Override public MethodVisitor visitMethod( final int access, final String name, final String descriptor, final String signature, final String[] exceptions) { checkState(); checkMethodAccess( version, access, Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_BRIDGE | Opcodes.ACC_VARARGS | Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STRICT | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED | Opcodes.ACC_DEPRECATED); if (!"".equals(name) && !"".equals(name)) { CheckMethodAdapter.checkMethodIdentifier(version, name, "method name"); } CheckMethodAdapter.checkMethodDescriptor(version, descriptor); if (signature != null) { checkMethodSignature(signature); } if (exceptions != null) { for (int i = 0; i < exceptions.length; ++i) { CheckMethodAdapter.checkInternalName( version, exceptions[i], "exception name at index " + i); } } CheckMethodAdapter checkMethodAdapter; MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); if (checkDataFlow) { if (cv instanceof ClassWriter) { methodVisitor = new CheckMethodAdapter.MethodWriterWrapper( api, version, (ClassWriter) cv, methodVisitor); } checkMethodAdapter = new CheckMethodAdapter(api, access, name, descriptor, methodVisitor, labelInsnIndices); } else { checkMethodAdapter = new CheckMethodAdapter(api, methodVisitor, labelInsnIndices); } checkMethodAdapter.version = version; return checkMethodAdapter; } @Override public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { checkState(); CheckMethodAdapter.checkDescriptor(version, descriptor, false); return new CheckAnnotationAdapter(super.visitAnnotation(descriptor, visible)); } @Override public AnnotationVisitor visitTypeAnnotation( final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { checkState(); int sort = new TypeReference(typeRef).getSort(); if (sort != TypeReference.CLASS_TYPE_PARAMETER && sort != TypeReference.CLASS_TYPE_PARAMETER_BOUND && sort != TypeReference.CLASS_EXTENDS) { throw new IllegalArgumentException( "Invalid type reference sort 0x" + Integer.toHexString(sort)); } checkTypeRef(typeRef); CheckMethodAdapter.checkDescriptor(version, descriptor, false); return new CheckAnnotationAdapter( super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)); } @Override public void visitAttribute(final Attribute attribute) { checkState(); if (attribute == null) { throw new IllegalArgumentException("Invalid attribute (must not be null)"); } super.visitAttribute(attribute); } @Override public void visitEnd() { checkState(); visitEndCalled = true; super.visitEnd(); } // ----------------------------------------------------------------------------------------------- // Utility methods // ----------------------------------------------------------------------------------------------- /** Checks that the visit method has been called and that visitEnd has not been called. */ private void checkState() { if (!visitCalled) { throw new IllegalStateException("Cannot visit member before visit has been called."); } if (visitEndCalled) { throw new IllegalStateException("Cannot visit member after visitEnd has been called."); } } /** * Checks that the given access flags do not contain invalid flags. This method also checks that * mutually incompatible flags are not set simultaneously. * * @param access the access flags to be checked. * @param possibleAccess the valid access flags. */ static void checkAccess(final int access, final int possibleAccess) { if ((access & ~possibleAccess) != 0) { throw new IllegalArgumentException("Invalid access flags: " + access); } int publicProtectedPrivate = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE; if (Integer.bitCount(access & publicProtectedPrivate) > 1) { throw new IllegalArgumentException( "public, protected and private are mutually exclusive: " + access); } if (Integer.bitCount(access & (Opcodes.ACC_FINAL | Opcodes.ACC_ABSTRACT)) > 1) { throw new IllegalArgumentException("final and abstract are mutually exclusive: " + access); } } /** * Checks that the given access flags do not contain invalid flags for a method. This method also * checks that mutually incompatible flags are not set simultaneously. * * @param version the class version. * @param access the method access flags to be checked. * @param possibleAccess the valid access flags. */ private static void checkMethodAccess( final int version, final int access, final int possibleAccess) { checkAccess(access, possibleAccess); if ((version & 0xFFFF) < Opcodes.V17 && Integer.bitCount(access & (Opcodes.ACC_STRICT | Opcodes.ACC_ABSTRACT)) > 1) { throw new IllegalArgumentException("strictfp and abstract are mutually exclusive: " + access); } } /** * Checks that the given name is a fully qualified name, using dots. * * @param version the class version. * @param name the name to be checked. * @param source the source of 'name' (e.g 'module' for a module name). */ static void checkFullyQualifiedName(final int version, final String name, final String source) { try { int startIndex = 0; int dotIndex; while ((dotIndex = name.indexOf('.', startIndex + 1)) != -1) { CheckMethodAdapter.checkIdentifier(version, name, startIndex, dotIndex, null); startIndex = dotIndex + 1; } CheckMethodAdapter.checkIdentifier(version, name, startIndex, name.length(), null); } catch (IllegalArgumentException e) { throw new IllegalArgumentException( "Invalid " + source + " (must be a fully qualified name): " + name, e); } } /** * Checks a class signature. * * @param signature a string containing the signature that must be checked. */ public static void checkClassSignature(final String signature) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // ClassSignature: // [TypeParameters] SuperclassSignature SuperinterfaceSignature* // SuperclassSignature: // ClassTypeSignature // SuperinterfaceSignature: // ClassTypeSignature int pos = 0; if (getChar(signature, 0) == '<') { pos = checkTypeParameters(signature, pos); } pos = checkClassTypeSignature(signature, pos); while (getChar(signature, pos) == 'L') { pos = checkClassTypeSignature(signature, pos); } if (pos != signature.length()) { throw new IllegalArgumentException(signature + ERROR_AT + pos); } } /** * Checks a method signature. * * @param signature a string containing the signature that must be checked. */ public static void checkMethodSignature(final String signature) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // MethodSignature: // [TypeParameters] ( JavaTypeSignature* ) Result ThrowsSignature* // Result: // JavaTypeSignature // VoidDescriptor // ThrowsSignature: // ^ ClassTypeSignature // ^ TypeVariableSignature int pos = 0; if (getChar(signature, 0) == '<') { pos = checkTypeParameters(signature, pos); } pos = checkChar('(', signature, pos); while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) { pos = checkJavaTypeSignature(signature, pos); } pos = checkChar(')', signature, pos); if (getChar(signature, pos) == 'V') { ++pos; } else { pos = checkJavaTypeSignature(signature, pos); } while (getChar(signature, pos) == '^') { ++pos; if (getChar(signature, pos) == 'L') { pos = checkClassTypeSignature(signature, pos); } else { pos = checkTypeVariableSignature(signature, pos); } } if (pos != signature.length()) { throw new IllegalArgumentException(signature + ERROR_AT + pos); } } /** * Checks a field signature. * * @param signature a string containing the signature that must be checked. */ public static void checkFieldSignature(final String signature) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // FieldSignature: // ReferenceTypeSignature int pos = checkReferenceTypeSignature(signature, 0); if (pos != signature.length()) { throw new IllegalArgumentException(signature + ERROR_AT + pos); } } /** * Checks the type parameters of a class or method signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeParameters(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // TypeParameters: // < TypeParameter TypeParameter* > int pos = startPos; pos = checkChar('<', signature, pos); pos = checkTypeParameter(signature, pos); while (getChar(signature, pos) != '>') { pos = checkTypeParameter(signature, pos); } return pos + 1; } /** * Checks a type parameter of a class or method signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeParameter(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // TypeParameter: // Identifier ClassBound InterfaceBound* // ClassBound: // : [ReferenceTypeSignature] // InterfaceBound: // : ReferenceTypeSignature int pos = startPos; pos = checkSignatureIdentifier(signature, pos); pos = checkChar(':', signature, pos); if ("L[T".indexOf(getChar(signature, pos)) != -1) { pos = checkReferenceTypeSignature(signature, pos); } while (getChar(signature, pos) == ':') { pos = checkReferenceTypeSignature(signature, pos + 1); } return pos; } /** * Checks a reference type signature. * * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkReferenceTypeSignature(final String signature, final int pos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // ReferenceTypeSignature: // ClassTypeSignature // TypeVariableSignature // ArrayTypeSignature // ArrayTypeSignature: // [ JavaTypeSignature switch (getChar(signature, pos)) { case 'L': return checkClassTypeSignature(signature, pos); case '[': return checkJavaTypeSignature(signature, pos + 1); default: return checkTypeVariableSignature(signature, pos); } } /** * Checks a class type signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkClassTypeSignature(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // ClassTypeSignature: // L [PackageSpecifier] SimpleClassTypeSignature ClassTypeSignatureSuffix* ; // PackageSpecifier: // Identifier / PackageSpecifier* // SimpleClassTypeSignature: // Identifier [TypeArguments] // ClassTypeSignatureSuffix: // . SimpleClassTypeSignature int pos = startPos; pos = checkChar('L', signature, pos); pos = checkSignatureIdentifier(signature, pos); while (getChar(signature, pos) == '/') { pos = checkSignatureIdentifier(signature, pos + 1); } if (getChar(signature, pos) == '<') { pos = checkTypeArguments(signature, pos); } while (getChar(signature, pos) == '.') { pos = checkSignatureIdentifier(signature, pos + 1); if (getChar(signature, pos) == '<') { pos = checkTypeArguments(signature, pos); } } return checkChar(';', signature, pos); } /** * Checks the type arguments in a class type signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeArguments(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // TypeArguments: // < TypeArgument TypeArgument* > int pos = startPos; pos = checkChar('<', signature, pos); pos = checkTypeArgument(signature, pos); while (getChar(signature, pos) != '>') { pos = checkTypeArgument(signature, pos); } return pos + 1; } /** * Checks a type argument in a class type signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeArgument(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // TypeArgument: // [WildcardIndicator] ReferenceTypeSignature // * // WildcardIndicator: // + // - int pos = startPos; char c = getChar(signature, pos); if (c == '*') { return pos + 1; } else if (c == '+' || c == '-') { pos++; } return checkReferenceTypeSignature(signature, pos); } /** * Checks a type variable signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkTypeVariableSignature(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // TypeVariableSignature: // T Identifier ; int pos = startPos; pos = checkChar('T', signature, pos); pos = checkSignatureIdentifier(signature, pos); return checkChar(';', signature, pos); } /** * Checks a Java type signature. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkJavaTypeSignature(final String signature, final int startPos) { // From https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1: // JavaTypeSignature: // ReferenceTypeSignature // BaseType // BaseType: // (one of) // B C D F I J S Z int pos = startPos; switch (getChar(signature, pos)) { case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S': case 'Z': return pos + 1; default: return checkReferenceTypeSignature(signature, pos); } } /** * Checks an identifier. * * @param signature a string containing the signature that must be checked. * @param startPos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkSignatureIdentifier(final String signature, final int startPos) { int pos = startPos; while (pos < signature.length() && ".;[/<>:".indexOf(signature.codePointAt(pos)) == -1) { pos = signature.offsetByCodePoints(pos, 1); } if (pos == startPos) { throw new IllegalArgumentException(signature + ": identifier expected at index " + startPos); } return pos; } /** * Checks a single character. * * @param c a character. * @param signature a string containing the signature that must be checked. * @param pos index of first character to be checked. * @return the index of the first character after the checked part. */ private static int checkChar(final char c, final String signature, final int pos) { if (getChar(signature, pos) == c) { return pos + 1; } throw new IllegalArgumentException(signature + ": '" + c + "' expected at index " + pos); } /** * Returns the string character at the given index, or 0. * * @param string a string. * @param pos an index in 'string'. * @return the character at the given index, or 0 if there is no such character. */ private static char getChar(final String string, final int pos) { return pos < string.length() ? string.charAt(pos) : (char) 0; } /** * Checks the reference to a type in a type annotation. * * @param typeRef a reference to an annotated type. */ static void checkTypeRef(final int typeRef) { int mask = 0; switch (typeRef >>> 24) { case TypeReference.CLASS_TYPE_PARAMETER: case TypeReference.METHOD_TYPE_PARAMETER: case TypeReference.METHOD_FORMAL_PARAMETER: mask = 0xFFFF0000; break; case TypeReference.FIELD: case TypeReference.METHOD_RETURN: case TypeReference.METHOD_RECEIVER: case TypeReference.LOCAL_VARIABLE: case TypeReference.RESOURCE_VARIABLE: case TypeReference.INSTANCEOF: case TypeReference.NEW: case TypeReference.CONSTRUCTOR_REFERENCE: case TypeReference.METHOD_REFERENCE: mask = 0xFF000000; break; case TypeReference.CLASS_EXTENDS: case TypeReference.CLASS_TYPE_PARAMETER_BOUND: case TypeReference.METHOD_TYPE_PARAMETER_BOUND: case TypeReference.THROWS: case TypeReference.EXCEPTION_PARAMETER: mask = 0xFFFFFF00; break; case TypeReference.CAST: case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: mask = 0xFF0000FF; break; default: break; } if (mask == 0 || (typeRef & ~mask) != 0) { throw new IllegalArgumentException( "Invalid type reference 0x" + Integer.toHexString(typeRef)); } } /** * Returns the package name of an internal name. * * @param name an internal name. * @return the package name or "" if there is no package. */ private static String packageName(final String name) { int index = name.lastIndexOf('/'); if (index == -1) { return ""; } return name.substring(0, index); } // ----------------------------------------------------------------------------------------------- // Static verification methods // ----------------------------------------------------------------------------------------------- /** * Checks the given class. * *

Usage: CheckClassAdapter <binary class name or class file name> * * @param args the command line arguments. * @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) { logger.println(USAGE); return; } ClassReader classReader; if (args[0].endsWith(".class")) { // Can't fix PMD warning for 1.5 compatibility. try (InputStream inputStream = new FileInputStream(args[0])) { // NOPMD(AvoidFileStream) classReader = new ClassReader(inputStream); } } else { classReader = new ClassReader(args[0]); } verify(classReader, false, logger); } /** * Checks the given class. * * @param classReader the class to be checked. * @param printResults whether to print the results of the bytecode verification. * @param printWriter where the results (or the stack trace in case of error) must be printed. */ public static void verify( final ClassReader classReader, final boolean printResults, final PrintWriter printWriter) { verify(classReader, null, printResults, printWriter); } /** * Checks the given class. * * @param classReader the class to be checked. * @param loader a ClassLoader which will be used to load referenced classes. May be * {@literal null}. * @param printResults whether to print the results of the bytecode verification. * @param printWriter where the results (or the stack trace in case of error) must be printed. */ public static void verify( final ClassReader classReader, final ClassLoader loader, final boolean printResults, final PrintWriter printWriter) { ClassNode classNode = new ClassNode(); classReader.accept( new CheckClassAdapter(/*latest*/ Opcodes.ASM9, classNode, false) {}, ClassReader.SKIP_DEBUG); Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName); List methods = classNode.methods; List interfaces = new ArrayList<>(); for (String interfaceName : classNode.interfaces) { interfaces.add(Type.getObjectType(interfaceName)); } for (MethodNode method : methods) { SimpleVerifier verifier = new SimpleVerifier( Type.getObjectType(classNode.name), syperType, interfaces, (classNode.access & Opcodes.ACC_INTERFACE) != 0); Analyzer analyzer = new Analyzer<>(verifier); if (loader != null) { verifier.setClassLoader(loader); } try { analyzer.analyze(classNode.name, method); } catch (AnalyzerException e) { e.printStackTrace(printWriter); } if (printResults) { printAnalyzerResult(method, analyzer, printWriter); } } printWriter.flush(); } static void printAnalyzerResult( final MethodNode method, final Analyzer analyzer, final PrintWriter printWriter) { Textifier textifier = new Textifier(); TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(textifier); printWriter.println(method.name + method.desc); for (int i = 0; i < method.instructions.size(); ++i) { method.instructions.get(i).accept(traceMethodVisitor); StringBuilder stringBuilder = new StringBuilder(); Frame frame = analyzer.getFrames()[i]; if (frame == null) { stringBuilder.append('?'); } else { for (int j = 0; j < frame.getLocals(); ++j) { stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' '); } stringBuilder.append(" : "); for (int j = 0; j < frame.getStackSize(); ++j) { stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' '); } } while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) { stringBuilder.append(' '); } printWriter.print(Integer.toString(i + 100000).substring(1)); printWriter.print( " " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1)); } for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) { tryCatchBlock.accept(traceMethodVisitor); printWriter.print(" " + textifier.text.get(textifier.text.size() - 1)); } printWriter.println(); } private static String getUnqualifiedName(final String name) { int lastSlashIndex = name.lastIndexOf('/'); if (lastSlashIndex == -1) { return name; } else { int endIndex = name.length(); if (name.charAt(endIndex - 1) == ';') { endIndex--; } int lastBracketIndex = name.lastIndexOf('['); if (lastBracketIndex == -1) { return name.substring(lastSlashIndex + 1, endIndex); } return name.substring(0, lastBracketIndex + 1) + name.substring(lastSlashIndex + 1, endIndex); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy