1 /* 2 * Copyright (c) 2012, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.graalvm.compiler.debug; 24 25 import java.util.Arrays; 26 import java.util.regex.Pattern; 27 28 import jdk.vm.ci.meta.JavaMethod; 29 import jdk.vm.ci.meta.JavaType; 30 import jdk.vm.ci.meta.Signature; 31 32 /** 33 * This class implements a method filter that can filter based on class name, method name and 34 * parameters. The syntax for the source pattern that is passed to the constructor is as follows: 35 * 36 * <pre> 37 * SourcePatterns = SourcePattern ["," SourcePatterns] . 38 * SourcePattern = [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] . 39 * Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" . 40 * Class = { package "." } class . 41 * </pre> 42 * 43 * 44 * Glob pattern matching (*, ?) is allowed in all parts of the source pattern. Examples for valid 45 * filters are: 46 * 47 * <ul> 48 * <li> 49 * 50 * <pre> 51 * visit(Argument;BlockScope) 52 * </pre> 53 * 54 * Matches all methods named "visit", with the first parameter of type "Argument", and the second 55 * parameter of type "BlockScope". The packages of the parameter types are irrelevant.</li> 56 * <li> 57 * 58 * <pre> 59 * arraycopy(Object;;;;) 60 * </pre> 61 * 62 * Matches all methods named "arraycopy", with the first parameter of type "Object", and four more 63 * parameters of any type. The packages of the parameter types are irrelevant.</li> 64 * <li> 65 * 66 * <pre> 67 * org.graalvm.compiler.core.graph.PostOrderNodeIterator.* 68 * </pre> 69 * 70 * Matches all methods in the class "org.graalvm.compiler.core.graph.PostOrderNodeIterator".</li> 71 * <li> 72 * 73 * <pre> 74 * * 75 * </pre> 76 * 77 * Matches all methods in all classes</li> 78 * <li> 79 * 80 * <pre> 81 * org.graalvm.compiler.core.graph.*.visit 82 * </pre> 83 * 84 * Matches all methods named "visit" in classes in the package "org.graalvm.compiler.core.graph". 85 * <li> 86 * 87 * <pre> 88 * arraycopy,toString 89 * </pre> 90 * 91 * Matches all methods named "arraycopy" or "toString", meaning that ',' acts as an <i>or</i> 92 * operator.</li> 93 * </ul> 94 */ 95 public class MethodFilter { 96 97 private final Pattern clazz; 98 private final Pattern methodName; 99 private final Pattern[] signature; 100 101 /** 102 * Parses a string containing list of comma separated filter patterns into an array of 103 * {@link MethodFilter}s. 104 */ 105 public static MethodFilter[] parse(String commaSeparatedPatterns) { 106 String[] filters = commaSeparatedPatterns.split(","); 107 MethodFilter[] methodFilters = new MethodFilter[filters.length]; 108 for (int i = 0; i < filters.length; i++) { 109 methodFilters[i] = new MethodFilter(filters[i]); 110 } 111 return methodFilters; 112 } 113 114 /** 115 * Determines if a given method is matched by a given array of filters. 116 */ 117 public static boolean matches(MethodFilter[] filters, JavaMethod method) { 118 for (MethodFilter filter : filters) { 119 if (filter.matches(method)) { 120 return true; 121 } 122 } 123 return false; 124 } 125 126 /** 127 * Determines if a given class name is matched by a given array of filters. 128 */ 129 public static boolean matchesClassName(MethodFilter[] filters, String className) { 130 for (MethodFilter filter : filters) { 131 if (filter.matchesClassName(className)) { 132 return true; 133 } 134 } 135 return false; 136 } 137 138 public MethodFilter(String sourcePattern) { 139 String pattern = sourcePattern.trim(); 140 141 // extract parameter part 142 int pos = pattern.indexOf('('); 143 if (pos != -1) { 144 if (pattern.charAt(pattern.length() - 1) != ')') { 145 throw new IllegalArgumentException("missing ')' at end of method filter pattern: " + pattern); 146 } 147 String[] signatureClasses = pattern.substring(pos + 1, pattern.length() - 1).split(";", -1); 148 signature = new Pattern[signatureClasses.length]; 149 for (int i = 0; i < signatureClasses.length; i++) { 150 signature[i] = createClassGlobPattern(signatureClasses[i].trim()); 151 } 152 pattern = pattern.substring(0, pos); 153 } else { 154 signature = null; 155 } 156 157 // If there is at least one "." then everything before the last "." is the class name. 158 // Otherwise, the pattern contains only the method name. 159 pos = pattern.lastIndexOf('.'); 160 if (pos != -1) { 161 clazz = createClassGlobPattern(pattern.substring(0, pos)); 162 methodName = Pattern.compile(createGlobString(pattern.substring(pos + 1))); 163 } else { 164 clazz = null; 165 methodName = Pattern.compile(createGlobString(pattern)); 166 } 167 } 168 169 public static String createGlobString(String pattern) { 170 return Pattern.quote(pattern).replace("?", "\\E.\\Q").replace("*", "\\E.*\\Q"); 171 } 172 173 private static Pattern createClassGlobPattern(String pattern) { 174 if (pattern.length() == 0) { 175 return null; 176 } else if (pattern.contains(".")) { 177 return Pattern.compile(createGlobString(pattern)); 178 } else { 179 return Pattern.compile("([^\\.\\$]*[\\.\\$])*" + createGlobString(pattern)); 180 } 181 } 182 183 public boolean hasSignature() { 184 return signature != null; 185 } 186 187 /** 188 * Determines if the class part of this filter matches a given class name. 189 */ 190 public boolean matchesClassName(String className) { 191 return clazz == null || clazz.matcher(className).matches(); 192 } 193 194 public boolean matches(JavaMethod o) { 195 // check method name first, since MetaUtil.toJavaName is expensive 196 if (methodName != null && !methodName.matcher(o.getName()).matches()) { 197 return false; 198 } 199 if (clazz != null && !clazz.matcher(o.getDeclaringClass().toJavaName()).matches()) { 200 return false; 201 } 202 return matchesSignature(o.getSignature()); 203 } 204 205 private boolean matchesSignature(Signature sig) { 206 if (signature == null) { 207 return true; 208 } 209 if (sig.getParameterCount(false) != signature.length) { 210 return false; 211 } 212 for (int i = 0; i < signature.length; i++) { 213 JavaType type = sig.getParameterType(i, null); 214 String javaName = type.toJavaName(); 215 if (signature[i] != null && !signature[i].matcher(javaName).matches()) { 216 return false; 217 } 218 } 219 return true; 220 } 221 222 public boolean matches(String javaClassName, String name, Signature sig) { 223 assert sig != null || signature == null; 224 // check method name first, since MetaUtil.toJavaName is expensive 225 if (methodName != null && !methodName.matcher(name).matches()) { 226 return false; 227 } 228 if (clazz != null && !clazz.matcher(javaClassName).matches()) { 229 return false; 230 } 231 return matchesSignature(sig); 232 } 233 234 @Override 235 public String toString() { 236 StringBuilder buf = new StringBuilder("MethodFilter["); 237 String sep = ""; 238 if (clazz != null) { 239 buf.append(sep).append("clazz=").append(clazz); 240 sep = ", "; 241 } 242 if (methodName != null) { 243 buf.append(sep).append("methodName=").append(methodName); 244 sep = ", "; 245 } 246 if (signature != null) { 247 buf.append(sep).append("signature=").append(Arrays.toString(signature)); 248 sep = ", "; 249 } 250 return buf.append("]").toString(); 251 } 252 }