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.ConcreteMethod;
20import com.android.dx.cf.code.Ropper;
21import com.android.dx.cf.direct.DirectClassFile;
22import com.android.dx.cf.direct.StdAttributeFactory;
23import com.android.dx.cf.iface.Member;
24import com.android.dx.cf.iface.Method;
25import com.android.dx.cf.iface.ParseObserver;
26import com.android.dx.rop.code.AccessFlags;
27import com.android.dx.rop.code.BasicBlock;
28import com.android.dx.rop.code.BasicBlockList;
29import com.android.dx.rop.code.DexTranslationAdvice;
30import com.android.dx.rop.code.RopMethod;
31import com.android.dx.rop.code.TranslationAdvice;
32import com.android.dx.ssa.Optimizer;
33import com.android.dx.util.ByteArray;
34import com.android.dx.util.Hex;
35import com.android.dx.util.IntList;
36
37/**
38 * Dumps the pred/succ graph of methods into a format compatible
39 * with the popular graph utility "dot".
40 */
41public class DotDumper implements ParseObserver {
42    private DirectClassFile classFile;
43
44    private final byte[] bytes;
45    private final String filePath;
46    private final boolean strictParse;
47    private final boolean optimize;
48    private final Args args;
49
50    static void dump(byte[] bytes, String filePath, Args args) {
51        new DotDumper(bytes, filePath, args).run();
52    }
53
54    DotDumper(byte[] bytes, String filePath, Args args) {
55        this.bytes = bytes;
56        this.filePath = filePath;
57        this.strictParse = args.strictParse;
58        this.optimize = args.optimize;
59        this.args = args;
60    }
61
62    private void run() {
63        ByteArray ba = new ByteArray(bytes);
64
65        /*
66         * First, parse the file completely, so we can safely refer to
67         * attributes, etc.
68         */
69        classFile = new DirectClassFile(ba, filePath, strictParse);
70        classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
71        classFile.getMagic(); // Force parsing to happen.
72
73        // Next, reparse it and observe the process.
74        DirectClassFile liveCf =
75            new DirectClassFile(ba, filePath, strictParse);
76        liveCf.setAttributeFactory(StdAttributeFactory.THE_ONE);
77        liveCf.setObserver(this);
78        liveCf.getMagic(); // Force parsing to happen.
79    }
80
81    /**
82     * @param name method name
83     * @return true if this method should be dumped
84     */
85    protected boolean shouldDumpMethod(String name) {
86        return args.method == null || args.method.equals(name);
87    }
88
89    public void changeIndent(int indentDelta) {
90        // This space intentionally left blank.
91    }
92
93    public void parsed(ByteArray bytes, int offset, int len, String human) {
94        // This space intentionally left blank.
95    }
96
97    /** {@inheritDoc} */
98    public void startParsingMember(ByteArray bytes, int offset, String name,
99                                   String descriptor) {
100        // This space intentionally left blank.
101    }
102
103    public void endParsingMember(ByteArray bytes, int offset, String name,
104                                 String descriptor, Member member) {
105        if (!(member instanceof Method)) {
106            return;
107        }
108
109        if (!shouldDumpMethod(name)) {
110            return;
111        }
112
113        ConcreteMethod meth = new ConcreteMethod((Method) member, classFile,
114                                                 true, true);
115
116        TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
117        RopMethod rmeth =
118            Ropper.convert(meth, advice, classFile.getMethods());
119
120        if (optimize) {
121            boolean isStatic = AccessFlags.isStatic(meth.getAccessFlags());
122            rmeth = Optimizer.optimize(rmeth,
123                    BaseDumper.computeParamWidth(meth, isStatic), isStatic,
124                    true, advice);
125        }
126
127        System.out.println("digraph "  + name + "{");
128
129        System.out.println("\tfirst -> n"
130                + Hex.u2(rmeth.getFirstLabel()) + ";");
131
132        BasicBlockList blocks = rmeth.getBlocks();
133
134        int sz = blocks.size();
135        for (int i = 0; i < sz; i++) {
136            BasicBlock bb = blocks.get(i);
137            int label = bb.getLabel();
138            IntList successors = bb.getSuccessors();
139
140            if (successors.size() == 0) {
141                System.out.println("\tn" + Hex.u2(label) + " -> returns;");
142            } else if (successors.size() == 1) {
143                System.out.println("\tn" + Hex.u2(label) + " -> n"
144                        + Hex.u2(successors.get(0)) + ";");
145            } else {
146                System.out.print("\tn" + Hex.u2(label) + " -> {");
147                for (int j = 0; j < successors.size(); j++ ) {
148                    int successor = successors.get(j);
149
150                    if (successor != bb.getPrimarySuccessor()) {
151                        System.out.print(" n" + Hex.u2(successor) + " ");
152                    }
153
154                }
155                System.out.println("};");
156
157                System.out.println("\tn" + Hex.u2(label) + " -> n"
158                        + Hex.u2(bb.getPrimarySuccessor())
159                        + " [label=\"primary\"];");
160
161
162            }
163        }
164
165        System.out.println("}");
166    }
167}
168