1/*
2 * Copyright (C) 2015 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.preload.classdataretrieval.jdwp;
18
19import com.android.ddmlib.Client;
20import com.android.preload.classdataretrieval.ClassDataRetriever;
21
22import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
23import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
24import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
25import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
26import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase;
27import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper;
28import org.apache.harmony.jpda.tests.share.JPDALogWriter;
29import org.apache.harmony.jpda.tests.share.JPDATestOptions;
30
31import java.util.HashMap;
32import java.util.Map;
33
34public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever {
35
36    private final Client client;
37
38    public JDWPClassDataRetriever() {
39        this(null);
40    }
41
42    public JDWPClassDataRetriever(Client client) {
43        this.client = client;
44    }
45
46
47    @Override
48    protected String getDebuggeeClassName() {
49        return "<unset>";
50    }
51
52    @Override
53    public Map<String, String> getClassData(Client client) {
54        return new JDWPClassDataRetriever(client).retrieve();
55    }
56
57    private Map<String, String> retrieve() {
58        if (client == null) {
59            throw new IllegalStateException();
60        }
61
62        settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort()));
63        settings.setDebuggeeSuspend("n");
64
65        logWriter = new JPDALogWriter(System.out, "", false);
66
67        try {
68            internalSetUp();
69
70            return retrieveImpl();
71        } catch (Exception e) {
72            e.printStackTrace();
73            return null;
74        } finally {
75            internalTearDown();
76        }
77    }
78
79    private Map<String, String> retrieveImpl() {
80        try {
81            // Suspend the app.
82            {
83                CommandPacket packet = new CommandPacket(
84                        JDWPCommands.VirtualMachineCommandSet.CommandSetID,
85                        JDWPCommands.VirtualMachineCommandSet.SuspendCommand);
86                ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
87                if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
88                    return null;
89                }
90            }
91
92            // List all classes.
93            CommandPacket packet = new CommandPacket(
94                    JDWPCommands.VirtualMachineCommandSet.CommandSetID,
95                    JDWPCommands.VirtualMachineCommandSet.AllClassesCommand);
96            ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
97
98            if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
99                return null;
100            }
101
102            int classCount = reply.getNextValueAsInt();
103            System.out.println("Runtime reported " + classCount + " classes.");
104
105            Map<Long, String> classes = new HashMap<Long, String>();
106            Map<Long, String> arrayClasses = new HashMap<Long, String>();
107
108            for (int i = 0; i < classCount; i++) {
109                byte refTypeTag = reply.getNextValueAsByte();
110                long typeID = reply.getNextValueAsReferenceTypeID();
111                String signature = reply.getNextValueAsString();
112                /* int status = */ reply.getNextValueAsInt();
113
114                switch (refTypeTag) {
115                    case JDWPConstants.TypeTag.CLASS:
116                    case JDWPConstants.TypeTag.INTERFACE:
117                        classes.put(typeID, signature);
118                        break;
119
120                    case JDWPConstants.TypeTag.ARRAY:
121                        arrayClasses.put(typeID, signature);
122                        break;
123                }
124            }
125
126            Map<String, String> result = new HashMap<String, String>();
127
128            // Parse all classes.
129            for (Map.Entry<Long, String> entry : classes.entrySet()) {
130                long typeID = entry.getKey();
131                String signature = entry.getValue();
132
133                if (!checkClass(typeID, signature, result)) {
134                    System.err.println("Issue investigating " + signature);
135                }
136            }
137
138            // For arrays, look at the leaf component type.
139            for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) {
140                long typeID = entry.getKey();
141                String signature = entry.getValue();
142
143                if (!checkArrayClass(typeID, signature, result)) {
144                    System.err.println("Issue investigating " + signature);
145                }
146            }
147
148            return result;
149        } finally {
150            // Resume the app.
151            {
152                CommandPacket packet = new CommandPacket(
153                        JDWPCommands.VirtualMachineCommandSet.CommandSetID,
154                        JDWPCommands.VirtualMachineCommandSet.ResumeCommand);
155                /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet);
156            }
157        }
158    }
159
160    private boolean checkClass(long typeID, String signature, Map<String, String> result) {
161        CommandPacket packet = new CommandPacket(
162                JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
163                JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
164        packet.setNextValueAsReferenceTypeID(typeID);
165        ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
166        if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
167            return false;
168        }
169
170        long classLoaderID = reply.getNextValueAsObjectID();
171
172        // TODO: Investigate the classloader to have a better string?
173        String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
174
175        result.put(getClassName(signature), classLoaderString);
176
177        return true;
178    }
179
180    private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) {
181        // Classloaders of array classes are the same as the component class'.
182        CommandPacket packet = new CommandPacket(
183                JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
184                JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand);
185        packet.setNextValueAsReferenceTypeID(typeID);
186        ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet);
187        if (reply.getErrorCode() != JDWPConstants.Error.NONE) {
188            return false;
189        }
190
191        long classLoaderID = reply.getNextValueAsObjectID();
192
193        // TODO: Investigate the classloader to have a better string?
194        String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID);
195
196        // For array classes, we *need* the signature directly.
197        result.put(signature, classLoaderString);
198
199        return true;
200    }
201
202    private static String getClassName(String signature) {
203        String withoutLAndSemicolon = signature.substring(1, signature.length() - 1);
204        return withoutLAndSemicolon.replace('/', '.');
205    }
206
207
208    private static JPDATestOptions createTestOptions(String address) {
209        JPDATestOptions options = new JPDATestOptions();
210        options.setAttachConnectorKind();
211        options.setTimeout(1000);
212        options.setWaitingTime(1000);
213        options.setTransportAddress(address);
214        return options;
215    }
216
217    @Override
218    protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() {
219        return new PreloadDebugeeWrapper(settings, logWriter);
220    }
221}
222