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   *   https://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.commons.compress.harmony.unpack200;
20  
21  import java.util.List;
22  
23  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
24  import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
25  import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantPoolEntry;
26  
27  /**
28   * Manages the constant pool used for re-creating class files.
29   */
30  public class SegmentConstantPool {
31  
32      /**
33       * Value {@value}.
34       */
35      public static final int ALL = 0;
36  
37      /**
38       * Value {@value}.
39       */
40      public static final int UTF_8 = 1;
41  
42      /**
43       * Value {@value}.
44       */
45      public static final int CP_INT = 2;
46  
47      // define in archive order
48  
49      /**
50       * Value {@value}.
51       */
52      public static final int CP_FLOAT = 3;
53  
54      /**
55       * Value {@value}.
56       */
57      public static final int CP_LONG = 4;
58  
59      /**
60       * Value {@value}.
61       */
62      public static final int CP_DOUBLE = 5;
63  
64      /**
65       * Value {@value}.
66       */
67      public static final int CP_STRING = 6;
68  
69      /**
70       * Value {@value}.
71       */
72      public static final int CP_CLASS = 7;
73  
74      /**
75       * Value {@value}.
76       */
77      public static final int SIGNATURE = 8; // TODO and more to come --
78  
79      /**
80       * Value {@value}.
81       */
82      public static final int CP_DESCR = 9;
83  
84      /**
85       * Value {@value}.
86       */
87      public static final int CP_FIELD = 10;
88  
89      /**
90       * Value {@value}.
91       */
92      public static final int CP_METHOD = 11;
93  
94      /**
95       * Value {@value}.
96       */
97      public static final int CP_IMETHOD = 12;
98  
99      /**
100      * Value {@value}.
101      */
102     protected static final String REGEX_MATCH_ALL = ".*";
103 
104     /**
105      * Value {@value}.
106      */
107     protected static final String INITSTRING = "<init>";
108 
109     /**
110      * Value {@value}.
111      */
112     protected static final String REGEX_MATCH_INIT = "^" + INITSTRING + ".*";
113 
114     /**
115      * We don't want a dependency on regex in Pack200. The only place one exists is in matchSpecificPoolEntryIndex(). To eliminate this dependency, we've
116      * implemented the world's stupidest regexMatch. It knows about the two forms we care about: .* (aka REGEX_MATCH_ALL) {@code ^<init>;.*} (aka
117      * REGEX_MATCH_INIT) and will answer correctly if those are passed as the regexString.
118      *
119      * @param regexString   String against which the compareString will be matched
120      * @param compareString String to match against the regexString
121      * @return boolean true if the compareString matches the regexString; otherwise false.
122      */
123     protected static boolean regexMatches(final String regexString, final String compareString) {
124         if (REGEX_MATCH_ALL.equals(regexString)) {
125             return true;
126         }
127         if (REGEX_MATCH_INIT.equals(regexString)) {
128             if (compareString.length() < INITSTRING.length()) {
129                 return false;
130             }
131             return INITSTRING.equals(compareString.substring(0, INITSTRING.length()));
132         }
133         throw new Error("regex trying to match a pattern I don't know: " + regexString);
134     }
135 
136     static int toIndex(final long index) throws Pack200Exception {
137         if (index < 0) {
138             throw new Pack200Exception("Cannot have a negative index.");
139         }
140         return toIntExact(index);
141     }
142 
143     static int toIntExact(final long index) throws Pack200Exception {
144         try {
145             return Math.toIntExact(index);
146         } catch (final ArithmeticException e) {
147             throw new Pack200Exception("index", e);
148         }
149     }
150 
151     private final CpBands bands;
152 
153     private final SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache();
154 
155     /**
156      * Constructs a new instance.
157      *
158      * @param bands Constant pool bands.
159      */
160     public SegmentConstantPool(final CpBands bands) {
161         this.bands = bands;
162     }
163 
164     /**
165      * Gets the CPClass associated with a class name. Returns null if the class doesn't exist.
166      *
167      * @param name Class name to look for (form: java/lang/Object)
168      * @return CPClass for that class name, or null if not found.
169      * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
170      */
171     public ConstantPoolEntry getClassPoolEntry(final String name) throws Pack200Exception {
172         final int index = matchSpecificPoolEntryIndex(bands.getCpClass(), name, 0);
173         return index == -1 ? null : getConstantPoolEntry(CP_CLASS, index);
174     }
175 
176     /**
177      * Gets the subset constant pool of the specified type to be just that which has the specified class name. Answer the ConstantPoolEntry at the desiredIndex
178      * of the subset pool.
179      *
180      * @param cp               type of constant pool array to search.
181      * @param desiredIndex     index of the constant pool.
182      * @param desiredClassName class to use to generate a subset of the pool.
183      * @return ConstantPoolEntry
184      * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
185      */
186     public ConstantPoolEntry getClassSpecificPoolEntry(final int cp, final long desiredIndex, final String desiredClassName) throws Pack200Exception {
187         final String[] array;
188         switch (cp) {
189         case CP_FIELD:
190             array = bands.getCpFieldClass();
191             break;
192         case CP_METHOD:
193             array = bands.getCpMethodClass();
194             break;
195         case CP_IMETHOD:
196             array = bands.getCpIMethodClass();
197             break;
198         default:
199             throw new Pack200Exception("Type is not supported yet: " + cp);
200         }
201         return getConstantPoolEntry(cp, matchSpecificPoolEntryIndex(array, desiredClassName, toIndex(desiredIndex)));
202     }
203 
204     /**
205      * Gets the constant pool entry of the given type and index.
206      *
207      * @param type Constant pool type.
208      * @param index Index into a specific constant pool.
209      * @return a constant pool entry.
210      * @throws Pack200Exception if a type is not supported or the index not in the range [0, {@link Integer#MAX_VALUE}].
211      */
212     public ConstantPoolEntry getConstantPoolEntry(final int type, final long index) throws Pack200Exception {
213         if (index == -1) {
214             return null;
215         }
216         final int actualIndex = toIndex(index);
217         switch (type) {
218         case UTF_8:
219             return bands.cpUTF8Value(actualIndex);
220         case CP_INT:
221             return bands.cpIntegerValue(actualIndex);
222         case CP_FLOAT:
223             return bands.cpFloatValue(actualIndex);
224         case CP_LONG:
225             return bands.cpLongValue(actualIndex);
226         case CP_DOUBLE:
227             return bands.cpDoubleValue(actualIndex);
228         case CP_STRING:
229             return bands.cpStringValue(actualIndex);
230         case CP_CLASS:
231             return bands.cpClassValue(actualIndex);
232         case SIGNATURE:
233             throw new Pack200Exception("Type SIGNATURE is not supported yet: " + SIGNATURE);
234         // return null /* new CPSignature(bands.getCpSignature()[index]) */;
235         case CP_DESCR:
236             throw new Pack200Exception("Type CP_DESCR is not supported yet: " + CP_DESCR);
237         // return null /* new CPDescriptor(bands.getCpDescriptor()[index])
238         // */;
239         case CP_FIELD:
240             return bands.cpFieldValue(actualIndex);
241         case CP_METHOD:
242             return bands.cpMethodValue(actualIndex);
243         case CP_IMETHOD:
244             return bands.cpIMethodValue(actualIndex);
245         default:
246             break;
247         }
248         // etc
249         throw new Pack200Exception("Type is not supported yet: " + type);
250     }
251 
252     /**
253      * Gets the {@code init} method for the specified class.
254      *
255      * @param cp               constant pool to search, must be {@link #CP_METHOD}.
256      * @param value            index of {@code init} method.
257      * @param desiredClassName String class name of the {@code init} method.
258      * @return CPMethod {@code init} method.
259      * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
260      */
261     public ConstantPoolEntry getInitMethodPoolEntry(final int cp, final long value, final String desiredClassName) throws Pack200Exception {
262         if (cp != CP_METHOD) {
263             throw new Pack200Exception("Nothing but CP_METHOD can be an <init>");
264         }
265         final int realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), bands.getCpMethodDescriptor(), desiredClassName, REGEX_MATCH_INIT,
266                 toIndex(value));
267         return getConstantPoolEntry(cp, realIndex);
268     }
269 
270     public ClassFileEntry getValue(final int cp, final long longIndex) throws Pack200Exception {
271         final int index = (int) longIndex;
272         if (index == -1) {
273             return null;
274         }
275         if (index < 0) {
276             throw new Pack200Exception("Cannot have a negative range");
277         }
278         switch (cp) {
279         case UTF_8:
280             return bands.cpUTF8Value(index);
281         case CP_INT:
282             return bands.cpIntegerValue(index);
283         case CP_FLOAT:
284             return bands.cpFloatValue(index);
285         case CP_LONG:
286             return bands.cpLongValue(index);
287         case CP_DOUBLE:
288             return bands.cpDoubleValue(index);
289         case CP_STRING:
290             return bands.cpStringValue(index);
291         case CP_CLASS:
292             return bands.cpClassValue(index);
293         case SIGNATURE:
294             return bands.cpSignatureValue(index);
295         case CP_DESCR:
296             return bands.cpNameAndTypeValue(index);
297         default:
298             break;
299         }
300         throw new Error("Tried to get a value I don't know about: " + cp);
301     }
302 
303     /**
304      * A number of things make use of subsets of structures. In one particular example, _super bytecodes will use a subset of method or field classes which have
305      * just those methods / fields defined in the superclass. Similarly, _this bytecodes use just those methods/fields defined in this class, and _init
306      * bytecodes use just those methods that start with {@code <init>}.
307      * <p>
308      * This method takes an array of names, a String to match for, an index and a boolean as parameters, and answers the array position in the array of the
309      * indexth element which matches (or equals) the String (depending on the state of the boolean)
310      * </p>
311      * <p>
312      * In other words, if the class array consists of: Object [position 0, 0th instance of Object] String [position 1, 0th instance of String] String [position
313      * 2, 1st instance of String] Object [position 3, 1st instance of Object] Object [position 4, 2nd instance of Object] then matchSpecificPoolEntryIndex(...,
314      * "Object", 2, false) will answer 4. matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1.
315      * </p>
316      *
317      * @param nameArray     Array of Strings against which the compareString is tested
318      * @param compareString String for which to search
319      * @param desiredIndex  nth element with that match (counting from 0)
320      * @return int index into nameArray, or -1 if not found.
321      */
322     protected int matchSpecificPoolEntryIndex(final String[] nameArray, final String compareString, final int desiredIndex) {
323         return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, REGEX_MATCH_ALL, desiredIndex);
324     }
325 
326     /**
327      * This method's function is to look through pairs of arrays. It keeps track of the number of hits it finds using the following basis of comparison for a
328      * hit: - the primaryArray[index] must be .equals() to the primaryCompareString - the secondaryArray[index] .matches() the secondaryCompareString. When the
329      * desiredIndex number of hits has been reached, the index into the original two arrays of the element hit is returned.
330      *
331      * @param primaryArray          The first array to search
332      * @param secondaryArray        The second array (must be same .length as primaryArray)
333      * @param primaryCompareString  The String to compare against primaryArray using .equals()
334      * @param secondaryCompareRegex The String to compare against secondaryArray using .matches()
335      * @param desiredIndex          The nth hit whose position we're seeking
336      * @return int index that represents the position of the nth hit in primaryArray and secondaryArray
337      */
338     protected int matchSpecificPoolEntryIndex(final String[] primaryArray, final String[] secondaryArray, final String primaryCompareString,
339             final String secondaryCompareRegex, final int desiredIndex) {
340         int instanceCount = -1;
341         final List<Integer> indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString);
342         if (indexList.isEmpty()) {
343             // Primary key not found, no chance of finding secondary
344             return -1;
345         }
346 
347         for (final Integer element : indexList) {
348             final int arrayIndex = element.intValue();
349             if (regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) {
350                 instanceCount++;
351                 if (instanceCount == desiredIndex) {
352                     return arrayIndex;
353                 }
354             }
355         }
356         // We didn't return in the for loop, so the desiredMatch
357         // with desiredIndex must not exist in the arrays.
358         return -1;
359     }
360 }