ClassPool.java revision 9bbcaae91fffe74cbc90608eaa98484192b11d77
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>,
63        EncodedValue, Instruction, ExceptionHandler> {
64    @Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
65
66    @Nonnull private final StringPool stringPool;
67    @Nonnull private final TypePool typePool;
68    @Nonnull private final FieldPool fieldPool;
69    @Nonnull private final MethodPool methodPool;
70    @Nonnull private final AnnotationSetPool annotationSetPool;
71    @Nonnull private final TypeListPool typeListPool;
72
73    public ClassPool(@Nonnull StringPool stringPool,
74                     @Nonnull TypePool typePool,
75                     @Nonnull FieldPool fieldPool,
76                     @Nonnull MethodPool methodPool,
77                     @Nonnull AnnotationSetPool annotationSetPool,
78                     @Nonnull TypeListPool typeListPool) {
79        this.stringPool = stringPool;
80        this.typePool = typePool;
81        this.fieldPool = fieldPool;
82        this.methodPool = methodPool;
83        this.annotationSetPool = annotationSetPool;
84        this.typeListPool = typeListPool;
85    }
86
87    public void intern(@Nonnull ClassDef classDef) {
88        PoolClassDef poolClassDef = new PoolClassDef(classDef);
89
90        PoolClassDef prev = internedItems.put(poolClassDef.getType(), poolClassDef);
91        if (prev != null) {
92            throw new ExceptionWithContext("Class %s has already been interned", poolClassDef.getType());
93        }
94
95        typePool.intern(poolClassDef.getType());
96        typePool.internNullable(poolClassDef.getSuperclass());
97        typeListPool.intern(poolClassDef.getInterfaces());
98        stringPool.internNullable(poolClassDef.getSourceFile());
99
100        HashSet<String> fields = new HashSet<String>();
101        for (Field field: poolClassDef.getFields()) {
102            String fieldDescriptor = ReferenceUtil.getShortFieldDescriptor(field);
103            if (!fields.add(fieldDescriptor)) {
104                throw new ExceptionWithContext("Multiple definitions for field %s->%s",
105                        poolClassDef.getType(), fieldDescriptor);
106            }
107            fieldPool.intern(field);
108
109            EncodedValue initialValue = field.getInitialValue();
110            if (initialValue != null) {
111                DexPool.internEncodedValue(initialValue, stringPool, typePool, fieldPool, methodPool);
112            }
113
114            annotationSetPool.intern(field.getAnnotations());
115        }
116
117        HashSet<String> methods = new HashSet<String>();
118        for (PoolMethod method: poolClassDef.getMethods()) {
119            String methodDescriptor = ReferenceUtil.getShortMethodDescriptor(method);
120            if (!methods.add(methodDescriptor)) {
121                throw new ExceptionWithContext("Multiple definitions for method %s->%s",
122                        poolClassDef.getType(), methodDescriptor);
123            }
124            methodPool.intern(method);
125            internCode(method);
126            internDebug(method);
127            annotationSetPool.intern(method.getAnnotations());
128
129            for (MethodParameter parameter: method.getParameters()) {
130                annotationSetPool.intern(parameter.getAnnotations());
131            }
132        }
133
134        annotationSetPool.intern(poolClassDef.getAnnotations());
135    }
136
137    private void internCode(@Nonnull Method method) {
138        // this also handles parameter names, which aren't directly tied to the MethodImplementation, even though the debug items are
139        boolean hasInstruction = false;
140
141        MethodImplementation methodImpl = method.getImplementation();
142        if (methodImpl != null) {
143            for (Instruction instruction: methodImpl.getInstructions()) {
144                hasInstruction = true;
145                if (instruction instanceof ReferenceInstruction) {
146                    Reference reference = ((ReferenceInstruction)instruction).getReference();
147                    switch (instruction.getOpcode().referenceType) {
148                        case ReferenceType.STRING:
149                            stringPool.intern((StringReference)reference);
150                            break;
151                        case ReferenceType.TYPE:
152                            typePool.intern((TypeReference)reference);
153                            break;
154                        case ReferenceType.FIELD:
155                            fieldPool.intern((FieldReference) reference);
156                            break;
157                        case ReferenceType.METHOD:
158                            methodPool.intern((MethodReference)reference);
159                            break;
160                        default:
161                            throw new ExceptionWithContext("Unrecognized reference type: %d",
162                                    instruction.getOpcode().referenceType);
163                    }
164                }
165            }
166
167            List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
168            if (!hasInstruction && tryBlocks.size() > 0) {
169                throw new ExceptionWithContext("Method %s has no instructions, but has try blocks.",
170                        ReferenceUtil.getMethodDescriptor(method));
171            }
172
173            for (TryBlock<? extends ExceptionHandler> tryBlock: methodImpl.getTryBlocks()) {
174                for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
175                    typePool.internNullable(handler.getExceptionType());
176                }
177            }
178        }
179    }
180
181    private void internDebug(@Nonnull Method method) {
182        for (MethodParameter param: method.getParameters()) {
183            String paramName = param.getName();
184            if (paramName != null) {
185                stringPool.intern(paramName);
186            }
187        }
188
189        MethodImplementation methodImpl = method.getImplementation();
190        if (methodImpl != null) {
191            for (DebugItem debugItem: methodImpl.getDebugItems()) {
192                switch (debugItem.getDebugItemType()) {
193                    case DebugItemType.START_LOCAL:
194                        StartLocal startLocal = (StartLocal)debugItem;
195                        stringPool.internNullable(startLocal.getName());
196                        typePool.internNullable(startLocal.getType());
197                        stringPool.internNullable(startLocal.getSignature());
198                        break;
199                    case DebugItemType.SET_SOURCE_FILE:
200                        stringPool.internNullable(((SetSourceFile) debugItem).getSourceFile());
201                        break;
202                }
203            }
204        }
205    }
206
207    private ImmutableList<PoolClassDef> sortedClasses = null;
208    @Nonnull @Override public Collection<? extends PoolClassDef> getSortedClasses() {
209        if (sortedClasses == null) {
210            sortedClasses = Ordering.natural().immutableSortedCopy(internedItems.values());
211        }
212        return sortedClasses;
213    }
214
215    @Nullable @Override
216    public Map.Entry<? extends PoolClassDef, Integer> getClassEntryByType(@Nullable CharSequence name) {
217        if (name == null) {
218            return null;
219        }
220
221        final PoolClassDef classDef = internedItems.get(name.toString());
222        if (classDef == null) {
223            return null;
224        }
225
226        return new Map.Entry<PoolClassDef, Integer>() {
227            @Override public PoolClassDef getKey() {
228                return classDef;
229            }
230
231            @Override public Integer getValue() {
232                return classDef.classDefIndex;
233            }
234
235            @Override public Integer setValue(Integer value) {
236                return classDef.classDefIndex = value;
237            }
238        };
239    }
240
241    @Nonnull @Override public CharSequence getType(@Nonnull PoolClassDef classDef) {
242        return classDef.getType();
243    }
244
245    @Override public int getAccessFlags(@Nonnull PoolClassDef classDef) {
246        return classDef.getAccessFlags();
247    }
248
249    @Nullable @Override public CharSequence getSuperclass(@Nonnull PoolClassDef classDef) {
250        return classDef.getSuperclass();
251    }
252
253    @Nullable @Override public TypeListPool.Key<SortedSet<String>> getSortedInterfaces(@Nonnull PoolClassDef classDef) {
254        return classDef.interfaces;
255    }
256
257    @Nullable @Override public CharSequence getSourceFile(@Nonnull PoolClassDef classDef) {
258        return classDef.getSourceFile();
259    }
260
261    private static final Predicate<Field> HAS_INITIALIZER = new Predicate<Field>() {
262        @Override
263        public boolean apply(Field input) {
264            EncodedValue encodedValue = input.getInitialValue();
265            return encodedValue != null && !EncodedValueUtils.isDefaultValue(encodedValue);
266        }
267    };
268
269    private static final Function<Field, EncodedValue> GET_INITIAL_VALUE = new Function<Field, EncodedValue>() {
270        @Override
271        public EncodedValue apply(Field input) {
272            EncodedValue initialValue = input.getInitialValue();
273            if (initialValue == null) {
274                return ImmutableEncodedValueFactory.defaultValueForType(input.getType());
275            }
276            return initialValue;
277        }
278    };
279
280    @Nullable @Override public Collection<? extends EncodedValue> getStaticInitializers(
281            @Nonnull PoolClassDef classDef) {
282        final SortedSet<Field> sortedStaticFields = classDef.getStaticFields();
283
284        final int lastIndex = CollectionUtils.lastIndexOf(sortedStaticFields, HAS_INITIALIZER);
285        if (lastIndex > -1) {
286            return new AbstractCollection<EncodedValue>() {
287                @Nonnull @Override public Iterator<EncodedValue> iterator() {
288                    return FluentIterable.from(sortedStaticFields)
289                            .limit(lastIndex+1)
290                            .transform(GET_INITIAL_VALUE).iterator();
291                }
292
293                @Override public int size() {
294                    return lastIndex+1;
295                }
296            };
297        }
298        return null;
299    }
300
301    @Nonnull @Override public Collection<? extends Field> getSortedStaticFields(@Nonnull PoolClassDef classDef) {
302        return classDef.getStaticFields();
303    }
304
305    @Nonnull @Override public Collection<? extends Field> getSortedInstanceFields(@Nonnull PoolClassDef classDef) {
306        return classDef.getInstanceFields();
307    }
308
309    @Nonnull @Override public Collection<? extends Field> getSortedFields(@Nonnull PoolClassDef classDef) {
310        return classDef.getFields();
311    }
312
313    @Nonnull @Override public Collection<PoolMethod> getSortedDirectMethods(@Nonnull PoolClassDef classDef) {
314        return classDef.getDirectMethods();
315    }
316
317    @Nonnull @Override public Collection<PoolMethod> getSortedVirtualMethods(@Nonnull PoolClassDef classDef) {
318        return classDef.getVirtualMethods();
319    }
320
321    @Nonnull @Override public Collection<? extends PoolMethod> getSortedMethods(@Nonnull PoolClassDef classDef) {
322        return classDef.getMethods();
323    }
324
325    @Override public int getFieldAccessFlags(@Nonnull Field field) {
326        return field.getAccessFlags();
327    }
328
329    @Override public int getMethodAccessFlags(@Nonnull PoolMethod method) {
330        return method.getAccessFlags();
331    }
332
333    @Nullable @Override public Set<? extends Annotation> getClassAnnotations(@Nonnull PoolClassDef classDef) {
334        Set<? extends Annotation> annotations = classDef.getAnnotations();
335        if (annotations.size() == 0) {
336            return null;
337        }
338        return annotations;
339    }
340
341    @Nullable @Override public Set<? extends Annotation> getFieldAnnotations(@Nonnull Field field) {
342        Set<? extends Annotation> annotations = field.getAnnotations();
343        if (annotations.size() == 0) {
344            return null;
345        }
346        return annotations;
347    }
348
349    @Nullable @Override public Set<? extends Annotation> getMethodAnnotations(@Nonnull PoolMethod method) {
350        Set<? extends Annotation> annotations = method.getAnnotations();
351        if (annotations.size() == 0) {
352            return null;
353        }
354        return annotations;
355    }
356
357    private static final Predicate<MethodParameter> HAS_PARAMETER_ANNOTATIONS = new Predicate<MethodParameter>() {
358        @Override
359        public boolean apply(MethodParameter input) {
360            return input.getAnnotations().size() > 0;
361        }
362    };
363
364    private static final Function<MethodParameter, Set<? extends Annotation>> PARAMETER_ANNOTATIONS =
365            new Function<MethodParameter, Set<? extends Annotation>>() {
366                @Override
367                public Set<? extends Annotation> apply(MethodParameter input) {
368                    return input.getAnnotations();
369                }
370            };
371
372    @Nullable @Override public List<? extends Set<? extends Annotation>> getParameterAnnotations(
373            @Nonnull final PoolMethod method) {
374        final int lastIndex = CollectionUtils.lastIndexOf(method.getParameters(), HAS_PARAMETER_ANNOTATIONS);
375
376        if (lastIndex > -1) {
377            return new AbstractForwardSequentialList<Set<? extends Annotation>>() {
378                @Nonnull @Override public Iterator<Set<? extends Annotation>> iterator() {
379                    return FluentIterable.from(method.getParameters())
380                            .limit(lastIndex+1)
381                            .transform(PARAMETER_ANNOTATIONS).iterator();
382                }
383
384                @Override public int size() {
385                    return lastIndex+1;
386                }
387            };
388        }
389        return null;
390    }
391
392    @Nullable @Override public Iterable<? extends DebugItem> getDebugItems(@Nonnull PoolMethod method) {
393        MethodImplementation impl = method.getImplementation();
394        if (impl != null) {
395            return impl.getDebugItems();
396        }
397        return null;
398    }
399
400    @Nullable @Override public Iterable<CharSequence> getParameterNames(@Nonnull PoolMethod method) {
401        return Iterables.transform(method.getParameters(), new Function<MethodParameter, CharSequence>() {
402            @Nullable @Override public CharSequence apply(MethodParameter input) {
403                return input.getName();
404            }
405        });
406    }
407
408    @Override public int getRegisterCount(@Nonnull PoolMethod method) {
409        MethodImplementation impl = method.getImplementation();
410        if (impl != null) {
411            return impl.getRegisterCount();
412        }
413        return 0;
414    }
415
416    @Nullable @Override public Iterable<? extends Instruction> getInstructions(@Nonnull PoolMethod method) {
417        MethodImplementation impl = method.getImplementation();
418        if (impl != null) {
419            return impl.getInstructions();
420        }
421        return null;
422    }
423
424    @Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks(
425            @Nonnull PoolMethod method) {
426        MethodImplementation impl = method.getImplementation();
427        if (impl != null) {
428            return impl.getTryBlocks();
429        }
430        return ImmutableList.of();
431    }
432
433    @Nullable @Override public CharSequence getExceptionType(@Nonnull ExceptionHandler handler) {
434        return handler.getExceptionType();
435    }
436
437    @Override public void setEncodedArrayOffset(@Nonnull PoolClassDef classDef, int offset) {
438        classDef.encodedArrayOffset = offset;
439    }
440
441    @Override public int getEncodedArrayOffset(@Nonnull PoolClassDef classDef) {
442        return classDef.encodedArrayOffset;
443    }
444
445    @Override public void setAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef, int offset) {
446        classDef.annotationDirectoryOffset = offset;
447    }
448
449    @Override public int getAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef) {
450        return classDef.annotationDirectoryOffset;
451    }
452
453    @Override public void setAnnotationSetRefListOffset(@Nonnull PoolMethod method, int offset) {
454        method.annotationSetRefListOffset = offset;
455
456    }
457    @Override public int getAnnotationSetRefListOffset(@Nonnull PoolMethod method) {
458        return method.annotationSetRefListOffset;
459    }
460
461    @Override public void setCodeItemOffset(@Nonnull PoolMethod method, int offset) {
462        method.codeItemOffset = offset;
463    }
464
465    @Override public int getCodeItemOffset(@Nonnull PoolMethod method) {
466        return method.codeItemOffset;
467    }
468
469    @Override public void setDebugItemOffset(@Nonnull PoolMethod method, int offset) {
470        method.debugInfoOffset = offset;
471    }
472
473    @Override public int getDebugItemOffset(@Nonnull PoolMethod method) {
474        return method.debugInfoOffset;
475    }
476
477    @Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
478                                         DebugItem debugItem) throws IOException {
479        switch (debugItem.getDebugItemType()) {
480            case DebugItemType.START_LOCAL: {
481                StartLocal startLocal = (StartLocal)debugItem;
482                writer.writeStartLocal(startLocal.getCodeAddress(),
483                        startLocal.getRegister(),
484                        startLocal.getName(),
485                        startLocal.getType(),
486                        startLocal.getSignature());
487                break;
488            }
489            case DebugItemType.END_LOCAL: {
490                EndLocal endLocal = (EndLocal)debugItem;
491                writer.writeEndLocal(endLocal.getCodeAddress(), endLocal.getRegister());
492                break;
493            }
494            case DebugItemType.RESTART_LOCAL: {
495                RestartLocal restartLocal = (RestartLocal)debugItem;
496                writer.writeRestartLocal(restartLocal.getCodeAddress(), restartLocal.getRegister());
497                break;
498            }
499            case DebugItemType.PROLOGUE_END: {
500                writer.writePrologueEnd(debugItem.getCodeAddress());
501                break;
502            }
503            case DebugItemType.EPILOGUE_BEGIN: {
504                writer.writeEpilogueBegin(debugItem.getCodeAddress());
505                break;
506            }
507            case DebugItemType.LINE_NUMBER: {
508                LineNumber lineNumber = (LineNumber)debugItem;
509                writer.writeLineNumber(lineNumber.getCodeAddress(), lineNumber.getLineNumber());
510                break;
511            }
512            case DebugItemType.SET_SOURCE_FILE: {
513                SetSourceFile setSourceFile = (SetSourceFile)debugItem;
514                writer.writeSetSourceFile(setSourceFile.getCodeAddress(), setSourceFile.getSourceFile());
515            }
516            default:
517                throw new ExceptionWithContext("Unexpected debug item type: %d", debugItem.getDebugItemType());
518        }
519    }
520
521    @Override public int getItemIndex(@Nonnull PoolClassDef classDef) {
522        return classDef.classDefIndex;
523    }
524
525    @Nonnull @Override public Collection<? extends Map.Entry<PoolClassDef, Integer>> getItems() {
526        class MapEntry implements Map.Entry<PoolClassDef, Integer> {
527            @Nonnull private final PoolClassDef classDef;
528
529            public MapEntry(@Nonnull PoolClassDef classDef) {
530                this.classDef = classDef;
531            }
532
533            @Override public PoolClassDef getKey() {
534                return classDef;
535            }
536
537            @Override public Integer getValue() {
538                return classDef.classDefIndex;
539            }
540
541            @Override public Integer setValue(Integer value) {
542                int prev = classDef.classDefIndex;
543                classDef.classDefIndex = value;
544                return prev;
545            }
546        }
547
548        return new AbstractCollection<Entry<PoolClassDef, Integer>>() {
549            @Nonnull @Override public Iterator<Entry<PoolClassDef, Integer>> iterator() {
550                return new Iterator<Entry<PoolClassDef, Integer>>() {
551                    Iterator<PoolClassDef> iter = internedItems.values().iterator();
552
553                    @Override public boolean hasNext() {
554                        return iter.hasNext();
555                    }
556
557                    @Override public Entry<PoolClassDef, Integer> next() {
558                        return new MapEntry(iter.next());
559                    }
560
561                    @Override public void remove() {
562                        throw new UnsupportedOperationException();
563                    }
564                };
565            }
566
567            @Override public int size() {
568                return internedItems.size();
569            }
570        };
571    }
572}
573