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.util;
33
34import com.google.common.collect.Iterators;
35import org.jf.dexlib2.AccessFlags;
36import org.jf.dexlib2.DebugItemType;
37import org.jf.dexlib2.dexbacked.DexBackedMethod;
38import org.jf.dexlib2.dexbacked.DexBackedMethodImplementation;
39import org.jf.dexlib2.dexbacked.DexBackedDexFile;
40import org.jf.dexlib2.dexbacked.DexReader;
41import org.jf.dexlib2.iface.MethodParameter;
42import org.jf.dexlib2.iface.debug.DebugItem;
43import org.jf.dexlib2.iface.debug.EndLocal;
44import org.jf.dexlib2.iface.debug.LocalInfo;
45import org.jf.dexlib2.immutable.debug.*;
46
47import javax.annotation.Nonnull;
48import javax.annotation.Nullable;
49import java.util.Arrays;
50import java.util.Iterator;
51
52public abstract class DebugInfo implements Iterable<DebugItem> {
53    /**
54     * Gets an iterator that yields the parameter names from the debug_info_item
55     *
56     * @param reader Optional. If provided, the reader must be positioned at the debug_info_item.parameters_size
57     *               field, and will
58     * @return An iterator that yields the parameter names as strings
59     */
60    @Nonnull public abstract Iterator<String> getParameterNames(@Nullable DexReader reader);
61
62    public static DebugInfo newOrEmpty(@Nonnull DexBackedDexFile dexFile, int debugInfoOffset,
63                                       @Nonnull DexBackedMethodImplementation methodImpl) {
64        if (debugInfoOffset == 0) {
65            return EmptyDebugInfo.INSTANCE;
66        }
67        return new DebugInfoImpl(dexFile, debugInfoOffset, methodImpl);
68    }
69
70    private static class EmptyDebugInfo extends DebugInfo {
71        public static final EmptyDebugInfo INSTANCE = new EmptyDebugInfo();
72        private EmptyDebugInfo() {}
73        @Nonnull @Override public Iterator<DebugItem> iterator() { return Iterators.emptyIterator(); }
74        @Nonnull @Override public Iterator<String> getParameterNames(@Nullable DexReader reader) {
75            return Iterators.emptyIterator();
76        }
77    }
78
79    private static class DebugInfoImpl extends DebugInfo {
80        @Nonnull public final DexBackedDexFile dexFile;
81        private final int debugInfoOffset;
82        @Nonnull private final DexBackedMethodImplementation methodImpl;
83
84        public DebugInfoImpl(@Nonnull DexBackedDexFile dexFile,
85                         int debugInfoOffset,
86                         @Nonnull DexBackedMethodImplementation methodImpl) {
87            this.dexFile = dexFile;
88            this.debugInfoOffset = debugInfoOffset;
89            this.methodImpl = methodImpl;
90        }
91
92        private static final LocalInfo EMPTY_LOCAL_INFO = new LocalInfo() {
93            @Nullable @Override public String getName() { return null; }
94            @Nullable @Override public String getType() { return null; }
95            @Nullable @Override public String getSignature() { return null; }
96        };
97
98        @Nonnull
99        @Override
100        public Iterator<DebugItem> iterator() {
101            DexReader reader = dexFile.readerAt(debugInfoOffset);
102            final int lineNumberStart = reader.readBigUleb128();
103            int registerCount = methodImpl.getRegisterCount();
104
105            //TODO: does dalvik allow references to invalid registers?
106            final LocalInfo[] locals = new LocalInfo[registerCount];
107            Arrays.fill(locals, EMPTY_LOCAL_INFO);
108
109            DexBackedMethod method = methodImpl.method;
110
111            // Create a MethodParameter iterator that uses our DexReader instance to read the parameter names.
112            // After we have finished iterating over the parameters, reader will "point to" the beginning of the
113            // debug instructions
114            final Iterator<? extends MethodParameter> parameterIterator =
115                    new ParameterIterator(method.getParameterTypes(),
116                            method.getParameterAnnotations(),
117                            getParameterNames(reader));
118
119            // first, we grab all the parameters and temporarily store them at the beginning of locals,
120            // disregarding any wide types
121            int parameterIndex = 0;
122            if (!AccessFlags.STATIC.isSet(methodImpl.method.getAccessFlags())) {
123                // add the local info for the "this" parameter
124                locals[parameterIndex++] = new LocalInfo() {
125                    @Override public String getName() { return "this"; }
126                    @Override public String getType() { return methodImpl.method.getDefiningClass(); }
127                    @Override public String getSignature() { return null; }
128                };
129            }
130            while (parameterIterator.hasNext()) {
131                locals[parameterIndex++] = parameterIterator.next();
132            }
133
134            if (parameterIndex < registerCount) {
135                // now, we push the parameter locals back to their appropriate register, starting from the end
136                int localIndex = registerCount-1;
137                while(--parameterIndex > -1) {
138                    LocalInfo currentLocal = locals[parameterIndex];
139                    String type = currentLocal.getType();
140                    if (type != null && (type.equals("J") || type.equals("D"))) {
141                        localIndex--;
142                        if (localIndex == parameterIndex) {
143                            // there's no more room to push, the remaining registers are already in the correct place
144                            break;
145                        }
146                    }
147                    locals[localIndex] = currentLocal;
148                    locals[parameterIndex] = EMPTY_LOCAL_INFO;
149                    localIndex--;
150                }
151            }
152
153            return new VariableSizeLookaheadIterator<DebugItem>(dexFile, reader.getOffset()) {
154                private int codeAddress = 0;
155                private int lineNumber = lineNumberStart;
156
157                @Nullable
158                protected DebugItem readNextItem(@Nonnull DexReader reader) {
159                    while (true) {
160                        int next = reader.readUbyte();
161                        switch (next) {
162                            case DebugItemType.END_SEQUENCE: {
163                                return null;
164                            }
165                            case DebugItemType.ADVANCE_PC: {
166                                int addressDiff = reader.readSmallUleb128();
167                                codeAddress += addressDiff;
168                                continue;
169                            }
170                            case DebugItemType.ADVANCE_LINE: {
171                                int lineDiff = reader.readSleb128();
172                                lineNumber += lineDiff;
173                                continue;
174                            }
175                            case DebugItemType.START_LOCAL: {
176                                int register = reader.readSmallUleb128();
177                                String name = dexFile.getOptionalString(reader.readSmallUleb128() - 1);
178                                String type = dexFile.getOptionalType(reader.readSmallUleb128() - 1);
179                                ImmutableStartLocal startLocal =
180                                        new ImmutableStartLocal(codeAddress, register, name, type, null);
181                                locals[register] = startLocal;
182                                return startLocal;
183                            }
184                            case DebugItemType.START_LOCAL_EXTENDED: {
185                                int register = reader.readSmallUleb128();
186                                String name = dexFile.getOptionalString(reader.readSmallUleb128() - 1);
187                                String type = dexFile.getOptionalType(reader.readSmallUleb128() - 1);
188                                String signature = dexFile.getOptionalString(reader.readSmallUleb128() - 1);
189                                ImmutableStartLocal startLocal =
190                                        new ImmutableStartLocal(codeAddress, register, name, type, signature);
191                                locals[register] = startLocal;
192                                return startLocal;
193                            }
194                            case DebugItemType.END_LOCAL: {
195                                int register = reader.readSmallUleb128();
196                                LocalInfo localInfo = locals[register];
197                                boolean replaceLocalInTable = true;
198                                if (localInfo instanceof EndLocal) {
199                                    localInfo = EMPTY_LOCAL_INFO;
200                                    // don't replace the local info in locals. The new EndLocal won't have any info at all,
201                                    // and we dont want to wipe out what's there, so that it is available for a subsequent
202                                    // RestartLocal
203                                    replaceLocalInTable = false;
204                                }
205                                ImmutableEndLocal endLocal =
206                                        new ImmutableEndLocal(codeAddress, register, localInfo.getName(),
207                                                localInfo.getType(), localInfo.getSignature());
208                                if (replaceLocalInTable) {
209                                    locals[register] = endLocal;
210                                }
211                                return endLocal;
212                            }
213                            case DebugItemType.RESTART_LOCAL: {
214                                int register = reader.readSmallUleb128();
215                                LocalInfo localInfo = locals[register];
216                                ImmutableRestartLocal restartLocal =
217                                        new ImmutableRestartLocal(codeAddress, register, localInfo.getName(),
218                                                localInfo.getType(), localInfo.getSignature());
219                                locals[register] = restartLocal;
220                                return restartLocal;
221                            }
222                            case DebugItemType.PROLOGUE_END: {
223                                return new ImmutablePrologueEnd(codeAddress);
224                            }
225                            case DebugItemType.EPILOGUE_BEGIN: {
226                                return new ImmutableEpilogueBegin(codeAddress);
227                            }
228                            case DebugItemType.SET_SOURCE_FILE: {
229                                String sourceFile = dexFile.getOptionalString(reader.readSmallUleb128() - 1);
230                                return new ImmutableSetSourceFile(codeAddress, sourceFile);
231                            }
232                            default: {
233                                int adjusted = next - 0x0A;
234                                codeAddress += adjusted / 15;
235                                lineNumber += (adjusted % 15) - 4;
236                                return new ImmutableLineNumber(codeAddress, lineNumber);
237                            }
238                        }
239                    }
240                }
241            };
242        }
243
244        @Nonnull
245        @Override
246        public VariableSizeIterator<String> getParameterNames(@Nullable DexReader reader) {
247            if (reader == null) {
248                reader = dexFile.readerAt(debugInfoOffset);
249                reader.skipUleb128();
250            }
251            //TODO: make sure dalvik doesn't allow more parameter names than we have parameters
252            final int parameterNameCount = reader.readSmallUleb128();
253            return new VariableSizeIterator<String>(reader, parameterNameCount) {
254                @Override protected String readNextItem(@Nonnull DexReader reader, int index) {
255                    return dexFile.getOptionalString(reader.readSmallUleb128() - 1);
256                }
257            };
258        }
259    }
260}
261