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 }