1 /*
2 * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * This source code is provided to illustrate the usage of a given feature
28 * or technique and has been deliberately simplified. Additional steps
29 * required for a production-quality application, such as security checks,
30 * input validation and proper error handling, might not be present in
31 * this sample code.
32 */
33
34
35 package com.sun.tools.example.debug.tty;
36
37 import com.sun.jdi.*;
38 import com.sun.jdi.request.*;
39
40 import java.util.ArrayList;
41 import java.util.List;
42
43 class BreakpointSpec extends EventRequestSpec {
44 String methodId;
45 List<String> methodArgs;
46 int lineNumber;
47 ThreadReference threadFilter; /* Thread to break in. null if global breakpoint. */
48 public static final String locationTokenDelimiter = ":( \t\n\r";
49
50 BreakpointSpec(ReferenceTypeSpec refSpec, int lineNumber, ThreadReference threadFilter) {
51 super(refSpec);
52 this.methodId = null;
53 this.methodArgs = null;
54 this.lineNumber = lineNumber;
55 this.threadFilter = threadFilter;
56 }
57
58 BreakpointSpec(ReferenceTypeSpec refSpec, String methodId, ThreadReference threadFilter,
59 List<String> methodArgs) throws MalformedMemberNameException {
60 super(refSpec);
61 this.methodId = methodId;
62 this.methodArgs = methodArgs;
63 this.lineNumber = 0;
64 this.threadFilter = threadFilter;
65 if (!isValidMethodName(methodId)) {
66 throw new MalformedMemberNameException(methodId);
67 }
68 }
69
70 /**
71 * The 'refType' is known to match, return the EventRequest.
72 */
73 @Override
74 EventRequest resolveEventRequest(ReferenceType refType)
75 throws AmbiguousMethodException,
76 AbsentInformationException,
77 InvalidTypeException,
78 NoSuchMethodException,
79 LineNotFoundException {
80 Location location = location(refType);
81 if (location == null) {
82 throw new InvalidTypeException();
83 }
84 EventRequestManager em = refType.virtualMachine().eventRequestManager();
85 BreakpointRequest bp = em.createBreakpointRequest(location);
86 bp.setSuspendPolicy(suspendPolicy);
87 if (threadFilter != null) {
88 bp.addThreadFilter(threadFilter);
89 }
90 bp.enable();
91 return bp;
92 }
93
94 String methodName() {
95 return methodId;
96 }
97
98 int lineNumber() {
99 return lineNumber;
100 }
101
102 List<String> methodArgs() {
103 return methodArgs;
104 }
105
106 boolean isMethodBreakpoint() {
107 return (methodId != null);
108 }
109
110 @Override
111 public int hashCode() {
112 return refSpec.hashCode() + lineNumber +
113 ((methodId != null) ? methodId.hashCode() : 0) +
114 ((methodArgs != null) ? methodArgs.hashCode() : 0) +
115 ((threadFilter != null) ? threadFilter.hashCode() : 0);
116 }
117
118 @Override
119 public boolean equals(Object obj) {
120 if (obj instanceof BreakpointSpec) {
121 BreakpointSpec breakpoint = (BreakpointSpec)obj;
122
123 return ((methodId != null) ?
124 methodId.equals(breakpoint.methodId)
125 : methodId == breakpoint.methodId) &&
126 ((methodArgs != null) ?
127 methodArgs.equals(breakpoint.methodArgs)
128 : methodArgs == breakpoint.methodArgs) &&
129 ((threadFilter != null) ?
130 threadFilter.equals(breakpoint.threadFilter)
131 : threadFilter == breakpoint.threadFilter) &&
132 refSpec.equals(breakpoint.refSpec) &&
133 (lineNumber == breakpoint.lineNumber);
134 } else {
135 return false;
136 }
137 }
138
139 @Override
140 String errorMessageFor(Exception e) {
141 if (e instanceof AmbiguousMethodException) {
142 return (MessageOutput.format("Method is overloaded; specify arguments",
143 methodName()));
144 /*
145 * TO DO: list the methods here
146 */
147 } else if (e instanceof NoSuchMethodException) {
148 return (MessageOutput.format("No method in",
149 new Object [] {methodName(),
150 refSpec.toString()}));
151 } else if (e instanceof AbsentInformationException) {
152 return (MessageOutput.format("No linenumber information for",
153 refSpec.toString()));
154 } else if (e instanceof LineNotFoundException) {
155 return (MessageOutput.format("No code at line",
156 new Object [] {Long.valueOf(lineNumber()),
157 refSpec.toString()}));
158 } else if (e instanceof InvalidTypeException) {
159 return (MessageOutput.format("Breakpoints can be located only in classes.",
160 refSpec.toString()));
161 } else {
162 return super.errorMessageFor( e);
163 }
164 }
165
166 @Override
167 public String toString() {
168 StringBuilder sb = new StringBuilder(refSpec.toString());
169 if (isMethodBreakpoint()) {
170 sb.append('.');
171 sb.append(methodId);
172 if (methodArgs != null) {
173 boolean first = true;
174 sb.append('(');
175 for (String arg : methodArgs) {
176 if (!first) {
177 sb.append(',');
178 }
179 sb.append(arg);
180 first = false;
181 }
182 sb.append(")");
183 }
184 } else {
185 sb.append(':');
186 sb.append(lineNumber);
187 }
188 return MessageOutput.format("breakpoint", sb.toString());
189 }
190
191 private Location location(ReferenceType refType) throws
192 AmbiguousMethodException,
193 AbsentInformationException,
194 NoSuchMethodException,
195 LineNotFoundException {
196 Location location = null;
197 if (isMethodBreakpoint()) {
198 Method method = findMatchingMethod(refType);
199 location = method.location();
200 } else {
201 // let AbsentInformationException be thrown
202 List<Location> locs = refType.locationsOfLine(lineNumber());
203 if (locs.size() == 0) {
204 throw new LineNotFoundException();
205 }
206 // TO DO: handle multiple locations
207 location = locs.get(0);
208 if (location.method() == null) {
209 throw new LineNotFoundException();
210 }
211 }
212 return location;
213 }
214
215 private boolean isValidMethodName(String s) {
216 return isJavaIdentifier(s) ||
217 s.equals("<init>") ||
218 s.equals("<clinit>");
219 }
220
221 /*
222 * Compare a method's argument types with a Vector of type names.
223 * Return true if each argument type has a name identical to the
224 * corresponding string in the vector (allowing for varars)
225 * and if the number of arguments in the method matches the
226 * number of names passed
227 */
228 private boolean compareArgTypes(Method method, List<String> nameList) {
229 List<String> argTypeNames = method.argumentTypeNames();
230
231 // If argument counts differ, we can stop here
232 if (argTypeNames.size() != nameList.size()) {
233 return false;
234 }
235
236 // Compare each argument type's name
237 int nTypes = argTypeNames.size();
238 for (int i = 0; i < nTypes; ++i) {
239 String comp1 = argTypeNames.get(i);
240 String comp2 = nameList.get(i);
241 if (! comp1.equals(comp2)) {
242 /*
243 * We have to handle varargs. EG, the
244 * method's last arg type is xxx[]
245 * while the nameList contains xxx...
246 * Note that the nameList can also contain
247 * xxx[] in which case we don't get here.
248 */
249 if (i != nTypes - 1 ||
250 !method.isVarArgs() ||
251 !comp2.endsWith("...")) {
252 return false;
253 }
254 /*
255 * The last types differ, it is a varargs
256 * method and the nameList item is varargs.
257 * We just have to compare the type names, eg,
258 * make sure we don't have xxx[] for the method
259 * arg type and yyy... for the nameList item.
260 */
261 int comp1Length = comp1.length();
262 if (comp1Length + 1 != comp2.length()) {
263 // The type names are different lengths
264 return false;
265 }
266 // We know the two type names are the same length
267 if (!comp1.regionMatches(0, comp2, 0, comp1Length - 2)) {
268 return false;
269 }
270 // We do have xxx[] and xxx... as the last param type
271 return true;
272 }
273 }
274
275 return true;
276 }
277
278
279 /*
280 * Remove unneeded spaces and expand class names to fully
281 * qualified names, if necessary and possible.
282 */
283 private String normalizeArgTypeName(String name) {
284 /*
285 * Separate the type name from any array modifiers,
286 * stripping whitespace after the name ends
287 */
288 int i = 0;
289 StringBuilder typePart = new StringBuilder();
290 StringBuilder arrayPart = new StringBuilder();
291 name = name.trim();
292 int nameLength = name.length();
293 /*
294 * For varargs, there can be spaces before the ... but not
295 * within the ... So, we will just ignore the ...
296 * while stripping blanks.
297 */
298 boolean isVarArgs = name.endsWith("...");
299 if (isVarArgs) {
300 nameLength -= 3;
301 }
302 while (i < nameLength) {
303 char c = name.charAt(i);
304 if (Character.isWhitespace(c) || c == '[') {
305 break; // name is complete
306 }
307 typePart.append(c);
308 i++;
309 }
310 while (i < nameLength) {
311 char c = name.charAt(i);
312 if ( (c == '[') || (c == ']')) {
313 arrayPart.append(c);
314 } else if (!Character.isWhitespace(c)) {
315 throw new IllegalArgumentException
316 (MessageOutput.format("Invalid argument type name"));
317 }
318 i++;
319 }
320 name = typePart.toString();
321
322 /*
323 * When there's no sign of a package name already, try to expand the
324 * the name to a fully qualified class name
325 */
326 if ((name.indexOf('.') == -1) || name.startsWith("*.")) {
327 try {
328 ReferenceType argClass = Env.getReferenceTypeFromToken(name);
329 if (argClass != null) {
330 name = argClass.name();
331 }
332 } catch (IllegalArgumentException e) {
333 // We'll try the name as is
334 }
335 }
336 name += arrayPart.toString();
337 if (isVarArgs) {
338 name += "...";
339 }
340 return name;
341 }
342
343 /*
344 * Attempt an unambiguous match of the method name and
345 * argument specification to a method. If no arguments
346 * are specified, the method must not be overloaded.
347 * Otherwise, the argument types much match exactly
348 */
349 private Method findMatchingMethod(ReferenceType refType)
350 throws AmbiguousMethodException,
351 NoSuchMethodException {
352
353 // Normalize the argument string once before looping below.
354 List<String> argTypeNames = null;
355 if (methodArgs() != null) {
356 argTypeNames = new ArrayList<String>(methodArgs().size());
357 for (String name : methodArgs()) {
358 name = normalizeArgTypeName(name);
359 argTypeNames.add(name);
360 }
361 }
362
363 // Check each method in the class for matches
364 Method firstMatch = null; // first method with matching name
365 Method exactMatch = null; // (only) method with same name & sig
366 int matchCount = 0; // > 1 implies overload
367 for (Method candidate : refType.methods()) {
368 if (candidate.name().equals(methodName())) {
369 matchCount++;
370
371 // Remember the first match in case it is the only one
372 if (matchCount == 1) {
373 firstMatch = candidate;
374 }
375
376 // If argument types were specified, check against candidate
377 if ((argTypeNames != null)
378 && compareArgTypes(candidate, argTypeNames) == true) {
379 exactMatch = candidate;
380 break;
381 }
382 }
383 }
384
385 // Determine method for breakpoint
386 Method method = null;
387 if (exactMatch != null) {
388 // Name and signature match
389 method = exactMatch;
390 } else if ((argTypeNames == null) && (matchCount > 0)) {
391 // At least one name matched and no arg types were specified
392 if (matchCount == 1) {
393 method = firstMatch; // Only one match; safe to use it
394 } else {
395 throw new AmbiguousMethodException();
396 }
397 } else {
398 throw new NoSuchMethodException(methodName());
399 }
400 return method;
401 }
402 }