View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.dependency.analyzer.asm;
20  
21  import java.nio.Buffer;
22  import java.nio.ByteBuffer;
23  import java.nio.ByteOrder;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.objectweb.asm.Type;
31  
32  /**
33   * A small parser to read the constant pool directly, in case it contains references
34   * ASM does not support.
35   *
36   * Adapted from <a href="https://stackoverflow.com/questions/32255023/how-would-i-go-about-parsing-the-java-class-file-constant-pool/32278587#32278587">Stack Overflow</a>
37   *
38   * Constant pool types:
39   *
40   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4">JVM 9 Sepc</a>
41   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-4.html#jvms-4.4">JVM 10 Sepc</a>
42   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.4">JVM 20 Sepc</a>
43   */
44  public class ConstantPoolParser {
45      /** Constant <code>HEAD=0xcafebabe</code> */
46      public static final int HEAD = 0xcafebabe;
47  
48      // Constant pool types
49  
50      /** Constant <code>CONSTANT_UTF8=1</code> */
51      public static final byte CONSTANT_UTF8 = 1;
52  
53      /** Constant <code>CONSTANT_INTEGER=3</code> */
54      public static final byte CONSTANT_INTEGER = 3;
55  
56      /** Constant <code>CONSTANT_FLOAT=4</code> */
57      public static final byte CONSTANT_FLOAT = 4;
58  
59      /** Constant <code>CONSTANT_LONG=5</code> */
60      public static final byte CONSTANT_LONG = 5;
61  
62      /** Constant <code>CONSTANT_DOUBLE=6</code> */
63      public static final byte CONSTANT_DOUBLE = 6;
64  
65      /** Constant <code>CONSTANT_CLASS=7</code> */
66      public static final byte CONSTANT_CLASS = 7;
67  
68      /** Constant <code>CONSTANT_STRING=8</code> */
69      public static final byte CONSTANT_STRING = 8;
70  
71      /** Constant <code>CONSTANT_FIELDREF=9</code> */
72      public static final byte CONSTANT_FIELDREF = 9;
73  
74      /** Constant <code>CONSTANT_METHODREF=10</code> */
75      public static final byte CONSTANT_METHODREF = 10;
76  
77      /** Constant <code>CONSTANT_INTERFACEMETHODREF=11</code> */
78      public static final byte CONSTANT_INTERFACEMETHODREF = 11;
79  
80      /** Constant <code>CONSTANT_NAME_AND_TYPE=12</code> */
81      public static final byte CONSTANT_NAME_AND_TYPE = 12;
82  
83      /** Constant <code>CONSTANT_METHODHANDLE=15</code> */
84      public static final byte CONSTANT_METHODHANDLE = 15;
85  
86      /** Constant <code>CONSTANT_METHOD_TYPE=16</code> */
87      public static final byte CONSTANT_METHOD_TYPE = 16;
88  
89      /** Constant <code>CONSTANT_INVOKE=17</code> */
90      public static final byte CONSTANT_INVOKE = 17;
91  
92      /** Constant <code>CONSTANT_INVOKE_DYNAMIC=18</code> */
93      public static final byte CONSTANT_INVOKE_DYNAMIC = 18;
94  
95      /** Constant <code>CONSTANT_MODULE=19</code> */
96      public static final byte CONSTANT_MODULE = 19;
97  
98      /** Constant <code>CONSTANT_PACKAGE=20</code> */
99      public static final byte CONSTANT_PACKAGE = 20;
100 
101     private static final int OXF0 = 0xf0;
102 
103     private static final int OXE0 = 0xe0;
104 
105     private static final int OX3F = 0x3F;
106 
107     static Set<String> getConstantPoolClassReferences(byte[] b) throws UnknownConstantPoolTypeException {
108         return parseConstantPoolClassReferences(ByteBuffer.wrap(b));
109     }
110 
111     private static Set<String> parseConstantPoolClassReferences(ByteBuffer buf)
112             throws UnknownConstantPoolTypeException {
113         if (buf.order(ByteOrder.BIG_ENDIAN).getInt() != HEAD) {
114             return Collections.emptySet();
115         }
116         buf.getChar();
117         buf.getChar(); // minor + ver
118         Set<Integer> classReferences = new HashSet<>();
119         Set<Integer> typeReferences = new HashSet<>();
120         Map<Integer, String> stringConstants = new HashMap<>();
121         for (int ix = 1, num = buf.getChar(); ix < num; ix++) {
122             byte tag = buf.get();
123             switch (tag) {
124                 case CONSTANT_UTF8:
125                     stringConstants.put(ix, decodeString(buf));
126                     break;
127                 case CONSTANT_CLASS:
128                     classReferences.add((int) buf.getChar());
129                     break;
130                 case CONSTANT_METHOD_TYPE:
131                     consumeMethodType(buf);
132                     break;
133                 case CONSTANT_FIELDREF:
134                 case CONSTANT_METHODREF:
135                 case CONSTANT_INTERFACEMETHODREF:
136                     consumeReference(buf);
137                     break;
138                 case CONSTANT_NAME_AND_TYPE:
139                     buf.getChar();
140                     typeReferences.add((int) buf.getChar());
141                     break;
142                 case CONSTANT_INTEGER:
143                     consumeInt(buf);
144                     break;
145                 case CONSTANT_FLOAT:
146                     consumeFloat(buf);
147                     break;
148                 case CONSTANT_DOUBLE:
149                     consumeDouble(buf);
150                     ix++;
151                     break;
152                 case CONSTANT_LONG:
153                     consumeLong(buf);
154                     ix++;
155                     break;
156                 case CONSTANT_STRING:
157                     consumeString(buf);
158                     break;
159                 case CONSTANT_METHODHANDLE:
160                     consumeMethodHandle(buf);
161                     break;
162                 case CONSTANT_INVOKE:
163                     consumeDynamic(buf);
164                     break;
165                 case CONSTANT_INVOKE_DYNAMIC:
166                     consumeInvokeDynamic(buf);
167                     break;
168                 case CONSTANT_MODULE:
169                     consumeModule(buf);
170                     break;
171                 case CONSTANT_PACKAGE:
172                     consumePackage(buf);
173                     break;
174                 default:
175                     throw new UnknownConstantPoolTypeException("Unknown constant pool type '" + tag + "'");
176             }
177         }
178 
179         Set<String> result = new HashSet<>();
180 
181         for (Integer classRef : classReferences) {
182             addClassToResult(result, stringConstants.get(classRef));
183         }
184 
185         for (Integer typeRef : typeReferences) {
186             String typeName = stringConstants.get(typeRef);
187 
188             if (Type.getType(typeName).getSort() == Type.METHOD) {
189                 addClassToResult(result, Type.getReturnType(typeName).getInternalName());
190                 Type[] argumentTypes = Type.getArgumentTypes(typeName);
191                 for (Type argumentType : argumentTypes) {
192                     addClassToResult(result, argumentType.getInternalName());
193                 }
194             }
195         }
196 
197         return result;
198     }
199 
200     private static void addClassToResult(Set<String> result, String className) {
201         // filter out things from unnamed package, probably a false-positive
202         if (isImportableClass(className)) {
203             result.add(className);
204         }
205     }
206 
207     @SuppressWarnings("RedundantCast")
208     private static String decodeString(ByteBuffer buf) {
209         int size = buf.getChar();
210         int oldLimit = buf.limit();
211         // Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer
212         ((Buffer) buf).limit(buf.position() + size);
213         StringBuilder sb = new StringBuilder(size + (size >> 1) + 16);
214         while (buf.hasRemaining()) {
215             byte b = buf.get();
216             if (b > 0) {
217                 sb.append((char) b);
218             } else {
219                 int b2 = buf.get();
220                 if ((b & OXF0) != OXE0) {
221                     sb.append((char) ((b & 0x1F) << 6 | b2 & OX3F));
222                 } else {
223                     int b3 = buf.get();
224                     sb.append((char) ((b & 0x0F) << 12 | (b2 & OX3F) << 6 | b3 & OX3F));
225                 }
226             }
227         }
228         ((Buffer) buf).limit(oldLimit);
229         return sb.toString();
230     }
231 
232     private static boolean isImportableClass(String className) {
233         // without a slash, class must be in unnamed package, which can't be imported
234         return className.indexOf('/') != -1;
235     }
236 
237     private static void consumeMethodType(ByteBuffer buf) {
238         buf.getChar();
239     }
240 
241     private static void consumeReference(ByteBuffer buf) {
242         buf.getChar();
243         buf.getChar();
244     }
245 
246     private static void consumeInt(ByteBuffer buf) {
247         buf.getInt();
248     }
249 
250     private static void consumeFloat(ByteBuffer buf) {
251         buf.getFloat();
252     }
253 
254     private static void consumeDouble(ByteBuffer buf) {
255         buf.getDouble();
256     }
257 
258     private static void consumeLong(ByteBuffer buf) {
259         buf.getLong();
260     }
261 
262     private static void consumeString(ByteBuffer buf) {
263         buf.getChar();
264     }
265 
266     private static void consumeMethodHandle(ByteBuffer buf) {
267         buf.get();
268         buf.getChar();
269     }
270 
271     private static void consumeDynamic(ByteBuffer buf) {
272         buf.getChar(); // u2 bootstrap_method_attr_index;
273         buf.getChar(); // u2 name_and_type_index;
274     }
275 
276     private static void consumeInvokeDynamic(ByteBuffer buf) {
277         buf.getChar();
278         buf.getChar();
279     }
280 
281     private static void consumeModule(ByteBuffer buf) {
282         buf.getChar();
283     }
284 
285     private static void consumePackage(ByteBuffer buf) {
286         buf.getChar();
287     }
288 }