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