1/*
2 * Copyright 2012, 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.dexbacked;
33
34import com.google.common.collect.ImmutableList;
35import com.google.common.collect.ImmutableSet;
36import org.jf.dexlib2.base.reference.BaseMethodReference;
37import org.jf.dexlib2.dexbacked.raw.MethodIdItem;
38import org.jf.dexlib2.dexbacked.raw.ProtoIdItem;
39import org.jf.dexlib2.dexbacked.raw.TypeListItem;
40import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference;
41import org.jf.dexlib2.dexbacked.util.AnnotationsDirectory;
42import org.jf.dexlib2.dexbacked.util.FixedSizeList;
43import org.jf.dexlib2.dexbacked.util.ParameterIterator;
44import org.jf.dexlib2.iface.Annotation;
45import org.jf.dexlib2.iface.Method;
46import org.jf.dexlib2.iface.MethodImplementation;
47import org.jf.dexlib2.iface.MethodParameter;
48import org.jf.util.AbstractForwardSequentialList;
49
50import javax.annotation.Nonnull;
51import javax.annotation.Nullable;
52import java.util.Iterator;
53import java.util.List;
54import java.util.Set;
55
56public class DexBackedMethod extends BaseMethodReference implements Method {
57    @Nonnull public final DexBackedDexFile dexFile;
58    @Nonnull public final DexBackedClassDef classDef;
59
60    public final int accessFlags;
61
62    private final int codeOffset;
63    private final int parameterAnnotationSetListOffset;
64    private final int methodAnnotationSetOffset;
65
66    public final int methodIndex;
67    private final int startOffset;
68
69    private int methodIdItemOffset;
70    private int protoIdItemOffset;
71    private int parametersOffset = -1;
72
73    public DexBackedMethod(@Nonnull DexReader reader,
74                           @Nonnull DexBackedClassDef classDef,
75                           int previousMethodIndex) {
76        this.dexFile = reader.dexBuf;
77        this.classDef = classDef;
78        startOffset = reader.getOffset();
79
80        // large values may be used for the index delta, which cause the cumulative index to overflow upon
81        // addition, effectively allowing out of order entries.
82        int methodIndexDiff = reader.readLargeUleb128();
83        this.methodIndex = methodIndexDiff + previousMethodIndex;
84        this.accessFlags = reader.readSmallUleb128();
85        this.codeOffset = reader.readSmallUleb128();
86
87        this.methodAnnotationSetOffset = 0;
88        this.parameterAnnotationSetListOffset = 0;
89    }
90
91    public DexBackedMethod(@Nonnull DexReader reader,
92                           @Nonnull DexBackedClassDef classDef,
93                           int previousMethodIndex,
94                           @Nonnull AnnotationsDirectory.AnnotationIterator methodAnnotationIterator,
95                           @Nonnull AnnotationsDirectory.AnnotationIterator paramaterAnnotationIterator) {
96        this.dexFile = reader.dexBuf;
97        this.classDef = classDef;
98        startOffset = reader.getOffset();
99
100        // large values may be used for the index delta, which cause the cumulative index to overflow upon
101        // addition, effectively allowing out of order entries.
102        int methodIndexDiff = reader.readLargeUleb128();
103        this.methodIndex = methodIndexDiff + previousMethodIndex;
104        this.accessFlags = reader.readSmallUleb128();
105        this.codeOffset = reader.readSmallUleb128();
106
107        this.methodAnnotationSetOffset = methodAnnotationIterator.seekTo(methodIndex);
108        this.parameterAnnotationSetListOffset = paramaterAnnotationIterator.seekTo(methodIndex);
109    }
110
111    public int getMethodIndex() { return methodIndex; }
112    @Nonnull @Override public String getDefiningClass() { return classDef.getType(); }
113    @Override public int getAccessFlags() { return accessFlags; }
114
115    @Nonnull
116    @Override
117    public String getName() {
118        return dexFile.getString(dexFile.readSmallUint(getMethodIdItemOffset() + MethodIdItem.NAME_OFFSET));
119    }
120
121    @Nonnull
122    @Override
123    public String getReturnType() {
124        return dexFile.getType(dexFile.readSmallUint(getProtoIdItemOffset() + ProtoIdItem.RETURN_TYPE_OFFSET));
125    }
126
127    @Nonnull
128    @Override
129    public List<? extends MethodParameter> getParameters() {
130        int parametersOffset = getParametersOffset();
131        if (parametersOffset > 0) {
132            final List<String> parameterTypes = getParameterTypes();
133
134            return new AbstractForwardSequentialList<MethodParameter>() {
135                @Nonnull @Override public Iterator<MethodParameter> iterator() {
136                    return new ParameterIterator(parameterTypes,
137                            getParameterAnnotations(),
138                            getParameterNames());
139                }
140
141                @Override public int size() {
142                    return parameterTypes.size();
143                }
144            };
145        }
146        return ImmutableList.of();
147    }
148
149    @Nonnull
150    public List<? extends Set<? extends DexBackedAnnotation>> getParameterAnnotations() {
151        return AnnotationsDirectory.getParameterAnnotations(dexFile, parameterAnnotationSetListOffset);
152    }
153
154    @Nonnull
155    public Iterator<String> getParameterNames() {
156        DexBackedMethodImplementation methodImpl = getImplementation();
157        if (methodImpl != null) {
158            return methodImpl.getParameterNames(null);
159        }
160        return ImmutableSet.<String>of().iterator();
161    }
162
163    @Nonnull
164    @Override
165    public List<String> getParameterTypes() {
166        final int parametersOffset = getParametersOffset();
167        if (parametersOffset > 0) {
168            final int parameterCount = dexFile.readSmallUint(parametersOffset + TypeListItem.SIZE_OFFSET);
169            final int paramListStart = parametersOffset + TypeListItem.LIST_OFFSET;
170            return new FixedSizeList<String>() {
171                @Nonnull
172                @Override
173                public String readItem(final int index) {
174                    return dexFile.getType(dexFile.readUshort(paramListStart + 2*index));
175                }
176                @Override public int size() { return parameterCount; }
177            };
178        }
179        return ImmutableList.of();
180    }
181
182    @Nonnull
183    @Override
184    public Set<? extends Annotation> getAnnotations() {
185        return AnnotationsDirectory.getAnnotations(dexFile, methodAnnotationSetOffset);
186    }
187
188    @Nullable
189    @Override
190    public DexBackedMethodImplementation getImplementation() {
191        if (codeOffset > 0) {
192            return new DexBackedMethodImplementation(dexFile, this, codeOffset);
193        }
194        return null;
195    }
196
197    private int getMethodIdItemOffset() {
198        if (methodIdItemOffset == 0) {
199            methodIdItemOffset = dexFile.getMethodIdItemOffset(methodIndex);
200        }
201        return methodIdItemOffset;
202    }
203
204    private int getProtoIdItemOffset() {
205        if (protoIdItemOffset == 0) {
206            int protoIndex = dexFile.readUshort(getMethodIdItemOffset() + MethodIdItem.PROTO_OFFSET);
207            protoIdItemOffset = dexFile.getProtoIdItemOffset(protoIndex);
208        }
209        return protoIdItemOffset;
210    }
211
212    private int getParametersOffset() {
213        if (parametersOffset == -1) {
214            parametersOffset = dexFile.readSmallUint(getProtoIdItemOffset() + ProtoIdItem.PARAMETERS_OFFSET);
215        }
216        return parametersOffset;
217    }
218
219    /**
220     * Skips the reader over the specified number of encoded_method structures
221     *
222     * @param reader The reader to skip
223     * @param count The number of encoded_method structures to skip over
224     */
225    public static void skipMethods(@Nonnull DexReader reader, int count) {
226        for (int i=0; i<count; i++) {
227            reader.skipUleb128();
228            reader.skipUleb128();
229            reader.skipUleb128();
230        }
231    }
232
233    /**
234     * Calculate and return the private size of a method definition.
235     *
236     * Calculated as: method_idx_diff + access_flags + code_off +
237     * implementation size + reference size
238     *
239     * @return size in bytes
240     */
241    public int getSize() {
242        int size = 0;
243
244        DexReader reader = dexFile.readerAt(startOffset);
245        reader.readLargeUleb128(); //method_idx_diff
246        reader.readSmallUleb128(); //access_flags
247        reader.readSmallUleb128(); //code_off
248        size += reader.getOffset() - startOffset;
249
250        DexBackedMethodImplementation impl = getImplementation();
251        if (impl != null) {
252            size += impl.getSize();
253        }
254
255        DexBackedMethodReference methodRef = new DexBackedMethodReference(dexFile, methodIndex);
256        size += methodRef.getSize();
257
258        return size;
259    }
260}
261