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.classfile;
18  
19  import java.io.DataInput;
20  import java.io.DataOutputStream;
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.Iterator;
24  
25  import org.apache.bcel.Const;
26  
27  /**
28   * This class represents the constant pool, i.e., a table of constants, of a parsed classfile. It may contain null references, due to the JVM specification that
29   * skips an entry after an 8-byte constant (double, long) entry. Those interested in generating constant pools programmatically should see
30   * <a href="../generic/ConstantPoolGen.html"> ConstantPoolGen</a>.
31   *
32   * @see Constant
33   * @see org.apache.bcel.generic.ConstantPoolGen
34   */
35  public class ConstantPool implements Cloneable, Node, Iterable<Constant> {
36  
37      private static String escape(final String str) {
38          final int len = str.length();
39          final StringBuilder buf = new StringBuilder(len + 5);
40          final char[] ch = str.toCharArray();
41          for (int i = 0; i < len; i++) {
42              switch (ch[i]) {
43              case '\n':
44                  buf.append("\\n");
45                  break;
46              case '\r':
47                  buf.append("\\r");
48                  break;
49              case '\t':
50                  buf.append("\\t");
51                  break;
52              case '\b':
53                  buf.append("\\b");
54                  break;
55              case '"':
56                  buf.append("\\\"");
57                  break;
58              default:
59                  buf.append(ch[i]);
60              }
61          }
62          return buf.toString();
63      }
64  
65      private Constant[] constantPool;
66  
67      /**
68       * @param constantPool Array of constants
69       */
70      public ConstantPool(final Constant[] constantPool) {
71          setConstantPool(constantPool);
72      }
73  
74      /**
75       * Reads constants from given input stream.
76       *
77       * @param input Input stream
78       * @throws IOException if problem in readUnsignedShort or readConstant
79       */
80      public ConstantPool(final DataInput input) throws IOException {
81          byte tag;
82          final int constantPoolCount = input.readUnsignedShort();
83          constantPool = new Constant[constantPoolCount];
84          /*
85           * constantPool[0] is unused by the compiler and may be used freely by the implementation.
86           * constantPool[0] is currently unused by the implementation.
87           */
88          for (int i = 1; i < constantPoolCount; i++) {
89              constantPool[i] = Constant.readConstant(input);
90              /*
91               * Quote from the JVM specification: "All eight byte constants take up two spots in the constant pool. If this is the n'th byte in the constant
92               * pool, then the next item will be numbered n+2"
93               *
94               * Thus we have to increment the index counter.
95               */
96              tag = constantPool[i].getTag();
97              if (tag == Const.CONSTANT_Double || tag == Const.CONSTANT_Long) {
98                  i++;
99              }
100         }
101     }
102 
103     /**
104      * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. I.e., the hierarchy of methods, fields,
105      * attributes, etc. spawns a tree of objects.
106      *
107      * @param v Visitor object
108      */
109     @Override
110     public void accept(final Visitor v) {
111         v.visitConstantPool(this);
112     }
113 
114     /**
115      * Resolves constant to a string representation.
116      *
117      * @param c Constant to be printed
118      * @return String representation
119      * @throws IllegalArgumentException if c is unknown constant type
120      */
121     public String constantToString(Constant c) throws IllegalArgumentException {
122         String str;
123         int i;
124         final byte tag = c.getTag();
125         switch (tag) {
126         case Const.CONSTANT_Class:
127             i = ((ConstantClass) c).getNameIndex();
128             c = getConstantUtf8(i);
129             str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false);
130             break;
131         case Const.CONSTANT_String:
132             i = ((ConstantString) c).getStringIndex();
133             c = getConstantUtf8(i);
134             str = "\"" + escape(((ConstantUtf8) c).getBytes()) + "\"";
135             break;
136         case Const.CONSTANT_Utf8:
137             str = ((ConstantUtf8) c).getBytes();
138             break;
139         case Const.CONSTANT_Double:
140             str = String.valueOf(((ConstantDouble) c).getBytes());
141             break;
142         case Const.CONSTANT_Float:
143             str = String.valueOf(((ConstantFloat) c).getBytes());
144             break;
145         case Const.CONSTANT_Long:
146             str = String.valueOf(((ConstantLong) c).getBytes());
147             break;
148         case Const.CONSTANT_Integer:
149             str = String.valueOf(((ConstantInteger) c).getBytes());
150             break;
151         case Const.CONSTANT_NameAndType:
152             str = constantToString(((ConstantNameAndType) c).getNameIndex(), Const.CONSTANT_Utf8) + " "
153                     + constantToString(((ConstantNameAndType) c).getSignatureIndex(), Const.CONSTANT_Utf8);
154             break;
155         case Const.CONSTANT_InterfaceMethodref:
156         case Const.CONSTANT_Methodref:
157         case Const.CONSTANT_Fieldref:
158             str = constantToString(((ConstantCP) c).getClassIndex(), Const.CONSTANT_Class) + "."
159                     + constantToString(((ConstantCP) c).getNameAndTypeIndex(), Const.CONSTANT_NameAndType);
160             break;
161         case Const.CONSTANT_MethodHandle:
162             // Note that the ReferenceIndex may point to a Fieldref, Methodref or
163             // InterfaceMethodref - so we need to peek ahead to get the actual type.
164             final ConstantMethodHandle cmh = (ConstantMethodHandle) c;
165             str = Const.getMethodHandleName(cmh.getReferenceKind()) + " "
166                     + constantToString(cmh.getReferenceIndex(), getConstant(cmh.getReferenceIndex()).getTag());
167             break;
168         case Const.CONSTANT_MethodType:
169             final ConstantMethodType cmt = (ConstantMethodType) c;
170             str = constantToString(cmt.getDescriptorIndex(), Const.CONSTANT_Utf8);
171             break;
172         case Const.CONSTANT_InvokeDynamic:
173             final ConstantInvokeDynamic cid = (ConstantInvokeDynamic) c;
174             str = cid.getBootstrapMethodAttrIndex() + ":" + constantToString(cid.getNameAndTypeIndex(), Const.CONSTANT_NameAndType);
175             break;
176         case Const.CONSTANT_Dynamic:
177             final ConstantDynamic cd = (ConstantDynamic) c;
178             str = cd.getBootstrapMethodAttrIndex() + ":" + constantToString(cd.getNameAndTypeIndex(), Const.CONSTANT_NameAndType);
179             break;
180         case Const.CONSTANT_Module:
181             i = ((ConstantModule) c).getNameIndex();
182             c = getConstantUtf8(i);
183             str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false);
184             break;
185         case Const.CONSTANT_Package:
186             i = ((ConstantPackage) c).getNameIndex();
187             c = getConstantUtf8(i);
188             str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false);
189             break;
190         default: // Never reached
191             throw new IllegalArgumentException("Unknown constant type " + tag);
192         }
193         return str;
194     }
195 
196     /**
197      * Retrieves constant at 'index' from constant pool and resolve it to a string representation.
198      *
199      * @param index of constant in constant pool
200      * @param tag   expected type
201      * @return String representation
202      */
203     public String constantToString(final int index, final byte tag) {
204         return constantToString(getConstant(index, tag));
205     }
206 
207     /**
208      * @return deep copy of this constant pool
209      */
210     public ConstantPool copy() {
211         ConstantPool c = null;
212         try {
213             c = (ConstantPool) clone();
214             c.constantPool = new Constant[constantPool.length];
215             for (int i = 1; i < constantPool.length; i++) {
216                 if (constantPool[i] != null) {
217                     c.constantPool[i] = constantPool[i].copy();
218                 }
219             }
220         } catch (final CloneNotSupportedException e) {
221             // TODO should this throw?
222         }
223         return c;
224     }
225 
226     /**
227      * Dump constant pool to file stream in binary format.
228      *
229      * @param file Output file stream
230      * @throws IOException if problem in writeShort or dump
231      */
232     public void dump(final DataOutputStream file) throws IOException {
233         /*
234          * Constants over the size of the constant pool shall not be written out. This is a redundant measure as the ConstantPoolGen should have already
235          * reported an error back in the situation.
236          */
237         final int size = Math.min(constantPool.length, Const.MAX_CP_ENTRIES);
238 
239         file.writeShort(size);
240         for (int i = 1; i < size; i++) {
241             if (constantPool[i] != null) {
242                 constantPool[i].dump(file);
243             }
244         }
245     }
246 
247     /**
248      * Gets constant from constant pool.
249      *
250      * @param index Index in constant pool
251      * @return Constant value
252      * @see Constant
253      * @throws ClassFormatException if index is invalid
254      */
255     @SuppressWarnings("unchecked")
256     public <T extends Constant> T getConstant(final int index) throws ClassFormatException {
257         return (T) getConstant(index, Constant.class);
258     }
259 
260     /**
261      * Gets constant from constant pool and check whether it has the expected type.
262      *
263      * @param index Index in constant pool
264      * @param tag   Tag of expected constant, i.e., its type
265      * @return Constant value
266      * @see Constant
267      * @throws ClassFormatException if constant type does not match tag
268      */
269     @SuppressWarnings("unchecked")
270     public <T extends Constant> T getConstant(final int index, final byte tag) throws ClassFormatException {
271         return (T) getConstant(index, tag, Constant.class);
272     }
273 
274     /**
275      * Gets constant from constant pool and check whether it has the expected type.
276      *
277      * @param index Index in constant pool
278      * @param tag   Tag of expected constant, i.e., its type
279      * @return Constant value
280      * @see Constant
281      * @throws ClassFormatException if constant type does not match tag
282      * @since 6.6.0
283      */
284     public <T extends Constant> T getConstant(final int index, final byte tag, final Class<T> castTo) throws ClassFormatException {
285         final T c = getConstant(index);
286         if (c == null || c.getTag() != tag) {
287             throw new ClassFormatException("Expected class '" + Const.getConstantName(tag) + "' at index " + index + " and got " + c);
288         }
289         return c;
290     }
291 
292     /**
293      * Gets constant from constant pool.
294      *
295      * @param <T> A {@link Constant} subclass
296      * @param index Index in constant pool
297      * @param castTo The {@link Constant} subclass to cast to.
298      * @return Constant value
299      * @see Constant
300      * @throws ClassFormatException if index is invalid
301      * @since 6.6.0
302      */
303     public <T extends Constant> T getConstant(final int index, final Class<T> castTo) throws ClassFormatException {
304         if (index >= constantPool.length || index < 1) {
305             throw new ClassFormatException("Invalid constant pool reference using index: " + index + ". Constant pool size is: " + constantPool.length);
306         }
307         if (constantPool[index] != null && !castTo.isAssignableFrom(constantPool[index].getClass())) {
308             throw new ClassFormatException("Invalid constant pool reference at index: " + index +
309                     ". Expected " + castTo + " but was " + constantPool[index].getClass());
310         }
311         if (index > 1) {
312             final Constant prev = constantPool[index - 1];
313             if (prev != null && (prev.getTag() == Const.CONSTANT_Double || prev.getTag() == Const.CONSTANT_Long)) {
314                 throw new ClassFormatException("Constant pool at index " + index + " is invalid. The index is unused due to the preceeding "
315                         + Const.getConstantName(prev.getTag()) + ".");
316             }
317         }
318         // Previous check ensures this won't throw a ClassCastException
319         final T c = castTo.cast(constantPool[index]);
320         if (c == null) {
321             throw new ClassFormatException("Constant pool at index " + index + " is null.");
322         }
323         return c;
324     }
325 
326     /**
327      * Gets constant from constant pool and check whether it has the expected type.
328      *
329      * @param index Index in constant pool
330      * @return ConstantInteger value
331      * @see ConstantInteger
332      * @throws ClassFormatException if constant type does not match tag
333      */
334     public ConstantInteger getConstantInteger(final int index) {
335         return getConstant(index, Const.CONSTANT_Integer, ConstantInteger.class);
336     }
337 
338     /**
339      * @return Array of constants.
340      * @see Constant
341      */
342     public Constant[] getConstantPool() {
343         return constantPool;
344     }
345 
346     /**
347      * Gets string from constant pool and bypass the indirection of 'ConstantClass' and 'ConstantString' objects. I.e. these classes have an index field that
348      * points to another entry of the constant pool of type 'ConstantUtf8' which contains the real data.
349      *
350      * @param index Index in constant pool
351      * @param tag   Tag of expected constant, either ConstantClass or ConstantString
352      * @return Contents of string reference
353      * @see ConstantClass
354      * @see ConstantString
355      * @throws IllegalArgumentException if tag is invalid
356      */
357     public String getConstantString(final int index, final byte tag) throws IllegalArgumentException {
358         int i;
359         /*
360          * This switch() is not that elegant, since the four classes have the same contents, they just differ in the name of the index field variable. But we
361          * want to stick to the JVM naming conventions closely though we could have solved these more elegantly by using the same variable name or by
362          * subclassing.
363          */
364         switch (tag) {
365         case Const.CONSTANT_Class:
366             i = getConstant(index, ConstantClass.class).getNameIndex();
367             break;
368         case Const.CONSTANT_String:
369             i = getConstant(index, ConstantString.class).getStringIndex();
370             break;
371         case Const.CONSTANT_Module:
372             i = getConstant(index, ConstantModule.class).getNameIndex();
373             break;
374         case Const.CONSTANT_Package:
375             i = getConstant(index, ConstantPackage.class).getNameIndex();
376             break;
377         case Const.CONSTANT_Utf8:
378             return getConstantUtf8(index).getBytes();
379         default:
380             throw new IllegalArgumentException("getConstantString called with illegal tag " + tag);
381         }
382         // Finally get the string from the constant pool
383         return getConstantUtf8(i).getBytes();
384     }
385 
386     /**
387      * Gets constant from constant pool and check whether it has the expected type.
388      *
389      * @param index Index in constant pool
390      * @return ConstantUtf8 value
391      * @see ConstantUtf8
392      * @throws ClassFormatException if constant type does not match tag
393      */
394     public ConstantUtf8 getConstantUtf8(final int index) throws ClassFormatException {
395         return getConstant(index, Const.CONSTANT_Utf8, ConstantUtf8.class);
396     }
397 
398     /**
399      * @return Length of constant pool.
400      */
401     public int getLength() {
402         return constantPool.length;
403     }
404 
405     @Override
406     public Iterator<Constant> iterator() {
407         return Arrays.stream(constantPool).iterator();
408     }
409 
410     /**
411      * @param constant Constant to set
412      */
413     public void setConstant(final int index, final Constant constant) {
414         constantPool[index] = constant;
415     }
416 
417     /**
418      * @param constantPool
419      */
420     public void setConstantPool(final Constant[] constantPool) {
421         this.constantPool = constantPool != null ? constantPool : Constant.EMPTY_ARRAY;
422     }
423 
424     /**
425      * @return String representation.
426      */
427     @Override
428     public String toString() {
429         final StringBuilder buf = new StringBuilder();
430         for (int i = 1; i < constantPool.length; i++) {
431             buf.append(i).append(")").append(constantPool[i]).append("\n");
432         }
433         return buf.toString();
434     }
435 }