View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.bcel;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNull;
21  
22  import java.lang.reflect.InvocationTargetException;
23  import java.util.LinkedList;
24  import java.util.List;
25  
26  import org.apache.bcel.classfile.JavaClass;
27  import org.apache.bcel.classfile.LocalVariableTypeTable;
28  import org.apache.bcel.classfile.Method;
29  import org.apache.bcel.generic.ACONST_NULL;
30  import org.apache.bcel.generic.ALOAD;
31  import org.apache.bcel.generic.ConstantPoolGen;
32  import org.apache.bcel.generic.GETSTATIC;
33  import org.apache.bcel.generic.INVOKEVIRTUAL;
34  import org.apache.bcel.generic.Instruction;
35  import org.apache.bcel.generic.InstructionList;
36  import org.apache.bcel.generic.LocalVariableGen;
37  import org.apache.bcel.generic.MethodGen;
38  import org.apache.bcel.generic.Type;
39  import org.junit.jupiter.api.Test;
40  
41  public class LocalVariableTypeTableTestCase extends AbstractTestCase {
42  
43      public class TestClassLoader extends ClassLoader {
44  
45          public TestClassLoader(final ClassLoader parent) {
46              super(parent);
47          }
48  
49          public Class<?> findClass(final String name, final byte[] bytes) {
50              return defineClass(name, bytes, 0, bytes.length);
51          }
52      }
53  
54      public InstructionList createPrintln(final ConstantPoolGen cp, final Instruction instruction) {
55          final InstructionList il = new InstructionList();
56  
57          final int out = cp.addFieldref("java.lang.System", "out", "Ljava/io/PrintStream;");
58          final int println = cp.addMethodref("java.io.PrintStream", "println", "(Ljava/lang/String;)V");
59          il.append(new GETSTATIC(out));
60          il.append(instruction);
61          il.append(new INVOKEVIRTUAL(println));
62  
63          return il;
64      }
65  
66      public int findFirstStringLocalVariableOffset(final Method method) {
67          final Type[] argumentTypes = method.getArgumentTypes();
68          int offset = -1;
69  
70          for (int i = 0, count = argumentTypes.length; i < count; i++) {
71              if (Type.STRING.getSignature().equals(argumentTypes[i].getSignature())) {
72                  if (method.isStatic()) {
73                      offset = i;
74                  } else {
75                      offset = i + 1;
76                  }
77  
78                  break;
79              }
80          }
81  
82          return offset;
83      }
84  
85      private byte[] getBytesFromClass(final String className) throws ClassNotFoundException {
86          final JavaClass clazz = getTestJavaClass(className);
87          final ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
88  
89          final Method[] methods = clazz.getMethods();
90  
91          for (int i = 0; i < methods.length; i++) {
92              final Method method = methods[i];
93              if (!method.isNative() && !method.isAbstract()) {
94                  methods[i] = injection(clazz, method, cp, findFirstStringLocalVariableOffset(method));
95              }
96          }
97  
98          clazz.setConstantPool(cp.getFinalConstantPool());
99  
100         return clazz.getBytes();
101     }
102 
103     public Method injection(final JavaClass clazz, Method method, final ConstantPoolGen cp, final int firstStringOffset) {
104         final MethodGen methodGen = new MethodGen(method, clazz.getClassName(), cp);
105 
106         final InstructionList instructionList = methodGen.getInstructionList();
107         instructionList.insert(instructionList.getStart(), makeWillBeAddedInstructionList(methodGen, firstStringOffset));
108 
109         methodGen.setMaxStack();
110         methodGen.setMaxLocals();
111 
112         method = methodGen.getMethod();
113         instructionList.dispose();
114 
115         return method;
116     }
117 
118     public InstructionList makeWillBeAddedInstructionList(final MethodGen methodGen, final int firstStringOffset) {
119         if (firstStringOffset == -1) {
120             return new InstructionList();
121         }
122 
123         final LocalVariableGen localVariableGen = methodGen.getLocalVariables()[firstStringOffset];
124         Instruction instruction;
125 
126         if (localVariableGen != null) {
127             instruction = new ALOAD(localVariableGen.getIndex());
128         } else {
129             instruction = new ACONST_NULL();
130         }
131 
132         return createPrintln(methodGen.getConstantPool(), instruction);
133     }
134 
135     @Test
136     public void testGetLocalVariableTypeTable() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
137         final JavaClass testJavaClass = getTestJavaClass("org/apache/commons/lang3/function/TriFunction");
138         final String expectedToString = "LocalVariableTypes(startPc = 0, length = 17, index = 0:org.apache.commons.lang3.function.TriFunction<T, U, V, R> this)";
139         for (final Method method : testJavaClass.getMethods()) {
140             if ("lambda$andThen$0".equals(method.getName())) {
141                 final LocalVariableTypeTable localVariableTypeTable = method.getLocalVariableTypeTable();
142                 assertEquals(expectedToString, localVariableTypeTable.toString());
143             }
144             if ("apply".equals(method.getName())) {
145                 assertNull(method.getLocalVariableTypeTable());
146             }
147         }
148         assertNull(Repository.lookupClass(Object.class).getMethod(Object.class.getMethod("toString")).getLocalVariableTypeTable());
149     }
150 
151     @Test
152     public void testWithGenericArguement() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
153         final String targetClass = PACKAGE_BASE_NAME + ".data.SimpleClassHasMethodIncludeGenericArgument";
154         final TestClassLoader loader = new TestClassLoader(getClass().getClassLoader());
155         final Class<?> cls = loader.findClass(targetClass, getBytesFromClass(targetClass));
156 
157         java.lang.reflect.Method method = cls.getDeclaredMethod("a", String.class, List.class);
158         method.invoke(null, "a1", new LinkedList<>());
159         method = cls.getDeclaredMethod("b", String.class, List.class);
160         method.invoke(null, "b1", new LinkedList<>());
161         method = cls.getDeclaredMethod("c", String.class, String.class);
162         method.invoke(null, "c1", "c2");
163         method = cls.getDeclaredMethod("d", List.class, String.class);
164         method.invoke(null, new LinkedList<>(), "d2");
165     }
166 }