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.builder.MutableMethodImplementation;
40import org.jf.dexlib2.iface.*;
41import org.jf.dexlib2.iface.debug.*;
42import org.jf.dexlib2.iface.instruction.Instruction;
43import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
44import org.jf.dexlib2.iface.reference.*;
45import org.jf.dexlib2.iface.value.EncodedValue;
46import org.jf.dexlib2.immutable.value.ImmutableEncodedValueFactory;
47import org.jf.dexlib2.util.EncodedValueUtils;
48import org.jf.dexlib2.util.ReferenceUtil;
49import org.jf.dexlib2.writer.ClassSection;
50import org.jf.dexlib2.writer.DebugWriter;
51import org.jf.util.AbstractForwardSequentialList;
52import org.jf.util.CollectionUtils;
53import org.jf.util.ExceptionWithContext;
54
55import javax.annotation.Nonnull;
56import javax.annotation.Nullable;
57import java.io.IOException;
58import java.util.*;
59import java.util.Map.Entry;
60
61public class ClassPool implements ClassSection<CharSequence, CharSequence,
62        TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
63        Set<? extends Annotation>, EncodedValue> {
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.getMethodDescriptor(method, true);
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                    Iterable<Field> fields = Iterables.limit(sortedStaticFields, lastIndex + 1);
289                    return Iterables.transform(fields, 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 List<? extends MethodParameter> parameters = method.getParameters();
374        boolean hasParameterAnnotations = Iterables.any(parameters, HAS_PARAMETER_ANNOTATIONS);
375
376        if (hasParameterAnnotations) {
377            return new AbstractForwardSequentialList<Set<? extends Annotation>>() {
378                @Nonnull @Override public Iterator<Set<? extends Annotation>> iterator() {
379                    return Iterables.transform(parameters, PARAMETER_ANNOTATIONS).iterator();
380                }
381
382                @Override public int size() {
383                    return parameters.size();
384                }
385            };
386        }
387        return null;
388    }
389
390    @Nullable @Override public Iterable<? extends DebugItem> getDebugItems(@Nonnull PoolMethod method) {
391        MethodImplementation impl = method.getImplementation();
392        if (impl != null) {
393            return impl.getDebugItems();
394        }
395        return null;
396    }
397
398    @Nullable @Override public Iterable<CharSequence> getParameterNames(@Nonnull PoolMethod method) {
399        return Iterables.transform(method.getParameters(), new Function<MethodParameter, CharSequence>() {
400            @Nullable @Override public CharSequence apply(MethodParameter input) {
401                return input.getName();
402            }
403        });
404    }
405
406    @Override public int getRegisterCount(@Nonnull PoolMethod method) {
407        MethodImplementation impl = method.getImplementation();
408        if (impl != null) {
409            return impl.getRegisterCount();
410        }
411        return 0;
412    }
413
414    @Nullable @Override public Iterable<? extends Instruction> getInstructions(@Nonnull PoolMethod method) {
415        MethodImplementation impl = method.getImplementation();
416        if (impl != null) {
417            return impl.getInstructions();
418        }
419        return null;
420    }
421
422    @Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks(
423            @Nonnull PoolMethod method) {
424        MethodImplementation impl = method.getImplementation();
425        if (impl != null) {
426            return impl.getTryBlocks();
427        }
428        return ImmutableList.of();
429    }
430
431    @Nullable @Override public CharSequence getExceptionType(@Nonnull ExceptionHandler handler) {
432        return handler.getExceptionType();
433    }
434
435    @Nonnull @Override
436    public MutableMethodImplementation makeMutableMethodImplementation(@Nonnull PoolMethod poolMethod) {
437        return new MutableMethodImplementation(poolMethod.getImplementation());
438    }
439
440    @Override public void setEncodedArrayOffset(@Nonnull PoolClassDef classDef, int offset) {
441        classDef.encodedArrayOffset = offset;
442    }
443
444    @Override public int getEncodedArrayOffset(@Nonnull PoolClassDef classDef) {
445        return classDef.encodedArrayOffset;
446    }
447
448    @Override public void setAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef, int offset) {
449        classDef.annotationDirectoryOffset = offset;
450    }
451
452    @Override public int getAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef) {
453        return classDef.annotationDirectoryOffset;
454    }
455
456    @Override public void setAnnotationSetRefListOffset(@Nonnull PoolMethod method, int offset) {
457        method.annotationSetRefListOffset = offset;
458
459    }
460    @Override public int getAnnotationSetRefListOffset(@Nonnull PoolMethod method) {
461        return method.annotationSetRefListOffset;
462    }
463
464    @Override public void setCodeItemOffset(@Nonnull PoolMethod method, int offset) {
465        method.codeItemOffset = offset;
466    }
467
468    @Override public int getCodeItemOffset(@Nonnull PoolMethod method) {
469        return method.codeItemOffset;
470    }
471
472    @Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
473                                         DebugItem debugItem) throws IOException {
474        switch (debugItem.getDebugItemType()) {
475            case DebugItemType.START_LOCAL: {
476                StartLocal startLocal = (StartLocal)debugItem;
477                writer.writeStartLocal(startLocal.getCodeAddress(),
478                        startLocal.getRegister(),
479                        startLocal.getName(),
480                        startLocal.getType(),
481                        startLocal.getSignature());
482                break;
483            }
484            case DebugItemType.END_LOCAL: {
485                EndLocal endLocal = (EndLocal)debugItem;
486                writer.writeEndLocal(endLocal.getCodeAddress(), endLocal.getRegister());
487                break;
488            }
489            case DebugItemType.RESTART_LOCAL: {
490                RestartLocal restartLocal = (RestartLocal)debugItem;
491                writer.writeRestartLocal(restartLocal.getCodeAddress(), restartLocal.getRegister());
492                break;
493            }
494            case DebugItemType.PROLOGUE_END: {
495                writer.writePrologueEnd(debugItem.getCodeAddress());
496                break;
497            }
498            case DebugItemType.EPILOGUE_BEGIN: {
499                writer.writeEpilogueBegin(debugItem.getCodeAddress());
500                break;
501            }
502            case DebugItemType.LINE_NUMBER: {
503                LineNumber lineNumber = (LineNumber)debugItem;
504                writer.writeLineNumber(lineNumber.getCodeAddress(), lineNumber.getLineNumber());
505                break;
506            }
507            case DebugItemType.SET_SOURCE_FILE: {
508                SetSourceFile setSourceFile = (SetSourceFile)debugItem;
509                writer.writeSetSourceFile(setSourceFile.getCodeAddress(), setSourceFile.getSourceFile());
510            }
511            default:
512                throw new ExceptionWithContext("Unexpected debug item type: %d", debugItem.getDebugItemType());
513        }
514    }
515
516    @Override public int getItemIndex(@Nonnull PoolClassDef classDef) {
517        return classDef.classDefIndex;
518    }
519
520    @Nonnull @Override public Collection<? extends Map.Entry<PoolClassDef, Integer>> getItems() {
521        class MapEntry implements Map.Entry<PoolClassDef, Integer> {
522            @Nonnull private final PoolClassDef classDef;
523
524            public MapEntry(@Nonnull PoolClassDef classDef) {
525                this.classDef = classDef;
526            }
527
528            @Override public PoolClassDef getKey() {
529                return classDef;
530            }
531
532            @Override public Integer getValue() {
533                return classDef.classDefIndex;
534            }
535
536            @Override public Integer setValue(Integer value) {
537                int prev = classDef.classDefIndex;
538                classDef.classDefIndex = value;
539                return prev;
540            }
541        }
542
543        return new AbstractCollection<Entry<PoolClassDef, Integer>>() {
544            @Nonnull @Override public Iterator<Entry<PoolClassDef, Integer>> iterator() {
545                return new Iterator<Entry<PoolClassDef, Integer>>() {
546                    Iterator<PoolClassDef> iter = internedItems.values().iterator();
547
548                    @Override public boolean hasNext() {
549                        return iter.hasNext();
550                    }
551
552                    @Override public Entry<PoolClassDef, Integer> next() {
553                        return new MapEntry(iter.next());
554                    }
555
556                    @Override public void remove() {
557                        throw new UnsupportedOperationException();
558                    }
559                };
560            }
561
562            @Override public int size() {
563                return internedItems.size();
564            }
565        };
566    }
567}
568