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 }