spring basic learning

title: "BasicValue-BasicVerifier"
sequence: "406"

In this chapter, the core content is the following two lines of code. These two lines of code contain the four most important classes of Analyzer, Frame, Interpreter and Value in asm-analysis.jar:

    ┌── Analyzer
    │        ┌── Value                                   ┌── Interpreter
    │        │                                           │
 Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicVerifier());
 Frame<BasicValue>[] frames = analyzer.analyze(owner, mn);
    │        │
    │        └── Value
    └── Frame

In this article, we will introduce the BasicVerifier class:

 ┌───┬───────────────────┬─────────────┬───────┐
 │ 0 │    Interpreter    │    Value    │ Range │
 ├───┼───────────────────┼─────────────┼───────┤
 │ 1 │ BasicInterpreter  │ BasicValue  │   7   │
 ├───┼───────────────────┼─────────────┼───────┤
 │ 2 │   BasicVerifier   │ BasicValue  │   7   │
 ├───┼───────────────────┼─────────────┼───────┤
 │ 3 │  SimpleVerifier   │ BasicValue  │   N   │
 ├───┼───────────────────┼─────────────┼───────┤
 │ 4 │ SourceInterpreter │ SourceValue │   N   │
 └───┴───────────────────┴─────────────┴───────┘

In the above table, we focus on the following three points:

  • First, the inheritance relationship of classes. The BasicVerifier class inherits from the BasicInterpreter class, while the BasicInterpreter class inherits from the Interpreter abstract class. In addition, the SimpleVerifier class is a subclass of the current BasicVerifier class.

  • Second, class cooperation. BasicVerifier is used with the BasicValue class.

  • Third, class expression ability. There are seven BasicValue objects that can be used by the BasicVerifier class, that is, the seven static field values defined by the BasicValue class.

It is worth noting that why do the BasicInterpreter class and BasicVerifier class use the same seven static fields defined by the BasicValue class? Because the BasicVerifier class follows the newValue() method of the BasicInterpreter class; newValue() is the factory that produces the Value object. Accordingly, SimpleVerifier re implements the newValue() method, so you can generate many instances of the BasicValue class.

BasicVerifier

Before introducing the specific components of the BasicVerifier class, let's ask two questions:

  • First question, what is the meaning of class name?

  • The second question is, does this class play a big role?

First, answer the first question. The BasicVerifier class name has the meaning of verify. So, What to check? (What) checks whether the instruction is used correctly. How to check it? The (how) instruction itself will carry type information (expected type) to make it compatible with the data type (actual type) in local variable and operand stack. So, the key to understanding the BasicVerifier class is to see how it implements type checking.

  • For instance it checks that the operands of an IADD instruction are BasicValue.INT_VALUE values (while BasicInterpreter just returns the result, i.e. BasicValue.INT_VALUE).

    • In the BasicVerifier class, when the binaryOperation method processes the IADD instruction, it will check whether the two operand s are BasicValue.INT_VALUE type, and then call the implementation of the parent class (BasicInterpreter).

    • In the BasicInterpreter class, when the binaryOperation method processes the IADD instruction, it will directly return BasicValue.INT_VALUE type.

  • For instance this class can detect that the ISTORE 1 ALOAD 1 sequence is invalid.

    • In the BasicVerifier class, when the copyOperation method processes the ISTORE instruction, it will check whether the parameter value is BasicValue.INT_VALUE type.

    • In the BasicVerifier class, when the copyOperation method processes the ALOAD instruction, it will check whether the parameter value is a reference type (isReference() method).

Second, answer the second question. From the perspective of practical application, the BasicVerifier class is of little value. Why is it of little value? Because it can only use the seven static field values defined by BasicValue, it has very limited ability to express problems and can check simple errors. Then, we focus on learning and imitating the processing ideas of basic verifier inspection types.

class info

In the first part, the BasicVerifier class inherits from the BasicInterpreter class.

 public class BasicVerifier extends BasicInterpreter {
 }

fields

The second part, the fields defined by the BasicVerifier class.

 public class BasicVerifier extends BasicInterpreter {
     // No fields defined
 }

constructors

In the third part, what are the construction methods defined by the BasicVerifier class.

 public class BasicVerifier extends BasicInterpreter {
     public BasicVerifier() {
         super(ASM9);
         if (getClass() != BasicVerifier.class) {
             throw new IllegalStateException();
         }
     }
 ​
     protected BasicVerifier(int api) {
         super(api);
     }
 }

methods

The fourth part is about the methods defined by the BasicVerifier class.

opcode related methods

In the Interpreter class, seven abstract method s related to opcode are defined:

  1. newOperation

  2. copyOperation

  3. unaryOperation

  4. binaryOperation

  5. ternaryOperation

  6. naryOperation

  7. returnOperation

These seven methods are implemented in the BasicInterpreter class.

In the BasicVerifier class, six methods are re implemented, and one method (newOperation) follows the implementation of the BasicInterpreter class.

Among the six re implementation methods, their overall idea is the same: compare the expected value (expected type) with the actual value (actual type). Here, take the copyOperation method as an example, where value is the actual value and expected is the expected value. If the two do not match, an exception of AnalyzerException type will be thrown.

 public class BasicVerifier extends BasicInterpreter {
     @Override
     public BasicValue copyOperation(final AbstractInsnNode insn, final BasicValue value) throws AnalyzerException {
         Value expected;
         switch (insn.getOpcode()) {
             case ILOAD:
             case ISTORE:
                 expected = BasicValue.INT_VALUE;
                 break;
             case FLOAD:
             case FSTORE:
                 expected = BasicValue.FLOAT_VALUE;
                 break;
             case LLOAD:
             case LSTORE:
                 expected = BasicValue.LONG_VALUE;
                 break;
             case DLOAD:
             case DSTORE:
                 expected = BasicValue.DOUBLE_VALUE;
                 break;
             case ALOAD:
                 if (!value.isReference()) {
                     throw new AnalyzerException(insn, null, "an object reference", value);
                 }
                 return value;
             case ASTORE:
                 if (!value.isReference() && !BasicValue.RETURNADDRESS_VALUE.equals(value)) {
                     throw new AnalyzerException(insn, null, "an object reference or a return address", value);
                 }
                 return value;
             default:
                 return value;
         }
         if (!expected.equals(value)) {
             throw new AnalyzerException(insn, null, expected, value);
         }
         return value;
     }
 }

Custom protected method

In the BasicVerifier class, it not only inherits from the Interpreter's methods, but also defines some of its own protected methods. Although these protected methods are relatively simple, subclasses (for example, simplevifier) can extend these methods.

 public class BasicVerifier extends BasicInterpreter {
     protected boolean isArrayValue(final BasicValue value) {
         return value.isReference();
     }
     protected BasicValue getElementValue(final BasicValue objectArrayValue) throws AnalyzerException {
         return BasicValue.REFERENCE_VALUE;
     }
     protected boolean isSubTypeOf(final BasicValue value, final BasicValue expected) {
         return value.equals(expected);
     }
 }

Array types: basicverifier.isarrayvalue (basicvalue) and basicverifier.getelementvalue (basicvalueobjectarrayvalue) methods

  • When encountering the arraylength instruction, judge whether the top of the operand stack is an array.

  • When the aaload instruction is encountered, the element type is obtained according to the current array type.

Compatibility between two types: basicverifier.issubtypeof (basicvalue, basicvalue expected)

  • When invokevirtual and other instructions are encountered, whether the actual return type is compatible with the return value type of the method.

Reference type: BasicValue.isReference()

  • When an instruction such as ifnull or areturn is encountered, it needs a value of reference type, which cannot be a value of primitive type.

Example: detect unreasonable instruction combination

Suppose we want to implement the following test method:

 package sample;
 ​
 public class HelloWorld {
     public void test() {
         int a = 1;
         int b = 2;
         int c = a + b;
     }
 }

We can use BasicVerifier to check that the ISTORE 1 ALOAD 1 instruction combination is unreasonable.

import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.*;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        MethodNode mn = new MethodNode(ACC_PUBLIC, "test", "()V", null, null);

        InsnList il = mn.instructions;
        il.add(new InsnNode(ICONST_1));
        il.add(new VarInsnNode(ISTORE, 1));
        il.add(new InsnNode(ICONST_2));
        il.add(new VarInsnNode(ISTORE, 2));
        il.add(new VarInsnNode(ILOAD, 1)); // If you replace ILOAD with ALOAD, an error will be reported
        il.add(new VarInsnNode(ILOAD, 2));
        il.add(new InsnNode(IADD));
        il.add(new VarInsnNode(ISTORE, 3));
        il.add(new InsnNode(RETURN));

        mn.maxStack = 2;
        mn.maxLocals = 4;

        Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicVerifier());
        analyzer.analyze("sample/HelloWorld", mn);
    }
}

summary

The contents of this paper are summarized as follows:

  • The first point is to introduce the BasicVerifier class, which belongs to the Interpreter and is characterized by type checking.

    • Type checking method: compare an actual value with an expected value to determine whether they are compatible (corresponding to the isSubTypeOf() method, or equal, or the relationship between parent and child classes); If compatible, there is no error in the type; If incompatible, the type has an error.

  • The second point is the code example to learn the idea of basic verifier checking types.

 

Posted on Tue, 23 Nov 2021 05:21:41 -0500 by simulant