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 }