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 }