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