1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dx.command.dump;
18
19import com.android.dx.cf.code.BasicBlocker;
20import com.android.dx.cf.code.ByteBlock;
21import com.android.dx.cf.code.ByteBlockList;
22import com.android.dx.cf.code.ByteCatchList;
23import com.android.dx.cf.code.BytecodeArray;
24import com.android.dx.cf.code.ConcreteMethod;
25import com.android.dx.cf.code.Ropper;
26import com.android.dx.cf.direct.CodeObserver;
27import com.android.dx.cf.direct.DirectClassFile;
28import com.android.dx.cf.direct.StdAttributeFactory;
29import com.android.dx.cf.iface.Member;
30import com.android.dx.cf.iface.Method;
31import com.android.dx.dex.DexOptions;
32import com.android.dx.rop.code.AccessFlags;
33import com.android.dx.rop.code.BasicBlock;
34import com.android.dx.rop.code.BasicBlockList;
35import com.android.dx.rop.code.DexTranslationAdvice;
36import com.android.dx.rop.code.Insn;
37import com.android.dx.rop.code.InsnList;
38import com.android.dx.rop.code.RopMethod;
39import com.android.dx.rop.code.TranslationAdvice;
40import com.android.dx.rop.cst.CstType;
41import com.android.dx.ssa.Optimizer;
42import com.android.dx.util.ByteArray;
43import com.android.dx.util.Hex;
44import com.android.dx.util.IntList;
45import java.io.PrintStream;
46
47/**
48 * Utility to dump basic block info from methods in a human-friendly form.
49 */
50public class BlockDumper
51        extends BaseDumper {
52    /** whether or not to registerize (make rop blocks) */
53    private boolean rop;
54
55    /**
56     * {@code null-ok;} the class file object being constructed;
57     * becomes non-null during {@link #dump}
58     */
59    protected DirectClassFile classFile;
60
61    /** whether or not to suppress dumping */
62    protected boolean suppressDump;
63
64    /** whether this is the first method being dumped */
65    private boolean first;
66
67    /** whether or not to run the ssa optimziations */
68    private boolean optimize;
69
70    /**
71     * Dumps the given array, interpreting it as a class file and dumping
72     * methods with indications of block-level stuff.
73     *
74     * @param bytes {@code non-null;} bytes of the (alleged) class file
75     * @param out {@code non-null;} where to dump to
76     * @param filePath the file path for the class, excluding any base
77     * directory specification
78     * @param rop whether or not to registerize (make rop blocks)
79     * @param args commandline parsedArgs
80     */
81    public static void dump(byte[] bytes, PrintStream out,
82            String filePath, boolean rop, Args args) {
83        BlockDumper bd = new BlockDumper(bytes, out, filePath,
84                rop, args);
85        bd.dump();
86    }
87
88    /**
89     * Constructs an instance. This class is not publicly instantiable.
90     * Use {@link #dump}.
91     */
92    BlockDumper(byte[] bytes, PrintStream out, String filePath,
93            boolean rop, Args args) {
94        super(bytes, out, filePath, args);
95
96        this.rop = rop;
97        this.classFile = null;
98        this.suppressDump = true;
99        this.first = true;
100        this.optimize = args.optimize;
101    }
102
103    /**
104     * Does the dumping.
105     */
106    public void dump() {
107        byte[] bytes = getBytes();
108        ByteArray ba = new ByteArray(bytes);
109
110        /*
111         * First, parse the file completely, so we can safely refer to
112         * attributes, etc.
113         */
114        classFile = new DirectClassFile(ba, getFilePath(), getStrictParse());
115        classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
116        classFile.getMagic(); // Force parsing to happen.
117
118        // Next, reparse it and observe the process.
119        DirectClassFile liveCf =
120            new DirectClassFile(ba, getFilePath(), getStrictParse());
121        liveCf.setAttributeFactory(StdAttributeFactory.THE_ONE);
122        liveCf.setObserver(this);
123        liveCf.getMagic(); // Force parsing to happen.
124    }
125
126    /** {@inheritDoc} */
127    @Override
128    public void changeIndent(int indentDelta) {
129        if (!suppressDump) {
130            super.changeIndent(indentDelta);
131        }
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public void parsed(ByteArray bytes, int offset, int len, String human) {
137        if (!suppressDump) {
138            super.parsed(bytes, offset, len, human);
139        }
140    }
141
142    /**
143     * @param name method name
144     * @return true if this method should be dumped
145     */
146    protected boolean shouldDumpMethod(String name) {
147        return args.method == null || args.method.equals(name);
148    }
149
150    /** {@inheritDoc} */
151    @Override
152    public void startParsingMember(ByteArray bytes, int offset, String name,
153            String descriptor) {
154        if (descriptor.indexOf('(') < 0) {
155            // It's a field, not a method
156            return;
157        }
158
159        if (!shouldDumpMethod(name)) {
160            return;
161        }
162
163        // Reset the dump cursor to the start of the method.
164        setAt(bytes, offset);
165
166        suppressDump = false;
167
168        if (first) {
169            first = false;
170        } else {
171            parsed(bytes, offset, 0, "\n");
172        }
173
174        parsed(bytes, offset, 0, "method " + name + " " + descriptor);
175        suppressDump = true;
176    }
177
178    /** {@inheritDoc} */
179    @Override
180    public void endParsingMember(ByteArray bytes, int offset, String name,
181            String descriptor, Member member) {
182        if (!(member instanceof Method)) {
183            return;
184        }
185
186        if (!shouldDumpMethod(name)) {
187            return;
188        }
189
190        if ((member.getAccessFlags() & (AccessFlags.ACC_ABSTRACT |
191                AccessFlags.ACC_NATIVE)) != 0) {
192            return;
193        }
194
195        ConcreteMethod meth =
196            new ConcreteMethod((Method) member, classFile, true, true);
197
198        if (rop) {
199            ropDump(meth);
200        } else {
201            regularDump(meth);
202        }
203    }
204
205    /**
206     * Does a regular basic block dump.
207     *
208     * @param meth {@code non-null;} method data to dump
209     */
210    private void regularDump(ConcreteMethod meth) {
211        BytecodeArray code = meth.getCode();
212        ByteArray bytes = code.getBytes();
213        ByteBlockList list = BasicBlocker.identifyBlocks(meth);
214        int sz = list.size();
215        CodeObserver codeObserver = new CodeObserver(bytes, BlockDumper.this);
216
217        // Reset the dump cursor to the start of the bytecode.
218        setAt(bytes, 0);
219
220        suppressDump = false;
221
222        int byteAt = 0;
223        for (int i = 0; i < sz; i++) {
224            ByteBlock bb = list.get(i);
225            int start = bb.getStart();
226            int end = bb.getEnd();
227
228            if (byteAt < start) {
229                parsed(bytes, byteAt, start - byteAt,
230                       "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(start));
231            }
232
233            parsed(bytes, start, 0,
234                    "block " + Hex.u2(bb.getLabel()) + ": " +
235                    Hex.u2(start) + ".." + Hex.u2(end));
236            changeIndent(1);
237
238            int len;
239            for (int j = start; j < end; j += len) {
240                len = code.parseInstruction(j, codeObserver);
241                codeObserver.setPreviousOffset(j);
242            }
243
244            IntList successors = bb.getSuccessors();
245            int ssz = successors.size();
246            if (ssz == 0) {
247                parsed(bytes, end, 0, "returns");
248            } else {
249                for (int j = 0; j < ssz; j++) {
250                    int succ = successors.get(j);
251                    parsed(bytes, end, 0, "next " + Hex.u2(succ));
252                }
253            }
254
255            ByteCatchList catches = bb.getCatches();
256            int csz = catches.size();
257            for (int j = 0; j < csz; j++) {
258                ByteCatchList.Item one = catches.get(j);
259                CstType exceptionClass = one.getExceptionClass();
260                parsed(bytes, end, 0,
261                       "catch " +
262                       ((exceptionClass == CstType.OBJECT) ? "<any>" :
263                        exceptionClass.toHuman()) + " -> " +
264                       Hex.u2(one.getHandlerPc()));
265            }
266
267            changeIndent(-1);
268            byteAt = end;
269        }
270
271        int end = bytes.size();
272        if (byteAt < end) {
273            parsed(bytes, byteAt, end - byteAt,
274                    "dead code " + Hex.u2(byteAt) + ".." + Hex.u2(end));
275        }
276
277        suppressDump = true;
278    }
279
280    /**
281     * Does a registerizing dump.
282     *
283     * @param meth {@code non-null;} method data to dump
284     */
285    private void ropDump(ConcreteMethod meth) {
286        TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
287        BytecodeArray code = meth.getCode();
288        ByteArray bytes = code.getBytes();
289        RopMethod rmeth = Ropper.convert(meth, advice, classFile.getMethods(), dexOptions);
290        StringBuffer sb = new StringBuffer(2000);
291
292        if (optimize) {
293            boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
294            int paramWidth = computeParamWidth(meth, isStatic);
295            rmeth =
296                Optimizer.optimize(rmeth, paramWidth, isStatic, true, advice);
297        }
298
299        BasicBlockList blocks = rmeth.getBlocks();
300        int[] order = blocks.getLabelsInOrder();
301
302        sb.append("first " + Hex.u2(rmeth.getFirstLabel()) + "\n");
303
304        for (int label : order) {
305            BasicBlock bb = blocks.get(blocks.indexOfLabel(label));
306            sb.append("block ");
307            sb.append(Hex.u2(label));
308            sb.append("\n");
309
310            IntList preds = rmeth.labelToPredecessors(label);
311            int psz = preds.size();
312            for (int i = 0; i < psz; i++) {
313                sb.append("  pred ");
314                sb.append(Hex.u2(preds.get(i)));
315                sb.append("\n");
316            }
317
318            InsnList il = bb.getInsns();
319            int ilsz = il.size();
320            for (int i = 0; i < ilsz; i++) {
321                Insn one = il.get(i);
322                sb.append("  ");
323                sb.append(il.get(i).toHuman());
324                sb.append("\n");
325            }
326
327            IntList successors = bb.getSuccessors();
328            int ssz = successors.size();
329            if (ssz == 0) {
330                sb.append("  returns\n");
331            } else {
332                int primary = bb.getPrimarySuccessor();
333                for (int i = 0; i < ssz; i++) {
334                    int succ = successors.get(i);
335                    sb.append("  next ");
336                    sb.append(Hex.u2(succ));
337
338                    if ((ssz != 1) && (succ == primary)) {
339                        sb.append(" *");
340                    }
341
342                    sb.append("\n");
343                }
344            }
345        }
346
347        suppressDump = false;
348        setAt(bytes, 0);
349        parsed(bytes, 0, bytes.size(), sb.toString());
350        suppressDump = true;
351    }
352}
353