8357739: [jittester] disable the hashCode method

Reviewed-by: lmesnik
This commit is contained in:
Evgeny Nikitin 2025-07-03 16:58:30 +00:00 committed by Leonid Mesnik
parent 66836d40b8
commit a2315ddd2a
4 changed files with 435 additions and 154 deletions

View File

@ -31,3 +31,6 @@ java/lang/System::nanoTime()
java/lang/annotation/IncompleteAnnotationException::IncompleteAnnotationException(Ljava/lang/Class;Ljava/lang/String;)
java/util/AbstractSet::toString()
java/util/HashSet::toString()
#Unstable methods
*::hashCode

View File

@ -0,0 +1,256 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.jittester;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.lib.Asserts;
import static java.util.function.Predicate.not;
/**
* A wrapper for string method templates, similar to the CompileCommand patterns.
*/
public final class MethodTemplate {
/**
* String that can have wildcard symbols on its ends, allowing it to match a family of strings.
* For example, "abc*" matches "abc123", and so on.
*/
public static class WildcardString {
private final String pattern;
private final boolean frontWildcarded;
private final boolean tailWildcarded;
/**
* Creates a WildcardString from given string.
* @param pattern string pattern, like "some*"
*/
public WildcardString(String pattern) {
// check for the leading '*'
frontWildcarded = pattern.charAt(0) == '*';
pattern = frontWildcarded ? pattern.substring(1) : pattern;
// check for the trailing '*'
tailWildcarded = pattern.length() > 0 && pattern.charAt(pattern.length() - 1) == '*';
pattern = tailWildcarded ? pattern.substring(0, pattern.length() - 1) : pattern;
this.pattern = pattern;
}
/**
* Returns true it this WildcardString matches given other string.
* @param other the string that this WildcardString should be matched against
* @return true in case of a match.
*/
public boolean matches(String other) {
boolean result = pattern.equals(other);
result |= frontWildcarded ? other.endsWith(pattern) : result;
result |= tailWildcarded ? other.startsWith(pattern) : result;
result |= tailWildcarded && frontWildcarded ? other.contains(pattern) : result;
return result;
}
}
private static final Pattern METHOD_PATTERN = Pattern.compile(generateMethodPattern());
private final WildcardString klassName;
private final WildcardString methodName;
private final Optional<List<Class<?>>> signature;
private MethodTemplate(String klassName, String methodName, Optional<List<Class<?>>> signature) {
this.klassName = new WildcardString(klassName);
this.methodName = new WildcardString(methodName);
this.signature = signature;
}
private static String generateMethodPattern() {
// Sample valid template(s): java/lang/String::indexOf(Ljava/lang/String;I)
// java/lang/::*(Ljava/lang/String;I)
// *String::indexOf(*)
// java/lang/*::indexOf
String primitiveType = "[ZBSCIJFD]"; // Simply a letter, like 'I'
String referenceType = "L[\\w/$]+;"; // Like 'Ljava/lang/String;'
String primOrRefType =
"\\[?" + primitiveType + // Bracket is optional: '[Z', or 'Z'
"|" +
"\\[?" + referenceType; // Bracket is optional: '[LSomeObject;' or 'LSomeObject;'
String argTypesOrWildcard = "(" + // Method argument(s) Ljava/lang/String;Z...
"(" + primOrRefType + ")*" +
")|\\*"; // .. or a wildcard:
return
"(?<klassName>[\\w/$]*\\*?)" + // Class name, like 'java/lang/String'
"::" + // Simply '::'
"(?<methodName>\\*?[\\w$]+\\*?)" + // method name, 'indexOf''
"(\\((?<argTypes>" + // Method argument(s) in brackets:
argTypesOrWildcard + // (Ljava/lang/String;Z) or '*' or nothing
")\\))?";
}
/**
* Returns true iff none of the given MethodTemplates matches the given Executable.
*
* @param templates the collection of templates to check
* @param method the executable to match the colletions templates
* @return true if none of the given templates matches the method, false otherwise
*/
public static boolean noneMatches(Collection<MethodTemplate> templates, Executable method) {
for (MethodTemplate template : templates) {
if (template.matches(method)) {
return false;
}
}
return true;
}
/**
* Returns true if this MethodTemplate matches the given Executable.
*
* @param other the Executable to try to match to
* @return whether the other matches this MethodTemplate
*/
public boolean matches(Executable other) {
boolean result = klassName.matches(other.getDeclaringClass().getName());
result &= (other instanceof Constructor)
? result
: methodName.matches(other.getName());
return result &&
signature.map(Arrays.asList(other.getParameterTypes())::equals)
.orElse(true);
}
/**
* Parses the given string and returs a MethodTemplate.
*
* @param methodStr the string to parse
* @return created MethodTemplate
*/
public static MethodTemplate parse(String methodStr) {
Matcher matcher = METHOD_PATTERN.matcher(methodStr);
String msg = String.format("Format of the methods exclude input file is incorrect,"
+ " methodStr \"%s\" has wrong format", methodStr);
Asserts.assertTrue(matcher.matches(), msg);
String klassName = matcher.group("klassName").replaceAll("/", "\\.");
String methodName = matcher.group("methodName");
Optional<List<Class<?>>> signature = Optional.ofNullable(matcher.group("argTypes"))
.filter(not("*"::equals))
.map(MethodTemplate::parseSignature);
return new MethodTemplate(klassName, methodName, signature);
}
private static List<Class<?>> parseSignature(String signature) {
List<Class<?>> sigClasses = new ArrayList<>();
char typeChar;
boolean isArray;
String klassName;
StringBuilder sb;
StringBuilder arrayDim;
try (StringReader str = new StringReader(signature)) {
int symbol = str.read();
while (symbol != -1) {
typeChar = (char) symbol;
arrayDim = new StringBuilder();
Class<?> primArrayClass = null;
if (typeChar == '[') {
isArray = true;
arrayDim.append('[');
symbol = str.read();
while (symbol == '[') {
arrayDim.append('[');
symbol = str.read();
}
typeChar = (char) symbol;
if (typeChar != 'L') {
primArrayClass = Class.forName(arrayDim.toString() + typeChar);
}
} else {
isArray = false;
}
switch (typeChar) {
case 'Z':
sigClasses.add(isArray ? primArrayClass : boolean.class);
break;
case 'I':
sigClasses.add(isArray ? primArrayClass : int.class);
break;
case 'J':
sigClasses.add(isArray ? primArrayClass : long.class);
break;
case 'F':
sigClasses.add(isArray ? primArrayClass : float.class);
break;
case 'D':
sigClasses.add(isArray ? primArrayClass : double.class);
break;
case 'B':
sigClasses.add(isArray ? primArrayClass : byte.class);
break;
case 'S':
sigClasses.add(isArray ? primArrayClass : short.class);
break;
case 'C':
sigClasses.add(isArray ? primArrayClass : char.class);
break;
case 'L':
sb = new StringBuilder();
symbol = str.read();
while (symbol != ';') {
sb.append((char) symbol);
symbol = str.read();
}
klassName = sb.toString().replaceAll("/", "\\.");
if (isArray) {
klassName = arrayDim.toString() + "L" + klassName + ";";
}
Class<?> klass = Class.forName(klassName);
sigClasses.add(klass);
break;
default:
throw new Error("Unknown type " + typeChar);
}
symbol = str.read();
}
} catch (IOException | ClassNotFoundException ex) {
throw new Error("Unexpected exception while parsing exclude methods file", ex);
}
return sigClasses;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,9 +23,7 @@
package jdk.test.lib.jittester;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -35,22 +33,31 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.test.lib.Asserts;
import jdk.test.lib.jittester.functions.FunctionInfo;
import jdk.test.lib.jittester.types.TypeArray;
import jdk.test.lib.jittester.types.TypeKlass;
import static java.util.function.Predicate.not;
/**
* Class used for parsing included classes file and excluded methods file
*/
public class TypesParser {
private List<MethodTemplate> methodsToExclude;
private static final HashMap<Class<?>, Type> TYPE_CACHE = new HashMap<>();
private static String trimComment(String source) {
int commentStart = source.indexOf('#');
return commentStart == -1 ? source : source.substring(0, commentStart);
}
/**
* Parses included classes file and excluded methods file to TypeList and SymbolTable.
* This routine takes all classes named in the classes file and puts them to the TypeList,
@ -62,27 +69,21 @@ public class TypesParser {
public static void parseTypesAndMethods(String klassesFileName, String exMethodsFileName) {
Asserts.assertNotNull(klassesFileName, "Classes input file name is null");
Asserts.assertFalse(klassesFileName.isEmpty(), "Classes input file name is empty");
List<Class<?>> klasses = parseKlasses(klassesFileName);
Set<Executable> methodsToExclude;
if (exMethodsFileName != null && !exMethodsFileName.isEmpty()) {
methodsToExclude = parseMethods(exMethodsFileName);
} else {
methodsToExclude = new HashSet<>();
}
klasses.stream().forEach(klass -> {
TypeKlass typeKlass = (TypeKlass) getType(klass);
if (TypeList.isReferenceType(typeKlass)) {
return;
}
TypeList.add(typeKlass);
Set<Executable> methods = new HashSet<>();
methods.addAll(Arrays.asList(klass.getMethods()));
methods.addAll(Arrays.asList(klass.getConstructors()));
methods.removeAll(methodsToExclude);
methods.stream().forEach(method -> {
if (method.isSynthetic()) {
return;
}
TypesParser theParser = new TypesParser();
theParser.initMethodsToExclude(exMethodsFileName);
parseKlasses(klassesFileName)
.stream()
.filter(klass -> !TypeList.isReferenceType(getTypeKlass(klass)))
.forEach(theParser::processKlass);
}
private void processKlass(Class<?> klass) {
TypeKlass typeKlass = getTypeKlass(klass);
TypeList.add(typeKlass);
Stream.concat(Arrays.stream(klass.getMethods()), Arrays.stream(klass.getConstructors()))
.filter(not(Executable::isSynthetic))
.filter(method -> MethodTemplate.noneMatches(methodsToExclude, method))
.forEach(method -> {
String name = method.getName();
boolean isConstructor = false;
Type returnType;
@ -106,10 +107,8 @@ public class TypesParser {
paramList.add(new VariableInfo("arg" + argNum, typeKlass, paramType,
VariableInfo.LOCAL | VariableInfo.INITIALIZED));
}
typeKlass.addSymbol(new FunctionInfo(name, typeKlass, returnType, 1, flags,
paramList));
typeKlass.addSymbol(new FunctionInfo(name, typeKlass, returnType, 1, flags, paramList));
});
});
}
private static Type getType(Class<?> klass) {
@ -155,6 +154,10 @@ public class TypesParser {
return type;
}
private static TypeKlass getTypeKlass(Class<?> klass) {
return (TypeKlass) getType(klass);
}
private static int getArrayClassDimension(Class<?> klass) {
if (!klass.isArray()) {
return 0;
@ -234,133 +237,24 @@ public class TypesParser {
return klassesList;
}
private static Set<Executable> parseMethods(String methodsFileName) {
Asserts.assertNotNull(methodsFileName, "Methods exclude input file name is null");
Asserts.assertFalse(methodsFileName.isEmpty(), "Methods exclude input file name is empty");
LinkedList<String> methodNamesList = new LinkedList<>();
Path klassesFilePath = Paths.get(methodsFileName);
try {
Files.lines(klassesFilePath).forEach(line -> {
line = line.trim();
if (line.isEmpty()) {
return;
}
String msg = String.format("Format of the methods exclude input file \"%s\" is incorrect,"
+ " line \"%s\" has wrong format", methodsFileName, line);
Asserts.assertTrue(line.matches("\\w[\\w/$]*::[\\w$]+\\((\\[?[ZBSCIJFD]|\\[?L[\\w/$]+;)*\\)"), msg);
methodNamesList.add(line.substring(0, line.length() - 1));
});
} catch (IOException ex) {
throw new Error("Error reading exclude method file", ex);
}
Set<Executable> methodsList = new HashSet<>();
methodNamesList.forEach(methodName -> {
String[] klassAndNameAndSig = methodName.split("::");
String klassName = klassAndNameAndSig[0].replaceAll("/", "\\.");
String[] nameAndSig = klassAndNameAndSig[1].split("[\\(\\)]");
String name = nameAndSig[0];
String signature = "";
if (nameAndSig.length > 1) {
signature = nameAndSig[1];
}
Class<?> klass = null;
List<Class<?>> signatureTypes = null;
private void initMethodsToExclude(String methodsFileName) {
if (methodsFileName != null && !methodsFileName.isEmpty()) {
Path methodsFilePath = Paths.get(methodsFileName);
try {
klass = Class.forName(klassName);
signatureTypes = parseSignature(signature);
} catch (ClassNotFoundException ex) {
throw new Error("Unexpected exception while parsing exclude methods file", ex);
}
try {
Executable method;
if (name.equals(klass.getSimpleName())) {
method = klass.getConstructor(signatureTypes.toArray(new Class<?>[0]));
} else {
method = klass.getMethod(name, signatureTypes.toArray(new Class<?>[0]));
}
methodsList.add(method);
} catch (NoSuchMethodException | SecurityException ex) {
throw new Error("Unexpected exception while parsing exclude methods file", ex);
}
});
return methodsList;
}
methodsToExclude = Files.lines(methodsFilePath)
// Cleaning nonimportant parts
.map(TypesParser::trimComment)
.map(String::trim)
.filter(not(String::isEmpty))
private static List<Class<?>> parseSignature(String signature) throws ClassNotFoundException {
LinkedList<Class<?>> sigClasses = new LinkedList<>();
char typeChar;
boolean isArray;
String klassName;
StringBuilder sb;
StringBuilder arrayDim;
try (StringReader str = new StringReader(signature)) {
int symbol = str.read();
while (symbol != -1){
typeChar = (char) symbol;
arrayDim = new StringBuilder();
Class<?> primArrayClass = null;
if (typeChar == '[') {
isArray = true;
arrayDim.append('[');
symbol = str.read();
while (symbol == '['){
arrayDim.append('[');
symbol = str.read();
}
typeChar = (char) symbol;
if (typeChar != 'L') {
primArrayClass = Class.forName(arrayDim.toString() + typeChar);
}
} else {
isArray = false;
}
switch (typeChar) {
case 'Z':
sigClasses.add(isArray ? primArrayClass : boolean.class);
break;
case 'I':
sigClasses.add(isArray ? primArrayClass : int.class);
break;
case 'J':
sigClasses.add(isArray ? primArrayClass : long.class);
break;
case 'F':
sigClasses.add(isArray ? primArrayClass : float.class);
break;
case 'D':
sigClasses.add(isArray ? primArrayClass : double.class);
break;
case 'B':
sigClasses.add(isArray ? primArrayClass : byte.class);
break;
case 'S':
sigClasses.add(isArray ? primArrayClass : short.class);
break;
case 'C':
sigClasses.add(isArray ? primArrayClass : char.class);
break;
case 'L':
sb = new StringBuilder();
symbol = str.read();
while (symbol != ';') {
sb.append((char) symbol);
symbol = str.read();
}
klassName = sb.toString().replaceAll("/", "\\.");
if (isArray) {
klassName = arrayDim.toString() + "L" + klassName + ";";
}
Class<?> klass = Class.forName(klassName);
sigClasses.add(klass);
break;
default:
throw new Error("Unknown type " + typeChar);
}
symbol = str.read();
// Actual parsing
.map(MethodTemplate::parse)
.collect(Collectors.toList());
} catch (IOException ex) {
throw new Error("Error reading exclude method file", ex);
}
} catch (IOException ex) {
throw new Error("Unexpected exception while parsing exclude methods file", ex);
} else {
methodsToExclude = new ArrayList<>();
}
return sigClasses;
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.jittester;
import java.lang.reflect.Executable;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
/*
* @test
* @summary Unit tests for JITTester string method templates
*
* @library /test/lib
* /test/hotspot/jtreg/testlibrary/jittester/src
*
* @run testng jdk.test.lib.jittester.MethodTemplateTest
*/
public class MethodTemplateTest {
@Test
public void testMatchingPatterns() throws NoSuchMethodException {
Tester.forMethod(System.class, "getenv", String.class)
.assertMatches("java/lang/System::getenv(Ljava/lang/String;)")
.assertMatches("*::getenv(Ljava/lang/String;)")
.assertMatches("java/lang/*::getenv(Ljava/lang/String;)")
.assertMatches("java/lang/System::*env*(Ljava/lang/String;)")
.assertMatches("java/lang/System::getenv")
.assertMatches("java/lang/System::getenv(*)");
Tester.forCtor(RuntimeException.class, Throwable.class)
.assertMatches("java/lang/RuntimeException::RuntimeException(Ljava/lang/Throwable;)");
Tester.forMethod(String.class, "regionMatches", int.class, String.class, int.class, int.class)
.assertMatches("java/lang/String::regionMatches(ILjava/lang/String;II)");
}
@Test
public void testNonMatchingPatterns() throws NoSuchMethodException {
Tester.forMethod(String.class, "regionMatches", int.class, String.class, int.class, int.class)
.assertDoesNotMatch("java/lang/String::regionMatches(IIILjava/lang/String;)");
Tester.forMethod(String.class, "endsWith", String.class)
.assertDoesNotMatch("java/lang/String::startsWith(Ljava/lang/String;)");
}
@Test
public void testWildcardStrings() {
assertTrue(new MethodTemplate.WildcardString("Torment")
.matches("Torment"));
assertTrue(new MethodTemplate.WildcardString("Torm*")
.matches("Torment"));
assertTrue(new MethodTemplate.WildcardString("*ent")
.matches("Torment"));
assertTrue(new MethodTemplate.WildcardString("*")
.matches("Something"));
assertTrue(new MethodTemplate.WildcardString("**")
.matches("Something"));
assertTrue(new MethodTemplate.WildcardString("*Middle*")
.matches("OnlyMiddleMatches"));
assertFalse(new MethodTemplate.WildcardString("Wrong")
.matches("Correct"));
assertFalse(new MethodTemplate.WildcardString("Joy")
.matches("Joyfull"));
assertFalse(new MethodTemplate.WildcardString("*Torm*")
.matches("Sorrow"));
}
static final class Tester {
private final Executable executable;
private Tester(Executable executable) {
this.executable = executable;
}
public Tester assertMatches(String stringTemplate) {
MethodTemplate template = MethodTemplate.parse(stringTemplate);
assertTrue(template.matches(executable),
"Method '" + executable + "' does not match template '" + stringTemplate + "'");
return this;
}
public Tester assertDoesNotMatch(String stringTemplate) {
MethodTemplate template = MethodTemplate.parse(stringTemplate);
assertFalse(template.matches(executable),
"Method '" + executable + "' erroneously matches template '" + stringTemplate + "'");
return this;
}
public static Tester forMethod(Class klass, String name, Class<?>... arguments)
throws NoSuchMethodException {
return new Tester(klass.getDeclaredMethod(name, arguments));
}
public static Tester forCtor(Class klass, Class<?>... arguments)
throws NoSuchMethodException {
return new Tester(klass.getConstructor(arguments));
}
}
}