1 /*
   2  * Copyright (c) 2015, 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 
  24 package jdk.test.lib.jittester;
  25 
  26 import java.io.File;
  27 import java.io.IOException;
  28 import java.io.StringReader;
  29 import java.lang.reflect.Executable;
  30 import java.lang.reflect.Method;
  31 import java.lang.reflect.Modifier;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.HashMap;
  37 import java.util.HashSet;
  38 import java.util.LinkedList;
  39 import java.util.List;
  40 import java.util.Set;
  41 import jdk.test.lib.Asserts;
  42 import jdk.test.lib.jittester.functions.FunctionInfo;
  43 import jdk.test.lib.jittester.types.TypeArray;
  44 import jdk.test.lib.jittester.types.TypeKlass;
  45 import jdk.test.lib.jittester.types.TypeVoid;
  46 
  47 /**
  48  * Class used for parsing included classes file and excluded methods file
  49  */
  50 public class TypesParser {
  51 
  52     private static final HashMap<Class<?>, Type> TYPE_CACHE = new HashMap<>();
  53 
  54     /**
  55      * Parses included classes file and excluded methods file to TypeList and SymbolTable.
  56      * This routine takes all classes named in the classes file and puts them to the TypeList,
  57      * it also puts all the methods of the classes to the SymbolTable.
  58      * Excluded methods file is used to remove methods from the SymbolTable.
  59      * @param klassesFileName - name of the included classes file
  60      * @param exMethodsFileName - name of the excluded method file
  61      */
  62     public static void parseTypesAndMethods(String klassesFileName, String exMethodsFileName) {
  63         Asserts.assertNotNull(klassesFileName, "Classes input file name is null");
  64         Asserts.assertFalse(klassesFileName.isEmpty(), "Classes input file name is empty");
  65         Set<Class<?>> klasses = parseKlasses(klassesFileName);
  66         Set<Executable> methodsToExclude;
  67         if (exMethodsFileName != null && !exMethodsFileName.isEmpty()) {
  68             methodsToExclude = parseMethods(exMethodsFileName);
  69         } else {
  70             methodsToExclude = new HashSet<>();
  71         }
  72         klasses.stream().forEach(klass -> {
  73             TypeKlass typeKlass = (TypeKlass) getType(klass);
  74             if (TypeList.isReferenceType(typeKlass)) {
  75                 return;
  76             }
  77             TypeList.add(typeKlass);
  78             Set<Executable> methods = new HashSet<>();
  79             methods.addAll(Arrays.asList(klass.getMethods()));
  80             methods.addAll(Arrays.asList(klass.getConstructors()));
  81             methods.removeAll(methodsToExclude);
  82             methods.stream().forEach(method -> {
  83                 if (method.isSynthetic()) {
  84                     return;
  85                 }
  86                 String name = method.getName();
  87                 boolean isConstructor = false;
  88                 Type returnType;
  89                 if (name.equals(klass.getName())) {
  90                     isConstructor = true;
  91                     returnType = typeKlass;
  92                 } else {
  93                     returnType = getType(((Method) method).getReturnType());
  94                 }
  95                 ArrayList<VariableInfo> paramList = new ArrayList<>();
  96                 int flags = getMethodFlags(method);
  97                 if (!isConstructor && ((flags & FunctionInfo.STATIC) == 0)) {
  98                     paramList.add(new VariableInfo("this", typeKlass, typeKlass,
  99                             VariableInfo.LOCAL | VariableInfo.INITIALIZED));
 100                 }
 101                 Class<?>[] paramKlasses = method.getParameterTypes();
 102                 int argNum = 0;
 103                 for (Class<?> paramKlass : paramKlasses) {
 104                     argNum++;
 105                     Type paramType = getType(paramKlass);
 106                     paramList.add(new VariableInfo("arg" + argNum, typeKlass, paramType,
 107                             VariableInfo.LOCAL | VariableInfo.INITIALIZED));
 108                 }
 109                 typeKlass.addSymbol(new FunctionInfo(name, typeKlass, returnType, 1, flags,
 110                         paramList));
 111             });
 112         });
 113     }
 114 
 115     private static Type getType(Class<?> klass) {
 116         Type type = TYPE_CACHE.get(klass);
 117         if (type != null) {
 118             return type;
 119         }
 120         if (klass.isPrimitive()) {
 121             if (klass.equals(void.class)) {
 122                 type = new TypeVoid();
 123             } else {
 124                 type = TypeList.find(klass.getName());
 125             }
 126         } else {
 127             int flags = getKlassFlags(klass);
 128             if (klass.isArray()) {
 129                 TypeKlass elementType
 130                         = new TypeKlass(klass.getCanonicalName().replaceAll("\\[\\]", ""), flags);
 131                 int dim = getArrayClassDimension(klass);
 132                 type = new TypeArray(elementType, dim);
 133             } else {
 134                 String canonicalName = klass.getCanonicalName();
 135                 if (!"java.lang.Object".equals(canonicalName)) {
 136                     flags |= TypeKlass.FINAL;
 137                 }
 138                 type = new TypeKlass(canonicalName, flags);
 139             }
 140             Class<?> parentKlass = klass.getSuperclass();
 141             if (parentKlass != null) {
 142                 TypeKlass parentTypeKlass = (TypeKlass) getType(parentKlass);
 143                 ((TypeKlass) type).addParent(parentTypeKlass.getName());
 144                 ((TypeKlass) type).setParent(parentTypeKlass);
 145             }
 146         }
 147         TYPE_CACHE.put(klass, type);
 148         return type;
 149     }
 150 
 151     private static int getArrayClassDimension(Class<?> klass) {
 152         if (!klass.isArray()) {
 153             return 0;
 154         }
 155         String name = klass.getName();
 156         int begin = name.indexOf('[');
 157         name = name.substring(begin, name.length());
 158         return name.length() / 2;
 159     }
 160 
 161     private static int getKlassFlags(Class<?> klass) {
 162         int flags = TypeKlass.NONE;
 163         if (klass.isInterface()) {
 164             flags = flags | TypeKlass.INTERFACE;
 165         } else if ((klass.getModifiers() & Modifier.ABSTRACT) != 0) {
 166             flags = flags | TypeKlass.ABSTRACT;
 167         } else if ((klass.getModifiers() & Modifier.FINAL) != 0) {
 168             flags = flags | TypeKlass.FINAL;
 169         }
 170         return flags;
 171     }
 172 
 173     private static int getMethodFlags(Executable method) {
 174         int flags = FunctionInfo.NONE;
 175         int modifiers = method.getModifiers();
 176         if (Modifier.isAbstract(modifiers)) {
 177             flags |= FunctionInfo.ABSTRACT;
 178         }
 179         if (Modifier.isFinal(modifiers)) {
 180             flags |= FunctionInfo.FINAL;
 181         }
 182         if (Modifier.isPublic(modifiers)) {
 183             flags |= FunctionInfo.PUBLIC;
 184         } else if (Modifier.isProtected(modifiers)) {
 185             flags |= FunctionInfo.PROTECTED;
 186         } else if (Modifier.isPrivate(modifiers)) {
 187             flags |= FunctionInfo.PRIVATE;
 188         } else {
 189             flags |= FunctionInfo.DEFAULT;
 190         }
 191         if (Modifier.isStatic(modifiers)) {
 192             flags |= FunctionInfo.STATIC;
 193         }
 194         if (Modifier.isSynchronized(modifiers)) {
 195             flags |= FunctionInfo.SYNCHRONIZED;
 196         }
 197         return flags;
 198     }
 199 
 200     private static Set<Class<?>> parseKlasses(String klassesFileName) {
 201         Asserts.assertNotNull(klassesFileName, "Classes input file name is null");
 202         Asserts.assertFalse(klassesFileName.isEmpty(), "Classes input file name is empty");
 203         Set<String> klassNamesSet = new HashSet<>();
 204         Path klassesFilePath = (new File(klassesFileName)).toPath();
 205         try {
 206             Files.lines(klassesFilePath).forEach(line -> {
 207                 line = line.trim();
 208                 if (line.isEmpty()) {
 209                     return;
 210                 }
 211                 String msg = String.format("Format of the classes input file \"%s\" is incorrect,"
 212                         + " line \"%s\" has wrong format", klassesFileName, line);
 213                 Asserts.assertTrue(line.matches("\\w[\\w\\.$]*"), msg);
 214                 klassNamesSet.add(line.replaceAll(";", ""));
 215             });
 216         } catch (IOException ex) {
 217             throw new Error("Error reading klasses file", ex);
 218         }
 219         Set<Class<?>> klassesSet = new HashSet<>();
 220         klassNamesSet.stream().forEach(klassName -> {
 221             try {
 222                 klassesSet.add(Class.forName(klassName));
 223             } catch (ClassNotFoundException ex) {
 224                 throw new Error("Unexpected exception while parsing klasses file", ex);
 225             }
 226         });
 227         return klassesSet;
 228     }
 229 
 230     private static Set<Executable> parseMethods(String methodsFileName) {
 231         Asserts.assertNotNull(methodsFileName, "Methods exclude input file name is null");
 232         Asserts.assertFalse(methodsFileName.isEmpty(), "Methods exclude input file name is empty");
 233         LinkedList<String> methodNamesList = new LinkedList<>();
 234         Path klassesFilePath = (new File(methodsFileName)).toPath();
 235         try {
 236             Files.lines(klassesFilePath).forEach(line -> {
 237                 line = line.trim();
 238                 if (line.isEmpty()) {
 239                     return;
 240                 }
 241                 String msg = String.format("Format of the methods exclude input file \"%s\" is incorrect,"
 242                         + " line \"%s\" has wrong format", methodsFileName, line);
 243                 Asserts.assertTrue(line.matches("\\w[\\w/$]*::[\\w$]+\\((\\[?[ZBSCIJFD]|\\[?L[\\w/$]+;)*\\)"), msg);
 244                 methodNamesList.add(line.substring(0, line.length() - 1));
 245             });
 246         } catch (IOException ex) {
 247             throw new Error("Error reading exclude method file", ex);
 248         }
 249         Set<Executable> methodsList = new HashSet<>();
 250         methodNamesList.stream().forEach(methodName -> {
 251             String[] klassAndNameAndSig = methodName.split("::");
 252             String klassName = klassAndNameAndSig[0].replaceAll("/", "\\.");
 253             String[] nameAndSig = klassAndNameAndSig[1].split("[\\(\\)]");
 254             String name = nameAndSig[0];
 255             String signature = "";
 256             if (nameAndSig.length > 1) {
 257                 signature = nameAndSig[1];
 258             }
 259             Class<?> klass = null;
 260             List<Class<?>> signatureTypes = null;
 261             try {
 262                 klass = Class.forName(klassName);
 263                 signatureTypes = parseSignature(signature);
 264             } catch (ClassNotFoundException ex) {
 265                 throw new Error("Unexpected exception while parsing exclude methods file", ex);
 266             }
 267             try {
 268                 Executable method;
 269                 if (name.equals(klass.getSimpleName())) {
 270                     method = klass.getConstructor(signatureTypes.toArray(new Class<?>[0]));
 271                 } else {
 272                     method = klass.getMethod(name, signatureTypes.toArray(new Class<?>[0]));
 273                 }
 274                 methodsList.add(method);
 275             } catch (NoSuchMethodException | SecurityException ex) {
 276                 throw new Error("Unexpected exception while parsing exclude methods file", ex);
 277             }
 278         });
 279         return methodsList;
 280     }
 281 
 282     private static List<Class<?>> parseSignature(String signature) throws ClassNotFoundException {
 283         LinkedList<Class<?>> sigClasses = new LinkedList<>();
 284         char typeChar;
 285         boolean isArray;
 286         String klassName;
 287         StringBuilder sb;
 288         StringBuilder arrayDim;
 289         try (StringReader str = new StringReader(signature)) {
 290             int symbol = str.read();
 291             while (symbol != -1){
 292                 typeChar = (char) symbol;
 293                 arrayDim = new StringBuilder();
 294                 Class<?> primArrayClass = null;
 295                 if (typeChar == '[') {
 296                     isArray = true;
 297                     arrayDim.append('[');
 298                     symbol = str.read();
 299                     while (symbol == '['){
 300                         arrayDim.append('[');
 301                         symbol = str.read();
 302                     }
 303                     typeChar = (char) symbol;
 304                     if (typeChar != 'L') {
 305                         primArrayClass = Class.forName(arrayDim.toString() + typeChar);
 306                     }
 307                 } else {
 308                     isArray = false;
 309                 }
 310                 switch (typeChar) {
 311                     case 'Z':
 312                         sigClasses.add(isArray ? primArrayClass : boolean.class);
 313                         break;
 314                     case 'I':
 315                         sigClasses.add(isArray ? primArrayClass : int.class);
 316                         break;
 317                     case 'J':
 318                         sigClasses.add(isArray ? primArrayClass : long.class);
 319                         break;
 320                     case 'F':
 321                         sigClasses.add(isArray ? primArrayClass : float.class);
 322                         break;
 323                     case 'D':
 324                         sigClasses.add(isArray ? primArrayClass : double.class);
 325                         break;
 326                     case 'B':
 327                         sigClasses.add(isArray ? primArrayClass : byte.class);
 328                         break;
 329                     case 'S':
 330                         sigClasses.add(isArray ? primArrayClass : short.class);
 331                         break;
 332                     case 'C':
 333                         sigClasses.add(isArray ? primArrayClass : char.class);
 334                         break;
 335                     case 'L':
 336                         sb = new StringBuilder();
 337                         symbol = str.read();
 338                         while (symbol != ';') {
 339                             sb.append((char) symbol);
 340                             symbol = str.read();
 341                         }
 342                         klassName = sb.toString().replaceAll("/", "\\.");
 343                         if (isArray) {
 344                             klassName = arrayDim.toString() + "L" + klassName + ";";
 345                         }
 346                         Class<?> klass = Class.forName(klassName);
 347                         sigClasses.add(klass);
 348                         break;
 349                     default:
 350                         throw new Error("Unknown type " + typeChar);
 351                 }
 352                 symbol = str.read();
 353             }
 354         } catch (IOException ex) {
 355             throw new Error("Unexpected exception while parsing exclude methods file", ex);
 356         }
 357         return sigClasses;
 358     }
 359 }