ClassPool.java revision 99b46173c5294d186ccf2e647b86346a22b247c8
1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer.pool;
33
34import com.google.common.base.Function;
35import com.google.common.base.Predicate;
36import com.google.common.collect.*;
37import org.jf.dexlib2.DebugItemType;
38import org.jf.dexlib2.ReferenceType;
39import org.jf.dexlib2.iface.*;
40import org.jf.dexlib2.iface.debug.*;
41import org.jf.dexlib2.iface.instruction.Instruction;
42import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
43import org.jf.dexlib2.iface.reference.*;
44import org.jf.dexlib2.iface.value.EncodedValue;
45import org.jf.dexlib2.immutable.value.ImmutableEncodedValueFactory;
46import org.jf.dexlib2.util.EncodedValueUtils;
47import org.jf.dexlib2.util.ReferenceUtil;
48import org.jf.dexlib2.writer.ClassSection;
49import org.jf.dexlib2.writer.DebugWriter;
50import org.jf.util.AbstractForwardSequentialList;
51import org.jf.util.CollectionUtils;
52import org.jf.util.ExceptionWithContext;
53
54import javax.annotation.Nonnull;
55import javax.annotation.Nullable;
56import java.io.IOException;
57import java.util.*;
58import java.util.Map.Entry;
59
60public class ClassPool implements ClassSection<CharSequence, CharSequence,
61        TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
62        Set<? extends Annotation>, EncodedValue> {
63    @Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
64
65    @Nonnull private final StringPool stringPool;
66    @Nonnull private final TypePool typePool;
67    @Nonnull private final FieldPool fieldPool;
68    @Nonnull private final MethodPool methodPool;
69    @Nonnull private final AnnotationSetPool annotationSetPool;
70    @Nonnull private final TypeListPool typeListPool;
71
72    public ClassPool(@Nonnull StringPool stringPool,
73                     @Nonnull TypePool typePool,
74                     @Nonnull FieldPool fieldPool,
75                     @Nonnull MethodPool methodPool,
76                     @Nonnull AnnotationSetPool annotationSetPool,
77                     @Nonnull TypeListPool typeListPool) {
78        this.stringPool = stringPool;
79        this.typePool = typePool;
80        this.fieldPool = fieldPool;
81        this.methodPool = methodPool;
82        this.annotationSetPool = annotationSetPool;
83        this.typeListPool = typeListPool;
84    }
85
86    public void intern(@Nonnull ClassDef classDef) {
87        PoolClassDef poolClassDef = new PoolClassDef(classDef);
88
89        PoolClassDef prev = internedItems.put(poolClassDef.getType(), poolClassDef);
90        if (prev != null) {
91            throw new ExceptionWithContext("Class %s has already been interned", poolClassDef.getType());
92        }
93
94        typePool.intern(poolClassDef.getType());
95        typePool.internNullable(poolClassDef.getSuperclass());
96        typeListPool.intern(poolClassDef.getInterfaces());
97        stringPool.internNullable(poolClassDef.getSourceFile());
98
99        HashSet<String> fields = new HashSet<String>();
100        for (Field field: poolClassDef.getFields()) {
101            String fieldDescriptor = ReferenceUtil.getShortFieldDescriptor(field);
102            if (!fields.add(fieldDescriptor)) {
103                throw new ExceptionWithContext("Multiple definitions for field %s->%s",
104                        poolClassDef.getType(), fieldDescriptor);
105            }
106            fieldPool.intern(field);
107
108            EncodedValue initialValue = field.getInitialValue();
109            if (initialValue != null) {
110                DexPool.internEncodedValue(initialValue, stringPool, typePool, fieldPool, methodPool);
111            }
112
113            annotationSetPool.intern(field.getAnnotations());
114        }
115
116        HashSet<String> methods = new HashSet<String>();
117        for (PoolMethod method: poolClassDef.getMethods()) {
118            String methodDescriptor = ReferenceUtil.getShortMethodDescriptor(method);
119            if (!methods.add(methodDescriptor)) {
120                throw new ExceptionWithContext("Multiple definitions for method %s->%s",
121                        poolClassDef.getType(), methodDescriptor);
122            }
123            methodPool.intern(method);
124            internCode(method);
125            internDebug(method);
126            annotationSetPool.intern(method.getAnnotations());
127
128            for (MethodParameter parameter: method.getParameters()) {
129                annotationSetPool.intern(parameter.getAnnotations());
130            }
131        }
132
133        annotationSetPool.intern(poolClassDef.getAnnotations());
134    }
135
136    private void internCode(@Nonnull Method method) {
137        // this also handles parameter names, which aren't directly tied to the MethodImplementation, even though the debug items are
138        boolean hasInstruction = false;
139
140        MethodImplementation methodImpl = method.getImplementation();
141        if (methodImpl != null) {
142            for (Instruction instruction: methodImpl.getInstructions()) {
143                hasInstruction = true;
144                if (instruction instanceof ReferenceInstruction) {
145                    Reference reference = ((ReferenceInstruction)instruction).getReference();
146                    switch (instruction.getOpcode().referenceType) {
147                        case ReferenceType.STRING:
148                            stringPool.intern((StringReference)reference);
149                            break;
150                        case ReferenceType.TYPE:
151                            typePool.intern((TypeReference)reference);
152                            break;
153                        case ReferenceType.FIELD:
154                            fieldPool.intern((FieldReference) reference);
155                            break;
156                        case ReferenceType.METHOD:
157                            methodPool.intern((MethodReference)reference);
158                            break;
159                        default:
160                            throw new ExceptionWithContext("Unrecognized reference type: %d",
161                                    instruction.getOpcode().referenceType);
162                    }
163                }
164            }
165
166            List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
167            if (!hasInstruction && tryBlocks.size() > 0) {
168                throw new ExceptionWithContext("Method %s has no instructions, but has try blocks.",
169                        ReferenceUtil.getMethodDescriptor(method));
170            }
171
172            for (TryBlock<? extends ExceptionHandler> tryBlock: methodImpl.getTryBlocks()) {
173                for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
174                    typePool.internNullable(handler.getExceptionType());
175                }
176            }
177        }
178    }
179
180    private void internDebug(@Nonnull Method method) {
181        for (MethodParameter param: method.getParameters()) {
182            String paramName = param.getName();
183            if (paramName != null) {
184                stringPool.intern(paramName);
185            }
186        }
187
188        MethodImplementation methodImpl = method.getImplementation();
189        if (methodImpl != null) {
190            for (DebugItem debugItem: methodImpl.getDebugItems()) {
191                switch (debugItem.getDebugItemType()) {
192                    case DebugItemType.START_LOCAL:
193                        StartLocal startLocal = (StartLocal)debugItem;
194                        stringPool.internNullable(startLocal.getName());
195                        typePool.internNullable(startLocal.getType());
196                        stringPool.internNullable(startLocal.getSignature());
197                        break;
198                    case DebugItemType.SET_SOURCE_FILE:
199                        stringPool.internNullable(((SetSourceFile) debugItem).getSourceFile());
200                        break;
201                }
202            }
203        }
204    }
205
206    private ImmutableList<PoolClassDef> sortedClasses = null;
207    @Nonnull @Override public Collection<? extends PoolClassDef> getSortedClasses() {
208        if (sortedClasses == null) {
209            sortedClasses = Ordering.natural().immutableSortedCopy(internedItems.values());
210        }
211        return sortedClasses;
212    }
213
214    @Nullable @Override
215    public Map.Entry<? extends PoolClassDef, Integer> getClassEntryByType(@Nullable CharSequence name) {
216        if (name == null) {
217            return null;
218        }
219
220        final PoolClassDef classDef = internedItems.get(name.toString());
221        if (classDef == null) {
222            return null;
223        }
224
225        return new Map.Entry<PoolClassDef, Integer>() {
226            @Override public PoolClassDef getKey() {
227                return classDef;
228            }
229
230            @Override public Integer getValue() {
231                return classDef.classDefIndex;
232            }
233
234            @Override public Integer setValue(Integer value) {
235                return classDef.classDefIndex = value;
236            }
237        };
238    }
239
240    @Nonnull @Override public CharSequence getType(@Nonnull PoolClassDef classDef) {
241        return classDef.getType();
242    }
243
244    @Override public int getAccessFlags(@Nonnull PoolClassDef classDef) {
245        return classDef.getAccessFlags();
246    }
247
248    @Nullable @Override public CharSequence getSuperclass(@Nonnull PoolClassDef classDef) {
249        return classDef.getSuperclass();
250    }
251
252    @Nullable @Override public TypeListPool.Key<SortedSet<String>> getSortedInterfaces(@Nonnull PoolClassDef classDef) {
253        return classDef.interfaces;
254    }
255
256    @Nullable @Override public CharSequence getSourceFile(@Nonnull PoolClassDef classDef) {
257        return classDef.getSourceFile();
258    }
259
260    private static final Predicate<Field> HAS_INITIALIZER = new Predicate<Field>() {
261        @Override
262        public boolean apply(Field input) {
263            EncodedValue encodedValue = input.getInitialValue();
264            return encodedValue != null && !EncodedValueUtils.isDefaultValue(encodedValue);
265        }
266    };
267
268    private static final Function<Field, EncodedValue> GET_INITIAL_VALUE = new Function<Field, EncodedValue>() {
269        @Override
270        public EncodedValue apply(Field input) {
271            EncodedValue initialValue = input.getInitialValue();
272            if (initialValue == null) {
273                return ImmutableEncodedValueFactory.defaultValueForType(input.getType());
274            }
275            return initialValue;
276        }
277    };
278
279    @Nullable @Override public Collection<? extends EncodedValue> getStaticInitializers(
280            @Nonnull PoolClassDef classDef) {
281        final SortedSet<Field> sortedStaticFields = classDef.getStaticFields();
282
283        final int lastIndex = CollectionUtils.lastIndexOf(sortedStaticFields, HAS_INITIALIZER);
284        if (lastIndex > -1) {
285            return new AbstractCollection<EncodedValue>() {
286                @Nonnull @Override public Iterator<EncodedValue> iterator() {
287                    return FluentIterable.from(sortedStaticFields)
288                            .limit(lastIndex+1)
289                            .transform(GET_INITIAL_VALUE).iterator();
290                }
291
292                @Override public int size() {
293                    return lastIndex+1;
294                }
295            };
296        }
297        return null;
298    }
299
300    @Nonnull @Override public Collection<? extends Field> getSortedStaticFields(@Nonnull PoolClassDef classDef) {
301        return classDef.getStaticFields();
302    }
303
304    @Nonnull @Override public Collection<? extends Field> getSortedInstanceFields(@Nonnull PoolClassDef classDef) {
305        return classDef.getInstanceFields();
306    }
307
308    @Nonnull @Override public Collection<? extends Field> getSortedFields(@Nonnull PoolClassDef classDef) {
309        return classDef.getFields();
310    }
311
312    @Nonnull @Override public Collection<PoolMethod> getSortedDirectMethods(@Nonnull PoolClassDef classDef) {
313        return classDef.getDirectMethods();
314    }
315
316    @Nonnull @Override public Collection<PoolMethod> getSortedVirtualMethods(@Nonnull PoolClassDef classDef) {
317        return classDef.getVirtualMethods();
318    }
319
320    @Nonnull @Override public Collection<? extends PoolMethod> getSortedMethods(@Nonnull PoolClassDef classDef) {
321        return classDef.getMethods();
322    }
323
324    @Override public int getFieldAccessFlags(@Nonnull Field field) {
325        return field.getAccessFlags();
326    }
327
328    @Override public int getMethodAccessFlags(@Nonnull PoolMethod method) {
329        return method.getAccessFlags();
330    }
331
332    @Nullable @Override public Set<? extends Annotation> getClassAnnotations(@Nonnull PoolClassDef classDef) {
333        Set<? extends Annotation> annotations = classDef.getAnnotations();
334        if (annotations.size() == 0) {
335            return null;
336        }
337        return annotations;
338    }
339
340    @Nullable @Override public Set<? extends Annotation> getFieldAnnotations(@Nonnull Field field) {
341        Set<? extends Annotation> annotations = field.getAnnotations();
342        if (annotations.size() == 0) {
343            return null;
344        }
345        return annotations;
346    }
347
348    @Nullable @Override public Set<? extends Annotation> getMethodAnnotations(@Nonnull PoolMethod method) {
349        Set<? extends Annotation> annotations = method.getAnnotations();
350        if (annotations.size() == 0) {
351            return null;
352        }
353        return annotations;
354    }
355
356    private static final Predicate<MethodParameter> HAS_PARAMETER_ANNOTATIONS = new Predicate<MethodParameter>() {
357        @Override
358        public boolean apply(MethodParameter input) {
359            return input.getAnnotations().size() > 0;
360        }
361    };
362
363    private static final Function<MethodParameter, Set<? extends Annotation>> PARAMETER_ANNOTATIONS =
364            new Function<MethodParameter, Set<? extends Annotation>>() {
365                @Override
366                public Set<? extends Annotation> apply(MethodParameter input) {
367                    return input.getAnnotations();
368                }
369            };
370
371    @Nullable @Override public List<? extends Set<? extends Annotation>> getParameterAnnotations(
372            @Nonnull final PoolMethod method) {
373        final int lastIndex = CollectionUtils.lastIndexOf(method.getParameters(), HAS_PARAMETER_ANNOTATIONS);
374
375        if (lastIndex > -1) {
376            return new AbstractForwardSequentialList<Set<? extends Annotation>>() {
377                @Nonnull @Override public Iterator<Set<? extends Annotation>> iterator() {
378                    return FluentIterable.from(method.getParameters())
379                            .limit(lastIndex+1)
380                            .transform(PARAMETER_ANNOTATIONS).iterator();
381                }
382
383                @Override public int size() {
384                    return lastIndex+1;
385                }
386            };
387        }
388        return null;
389    }
390
391    @Nullable @Override public Iterable<? extends DebugItem> getDebugItems(@Nonnull PoolMethod method) {
392        MethodImplementation impl = method.getImplementation();
393        if (impl != null) {
394            return impl.getDebugItems();
395        }
396        return null;
397    }
398
399    @Nullable @Override public Iterable<CharSequence> getParameterNames(@Nonnull PoolMethod method) {
400        return Iterables.transform(method.getParameters(), new Function<MethodParameter, CharSequence>() {
401            @Nullable @Override public CharSequence apply(MethodParameter input) {
402                return input.getName();
403            }
404        });
405    }
406
407    @Override public int getRegisterCount(@Nonnull PoolMethod method) {
408        MethodImplementation impl = method.getImplementation();
409        if (impl != null) {
410            return impl.getRegisterCount();
411        }
412        return 0;
413    }
414
415    @Nullable @Override public Iterable<? extends Instruction> getInstructions(@Nonnull PoolMethod method) {
416        MethodImplementation impl = method.getImplementation();
417        if (impl != null) {
418            return impl.getInstructions();
419        }
420        return null;
421    }
422
423    @Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks(
424            @Nonnull PoolMethod method) {
425        MethodImplementation impl = method.getImplementation();
426        if (impl != null) {
427            return impl.getTryBlocks();
428        }
429        return ImmutableList.of();
430    }
431
432    @Nullable @Override public CharSequence getExceptionType(@Nonnull ExceptionHandler handler) {
433        return handler.getExceptionType();
434    }
435
436    @Override public void setEncodedArrayOffset(@Nonnull PoolClassDef classDef, int offset) {
437        classDef.encodedArrayOffset = offset;
438    }
439
440    @Override public int getEncodedArrayOffset(@Nonnull PoolClassDef classDef) {
441        return classDef.encodedArrayOffset;
442    }
443
444    @Override public void setAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef, int offset) {
445        classDef.annotationDirectoryOffset = offset;
446    }
447
448    @Override public int getAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef) {
449        return classDef.annotationDirectoryOffset;
450    }
451
452    @Override public void setAnnotationSetRefListOffset(@Nonnull PoolMethod method, int offset) {
453        method.annotationSetRefListOffset = offset;
454
455    }
456    @Override public int getAnnotationSetRefListOffset(@Nonnull PoolMethod method) {
457        return method.annotationSetRefListOffset;
458    }
459
460    @Override public void setCodeItemOffset(@Nonnull PoolMethod method, int offset) {
461        method.codeItemOffset = offset;
462    }
463
464    @Override public int getCodeItemOffset(@Nonnull PoolMethod method) {
465        return method.codeItemOffset;
466    }
467
468    @Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
469                                         DebugItem debugItem) throws IOException {
470        switch (debugItem.getDebugItemType()) {
471            case DebugItemType.START_LOCAL: {
472                StartLocal startLocal = (StartLocal)debugItem;
473                writer.writeStartLocal(startLocal.getCodeAddress(),
474                        startLocal.getRegister(),
475                        startLocal.getName(),
476                        startLocal.getType(),
477                        startLocal.getSignature());
478                break;
479            }
480            case DebugItemType.END_LOCAL: {
481                EndLocal endLocal = (EndLocal)debugItem;
482                writer.writeEndLocal(endLocal.getCodeAddress(), endLocal.getRegister());
483                break;
484            }
485            case DebugItemType.RESTART_LOCAL: {
486                RestartLocal restartLocal = (RestartLocal)debugItem;
487                writer.writeRestartLocal(restartLocal.getCodeAddress(), restartLocal.getRegister());
488                break;
489            }
490            case DebugItemType.PROLOGUE_END: {
491                writer.writePrologueEnd(debugItem.getCodeAddress());
492                break;
493            }
494            case DebugItemType.EPILOGUE_BEGIN: {
495                writer.writeEpilogueBegin(debugItem.getCodeAddress());
496                break;
497            }
498            case DebugItemType.LINE_NUMBER: {
499                LineNumber lineNumber = (LineNumber)debugItem;
500                writer.writeLineNumber(lineNumber.getCodeAddress(), lineNumber.getLineNumber());
501                break;
502            }
503            case DebugItemType.SET_SOURCE_FILE: {
504                SetSourceFile setSourceFile = (SetSourceFile)debugItem;
505                writer.writeSetSourceFile(setSourceFile.getCodeAddress(), setSourceFile.getSourceFile());
506            }
507            default:
508                throw new ExceptionWithContext("Unexpected debug item type: %d", debugItem.getDebugItemType());
509        }
510    }
511
512    @Override public int getItemIndex(@Nonnull PoolClassDef classDef) {
513        return classDef.classDefIndex;
514    }
515
516    @Nonnull @Override public Collection<? extends Map.Entry<PoolClassDef, Integer>> getItems() {
517        class MapEntry implements Map.Entry<PoolClassDef, Integer> {
518            @Nonnull private final PoolClassDef classDef;
519
520            public MapEntry(@Nonnull PoolClassDef classDef) {
521                this.classDef = classDef;
522            }
523
524            @Override public PoolClassDef getKey() {
525                return classDef;
526            }
527
528            @Override public Integer getValue() {
529                return classDef.classDefIndex;
530            }
531
532            @Override public Integer setValue(Integer value) {
533                int prev = classDef.classDefIndex;
534                classDef.classDefIndex = value;
535                return prev;
536            }
537        }
538
539        return new AbstractCollection<Entry<PoolClassDef, Integer>>() {
540            @Nonnull @Override public Iterator<Entry<PoolClassDef, Integer>> iterator() {
541                return new Iterator<Entry<PoolClassDef, Integer>>() {
542                    Iterator<PoolClassDef> iter = internedItems.values().iterator();
543
544                    @Override public boolean hasNext() {
545                        return iter.hasNext();
546                    }
547
548                    @Override public Entry<PoolClassDef, Integer> next() {
549                        return new MapEntry(iter.next());
550                    }
551
552                    @Override public void remove() {
553                        throw new UnsupportedOperationException();
554                    }
555                };
556            }
557
558            @Override public int size() {
559                return internedItems.size();
560            }
561        };
562    }
563}
564