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("<vnew>") ||
219 s.equals("<clinit>");
220 }
221
222 /*
223 * Compare a method's argument types with a Vector of type names.
224 * Return true if each argument type has a name identical to the
225 * corresponding string in the vector (allowing for varars)
226 * and if the number of arguments in the method matches the
227 * number of names passed
228 */
229 private boolean compareArgTypes(Method method, List<String> nameList) {
230 List<String> argTypeNames = method.argumentTypeNames();
231
232 // If argument counts differ, we can stop here
233 if (argTypeNames.size() != nameList.size()) {
234 return false;
235 }
236
237 // Compare each argument type's name
238 int nTypes = argTypeNames.size();
239 for (int i = 0; i < nTypes; ++i) {
240 String comp1 = argTypeNames.get(i);
241 String comp2 = nameList.get(i);
242 if (! comp1.equals(comp2)) {
243 /*
244 * We have to handle varargs. EG, the
245 * method's last arg type is xxx[]
246 * while the nameList contains xxx...
247 * Note that the nameList can also contain
248 * xxx[] in which case we don't get here.
249 */
250 if (i != nTypes - 1 ||
251 !method.isVarArgs() ||
252 !comp2.endsWith("...")) {
253 return false;
254 }
255 /*
256 * The last types differ, it is a varargs
257 * method and the nameList item is varargs.
258 * We just have to compare the type names, eg,
259 * make sure we don't have xxx[] for the method
260 * arg type and yyy... for the nameList item.
261 */
262 int comp1Length = comp1.length();
263 if (comp1Length + 1 != comp2.length()) {
264 // The type names are different lengths
265 return false;
266 }
267 // We know the two type names are the same length
268 if (!comp1.regionMatches(0, comp2, 0, comp1Length - 2)) {
269 return false;
270 }
271 // We do have xxx[] and xxx... as the last param type
272 return true;
273 }
274 }
275
276 return true;
277 }
278
279
280 /*
281 * Remove unneeded spaces and expand class names to fully
282 * qualified names, if necessary and possible.
283 */
284 private String normalizeArgTypeName(String name) {
285 /*
286 * Separate the type name from any array modifiers,
287 * stripping whitespace after the name ends
288 */
289 int i = 0;
290 StringBuilder typePart = new StringBuilder();
291 StringBuilder arrayPart = new StringBuilder();
292 name = name.trim();
293 int nameLength = name.length();
294 /*
295 * For varargs, there can be spaces before the ... but not
296 * within the ... So, we will just ignore the ...
297 * while stripping blanks.
298 */
299 boolean isVarArgs = name.endsWith("...");
300 if (isVarArgs) {
301 nameLength -= 3;
302 }
303 while (i < nameLength) {
304 char c = name.charAt(i);
305 if (Character.isWhitespace(c) || c == '[') {
306 break; // name is complete
307 }
308 typePart.append(c);
309 i++;
310 }
311 while (i < nameLength) {
312 char c = name.charAt(i);
313 if ( (c == '[') || (c == ']')) {
314 arrayPart.append(c);
315 } else if (!Character.isWhitespace(c)) {
316 throw new IllegalArgumentException
317 (MessageOutput.format("Invalid argument type name"));
318 }
319 i++;
320 }
321 name = typePart.toString();
322
323 /*
324 * When there's no sign of a package name already, try to expand the
325 * the name to a fully qualified class name
326 */
327 if ((name.indexOf('.') == -1) || name.startsWith("*.")) {
328 try {
329 ReferenceType argClass = Env.getReferenceTypeFromToken(name);
330 if (argClass != null) {
331 name = argClass.name();
332 }
333 } catch (IllegalArgumentException e) {
334 // We'll try the name as is
335 }
336 }
337 name += arrayPart.toString();
338 if (isVarArgs) {
339 name += "...";
340 }
341 return name;
342 }
343
344 /*
345 * Attempt an unambiguous match of the method name and
346 * argument specification to a method. If no arguments
347 * are specified, the method must not be overloaded.
348 * Otherwise, the argument types much match exactly
349 */
350 private Method findMatchingMethod(ReferenceType refType)
351 throws AmbiguousMethodException,
352 NoSuchMethodException {
353
354 // Normalize the argument string once before looping below.
355 List<String> argTypeNames = null;
356 if (methodArgs() != null) {
357 argTypeNames = new ArrayList<String>(methodArgs().size());
358 for (String name : methodArgs()) {
359 name = normalizeArgTypeName(name);
360 argTypeNames.add(name);
361 }
362 }
363
364 // Check each method in the class for matches
365 Method firstMatch = null; // first method with matching name
366 Method exactMatch = null; // (only) method with same name & sig
367 int matchCount = 0; // > 1 implies overload
368 for (Method candidate : refType.methods()) {
369 if (candidate.name().equals(methodName())) {
370 matchCount++;
371
372 // Remember the first match in case it is the only one
373 if (matchCount == 1) {
374 firstMatch = candidate;
375 }
376
377 // If argument types were specified, check against candidate
378 if ((argTypeNames != null)
379 && compareArgTypes(candidate, argTypeNames) == true) {
380 exactMatch = candidate;
381 break;
382 }
383 }
384 }
385
386 // Determine method for breakpoint
387 Method method = null;
388 if (exactMatch != null) {
389 // Name and signature match
390 method = exactMatch;
391 } else if ((argTypeNames == null) && (matchCount > 0)) {
392 // At least one name matched and no arg types were specified
393 if (matchCount == 1) {
394 method = firstMatch; // Only one match; safe to use it
395 } else {
396 throw new AmbiguousMethodException();
397 }
398 } else {
399 throw new NoSuchMethodException(methodName());
400 }
401 return method;
402 }
403 }
--- EOF ---