From 01f01b6f7b8a2f0dbe940bffd567ff2b46732787 Mon Sep 17 00:00:00 2001 From: Igor Veresov Date: Tue, 3 Jun 2025 15:31:07 +0000 Subject: [PATCH 001/143] 8358283: Inconsistent failure mode for MetaspaceObj::operator new(size_t, MemTag) Reviewed-by: kvn, kbarrett --- src/hotspot/share/memory/allocation.cpp | 2 +- src/hotspot/share/memory/allocation.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/memory/allocation.cpp b/src/hotspot/share/memory/allocation.cpp index b01ab4014ed..0d99c1bea68 100644 --- a/src/hotspot/share/memory/allocation.cpp +++ b/src/hotspot/share/memory/allocation.cpp @@ -86,7 +86,7 @@ void* MetaspaceObj::operator new(size_t size, ClassLoaderData* loader_data, } // This is used for allocating training data. We are allocating training data in many cases where a GC cannot be triggered. -void* MetaspaceObj::operator new(size_t size, MemTag flags) throw() { +void* MetaspaceObj::operator new(size_t size, MemTag flags) { void* p = AllocateHeap(size, flags, CALLER_PC); memset(p, 0, size); return p; diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index 8cd6e6d4207..9e500135d0b 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -358,7 +358,7 @@ class MetaspaceObj { size_t word_size, Type type) throw(); // This is used for allocating training data. We are allocating training data in many cases where a GC cannot be triggered. - void* operator new(size_t size, MemTag flags) throw(); + void* operator new(size_t size, MemTag flags); void operator delete(void* p) = delete; // Declare a *static* method with the same signature in any subclass of MetaspaceObj From b6f827ef054959662190e21ce63fc3d3c45b92f3 Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Tue, 3 Jun 2025 15:36:29 +0000 Subject: [PATCH 002/143] 8348986: Improve coverage of enhanced exception messages Reviewed-by: dfuchs --- .../share/classes/java/net/HostPortrange.java | 42 ++- .../classes/java/net/Inet4AddressImpl.java | 6 +- .../share/classes/java/net/Inet6Address.java | 8 +- .../share/classes/java/net/InetAddress.java | 56 +-- .../classes/java/net/NetworkInterface.java | 6 +- .../share/classes/java/net/Proxy.java | 11 +- .../classes/java/net/SocketPermission.java | 14 +- .../classes/java/net/SocksSocketImpl.java | 8 +- src/java.base/share/classes/java/net/URI.java | 14 +- src/java.base/share/classes/java/net/URL.java | 7 +- .../classes/java/net/URLStreamHandler.java | 13 +- .../classes/jdk/internal/util/Exceptions.java | 322 ++++++++++++++++++ src/java.base/share/classes/module-info.java | 4 + .../classes/sun/net/util/IPAddressUtil.java | 8 +- .../sun/net/util/SocketExceptions.java | 96 ------ .../share/classes/sun/net/www/ParseUtil.java | 4 +- .../net/www/protocol/https/HttpsClient.java | 9 +- .../sun/net/www/protocol/jar/Handler.java | 24 +- .../net/www/protocol/jar/JarFileFactory.java | 6 +- .../www/protocol/jar/JarURLConnection.java | 17 +- .../sun/net/www/protocol/jmod/Handler.java | 2 +- .../sun/nio/ch/DatagramSocketAdaptor.java | 7 +- .../classes/sun/nio/ch/NioSocketImpl.java | 9 +- .../classes/sun/nio/ch/SocketAdaptor.java | 6 +- .../classes/sun/nio/ch/SocketChannelImpl.java | 8 +- .../share/conf/security/java.security | 30 +- src/java.base/share/native/libnet/net_util.c | 29 +- src/java.base/share/native/libnet/net_util.h | 4 +- .../ch/UnixAsynchronousSocketChannelImpl.java | 9 +- .../sun/nio/fs/UnixUserPrincipals.java | 12 +- .../unix/native/libnet/net_util_md.c | 31 +- .../WindowsAsynchronousSocketChannelImpl.java | 12 +- .../sun/nio/fs/WindowsSecurityDescriptor.java | 8 +- .../sun/nio/fs/WindowsUserPrincipals.java | 12 +- .../windows/native/libnet/Inet4AddressImpl.c | 7 +- .../windows/native/libnet/Inet6AddressImpl.c | 4 +- .../classes/com/sun/jndi/ldap/LdapURL.java | 5 +- .../classes/com/sun/jndi/toolkit/url/Uri.java | 34 +- .../net/http/HttpRequestBuilderImpl.java | 7 +- .../net/http/ResponseBodyHandlers.java | 10 +- .../net/http/websocket/OpeningHandshake.java | 12 +- .../share/classes/java/rmi/Naming.java | 29 +- .../httpserver/simpleserver/JWebServer.java | 11 + .../simpleserver/CommandLineNegativeTest.java | 6 +- test/jdk/java/net/URI/Test.java | 1 + .../TestJDKIncludeInExceptions.java | 6 +- test/jdk/sun/net/util/ExceptionsTest.java | 172 ++++++++++ 47 files changed, 883 insertions(+), 275 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/util/Exceptions.java delete mode 100644 src/java.base/share/classes/sun/net/util/SocketExceptions.java create mode 100644 test/jdk/sun/net/util/ExceptionsTest.java diff --git a/src/java.base/share/classes/java/net/HostPortrange.java b/src/java.base/share/classes/java/net/HostPortrange.java index fd516b8d2d7..289846841c9 100644 --- a/src/java.base/share/classes/java/net/HostPortrange.java +++ b/src/java.base/share/classes/java/net/HostPortrange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -29,6 +29,9 @@ import java.util.Formatter; import java.util.Locale; import sun.net.util.IPAddressUtil; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; + /** * Parses a string containing a host/domain name and port range */ @@ -56,7 +59,7 @@ class HostPortrange { return hostname.hashCode() + portrange[0] + portrange[1]; } - HostPortrange(String scheme, String str) { + HostPortrange(String scheme, String host) { // Parse the host name. A name has up to three components, the // hostname, a port number, or two numbers representing a port // range. "www.example.com:8080-9090" is a valid host name. @@ -67,21 +70,23 @@ class HostPortrange { // Refer to RFC 2732 for more information. // first separate string into two fields: hoststr, portstr - String hoststr, portstr = null; + String hoststr = null, portstr = null; this.scheme = scheme; // check for IPv6 address - if (str.charAt(0) == '[') { + if (host.charAt(0) == '[') { ipv6 = literal = true; - int rb = str.indexOf(']'); + int rb = host.indexOf(']'); if (rb != -1) { - hoststr = str.substring(1, rb); + hoststr = host.substring(1, rb); } else { - throw new IllegalArgumentException("invalid IPv6 address: " + str); + throw new IllegalArgumentException( + formatMsg("invalid IPv6 address%s", + filterNonSocketInfo(host).prefixWith(": "))); } - int sep = str.indexOf(':', rb + 1); - if (sep != -1 && str.length() > sep) { - portstr = str.substring(sep + 1); + int sep = host.indexOf(':', rb + 1); + if (sep != -1 && host.length() > sep) { + portstr = host.substring(sep + 1); } // need to normalize hoststr now byte[] ip = IPAddressUtil.textToNumericFormatV6(hoststr); @@ -94,16 +99,16 @@ class HostPortrange { + "%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]); - hostname = sb.toString(); + this.hostname = sb.toString(); } else { // not IPv6 therefore ':' is the port separator - int sep = str.indexOf(':'); - if (sep != -1 && str.length() > sep) { - hoststr = str.substring(0, sep); - portstr = str.substring(sep + 1); + int sep = host.indexOf(':'); + if (sep != -1 && host.length() > sep) { + hoststr = host.substring(0, sep); + portstr = host.substring(sep + 1); } else { - hoststr = sep == -1 ? str : str.substring(0, sep); + hoststr = sep == -1 ? host : host.substring(0, sep); } // is this a domain wildcard specification? if (hoststr.lastIndexOf('*') > 0) { @@ -150,13 +155,14 @@ class HostPortrange { } } } - hostname = hoststr; + this.hostname = hoststr; } try { portrange = parsePort(portstr); } catch (Exception e) { - throw new IllegalArgumentException("invalid port range: " + portstr); + throw new IllegalArgumentException( + formatMsg("invalid port range%s", filterNonSocketInfo(portstr).prefixWith(": "))); } } diff --git a/src/java.base/share/classes/java/net/Inet4AddressImpl.java b/src/java.base/share/classes/java/net/Inet4AddressImpl.java index c7feaf4c195..3d8ea56d99f 100644 --- a/src/java.base/share/classes/java/net/Inet4AddressImpl.java +++ b/src/java.base/share/classes/java/net/Inet4AddressImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -27,6 +27,8 @@ import java.io.IOException; import java.net.spi.InetAddressResolver.LookupPolicy; import static java.net.spi.InetAddressResolver.LookupPolicy.IPV4; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /* * Package private implementation of InetAddressImpl for IPv4. @@ -38,7 +40,7 @@ final class Inet4AddressImpl implements InetAddressImpl { public InetAddress[] lookupAllHostAddr(String hostname, LookupPolicy lookupPolicy) throws UnknownHostException { if ((lookupPolicy.characteristics() & IPV4) == 0) { - throw new UnknownHostException(hostname); + throw new UnknownHostException(formatMsg("%s", filterNonSocketInfo(hostname))); } return lookupAllHostAddr(hostname); } diff --git a/src/java.base/share/classes/java/net/Inet6Address.java b/src/java.base/share/classes/java/net/Inet6Address.java index 06a74ca3adc..f0f386a9d29 100644 --- a/src/java.base/share/classes/java/net/Inet6Address.java +++ b/src/java.base/share/classes/java/net/Inet6Address.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -35,6 +35,8 @@ import java.io.ObjectStreamField; import java.util.Enumeration; import java.util.Arrays; import java.util.Objects; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class represents an Internet Protocol version 6 (IPv6) address. @@ -581,7 +583,9 @@ class Inet6Address extends InetAddress { if (addrBytes.length == Inet4Address.INADDRSZ) { if (numericZone != -1 || ifname != null) { // IPv4-mapped address must not contain zone-id - throw new UnknownHostException(addressLiteral + ": invalid IPv4-mapped address"); + throw new UnknownHostException( + formatMsg("%sinvalid IPv4-mapped address", + filterNonSocketInfo(addressLiteral).suffixWith(": "))); } return new Inet4Address(null, addrBytes); } diff --git a/src/java.base/share/classes/java/net/InetAddress.java b/src/java.base/share/classes/java/net/InetAddress.java index 4bceebdcd0e..4d8a00249a9 100644 --- a/src/java.base/share/classes/java/net/InetAddress.java +++ b/src/java.base/share/classes/java/net/InetAddress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -54,6 +54,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; +import jdk.internal.util.Exceptions; import jdk.internal.access.JavaNetInetAddressAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Blocker; @@ -68,6 +69,8 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV4; import static java.net.spi.InetAddressResolver.LookupPolicy.IPV4_FIRST; import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6; import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class represents an Internet Protocol (IP) address. @@ -382,6 +385,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In } } ); + Exceptions.setup(); // needed for native exceptions init(); } @@ -902,7 +906,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In @Override public InetAddress[] get() throws UnknownHostException { if (inetAddresses == null) { - throw new UnknownHostException(host); + throw new UnknownHostException(formatMsg("%s", filterNonSocketInfo(host))); } return inetAddresses; } @@ -1095,7 +1099,9 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In } } if (inetAddresses == null || inetAddresses.length == 0) { - throw ex == null ? new UnknownHostException(host) : ex; + throw ex == null + ? new UnknownHostException(formatMsg("%s", filterNonSocketInfo(host))) + : ex; } return inetAddresses; } @@ -1203,16 +1209,19 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In } } } catch (IOException e) { - throw new UnknownHostException("Unable to resolve address " - + Arrays.toString(addr) + " as hosts file " + hostsFile - + " not found "); + throw new UnknownHostException( + formatMsg("Unable to resolve address %s as hosts file %s not found", + filterNonSocketInfo(Arrays.toString(addr)), + filterNonSocketInfo(hostsFile) + .replaceWith("from ${jdk.net.hosts.file} system property"))); } if ((host == null) || (host.isEmpty()) || (host.equals(" "))) { - throw new UnknownHostException("Requested address " - + Arrays.toString(addr) - + " resolves to an invalid entry in hosts file " - + hostsFile); + throw new UnknownHostException( + formatMsg("Requested address %s resolves to an invalid entry in hosts file %s", + filterNonSocketInfo(Arrays.toString(addr)), + filterNonSocketInfo(hostsFile) + .replaceWith("from ${jdk.net.hosts.file} system property"))); } return host; } @@ -1273,8 +1282,11 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In } } } catch (IOException e) { - throw new UnknownHostException("Unable to resolve host " + host - + " as hosts file " + hostsFile + " not found "); + throw new UnknownHostException( + formatMsg("Unable to resolve host %s as hosts file %s not found", + filterNonSocketInfo(host), filterNonSocketInfo(hostsFile) + .replaceWith("from ${jdk.net.hosts.file} system property"))); + } // Check if only IPv4 addresses are requested if (needIPv4 && !needIPv6) { @@ -1305,8 +1317,10 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In private void checkResultsList(List addressesList, String hostName) throws UnknownHostException { if (addressesList.isEmpty()) { - throw new UnknownHostException("Unable to resolve host " + hostName - + " in hosts file " + hostsFile); + throw new UnknownHostException( + formatMsg("Unable to resolve host %s in hosts file %s", + filterNonSocketInfo(hostName), filterNonSocketInfo(hostsFile) + .replaceWith("from ${jdk.net.hosts.file} system property"))); } } @@ -1543,7 +1557,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In // Here we check the address string for ambiguity only inetAddress = Inet4Address.parseAddressString(host, false); } catch (IllegalArgumentException iae) { - var uhe = new UnknownHostException(host); + var uhe = new UnknownHostException(formatMsg("%s", filterNonSocketInfo(host))); uhe.initCause(iae); throw uhe; } @@ -1570,7 +1584,8 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In private static UnknownHostException invalidIPv6LiteralException(String host, boolean wrapInBrackets) { String hostString = wrapInBrackets ? "[" + host + "]" : host; - return new UnknownHostException(hostString + ": invalid IPv6 address literal"); + return new UnknownHostException(formatMsg("%sinvalid IPv6 address literal", + filterNonSocketInfo(hostString).suffixWith(": "))); } /** @@ -1708,7 +1723,8 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In InetAddress[] result = addresses == null ? null : addresses.toArray(InetAddress[]::new); if (result == null || result.length == 0) { - throw ex == null ? new UnknownHostException(host) : ex; + throw ex == null ? new UnknownHostException(formatMsg("%s", filterNonSocketInfo(host))) + : ex; } return result; } @@ -1780,9 +1796,9 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In localAddr = getAllByName0(local, false)[0]; } catch (UnknownHostException uhe) { // Rethrow with a more informative error message. - UnknownHostException uhe2 = - new UnknownHostException(local + ": " + - uhe.getMessage()); + UnknownHostException uhe2 = + new UnknownHostException(formatMsg(filterNonSocketInfo(local) + .suffixWith(": ") + uhe.getMessage())); uhe2.initCause(uhe); throw uhe2; } diff --git a/src/java.base/share/classes/java/net/NetworkInterface.java b/src/java.base/share/classes/java/net/NetworkInterface.java index 8e0f7137315..f6f7d438864 100644 --- a/src/java.base/share/classes/java/net/NetworkInterface.java +++ b/src/java.base/share/classes/java/net/NetworkInterface.java @@ -34,6 +34,8 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class represents a Network Interface. @@ -323,7 +325,9 @@ public final class NetworkInterface { + addr.holder.family); } } else { - throw new IllegalArgumentException("invalid address type: " + addr); + throw new IllegalArgumentException( + formatMsg("invalid address type%s", + filterNonSocketInfo(addr.toString()).prefixWith(": "))); } return getByInetAddress0(addr); } diff --git a/src/java.base/share/classes/java/net/Proxy.java b/src/java.base/share/classes/java/net/Proxy.java index db434238558..4109c586058 100644 --- a/src/java.base/share/classes/java/net/Proxy.java +++ b/src/java.base/share/classes/java/net/Proxy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -26,6 +26,8 @@ package java.net; import java.util.Objects; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class represents a proxy setting, typically a type (http, socks) and @@ -93,8 +95,11 @@ public class Proxy { * incompatible */ public Proxy(Type type, SocketAddress sa) { - if ((type == Type.DIRECT) || !(sa instanceof InetSocketAddress)) - throw new IllegalArgumentException("type " + type + " is not compatible with address " + sa); + if ((type == Type.DIRECT) || !(sa instanceof InetSocketAddress)) { + throw new IllegalArgumentException( + formatMsg("type " + type + " is not compatible with address %s", + filterNonSocketInfo(String.valueOf(sa)))); + } this.type = type; this.sa = sa; } diff --git a/src/java.base/share/classes/java/net/SocketPermission.java b/src/java.base/share/classes/java/net/SocketPermission.java index e3a85bbf856..eb14099f686 100644 --- a/src/java.base/share/classes/java/net/SocketPermission.java +++ b/src/java.base/share/classes/java/net/SocketPermission.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -46,6 +46,8 @@ import sun.security.util.RegisteredDomain; import sun.security.util.SecurityConstants; import sun.security.util.Debug; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class represents access to a network via sockets. @@ -392,8 +394,8 @@ public final class SocketPermission extends Permission if (rb != -1) { host = host.substring(start, rb); } else { - throw new - IllegalArgumentException("invalid host/port: "+host); + throw new IllegalArgumentException( + formatMsg("invalid host/port%s", filterNonSocketInfo(host).prefixWith(": "))); } sep = hostport.indexOf(':', rb+1); } else { @@ -410,8 +412,8 @@ public final class SocketPermission extends Permission try { portrange = parsePort(port); } catch (Exception e) { - throw new - IllegalArgumentException("invalid port range: "+port); + throw new IllegalArgumentException( + formatMsg("invalid port range%s", filterNonSocketInfo(port).prefixWith(": "))); } } else { portrange = new int[] { PORT_MIN, PORT_MAX }; @@ -784,7 +786,7 @@ public final class SocketPermission extends Permission throw uhe; } catch (IndexOutOfBoundsException iobe) { invalid = true; - throw new UnknownHostException(getName()); + throw new UnknownHostException(formatMsg("%s", filterNonSocketInfo(getName()))); } } diff --git a/src/java.base/share/classes/java/net/SocksSocketImpl.java b/src/java.base/share/classes/java/net/SocksSocketImpl.java index 54ff612be02..71794d7a0a6 100644 --- a/src/java.base/share/classes/java/net/SocksSocketImpl.java +++ b/src/java.base/share/classes/java/net/SocksSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -37,6 +37,8 @@ import sun.net.spi.DefaultProxySelector; import sun.net.www.ParseUtil; import static sun.net.util.IPAddressUtil.isIPv6LiteralAddress; +import static jdk.internal.util.Exceptions.filterSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * SOCKS (V4 & V5) TCP socket implementation (RFC 1928). @@ -330,7 +332,7 @@ class SocksSocketImpl extends DelegatingSocketImpl implements SocksConsts { // SOCKS Protocol version 4 doesn't know how to deal with // DOMAIN type of addresses (unresolved addresses here) if (epoint.isUnresolved()) - throw new UnknownHostException(epoint.toString()); + throw new UnknownHostException(formatMsg("%s", filterSocketInfo(epoint.toString()))); connectV4(in, out, epoint, deadlineMillis); return; } @@ -349,7 +351,7 @@ class SocksSocketImpl extends DelegatingSocketImpl implements SocksConsts { // SOCKS Protocol version 4 doesn't know how to deal with // DOMAIN type of addresses (unresolved addresses here) if (epoint.isUnresolved()) - throw new UnknownHostException(epoint.toString()); + throw new UnknownHostException(formatMsg("%s", filterSocketInfo(epoint.toString()))); connectV4(in, out, epoint, deadlineMillis); return; } diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index cd116f3877b..daf63d19032 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -42,8 +42,12 @@ import java.nio.file.Path; import java.text.Normalizer; import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.util.Exceptions; import sun.nio.cs.UTF_8; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; + /** * Represents a Uniform Resource Identifier (URI) reference. * @@ -2032,7 +2036,8 @@ public final class URI { if (scheme != null) { if (path != null && !path.isEmpty() && path.charAt(0) != '/') - throw new URISyntaxException(s, "Relative path in absolute URI"); + throw new URISyntaxException(formatMsg("%s", filterNonSocketInfo(s)), + "Relative path in absolute URI"); } } @@ -2988,11 +2993,14 @@ public final class URI // -- Methods for throwing URISyntaxException in various ways -- private void fail(String reason) throws URISyntaxException { - throw new URISyntaxException(input, reason); + throw new URISyntaxException(formatMsg("%s", filterNonSocketInfo(input)), reason); } private void fail(String reason, int p) throws URISyntaxException { - throw new URISyntaxException(input, reason, p); + if (!Exceptions.enhancedNonSocketExceptions()) { + p = -1; + } + throw new URISyntaxException(formatMsg("%s", filterNonSocketInfo(input)), reason, p); } private void failExpecting(String expected, int p) diff --git a/src/java.base/share/classes/java/net/URL.java b/src/java.base/share/classes/java/net/URL.java index be4daa28f33..9266b6c94f1 100644 --- a/src/java.base/share/classes/java/net/URL.java +++ b/src/java.base/share/classes/java/net/URL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -44,6 +44,8 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.misc.ThreadTracker; import jdk.internal.misc.VM; import sun.net.util.IPAddressUtil; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * Class {@code URL} represents a Uniform Resource @@ -1168,7 +1170,8 @@ public final class URL implements java.io.Serializable { URI uri = new URI(toString()); if (authority != null && isBuiltinStreamHandler(handler)) { String s = IPAddressUtil.checkAuthority(this); - if (s != null) throw new URISyntaxException(authority, s); + if (s != null) + throw new URISyntaxException(formatMsg("%s", filterNonSocketInfo(authority)), s); } return uri; } diff --git a/src/java.base/share/classes/java/net/URLStreamHandler.java b/src/java.base/share/classes/java/net/URLStreamHandler.java index 1400741b2bb..f66902a451e 100644 --- a/src/java.base/share/classes/java/net/URLStreamHandler.java +++ b/src/java.base/share/classes/java/net/URLStreamHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -30,6 +30,9 @@ import java.util.Locale; import java.util.Objects; import sun.net.util.IPAddressUtil; +import static jdk.internal.util.Exceptions.formatMsg; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; + /** * The abstract class {@code URLStreamHandler} is the common * superclass for all stream protocol handlers. A stream protocol @@ -206,7 +209,7 @@ public abstract class URLStreamHandler { if (!IPAddressUtil. isIPv6LiteralAddress(host.substring(1, ind))) { throw new IllegalArgumentException( - "Invalid host: "+ host); + formatMsg("Invalid host%s", filterNonSocketInfo(host).prefixWith(": "))); } port = -1 ; @@ -220,12 +223,14 @@ public abstract class URLStreamHandler { } } else { throw new IllegalArgumentException( - "Invalid authority field: " + authority); + formatMsg("Invalid authority field%s", + filterNonSocketInfo(authority).prefixWith(": "))); } } } else { throw new IllegalArgumentException( - "Invalid authority field: " + authority); + formatMsg("Invalid authority field%s", + filterNonSocketInfo(authority).prefixWith(": "))); } } else { ind = host.indexOf(':'); diff --git a/src/java.base/share/classes/jdk/internal/util/Exceptions.java b/src/java.base/share/classes/jdk/internal/util/Exceptions.java new file mode 100644 index 00000000000..eb4286cd1af --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/Exceptions.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2018, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.internal.util; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnixDomainSocketAddress; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import sun.security.util.SecurityProperties; +import jdk.internal.misc.VM; + +/** + * Contains static utility methods which can filter exception + * message strings for sensitive information. + * + * Code using this mechanism should use formatMsg() + * to generate a formatted (enhanced or restricted) string for exception + * messages. + * + * The methods above take variable numbers of SensitiveInfo objects + * as parameters which contain the text that may have to be filtered. + * + * The SensitiveInfo objects should be generated with one of the following: + * public static SensitiveInfo filterSocketInfo(String s) + * public static SensitiveInfo filterNonSocketInfo(String s) + * public static SensitiveInfo filterJarName(String name) + * public static SensitiveInfo filterUserName(String name) + */ +public final class Exceptions { + private Exceptions() {} + + private static volatile boolean enhancedSocketExceptionText; + private static volatile boolean enhancedNonSocketExceptionText; + private static volatile boolean enhancedUserExceptionText; + private static volatile boolean enhancedJarExceptionText; + private static volatile boolean initialized = false; + + /** + * Base class for generating exception messages that may + * contain sensitive information which in certain contexts + * needs to be filtered out, in case it gets revealed in + * unexpected places. Exception messages are either enhanced + * or restricted. Enhanced messages include sensitive information. + * Restricted messages don't. + * + * Sub-class for any new category that needs to be independently + * controlled. Consider using a unique value for the + * SecurityProperties.includedInExceptions(String value) mechanism + * Current values defined are "jar", "userInfo" + * "hostInfo", "hostInfoExclSocket". + * + * New code can also piggy back on existing categories + * + * A SensitiveInfo contains the following components + * all of which default to empty strings. + * + * prefix, the sensitive info itself, a suffix + * and a replacement string. + * + * The composeFilteredText(boolean enhance) method generates + * an enhanced string when enhance is true. + * This comprises (enhance == true) + * prefix + info + suffix + * When (enhance == false), then by default the output is: + * "" empty string + * However, if a replacement is set, then when enhance == false + * the output is the replacement string. + */ + public abstract static class SensitiveInfo { + String info, suffix, prefix, replacement; + boolean enhanced; + + SensitiveInfo(String info) { + this.info = info; + prefix = suffix = replacement = ""; + } + public SensitiveInfo prefixWith(String prefix) { + this.prefix = prefix; + return this; + } + public SensitiveInfo suffixWith(String suffix) { + this.suffix = suffix; + return this; + } + public SensitiveInfo replaceWith(String replacement) { + this.replacement = replacement; + return this; + } + + public boolean enhanced() { + return enhanced; + } + + /** + * Implementation should call composeFilteredText(boolean flag) + * where flag contains the boolean value of whether + * the category is enabled or not. + */ + public abstract String output(); + + protected String composeFilteredText(boolean enhance) { + if (enhance) { + this.enhanced = true; + return prefix + info + suffix; + } else { + return replacement; + } + } + } + + static final class SocketInfo extends SensitiveInfo { + public SocketInfo(String host) { + super(host); + } + @Override + public String output() { + setup(); + return super.composeFilteredText(enhancedSocketExceptionText); + } + } + + static final class NonSocketInfo extends SensitiveInfo { + public NonSocketInfo(String host) { + super(host); + } + @Override + public String output() { + setup(); + return super.composeFilteredText(enhancedNonSocketExceptionText); + } + } + + static final class JarInfo extends SensitiveInfo { + public JarInfo(String name) { + super(name); + } + @Override + public String output() { + setup(); + return super.composeFilteredText(enhancedJarExceptionText); + } + } + + static final class UserInfo extends SensitiveInfo { + public UserInfo(String host) { + super(host); + } + @Override + public String output() { + setup(); + return super.composeFilteredText(enhancedUserExceptionText); + } + } + + // remove leading, trailing and duplicated space characters + static String trim(String s) { + int len = s.length(); + if (len == 0) return s; + + StringBuilder sb = new StringBuilder(); + + // initial value deals with leading spaces + boolean inSpace = true; + for (int i=0; i 0 && sb.charAt(sblen - 1) == ' ') + sb.deleteCharAt(sblen - 1); + return sb.toString(); + } + + public static SensitiveInfo filterSocketInfo(String host) { + return new SocketInfo(host); + } + + public static SensitiveInfo filterNonSocketInfo(String host) { + return new NonSocketInfo(host); + } + + public static SensitiveInfo filterJarName(String name) { + return new JarInfo(name); + } + + public static SensitiveInfo filterUserName(String name) { + return new UserInfo(name); + } + + /** + * Transform each SensitiveInfo into a String argument which is passed + * to String.format(). This string is then trimmed. + */ + public static String formatMsg(String format, SensitiveInfo... infos) { + String[] args = new String[infos.length]; + + int i = 0; + + for (SensitiveInfo info : infos) { + args[i++] = info.output(); + } + return trim(String.format(format, (Object[])args)); + } + + /** + * Simplification of above. Equivalent to: + * formatMsg("%s", SensitiveInfo[1]); // ie with one arg + */ + public static String formatMsg(SensitiveInfo info) { + return trim(info.output()); + } + + public static void setup() { + if (initialized || !VM.isBooted()) + return; + enhancedSocketExceptionText = SecurityProperties.includedInExceptions("hostInfo"); + enhancedNonSocketExceptionText = SecurityProperties.includedInExceptions("hostInfoExclSocket") + | enhancedSocketExceptionText; + + enhancedUserExceptionText = SecurityProperties.includedInExceptions("userInfo"); + enhancedJarExceptionText = SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS; + initialized = true; + } + + public static boolean enhancedNonSocketExceptions() { + setup(); + return enhancedNonSocketExceptionText; + } + + public static boolean enhancedSocketExceptions() { + setup(); + return enhancedSocketExceptionText; + } + + /** + * The enhanced message text is the socket address appended to + * the original IOException message + */ + public static IOException ioException(IOException e, SocketAddress addr) { + setup(); + if (addr == null) { + return e; + } + if (!enhancedSocketExceptionText) { + return create(e, e.getMessage()); + } + if (addr instanceof UnixDomainSocketAddress) { + return ofUnixDomain(e, (UnixDomainSocketAddress)addr); + } else if (addr instanceof InetSocketAddress) { + return ofInet(e, (InetSocketAddress)addr); + } else { + return e; + } + } + + private static IOException ofInet(IOException e, InetSocketAddress addr) { + return create(e, String.join(": ", e.getMessage(), addr.toString())); + } + + private static IOException ofUnixDomain(IOException e, UnixDomainSocketAddress addr) { + String path = addr.getPath().toString(); + StringBuilder sb = new StringBuilder(); + sb.append(e.getMessage()); + sb.append(": "); + sb.append(path); + String enhancedMsg = sb.toString(); + return create(e, enhancedMsg); + } + + // return a new instance of the same type with the given detail + // msg, or if the type doesn't support detail msgs, return given + // instance. + private static T create(T e, String msg) { + try { + Class clazz = e.getClass(); + @SuppressWarnings("unchecked") + Constructor ctor = (Constructor)clazz.getConstructor(String.class); + T e1 = (ctor.newInstance(msg)); + e1.setStackTrace(e.getStackTrace()); + return e1; + } catch (Exception e0) { + // Some eg AsynchronousCloseException have no detail msg + return e; + } + } +} diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index bbcdfef65ec..07de3f2c57f 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -270,9 +270,13 @@ module java.base { java.prefs, java.security.jgss, java.smartcardio, + java.naming, + java.rmi, + java.net.http, jdk.charsets, jdk.incubator.vector, jdk.internal.vm.ci, + jdk.httpserver, jdk.jlink, jdk.jpackage, jdk.net; diff --git a/src/java.base/share/classes/sun/net/util/IPAddressUtil.java b/src/java.base/share/classes/sun/net/util/IPAddressUtil.java index b3da295c4a7..21bbc5ac455 100644 --- a/src/java.base/share/classes/sun/net/util/IPAddressUtil.java +++ b/src/java.base/share/classes/sun/net/util/IPAddressUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -37,6 +37,9 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import static jdk.internal.util.Exceptions.formatMsg; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; + public class IPAddressUtil { private static final int INADDR4SZ = 4; private static final int INADDR16SZ = 16; @@ -161,7 +164,8 @@ public class IPAddressUtil { * @return an {@code IllegalArgumentException} instance */ public static IllegalArgumentException invalidIpAddressLiteral(String src) { - return new IllegalArgumentException("Invalid IP address literal: " + src); + return new IllegalArgumentException( + formatMsg("Invalid IP address literal%s", filterNonSocketInfo(src).prefixWith(": "))); } /* diff --git a/src/java.base/share/classes/sun/net/util/SocketExceptions.java b/src/java.base/share/classes/sun/net/util/SocketExceptions.java deleted file mode 100644 index 1198898edcb..00000000000 --- a/src/java.base/share/classes/sun/net/util/SocketExceptions.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2018, 2024, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 sun.net.util; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.net.InetSocketAddress; -import java.net.UnixDomainSocketAddress; -import java.net.SocketAddress; - -import sun.security.util.SecurityProperties; - -public final class SocketExceptions { - private SocketExceptions() {} - - private static final boolean enhancedExceptionText = - SecurityProperties.includedInExceptions("hostInfo"); - - /** - * Utility which takes an exception and returns either the same exception - * or a new exception of the same type with the same stack trace - * and detail message enhanced with addressing information from the - * given InetSocketAddress. - * - * If the system/security property "jdk.includeInExceptions" is not - * set or does not contain the category hostInfo, - * then the original exception is returned. - * - * Only specific IOException subtypes are supported. - */ - public static IOException of(IOException e, SocketAddress addr) { - if (!enhancedExceptionText || addr == null) { - return e; - } - if (addr instanceof UnixDomainSocketAddress) { - return ofUnixDomain(e, (UnixDomainSocketAddress)addr); - } else if (addr instanceof InetSocketAddress) { - return ofInet(e, (InetSocketAddress)addr); - } else { - return e; - } - } - - private static IOException ofInet(IOException e, InetSocketAddress addr) { - return create(e, String.join(": ", e.getMessage(), addr.toString())); - } - - private static IOException ofUnixDomain(IOException e, UnixDomainSocketAddress addr) { - String path = addr.getPath().toString(); - StringBuilder sb = new StringBuilder(); - sb.append(e.getMessage()); - sb.append(": "); - sb.append(path); - String enhancedMsg = sb.toString(); - return create(e, enhancedMsg); - } - - // return a new instance of the same type with the given detail - // msg, or if the type doesn't support detail msgs, return given - // instance. - private static IOException create(final IOException e, final String msg) { - try { - Class clazz = e.getClass(); - Constructor ctor = clazz.getConstructor(String.class); - IOException e1 = (IOException)(ctor.newInstance(msg)); - e1.setStackTrace(e.getStackTrace()); - return e1; - } catch (Exception e0) { - // Some eg AsynchronousCloseException have no detail msg - return e; - } - } -} diff --git a/src/java.base/share/classes/sun/net/www/ParseUtil.java b/src/java.base/share/classes/sun/net/www/ParseUtil.java index 51d1b8398b6..cac32e53288 100644 --- a/src/java.base/share/classes/sun/net/www/ParseUtil.java +++ b/src/java.base/share/classes/sun/net/www/ParseUtil.java @@ -40,6 +40,8 @@ import java.nio.charset.CodingErrorAction; import java.util.HexFormat; import sun.nio.cs.UTF_8; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * A class that contains useful routines common to sun.net.www @@ -502,7 +504,7 @@ public final class ParseUtil { { if (scheme != null) { if (path != null && !path.isEmpty() && path.charAt(0) != '/') - throw new URISyntaxException(s, + throw new URISyntaxException(formatMsg("%s", filterNonSocketInfo(s)), "Relative path in absolute URI"); } } diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java index fb2dfe6fff3..2f011f5805b 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java @@ -53,7 +53,8 @@ import sun.security.ssl.SSLSocketImpl; import sun.util.logging.PlatformLogger; import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; - +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * This class provides HTTPS client URL support, building on the standard @@ -559,8 +560,10 @@ final class HttpsClient extends HttpClient serverSocket.close(); session.invalidate(); - throw new IOException("HTTPS hostname wrong: should be <" - + url.getHost() + ">"); + throw new IOException(formatMsg("Wrong HTTPS hostname%s", + filterNonSocketInfo(url.getHost()) + .prefixWith(": should be <") + .suffixWith(">"))); } @Override diff --git a/src/java.base/share/classes/sun/net/www/protocol/jar/Handler.java b/src/java.base/share/classes/sun/net/www/protocol/jar/Handler.java index 2884f825b84..381d4244238 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jar/Handler.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jar/Handler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -27,6 +27,8 @@ package sun.net.www.protocol.jar; import java.io.IOException; import java.net.*; +import static jdk.internal.util.Exceptions.filterJarName; +import static jdk.internal.util.Exceptions.formatMsg; /* * Jar URL Handler @@ -181,8 +183,11 @@ public class Handler extends java.net.URLStreamHandler { String innerSpec = spec.substring(0, index - 1); newURL(innerSpec); } catch (MalformedURLException e) { - throw new NullPointerException("invalid url: " + - spec + " (" + e + ")"); + throw new NullPointerException( + formatMsg("invalid url: %s %s", filterJarName(spec), + filterJarName(e.getMessage()) + .prefixWith("(") + .suffixWith(")"))); } return spec; } @@ -193,19 +198,18 @@ public class Handler extends java.net.URLStreamHandler { if (spec.startsWith("/")) { int bangSlash = indexOfBangSlash(ctxFile); if (bangSlash == -1) { - throw new NullPointerException("malformed " + - "context url:" + - url + - ": no !/"); + throw new NullPointerException( + formatMsg("malformed context url%s : no !/", + filterJarName(String.valueOf(url)).prefixWith(": "))); } ctxFile = ctxFile.substring(0, bangSlash); } else { // chop up the last component int lastSlash = ctxFile.lastIndexOf('/'); if (lastSlash == -1) { - throw new NullPointerException("malformed " + - "context url:" + - url); + throw new NullPointerException( + formatMsg("malformed context url%s", + filterJarName(String.valueOf(url)).prefixWith(": "))); } else if (lastSlash < ctxFile.length() - 1) { ctxFile = ctxFile.substring(0, lastSlash + 1); } diff --git a/src/java.base/share/classes/sun/net/www/protocol/jar/JarFileFactory.java b/src/java.base/share/classes/sun/net/www/protocol/jar/JarFileFactory.java index 4d695c8d4e8..741854349d7 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jar/JarFileFactory.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jar/JarFileFactory.java @@ -35,6 +35,8 @@ import java.util.jar.JarFile; import jdk.internal.util.OperatingSystem; import sun.net.util.URLUtil; import sun.net.www.ParseUtil; +import static jdk.internal.util.Exceptions.filterJarName; +import static jdk.internal.util.Exceptions.formatMsg; /* A factory for cached JAR file. This class is used to both retrieve * and cache Jar files. @@ -108,7 +110,7 @@ class JarFileFactory implements URLJarFile.URLJarFileCloseController { result = URLJarFile.getJarFile(patched, this); } if (result == null) - throw new FileNotFoundException(url.toString()); + throw new FileNotFoundException(formatMsg("%s", filterJarName(url.toString()))); return result; } @@ -200,7 +202,7 @@ class JarFileFactory implements URLJarFile.URLJarFileCloseController { result = URLJarFile.getJarFile(url, this); } if (result == null) - throw new FileNotFoundException(url.toString()); + throw new FileNotFoundException(formatMsg("%s", filterJarName(url.toString()))); return result; } diff --git a/src/java.base/share/classes/sun/net/www/protocol/jar/JarURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/jar/JarURLConnection.java index 3a346308f71..96c04132551 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jar/JarURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jar/JarURLConnection.java @@ -37,6 +37,9 @@ import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import static jdk.internal.util.Exceptions.filterJarName; +import static jdk.internal.util.Exceptions.formatMsg; + /** * @author Benjamin Renaud @@ -129,9 +132,10 @@ public class JarURLConnection extends java.net.JarURLConnection { factory.closeIfNotCached(url, jarFile); } catch (Exception e) { } - throw new FileNotFoundException("JAR entry " + entryName + - " not found in " + - jarFile.getName()); + throw new FileNotFoundException( + formatMsg("JAR entry %s not found in jar file %s", + filterJarName(entryName), + filterJarName(jarFile.getName()))); } } @@ -169,9 +173,10 @@ public class JarURLConnection extends java.net.JarURLConnection { throw new IOException("no entry name specified"); } else { if (jarEntry == null) { - throw new FileNotFoundException("JAR entry " + entryName + - " not found in " + - jarFile.getName()); + throw new FileNotFoundException( + formatMsg("JAR entry %s not found in jar file %s", + filterJarName(entryName), + filterJarName(jarFile.getName()))); } result = new JarURLInputStream (jarFile.getInputStream(jarEntry)); } diff --git a/src/java.base/share/classes/sun/net/www/protocol/jmod/Handler.java b/src/java.base/share/classes/sun/net/www/protocol/jmod/Handler.java index 95bc2179a31..51a88cb229d 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jmod/Handler.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jmod/Handler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java b/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java index 4930e1447a3..44b05f97472 100644 --- a/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java +++ b/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -53,6 +53,8 @@ import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static jdk.internal.util.Exceptions.formatMsg; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; /** * A multicast datagram socket based on a datagram channel. @@ -490,7 +492,8 @@ public class DatagramSocketAdaptor NetworkInterface ni = NetworkInterface.getByInetAddress(inf); if (ni == null) { String address = inf.getHostAddress(); - throw new SocketException("No network interface with address " + address); + throw new SocketException(formatMsg("No network interface found with address %s", + filterNonSocketInfo(address))); } synchronized (outgoingInterfaceLock) { // set interface and update cached values diff --git a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java index b3939f2ca05..6705134648d 100644 --- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java @@ -58,10 +58,12 @@ import sun.net.ConnectionResetException; import sun.net.NetHooks; import sun.net.PlatformSocketImpl; import sun.net.ext.ExtendedSocketOptions; -import sun.net.util.SocketExceptions; +import jdk.internal.util.Exceptions; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * NIO based SocketImpl. @@ -554,7 +556,8 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp throw new IOException("Unsupported address type"); InetSocketAddress isa = (InetSocketAddress) remote; if (isa.isUnresolved()) { - throw new UnknownHostException(isa.getHostName()); + throw new UnknownHostException( + formatMsg(filterNonSocketInfo(isa.getHostName()))); } InetAddress address = isa.getAddress(); @@ -604,7 +607,7 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp assert Thread.currentThread().isVirtual(); throw new SocketException("Closed by interrupt"); } else { - throw SocketExceptions.of(ioe, isa); + throw Exceptions.ioException(ioe, isa); } } } diff --git a/src/java.base/share/classes/sun/nio/ch/SocketAdaptor.java b/src/java.base/share/classes/sun/nio/ch/SocketAdaptor.java index d8ed1cfb675..bf777ed23e4 100644 --- a/src/java.base/share/classes/sun/nio/ch/SocketAdaptor.java +++ b/src/java.base/share/classes/sun/nio/ch/SocketAdaptor.java @@ -40,6 +40,9 @@ import java.nio.channels.SocketChannel; import java.util.Set; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; + // Make a socket channel look like a socket. // @@ -92,7 +95,8 @@ class SocketAdaptor if (sc.isConnected()) throw new SocketException("Already connected"); close(); - throw new UnknownHostException(remote.toString()); + throw new UnknownHostException( + formatMsg(filterNonSocketInfo(remote.toString()))); } if (timeout < 0) throw new IllegalArgumentException("connect: timeout can't be negative"); diff --git a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java index 0980592abab..c1241a51f85 100644 --- a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java @@ -64,7 +64,7 @@ import jdk.internal.event.SocketWriteEvent; import sun.net.ConnectionResetException; import sun.net.NetHooks; import sun.net.ext.ExtendedSocketOptions; -import sun.net.util.SocketExceptions; +import jdk.internal.util.Exceptions; /** * An implementation of SocketChannels @@ -975,7 +975,7 @@ class SocketChannelImpl } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, sa); + throw Exceptions.ioException(ioe, sa); } } @@ -1065,7 +1065,7 @@ class SocketChannelImpl } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, remoteAddress); + throw Exceptions.ioException(ioe, remoteAddress); } } @@ -1313,7 +1313,7 @@ class SocketChannelImpl } catch (IOException ioe) { // connect failed, close the channel close(); - throw SocketExceptions.of(ioe, sa); + throw Exceptions.ioException(ioe, sa); } } diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index b750c1b82b0..61e8aaaf6d1 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1277,8 +1277,13 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep # # Enhanced exception message information # -# By default, exception messages should not include potentially sensitive -# information such as file names, host names, or port numbers. This property +# Exception messages may include potentially sensitive information such as file +# names, host names, or port numbers. By default, socket related exceptions +# have this information restricted (meaning the sensitive details are removed). +# Exception messages relating to JAR files and exceptions containing user +# identity information are also restricted by default. +# This property can be used to relax this restriction or to place further +# restrictions on other categories, defined below. The property # accepts one or more comma separated values, each of which represents a # category of enhanced exception message information to enable. Values are # case-insensitive. Leading and trailing whitespaces, surrounding each value, @@ -1291,17 +1296,28 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep # # The categories are: # -# hostInfo - IOExceptions thrown by java.net.Socket and the socket types in the -# java.nio.channels package will contain enhanced exception -# message information +# hostInfo - All networking related exceptions will contain enhanced +# exception message information. +# +# hostInfoExclSocket - The hostInfo category defined above, excluding +# IOExceptions thrown by java.net.Socket and the NetworkChannel +# types in the java.nio.channels package, will contain enhanced +# exception message information # # jar - enables more detailed information in the IOExceptions thrown # by classes in the java.util.jar package # +# userInfo - enables more detailed information in exceptions which may contain +# user identity information +# # The property setting in this file can be overridden by a system property of # the same name, with the same syntax and possible values. # -#jdk.includeInExceptions=hostInfo,jar +# If the property is not set or set to an empty string, then this is the most +# restricted setting with all categories disabled. The following is the default +# (out of the box) setting, meaning these categories are not restricted. +# +jdk.includeInExceptions=hostInfoExclSocket # # Disabled mechanisms for the Simple Authentication and Security Layer (SASL) @@ -1557,4 +1573,4 @@ jdk.tls.alpnCharset=ISO_8859_1 # java.security.PEMEncoder when configured for encryption with the # withEncryption method. # -jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 \ No newline at end of file +jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 diff --git a/src/java.base/share/native/libnet/net_util.c b/src/java.base/share/native/libnet/net_util.c index a198152563a..9c0f14b0d90 100644 --- a/src/java.base/share/native/libnet/net_util.c +++ b/src/java.base/share/native/libnet/net_util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -86,9 +86,36 @@ DEF_JNI_OnLoad(JavaVM *vm, void *reserved) /* check if SO_REUSEPORT is supported on this platform */ REUSEPORT_available = reuseport_supported(IPv6_available); + return JNI_VERSION_1_2; } +static int enhancedExceptionsInitialized = 0; +static int enhancedExceptionsAllowed = -1; + +#define CHECK_NULL_THROW_ERROR(X) \ + if (X == NULL) { \ + JNU_ThrowByName(env, "java/lang/InternalError", \ + "can't initialize enhanced exceptions"); \ + return -1; \ + } + +int getEnhancedExceptionsAllowed(JNIEnv *env) { + jclass cls; + jfieldID fid; + + if (enhancedExceptionsInitialized) { + return enhancedExceptionsAllowed; + } + cls = (*env)->FindClass(env, "jdk/internal/util/Exceptions"); + CHECK_NULL_THROW_ERROR(cls); + fid = (*env)->GetStaticFieldID(env, cls, "enhancedNonSocketExceptionText", "Z"); + CHECK_NULL_THROW_ERROR(fid); + enhancedExceptionsAllowed = (*env)->GetStaticBooleanField(env, cls, fid); + enhancedExceptionsInitialized = 1; + return enhancedExceptionsAllowed; +} + static int initialized = 0; JNIEXPORT void JNICALL initInetAddressIDs(JNIEnv *env) { diff --git a/src/java.base/share/native/libnet/net_util.h b/src/java.base/share/native/libnet/net_util.h index a1fa9571821..89537a7f47d 100644 --- a/src/java.base/share/native/libnet/net_util.h +++ b/src/java.base/share/native/libnet/net_util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -183,4 +183,6 @@ int lookupCharacteristicsToAddressFamily(int characteristics); int addressesInSystemOrder(int characteristics); +int getEnhancedExceptionsAllowed(JNIEnv *env); + #endif /* NET_UTILS_H */ diff --git a/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java b/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java index b9c099ef92f..5e75e3b2486 100644 --- a/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java +++ b/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -32,9 +32,10 @@ import java.util.concurrent.*; import java.io.IOException; import java.io.FileDescriptor; +import jdk.internal.util.Exceptions; + import sun.net.ConnectionResetException; import sun.net.NetHooks; -import sun.net.util.SocketExceptions; /** * Unix implementation of AsynchronousSocketChannel @@ -264,7 +265,7 @@ class UnixAsynchronousSocketChannelImpl if (e != null) { if (e instanceof IOException) { var isa = (InetSocketAddress)pendingRemote; - e = SocketExceptions.of((IOException)e, isa); + e = Exceptions.ioException((IOException)e, isa); } // close channel if connection cannot be established try { @@ -355,7 +356,7 @@ class UnixAsynchronousSocketChannelImpl // close channel if connect fails if (e != null) { if (e instanceof IOException) { - e = SocketExceptions.of((IOException)e, isa); + e = Exceptions.ioException((IOException)e, isa); } try { close(); diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixUserPrincipals.java b/src/java.base/unix/classes/sun/nio/fs/UnixUserPrincipals.java index f50dea108b0..b1e7b7cbcc4 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixUserPrincipals.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixUserPrincipals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -29,6 +29,9 @@ import java.nio.file.attribute.*; import java.io.IOException; import static sun.nio.fs.UnixNativeDispatcher.*; +import static jdk.internal.util.Exceptions.filterUserName; +import static jdk.internal.util.Exceptions.formatMsg; + /** * Unix implementation of java.nio.file.attribute.UserPrincipal */ @@ -132,18 +135,19 @@ public class UnixUserPrincipals { private static int lookupName(String name, boolean isGroup) throws IOException { - int id; + int id = -1; try { id = (isGroup) ? getgrnam(name) : getpwnam(name); } catch (UnixException x) { - throw new IOException(name + ": " + x.errorString()); + throw new IOException(formatMsg("%s " + x.errorString(), + filterUserName(name).suffixWith(": "))); } if (id == -1) { // lookup failed, allow input to be uid or gid try { id = Integer.parseInt(name); } catch (NumberFormatException ignore) { - throw new UserPrincipalNotFoundException(name); + throw new UserPrincipalNotFoundException(formatMsg("%s", filterUserName(name))); } } return id; diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index 1713d0a8ccd..d50130d188b 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -184,23 +184,36 @@ void NET_ThrowUnknownHostExceptionWithGaiError(JNIEnv *env, { int size; char *buf; - const char *format = "%s: %s"; const char *error_string = gai_strerror(gai_error); if (error_string == NULL) error_string = "unknown error"; + int enhancedExceptions = getEnhancedExceptionsAllowed(env); + + if (enhancedExceptions) { + size = strlen(hostname); + } else { + size = 0; + } + size += strlen(error_string) + 3; - size = strlen(format) + strlen(hostname) + strlen(error_string) + 2; buf = (char *) malloc(size); if (buf) { jstring s; - snprintf(buf, size, format, hostname, error_string); - s = JNU_NewStringPlatform(env, buf); - if (s != NULL) { - jobject x = JNU_NewObjectByName(env, + int n; + if (enhancedExceptions) { + n = snprintf(buf, size, "%s: %s", hostname, error_string); + } else { + n = snprintf(buf, size, " %s", error_string); + } + if (n >= 0) { + s = JNU_NewStringPlatform(env, buf); + if (s != NULL) { + jobject x = JNU_NewObjectByName(env, "java/net/UnknownHostException", "(Ljava/lang/String;)V", s); - if (x != NULL) - (*env)->Throw(env, x); + if (x != NULL) + (*env)->Throw(env, x); + } } free(buf); } diff --git a/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java b/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java index f8b5fab4316..36065f040a0 100644 --- a/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java +++ b/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -33,9 +33,9 @@ import java.nio.BufferOverflowException; import java.net.*; import java.util.concurrent.*; import java.io.IOException; +import jdk.internal.util.Exceptions; import jdk.internal.invoke.MhUtil; import jdk.internal.misc.Unsafe; -import sun.net.util.SocketExceptions; /** * Windows implementation of AsynchronousSocketChannel using overlapped I/O. @@ -253,7 +253,7 @@ class WindowsAsynchronousSocketChannelImpl if (exc != null) { closeChannel(); - exc = SocketExceptions.of(toIOException(exc), remote); + exc = Exceptions.ioException(toIOException(exc), remote); result.setFailure(exc); } Invoker.invoke(result); @@ -280,7 +280,7 @@ class WindowsAsynchronousSocketChannelImpl if (exc != null) { closeChannel(); IOException ee = toIOException(exc); - ee = SocketExceptions.of(ee, remote); + ee = Exceptions.ioException(ee, remote); result.setFailure(ee); } @@ -296,12 +296,12 @@ class WindowsAsynchronousSocketChannelImpl */ @Override public void failed(int error, IOException x) { - x = SocketExceptions.of(x, remote); + x = Exceptions.ioException(x, remote); if (isOpen()) { closeChannel(); result.setFailure(x); } else { - x = SocketExceptions.of(new AsynchronousCloseException(), remote); + x = Exceptions.ioException(new AsynchronousCloseException(), remote); result.setFailure(x); } Invoker.invoke(result); diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsSecurityDescriptor.java b/src/java.base/windows/classes/sun/nio/fs/WindowsSecurityDescriptor.java index da3460592f4..8c3dfa5546e 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsSecurityDescriptor.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsSecurityDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -33,6 +33,8 @@ import jdk.internal.misc.Unsafe; import static sun.nio.fs.WindowsNativeDispatcher.*; import static sun.nio.fs.WindowsConstants.*; +import static jdk.internal.util.Exceptions.filterUserName; +import static jdk.internal.util.Exceptions.formatMsg; /** * A SecurityDescriptor for use when setting a file's ACL or creating a file @@ -136,8 +138,8 @@ class WindowsSecurityDescriptor { Math.max(SIZEOF_ACCESS_ALLOWED_ACE, SIZEOF_ACCESS_DENIED_ACE); } catch (WindowsException x) { - throw new IOException("Failed to get SID for " + user.getName() - + ": " + x.errorString()); + throw new IOException(formatMsg("Failed to get SID %s : " + x.errorString(), + filterUserName(user.getName()).prefixWith("for "))); } } diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsUserPrincipals.java b/src/java.base/windows/classes/sun/nio/fs/WindowsUserPrincipals.java index 336bbe22cfb..0a4e2b9c9fa 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsUserPrincipals.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsUserPrincipals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -29,6 +29,8 @@ import java.io.IOException; import static sun.nio.fs.WindowsConstants.*; import static sun.nio.fs.WindowsNativeDispatcher.*; +import static jdk.internal.util.Exceptions.formatMsg; +import static jdk.internal.util.Exceptions.filterUserName; class WindowsUserPrincipals { private WindowsUserPrincipals() { } @@ -132,13 +134,14 @@ class WindowsUserPrincipals { static UserPrincipal lookup(String name) throws IOException { // invoke LookupAccountName to get buffer size needed for SID - int size; + int size = 0; try { size = LookupAccountName(name, 0L, 0); } catch (WindowsException x) { if (x.lastError() == ERROR_NONE_MAPPED) throw new UserPrincipalNotFoundException(name); - throw new IOException(name + ": " + x.errorString()); + throw new IOException(formatMsg("%s " + x.errorString(), + filterUserName(name).suffixWith(": "))); } assert size > 0; @@ -153,7 +156,8 @@ class WindowsUserPrincipals { // return user principal return fromSid(sidBuffer.address()); } catch (WindowsException x) { - throw new IOException(name + ": " + x.errorString()); + throw new IOException(formatMsg("%s " + x.errorString(), + filterUserName(name).suffixWith(": "))); } } } diff --git a/src/java.base/windows/native/libnet/Inet4AddressImpl.c b/src/java.base/windows/native/libnet/Inet4AddressImpl.c index 0e2d6a3455f..0ef0cae5be7 100644 --- a/src/java.base/windows/native/libnet/Inet4AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet4AddressImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -88,8 +88,9 @@ Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this, if (error) { // report error - NET_ThrowByNameWithLastError(env, "java/net/UnknownHostException", - hostname); + NET_ThrowByNameWithLastError( + env, "java/net/UnknownHostException", + getEnhancedExceptionsAllowed(env) ? hostname : ""); goto cleanupAndReturn; } else { int i = 0; diff --git a/src/java.base/windows/native/libnet/Inet6AddressImpl.c b/src/java.base/windows/native/libnet/Inet6AddressImpl.c index 244e2fefa9b..0281e2ddecb 100644 --- a/src/java.base/windows/native/libnet/Inet6AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet6AddressImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -84,7 +84,7 @@ Java_java_net_Inet6AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this, if (error) { // report error NET_ThrowByNameWithLastError(env, "java/net/UnknownHostException", - hostname); + getEnhancedExceptionsAllowed(env) ? hostname : ""); goto cleanupAndReturn; } else { int i = 0, inetCount = 0, inet6Count = 0, inetIndex = 0, diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java index 140f025b779..d9319818801 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java @@ -33,6 +33,8 @@ import java.util.Locale; import java.util.StringTokenizer; import com.sun.jndi.toolkit.url.Uri; import com.sun.jndi.toolkit.url.UrlUtil; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /* * Extract components of an LDAP URL. @@ -119,7 +121,8 @@ public final class LdapURL extends Uri { @Override protected MalformedURLException newInvalidURISchemeException(String uri) { - return new MalformedURLException("Not an LDAP URL: " + uri); + return new MalformedURLException(formatMsg("Not an LDAP URL%s", + filterNonSocketInfo(uri).prefixWith(": "))); } @Override diff --git a/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java b/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java index 10316b1ca69..a4db62868b0 100644 --- a/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java +++ b/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java @@ -29,6 +29,8 @@ package com.sun.jndi.toolkit.url; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** @@ -232,6 +234,12 @@ public class Uri { } } + private MalformedURLException newMalformedURLException(String prefix, String msg) { + return new MalformedURLException(prefix + + formatMsg(filterNonSocketInfo(msg) + .prefixWith(prefix.isEmpty()? "" : ": "))); + } + /* * Parses a URI string and sets this object's fields accordingly. * Use java.net.URI to validate the uri string syntax @@ -241,7 +249,7 @@ public class Uri { if (!isSchemeOnly(uri)) { URI u = new URI(uri); scheme = u.getScheme(); - if (scheme == null) throw new MalformedURLException("Invalid URI: " + uri); + if (scheme == null) throw newMalformedURLException("Invalid URI", uri); var auth = u.getRawAuthority(); hasAuthority = auth != null; if (hasAuthority) { @@ -253,7 +261,7 @@ public class Uri { + (port == -1 ? "" : (":" + port)); if (!hostport.equals(auth)) { // throw if we have user info or regname - throw new MalformedURLException("unsupported authority: " + auth); + throw newMalformedURLException("unsupported authority", auth); } } path = u.getRawPath(); @@ -262,7 +270,7 @@ public class Uri { } if (u.getRawFragment() != null) { if (!acceptsFragment()) { - throw new MalformedURLException("URI fragments not supported: " + uri); + throw newMalformedURLException("URI fragments not supported", uri); } fragment = "#" + u.getRawFragment(); } @@ -279,7 +287,7 @@ public class Uri { path = ""; } } catch (URISyntaxException e) { - var mue = new MalformedURLException(e.getMessage()); + var mue = newMalformedURLException("", e.getMessage()); mue.initCause(e); throw mue; } @@ -299,11 +307,11 @@ public class Uri { int qmark = uri.indexOf('?'); int fmark = uri.indexOf('#'); if (i < 0 || slash > 0 && i > slash || qmark > 0 && i > qmark || fmark > 0 && i > fmark) { - throw new MalformedURLException("Invalid URI: " + uri); + throw newMalformedURLException("Invalid URI", uri); } if (fmark > -1) { if (!acceptsFragment()) { - throw new MalformedURLException("URI fragments not supported: " + uri); + throw newMalformedURLException("URI fragments not supported", uri); } } if (i == uri.length() - 1) { @@ -349,26 +357,26 @@ public class Uri { String f = u.getRawFragment(); String ui = u.getRawUserInfo(); if (ui != null) { - throw new MalformedURLException("user info not supported in authority: " + ui); + throw newMalformedURLException("user info not supported in authority", ui); } if (!"/".equals(p)) { - throw new MalformedURLException("invalid authority: " + auth); + throw newMalformedURLException("invalid authority", auth); } if (q != null) { - throw new MalformedURLException("invalid trailing characters in authority: ?" + q); + throw newMalformedURLException("invalid trailing characters in authority '?'", "?" + q); } if (f != null) { - throw new MalformedURLException("invalid trailing characters in authority: #" + f); + throw newMalformedURLException("invalid trailing characters in authority: '#'", "#" + f); } String hostport = (host == null ? "" : host) + (port == -1?"":(":" + port)); if (!auth.equals(hostport)) { // throw if we have user info or regname - throw new MalformedURLException("Authority component is not server-based, " + - "or contains user info. Unsupported authority: " + auth); + throw newMalformedURLException("Authority component is not server-based, " + + "or contains user info. Unsupported authority", auth); } } catch (URISyntaxException e) { - var mue = new MalformedURLException(e.getMessage()); + var mue = newMalformedURLException("", e.getMessage()); mue.initCause(e); throw mue; } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java index e4dee55d8ea..a495fcce1ee 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, 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 @@ -39,6 +39,8 @@ import static java.util.Objects.requireNonNull; import static jdk.internal.net.http.common.Utils.isValidName; import static jdk.internal.net.http.common.Utils.isValidValue; import static jdk.internal.net.http.common.Utils.newIAE; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; public class HttpRequestBuilderImpl implements HttpRequest.Builder { @@ -82,7 +84,8 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder { throw newIAE("invalid URI scheme %s", scheme); } if (uri.getHost() == null) { - throw newIAE("unsupported URI %s", uri); + throw new IllegalArgumentException( + formatMsg("unsupported URI %s", filterNonSocketInfo(uri.toString()))); } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java index 79b2332ae37..a74c632c218 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -46,6 +46,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import jdk.internal.net.http.ResponseSubscribers.PathSubscriber; +import jdk.internal.util.Exceptions; import static java.util.regex.Pattern.CASE_INSENSITIVE; public final class ResponseBodyHandlers { @@ -190,7 +191,12 @@ public final class ResponseBodyHandlers { static final UncheckedIOException unchecked(ResponseInfo rinfo, String msg) { - String s = String.format("%s in response [%d, %s]", msg, rinfo.statusCode(), rinfo.headers()); + String s; + if (Exceptions.enhancedNonSocketExceptions()) { + s = String.format("%s in response [%d, %s]", msg, rinfo.statusCode(), rinfo.headers()); + } else { + s = String.format("%s in response [%d]", msg, rinfo.statusCode()); + } return new UncheckedIOException(new IOException(s)); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java index c1c0853504c..6e76a272cd0 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, 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 @@ -61,6 +61,8 @@ import java.util.concurrent.CompletableFuture; import static java.lang.String.format; import static jdk.internal.net.http.common.Utils.isValidName; import static jdk.internal.net.http.common.Utils.stringOf; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; public class OpeningHandshake { @@ -336,9 +338,13 @@ public class OpeningHandshake { if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))) throw illegal("invalid URI scheme: " + scheme); if (uri.getHost() == null) - throw illegal("URI must contain a host: " + uri); + throw new IllegalArgumentException( + formatMsg("URI must contain a host%s", + filterNonSocketInfo(uri.toString()).prefixWith(": "))); if (uri.getFragment() != null) - throw illegal("URI must not contain a fragment: " + uri); + throw new IllegalArgumentException( + formatMsg("URI must not contain a fragment%s", + filterNonSocketInfo(uri.toString()).prefixWith(": "))); return uri; } diff --git a/src/java.rmi/share/classes/java/rmi/Naming.java b/src/java.rmi/share/classes/java/rmi/Naming.java index c4677991770..cb8766564a9 100644 --- a/src/java.rmi/share/classes/java/rmi/Naming.java +++ b/src/java.rmi/share/classes/java/rmi/Naming.java @@ -28,6 +28,8 @@ import java.rmi.registry.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.formatMsg; /** * The Naming class provides methods for storing and obtaining @@ -221,6 +223,11 @@ public final class Naming { return LocateRegistry.getRegistry(parsed.host, parsed.port); } + private static MalformedURLException newMalformedURLException(String prefix, String msg) { + return new MalformedURLException( + prefix + formatMsg(filterNonSocketInfo(msg).prefixWith(": "))); + } + /** * Dissect Naming URL strings to obtain referenced host, port and * object name. @@ -240,8 +247,8 @@ public final class Naming { * '//:' forms will result in a URI syntax exception * Convert the authority to a localhost: form */ - MalformedURLException mue = new MalformedURLException( - "invalid URL String: " + str); + MalformedURLException mue = newMalformedURLException( + "invalid URL String", str); mue.initCause(ex); int indexSchemeEnd = str.indexOf(':'); int indexAuthorityBegin = str.indexOf("//:"); @@ -272,22 +279,22 @@ public final class Naming { { URI uri = new URI(str); if (uri.isOpaque()) { - throw new MalformedURLException( - "not a hierarchical URL: " + str); + throw newMalformedURLException( + "not a hierarchical URL", str); } if (uri.getFragment() != null) { - throw new MalformedURLException( - "invalid character, '#', in URL name: " + str); + throw newMalformedURLException( + "invalid character, '#', in URL name", str); } else if (uri.getQuery() != null) { - throw new MalformedURLException( - "invalid character, '?', in URL name: " + str); + throw newMalformedURLException( + "invalid character, '?', in URL name", str); } else if (uri.getUserInfo() != null) { - throw new MalformedURLException( - "invalid character, '@', in URL host: " + str); + throw newMalformedURLException( + "invalid character, '@', in URL host", str); } String scheme = uri.getScheme(); if (scheme != null && !scheme.equals("rmi")) { - throw new MalformedURLException("invalid URL scheme: " + str); + throw newMalformedURLException("invalid URL scheme", str); } String name = uri.getPath(); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java index c97162ef9bd..d6a42d366c8 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java @@ -35,6 +35,8 @@ public class JWebServer { private static final String SYS_PROP_MAX_CONNECTIONS = "jdk.httpserver.maxConnections"; private static final String DEFAULT_JWEBSERVER_MAX_CONNECTIONS = "200"; + private static final String SYS_PROP_ENHANCED_EXCEP = "jdk.includeInExceptions"; + private static final String DEFAULT_ENHANCED_EXCEP = "net"; /** * This constructor should never be called. */ @@ -61,6 +63,7 @@ public class JWebServer { */ public static void main(String... args) { setMaxReqTime(); + setEnhancedExceptions(); setMaxConnectionsIfNotSet(); int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true), "jwebserver", args); @@ -80,6 +83,14 @@ public class JWebServer { } } + static void setEnhancedExceptions() { + if (System.getProperty(SYS_PROP_ENHANCED_EXCEP) != null) { + // an explicit value has already been set, so we don't override it + return; + } + System.setProperty(SYS_PROP_ENHANCED_EXCEP, DEFAULT_ENHANCED_EXCEP); + } + static void setMaxConnectionsIfNotSet() { if (System.getProperty(SYS_PROP_MAX_CONNECTIONS) == null) { System.setProperty(SYS_PROP_MAX_CONNECTIONS, DEFAULT_JWEBSERVER_MAX_CONNECTIONS); diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java index 5a16bedc29d..a82b0bf5a6f 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java @@ -226,7 +226,11 @@ public class CommandLineNegativeTest { } static OutputAnalyzer simpleserver(String... args) throws Throwable { - var pb = new ProcessBuilder(args) + String[] nargs = new String[args.length + 1]; + nargs[0] = args[0]; + System.arraycopy(args, 1, nargs, 2, args.length-1); + nargs[1] = "-Djdk.includeInExceptions=hostInfo"; + var pb = new ProcessBuilder(nargs) .directory(TEST_DIR.toFile()); var outputAnalyser = ProcessTools.executeCommand(pb) .outputTo(System.out) diff --git a/test/jdk/java/net/URI/Test.java b/test/jdk/java/net/URI/Test.java index 95dd49ea34f..57da0ac3607 100644 --- a/test/jdk/java/net/URI/Test.java +++ b/test/jdk/java/net/URI/Test.java @@ -26,6 +26,7 @@ * @bug 4464135 4505046 4503239 4438319 4991359 4866303 7023363 7041800 * 7171415 6339649 6933879 8037396 8272072 8051627 8297687 8353013 * @author Mark Reinhold + * @run main/othervm Test */ import java.io.ByteArrayInputStream; diff --git a/test/jdk/jdk/security/JavaDotSecurity/TestJDKIncludeInExceptions.java b/test/jdk/jdk/security/JavaDotSecurity/TestJDKIncludeInExceptions.java index 353309c1654..011fc6bbaf4 100644 --- a/test/jdk/jdk/security/JavaDotSecurity/TestJDKIncludeInExceptions.java +++ b/test/jdk/jdk/security/JavaDotSecurity/TestJDKIncludeInExceptions.java @@ -28,7 +28,7 @@ import java.security.Security; * @bug 8207846 8208691 * @summary Test the default setting of the jdk.net.includeInExceptions * security property - * @comment In OpenJDK, this property is empty by default and on purpose. + * @comment In OpenJDK, this property has value "hostInfoExclSocket" by default * This test assures the default is not changed. * @run main TestJDKIncludeInExceptions */ @@ -36,9 +36,9 @@ public class TestJDKIncludeInExceptions { public static void main(String args[]) throws Exception { String incInExc = Security.getProperty("jdk.includeInExceptions"); - if (incInExc != null) { + if (incInExc == null || !incInExc.equals("hostInfoExclSocket")) { throw new RuntimeException("Test failed: default value of " + - "jdk.includeInExceptions security property is not null: " + + "jdk.includeInExceptions security property does not have expected value: " + incInExc); } } diff --git a/test/jdk/sun/net/util/ExceptionsTest.java b/test/jdk/sun/net/util/ExceptionsTest.java new file mode 100644 index 00000000000..d6717763afb --- /dev/null +++ b/test/jdk/sun/net/util/ExceptionsTest.java @@ -0,0 +1,172 @@ +/* + * 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. + */ +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; +import jdk.internal.util.Exceptions; +import jdk.internal.util.Exceptions.SensitiveInfo; +import static jdk.internal.util.Exceptions.formatMsg; +import static jdk.internal.util.Exceptions.filterNonSocketInfo; +import static jdk.internal.util.Exceptions.enhancedNonSocketExceptions; + +/* + * @test + * @bug 8348986 + * @summary Improve coverage of enhanced exception messages + * @modules java.base/jdk.internal.util + * @run main/othervm -Djdk.includeInExceptions=hostInfo ExceptionsTest + * @run main/othervm ExceptionsTest + * @run main/othervm -Djdk.includeInExceptions=userInfo ExceptionsTest + * @run main/othervm -Djdk.net.hosts.file=does.not.exist -Djdk.includeInExceptions=userInfo ExceptionsTest + * @run main/othervm -Djdk.net.hosts.file=does.not.exist -Djdk.includeInExceptions=hostInfo ExceptionsTest + */ +public class ExceptionsTest { + + static boolean netEnabled() { + System.out.printf("netEnabled = %b\n", enhancedNonSocketExceptions()); + return enhancedNonSocketExceptions(); + } + + static boolean dnsEnabled() { + System.out.printf("dnsEnabled = %b\n", enhancedNonSocketExceptions()); + return enhancedNonSocketExceptions(); + } + + static boolean hostFileEnabled() { + return System.getProperty("jdk.net.hosts.file", "").length() > 0; + } + + static String[][][] tests = { + // + // If a format argument is of the form ".pre(xxx)" or ".suf(yyy)", then that is + // interpreted as a .prefixWith("xxx") or .suffixWith("yyy") call to the preceding + // argument. .rep() signifies .replaceWith() + // + // Number of elements in array + // --------------------------- + // 1 N 2 + // + // Format string args to format Enhanced o/p non-enhanced o/p + // + /* 1 */ {{"foo: %s bar"}, {"abc"}, {"foo: abc bar", "foo: bar"}}, + /* 2 */ {{"foo: %s bar"}, {"a", "b"}, {"foo: a bar", "foo: bar"}}, + /* 3 */ {{"foo: %s bar"}, {null}, {"foo: null bar", "foo: bar"}}, + /* 4 */ {{"foo: %s bar"}, {""}, {"foo: bar", "foo: bar"}}, + /* 5 */ {{"%s foo: %s bar"}, {"a", "b"}, {"a foo: b bar", "foo: bar"}}, + /* 6 */ {{"foo: %s bar %s"}, {"a", "b"}, {"foo: a bar b", "foo: bar"}}, + /* 7 */ {{"foo: %s bar %s"}, {"abc", "def"}, {"foo: abc bar def", "foo: bar"}}, + /* 8 */ {{"%s bar %s"}, {"abc", ".pre(foo: )", "def"}, {"foo: abc bar def", "bar"}}, + /* 9 */ {{"%s baz"}, {"abc", ".suf(: bar)"}, {"abc: bar baz", "baz"}}, + /* 10 */ {{"%s baz"}, {"abc", ".suf(: bar)" + , ".rep(bob)"}, {"abc: bar baz", "bob baz"}} + }; + + + static void dnsTest() { + String host = "fub.z.a.bar.foo"; + try { + var addr = InetAddress.getByName(host); + } catch (IOException e) { + if (!dnsEnabled() && e.toString().contains(host)) + throw new RuntimeException("Name lookup failed"); + } + } + + static void hostFileTest() { + String result1 = "Unable to resolve host www.rte.ie as hosts file does.not.exist not found"; + String result2 = "Unable to resolve host as hosts file " + + "from ${jdk.net.hosts.file} system property not found"; + + try { + var a = InetAddress.getByName("www.rte.ie"); + } catch (IOException e) { + if (dnsEnabled() && !e.toString().contains(result1)) { + System.out.println("Lookup failed: " + e.toString()); + throw new RuntimeException("Name lookup failed"); + } + if (!dnsEnabled() && !e.toString().contains(result2)) { + System.out.println("Lookup failed: " + e.toString()); + throw new RuntimeException("Name lookup failed"); + } + } + } + + + final static String PRE = ".pre("; + final static String SUF = ".suf("; + final static String REP = ".rep("; + + static SensitiveInfo[] getArgs(String[] args) { + SensitiveInfo[] sa = new SensitiveInfo[args.length]; + + int index = 0; + for (String s : args) { + if (s != null && s.startsWith(PRE)) { + var preArg = s.substring(PRE.length(), s.indexOf(')')); + sa[index-1] = sa[index-1].prefixWith(preArg); + } else if (s != null && s.startsWith(SUF)) { + var sufArg = s.substring(SUF.length(), s.indexOf(')')); + sa[index-1] = sa[index-1].suffixWith(sufArg); + } else if (s != null && s.startsWith(REP)) { + var repArg = s.substring(REP.length(), s.indexOf(')')); + sa[index-1] = sa[index-1].replaceWith(repArg); + } else { + sa[index++] = filterNonSocketInfo(s); + } + } + return Arrays.copyOf(sa, index); + } + + public static void main(String[] a) { + if (!hostFileEnabled()) { + dnsTest(); + } else { + hostFileTest(); + return; + } + + int count = 1; + for (String[][] test : tests) { + String format = test[0][0]; + String expectedEnhanced = test[2][0]; + String expectedNormal = test[2][1]; + SensitiveInfo[] args = getArgs(test[1]); + + String output = formatMsg(format, args); + if (netEnabled()) { + if (!output.equals(expectedEnhanced)) { + var msg = String.format("FAIL %d: got: \"%s\" Expected: \"%s\"", count, + output, expectedEnhanced); + throw new RuntimeException(msg); + } + } else { + if (!output.equals(expectedNormal)) { + var msg = String.format("FAIL %d: got: \"%s\" Expected: \"%s\"", count, + output, expectedNormal); + throw new RuntimeException(msg); + } + } + count++; + } + } +} From 4604c86d2fced32c186680788ba98f74df071b23 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 3 Jun 2025 15:43:26 +0000 Subject: [PATCH 003/143] 8357425: (fs) SecureDirectoryStream setPermissions should use fchmodat Reviewed-by: alanb --- .../sun/nio/fs/UnixNativeDispatcher.java | 22 +++++- .../sun/nio/fs/UnixSecureDirectoryStream.java | 25 ++++--- .../native/libnio/fs/UnixNativeDispatcher.c | 23 +++++++ .../nio/file/DirectoryStream/SecureDS.java | 67 ++++++++++++++++++- 4 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java index 046f843371d..09611f23298 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -347,6 +347,18 @@ class UnixNativeDispatcher { } private static native void fchmod0(int fd, int mode) throws UnixException; + /** + * fchmodat(int fd, const char *path, mode_t mode, int flag) + */ + static void fchmodat(int fd, UnixPath path, int mode, int flag) + throws UnixException { + try (NativeBuffer buffer = copyToNativeBuffer(path)) { + fchmodat0(fd, buffer.address(), mode, flag); + } + } + private static native void fchmodat0(int fd, long pathAddress, int mode, int flag) + throws UnixException; + /** * futimens(int fildes, const struct timespec times[2]) */ @@ -558,6 +570,14 @@ class UnixNativeDispatcher { return (capabilities & SUPPORTS_XATTR) != 0; } + /** + * Supports fchmodat with AT_SYMLINK_NOFOLLOW flag + */ + static boolean fchmodatNoFollowSupported() { + return fchmodatNoFollowSupported0(); + } + private static native boolean fchmodatNoFollowSupported0(); + private static native int init(); static { jdk.internal.loader.BootLoader.loadLibrary("nio"); diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java index c583afb588a..5c0693870e6 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java @@ -414,15 +414,24 @@ class UnixSecureDirectoryStream if (!ds.isOpen()) throw new ClosedDirectoryStreamException(); - int fd = (file == null) ? dfd : open(); - try { - fchmod(fd, UnixFileModeAttribute.toUnixMode(perms)); - } catch (UnixException x) { - x.rethrowAsIOException(file); - } finally { - if (file != null && fd >= 0) - UnixNativeDispatcher.close(fd, e-> null); + int mode = UnixFileModeAttribute.toUnixMode(perms); + if (file == null) + fchmod(dfd, mode); + else if (followLinks) + fchmodat(dfd, file, mode, 0); + else if (fchmodatNoFollowSupported()) + fchmodat(dfd, file, mode, AT_SYMLINK_NOFOLLOW); + else { + int fd = open(); + try { + fchmod(fd, mode); + } finally { + if (fd >= 0) + UnixNativeDispatcher.close(fd, e-> null); + } } + } catch (UnixException x) { + x.rethrowAsIOException(file); } finally { ds.readLock().unlock(); } diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index fefaac94b48..fbf996886ae 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -398,6 +398,16 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this) return capabilities; } +JNIEXPORT jboolean JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_fchmodatNoFollowSupported0(JNIEnv* env, jclass this) { +#if defined(__linux__) + // Linux recognizes but does not support the AT_SYMLINK_NOFOLLOW flag + return JNI_FALSE; +#else + return JNI_TRUE; +#endif +} + JNIEXPORT jbyteArray JNICALL Java_sun_nio_fs_UnixNativeDispatcher_getcwd(JNIEnv* env, jclass this) { jbyteArray result = NULL; @@ -797,6 +807,19 @@ Java_sun_nio_fs_UnixNativeDispatcher_fchmod0(JNIEnv* env, jclass this, jint file } } +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_fchmodat0(JNIEnv* env, jclass this, + jint fd, jlong pathAddress, jint mode, jint flag) +{ + int err; + const char* path = (const char*)jlong_to_ptr(pathAddress); + + RESTARTABLE(fchmodat((int)fd, path, (mode_t)mode, (int)flag), err); + if (err == -1) { + throwUnixException(env, errno); + } +} + JNIEXPORT void JNICALL Java_sun_nio_fs_UnixNativeDispatcher_chown0(JNIEnv* env, jclass this, jlong pathAddress, jint uid, jint gid) diff --git a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java index e058d782c52..6199596b30c 100644 --- a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java +++ b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -22,10 +22,12 @@ */ /* @test - * @bug 4313887 6838333 8343020 + * @bug 4313887 6838333 8343020 8357425 * @summary Unit test for java.nio.file.SecureDirectoryStream * @requires (os.family == "linux" | os.family == "mac") - * @library .. + * @library .. /test/lib + * @build jdk.test.lib.Platform + * @run main SecureDS */ import java.nio.file.*; @@ -37,6 +39,8 @@ import java.nio.channels.*; import java.io.IOException; import java.util.*; +import jdk.test.lib.Platform; + public class SecureDS { static boolean supportsSymbolicLinks; @@ -54,6 +58,7 @@ public class SecureDS { // run tests doBasicTests(dir); doMoveTests(dir); + doSetPermissions(dir); miscTests(dir); } finally { @@ -172,6 +177,62 @@ public class SecureDS { delete(dir2); } + // Exercise setting permisions on the SecureDirectoryStream's view + static void doSetPermissions(Path dir) throws IOException { + Path aDir = createDirectory(dir.resolve("dir")); + + Set noperms = EnumSet.noneOf(PosixFilePermission.class); + Set permsDir = getPosixFilePermissions(aDir); + + try (SecureDirectoryStream stream = + (SecureDirectoryStream)newDirectoryStream(aDir);) { + + // Test setting permission on directory with no permissions + setPosixFilePermissions(aDir, noperms); + assertTrue(getPosixFilePermissions(aDir).equals(noperms)); + PosixFileAttributeView view = stream.getFileAttributeView(PosixFileAttributeView.class); + view.setPermissions(permsDir); + assertTrue(getPosixFilePermissions(aDir).equals(permsDir)); + + if (supportsSymbolicLinks) { + // Create a file and a link to the file + Path fileEntry = Path.of("file"); + Path file = createFile(aDir.resolve(fileEntry)); + Set permsFile = getPosixFilePermissions(file); + Path linkEntry = Path.of("link"); + Path link = createSymbolicLink(aDir.resolve(linkEntry), fileEntry); + Set permsLink = getPosixFilePermissions(link, NOFOLLOW_LINKS); + + // Test following link to file + view = stream.getFileAttributeView(link, PosixFileAttributeView.class); + view.setPermissions(noperms); + assertTrue(getPosixFilePermissions(file).equals(noperms)); + assertTrue(getPosixFilePermissions(link, NOFOLLOW_LINKS).equals(permsLink)); + view.setPermissions(permsFile); + assertTrue(getPosixFilePermissions(file).equals(permsFile)); + assertTrue(getPosixFilePermissions(link, NOFOLLOW_LINKS).equals(permsLink)); + + // Symbolic link permissions do not apply on Linux + if (!Platform.isLinux()) { + // Test not following link to file + view = stream.getFileAttributeView(link, PosixFileAttributeView.class, NOFOLLOW_LINKS); + view.setPermissions(noperms); + assertTrue(getPosixFilePermissions(file).equals(permsFile)); + assertTrue(getPosixFilePermissions(link, NOFOLLOW_LINKS).equals(noperms)); + view.setPermissions(permsLink); + assertTrue(getPosixFilePermissions(file).equals(permsFile)); + assertTrue(getPosixFilePermissions(link, NOFOLLOW_LINKS).equals(permsLink)); + } + + delete(link); + delete(file); + } + + // clean-up + delete(aDir); + } + } + // Exercise SecureDirectoryStream's move method static void doMoveTests(Path dir) throws IOException { Path dir1 = createDirectory(dir.resolve("dir1")); From d7def20afa045a0bb154655b02e56346f7f51347 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Tue, 3 Jun 2025 16:02:14 +0000 Subject: [PATCH 004/143] 8358448: JFR: Incorrect time unit for MethodTiming event Reviewed-by: mgronlun, ayang --- src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java index 3eb3116db55..cc1d8f681c7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java @@ -43,7 +43,7 @@ public final class MethodTimingEvent extends AbstractJDKEvent { public long invocations; @Label("Average Time") - @Timespan + @Timespan(Timespan.TICKS) public long average; public static void commit(long start, long method, long invocations, long average) { From 16e120b00842e340401b6930354edfb1515f6ca4 Mon Sep 17 00:00:00 2001 From: Patricio Chilano Mateo Date: Tue, 3 Jun 2025 16:12:53 +0000 Subject: [PATCH 005/143] 8357910: LoaderConstraintsTest.java fails when run with TEST_THREAD_FACTORY=Virtual Reviewed-by: dholmes, coleenp --- test/hotspot/jtreg/ProblemList-Virtual.txt | 1 - .../runtime/logging/LoaderConstraintsTest.java | 17 ++++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index cb5b92b96cf..dbd555c1cdd 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -92,7 +92,6 @@ gc/arguments/TestNewSizeThreadIncrease.java 0000000 generic-all gc/g1/TestSkipRebuildRemsetPhase.java 0000000 generic-all runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java 8346442 generic-all runtime/ErrorHandling/MachCodeFramesInErrorFile.java 0000000 generic-all -runtime/logging/LoaderConstraintsTest.java 8346442 generic-all runtime/Thread/AsyncExceptionOnMonitorEnter.java 0000000 generic-all runtime/Thread/StopAtExit.java 0000000 generic-all runtime/handshake/HandshakeWalkStackTest.java 0000000 generic-all diff --git a/test/hotspot/jtreg/runtime/logging/LoaderConstraintsTest.java b/test/hotspot/jtreg/runtime/logging/LoaderConstraintsTest.java index 39ad3b872a2..f1c493e5e93 100644 --- a/test/hotspot/jtreg/runtime/logging/LoaderConstraintsTest.java +++ b/test/hotspot/jtreg/runtime/logging/LoaderConstraintsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -38,6 +38,7 @@ import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import java.lang.ref.WeakReference; import java.lang.reflect.Method; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,22 +50,25 @@ public class LoaderConstraintsTest { private static ProcessBuilder pb; private static class ClassUnloadTestMain { public static void main(String... args) throws Exception { - String className = "test.Empty"; ClassLoader cl = ClassUnloadCommon.newClassLoader(); - Class c = cl.loadClass(className); - cl = null; c = null; - ClassUnloadCommon.triggerUnloading(); + Class c = cl.loadClass("test.Empty"); + // Causes class test.Empty to be linked, which triggers the + // constraint on class String due to override of toString(). + Constructor constructor = c.getDeclaredConstructor(); } } // Use the same command-line heap size setting as ../ClassUnload/UnloadTest.java static ProcessBuilder exec(String... args) throws Exception { + String classPath = System.getProperty("test.class.path", "."); + List argsList = new ArrayList<>(); Collections.addAll(argsList, args); Collections.addAll(argsList, "-Xmn8m"); Collections.addAll(argsList, "-Xbootclasspath/a:."); Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); Collections.addAll(argsList, "-XX:+WhiteBoxAPI"); + Collections.addAll(argsList, "-Dtest.class.path=" + classPath); Collections.addAll(argsList, ClassUnloadTestMain.class.getName()); return ProcessTools.createLimitedTestJavaProcessBuilder(argsList); } @@ -75,13 +79,12 @@ public class LoaderConstraintsTest { pb = exec("-Xlog:class+loader+constraints=info"); out = new OutputAnalyzer(pb.start()); out.shouldHaveExitValue(0); - out.shouldContain("[class,loader,constraints] adding new constraint for name: java/lang/Class, loader[0]: 'app', loader[1]: 'bootstrap'"); + out.stdoutShouldMatch("\\[class,loader,constraints\\] adding new constraint for name: java/lang/String, loader\\[0\\]: 'ClassUnloadCommonClassLoader' @[\\da-f]+, loader\\[1\\]: 'bootstrap'"); // -Xlog:class+loader+constraints=off pb = exec("-Xlog:class+loader+constraints=off"); out = new OutputAnalyzer(pb.start()); out.shouldHaveExitValue(0); out.shouldNotContain("[class,loader,constraints]"); - } } From 04c15466f68f1208084ee6e5f2322ace707d0446 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Tue, 3 Jun 2025 16:13:14 +0000 Subject: [PATCH 006/143] 8358095: Cleanup tests with explicit locale provider set to only CLDR Reviewed-by: bpb, naoto --- .../Format/NumberFormat/CurrencyFormat.java | 4 +- test/jdk/java/util/Calendar/Bug8007038.java | 4 +- test/jdk/java/util/Calendar/Bug8167273.java | 4 +- .../util/Calendar/CldrFormatNamesTest.java | 4 +- .../Calendar/GenericTimeZoneNamesTest.java | 7 +--- .../util/Calendar/JapaneseEraNameTest.java | 5 +-- .../java/util/Calendar/NarrowNamesTest.java | 4 +- .../util/Formatter/BasicTestLauncher.java | 7 ++-- .../AliasesShouldBeRecognizedInCLDR.java | 4 +- .../Locale/RequiredAvailableLocalesTest.java | 4 +- .../Locale/bcp47u/CurrencyFormatTests.java | 4 +- .../util/Locale/bcp47u/DisplayNameTests.java | 4 +- .../java/util/Locale/bcp47u/FormatTests.java | 4 +- .../java/util/Locale/bcp47u/SymbolsTests.java | 4 +- test/jdk/java/util/TimeZone/Bug8167143.java | 42 +++++++++---------- .../util/TimeZone/CLDRDisplayNamesTest.java | 2 +- .../TimeZone/ChineseTimeZoneNameTest.java | 4 +- .../sun/util/resources/cldr/Bug8134250.java | 4 +- .../sun/util/resources/cldr/Bug8134384.java | 2 +- .../cldr/LikelySubtagLocalesTest.java | 2 +- .../resources/cldr/TimeZoneNamesTest.java | 2 +- 21 files changed, 58 insertions(+), 63 deletions(-) diff --git a/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java b/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java index fa50ecde2aa..6d7a906e394 100644 --- a/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java +++ b/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -28,7 +28,7 @@ * @summary Basic tests for currency formatting. * Tests both COMPAT and CLDR data. * @modules jdk.localedata - * @run junit/othervm -Djava.locale.providers=CLDR CurrencyFormat + * @run junit CurrencyFormat */ import java.io.File; diff --git a/test/jdk/java/util/Calendar/Bug8007038.java b/test/jdk/java/util/Calendar/Bug8007038.java index 874aaf10922..310c3e8c2a0 100644 --- a/test/jdk/java/util/Calendar/Bug8007038.java +++ b/test/jdk/java/util/Calendar/Bug8007038.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -29,7 +29,7 @@ * @modules java.base/sun.util.locale.provider * @modules jdk.localedata * @compile -XDignore.symbol.file Bug8007038.java - * @run main/othervm -Djava.locale.providers=CLDR Bug8007038 CLDR + * @run main Bug8007038 CLDR */ import java.util.*; diff --git a/test/jdk/java/util/Calendar/Bug8167273.java b/test/jdk/java/util/Calendar/Bug8167273.java index e4de858d4da..de9a72f5dc8 100644 --- a/test/jdk/java/util/Calendar/Bug8167273.java +++ b/test/jdk/java/util/Calendar/Bug8167273.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -35,7 +35,7 @@ * java.base/sun.util.spi * jdk.localedata * @run main Bug8167273 testEraName - * @run main/othervm -Djava.locale.providers=CLDR Bug8167273 testCldr + * @run main Bug8167273 testCldr * @run main Bug8167273 testEmptyEraNames */ import java.text.DateFormatSymbols; diff --git a/test/jdk/java/util/Calendar/CldrFormatNamesTest.java b/test/jdk/java/util/Calendar/CldrFormatNamesTest.java index ea6db600640..36369bef8a7 100644 --- a/test/jdk/java/util/Calendar/CldrFormatNamesTest.java +++ b/test/jdk/java/util/Calendar/CldrFormatNamesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -28,7 +28,7 @@ * @modules java.base/sun.util.locale.provider * jdk.localedata * @compile -XDignore.symbol.file CldrFormatNamesTest.java - * @run main/othervm -Djava.locale.providers=CLDR CldrFormatNamesTest + * @run main CldrFormatNamesTest */ import java.util.*; diff --git a/test/jdk/java/util/Calendar/GenericTimeZoneNamesTest.java b/test/jdk/java/util/Calendar/GenericTimeZoneNamesTest.java index 17fd09d4458..4c0e6c73585 100644 --- a/test/jdk/java/util/Calendar/GenericTimeZoneNamesTest.java +++ b/test/jdk/java/util/Calendar/GenericTimeZoneNamesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -26,12 +26,9 @@ * @bug 8003267 * @summary Unit test for generic time zone names support. This test is locale * data-dependent and assumes that both JRE and CLDR have the same - * geneic time zone names in English. + * generic time zone names in English. * @modules java.base/sun.util.locale.provider - * @comment Locale providers: default * @run main GenericTimeZoneNamesTest en-US - * @comment Locale providers: CLDR - * @run main/othervm -Djava.locale.providers=CLDR GenericTimeZoneNamesTest en-US */ import java.util.Locale; diff --git a/test/jdk/java/util/Calendar/JapaneseEraNameTest.java b/test/jdk/java/util/Calendar/JapaneseEraNameTest.java index 8fccfe7bb16..11e35927adf 100644 --- a/test/jdk/java/util/Calendar/JapaneseEraNameTest.java +++ b/test/jdk/java/util/Calendar/JapaneseEraNameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -27,8 +27,7 @@ * @summary Test the localized Japanese new era name (May 1st. 2019-) * is retrieved no matter CLDR provider contains the name or not. * @modules jdk.localedata - * @run testng/othervm JapaneseEraNameTest - * @run testng/othervm -Djava.locale.providers=CLDR JapaneseEraNameTest + * @run testng JapaneseEraNameTest */ import static java.util.Calendar.*; diff --git a/test/jdk/java/util/Calendar/NarrowNamesTest.java b/test/jdk/java/util/Calendar/NarrowNamesTest.java index ab79e339444..2be43fdc1c0 100644 --- a/test/jdk/java/util/Calendar/NarrowNamesTest.java +++ b/test/jdk/java/util/Calendar/NarrowNamesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -31,7 +31,7 @@ * @comment Locale providers: CLDR,SPI * @run main/othervm -Djava.locale.providers=CLDR,SPI NarrowNamesTest CLDR,SPI * @comment Locale providers: CLDR - * @run main/othervm -Djava.locale.providers=CLDR NarrowNamesTest CLDR + * @run main NarrowNamesTest CLDR */ import java.time.LocalDateTime; diff --git a/test/jdk/java/util/Formatter/BasicTestLauncher.java b/test/jdk/java/util/Formatter/BasicTestLauncher.java index 4dfc0e36743..3fa35c901cb 100644 --- a/test/jdk/java/util/Formatter/BasicTestLauncher.java +++ b/test/jdk/java/util/Formatter/BasicTestLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -58,8 +58,7 @@ import org.junit.jupiter.params.provider.ValueSource; * @run junit BasicTestLauncher */ public class BasicTestLauncher { - // Locale flag for testJVM - private static final String JAVA_OPTS = "-Djava.locale.providers=CLDR"; + // Test class private static final String TEST_CLASS = "Basic"; @@ -82,7 +81,7 @@ public class BasicTestLauncher { */ private static OutputAnalyzer RunTest(String timeZone) throws IOException{ // Build and run Basic class with correct configuration - ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(JAVA_OPTS, TEST_CLASS); + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(TEST_CLASS); pb.environment().put("TZ", timeZone); Process process = pb.start(); return new OutputAnalyzer(process); diff --git a/test/jdk/java/util/Locale/AliasesShouldBeRecognizedInCLDR.java b/test/jdk/java/util/Locale/AliasesShouldBeRecognizedInCLDR.java index fa41aeea07c..5af83062fba 100644 --- a/test/jdk/java/util/Locale/AliasesShouldBeRecognizedInCLDR.java +++ b/test/jdk/java/util/Locale/AliasesShouldBeRecognizedInCLDR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -26,7 +26,7 @@ * @bug 8179071 8202537 8231273 8251317 * @summary Test that language aliases of CLDR supplemental metadata are handled correctly. * @modules jdk.localedata - * @run junit/othervm -Djava.locale.providers=CLDR AliasesShouldBeRecognizedInCLDR + * @run junit AliasesShouldBeRecognizedInCLDR */ /* diff --git a/test/jdk/java/util/Locale/RequiredAvailableLocalesTest.java b/test/jdk/java/util/Locale/RequiredAvailableLocalesTest.java index a1d6edd5d70..017e8af0fde 100644 --- a/test/jdk/java/util/Locale/RequiredAvailableLocalesTest.java +++ b/test/jdk/java/util/Locale/RequiredAvailableLocalesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -25,7 +25,7 @@ * @bug 8276186 8174269 * @summary Checks whether getAvailableLocales() returns at least Locale.ROOT and * Locale.US instances. - * @run testng/othervm -Djava.locale.providers=CLDR RequiredAvailableLocalesTest + * @run testng RequiredAvailableLocalesTest */ import java.lang.invoke.MethodHandles; diff --git a/test/jdk/java/util/Locale/bcp47u/CurrencyFormatTests.java b/test/jdk/java/util/Locale/bcp47u/CurrencyFormatTests.java index d3e14fb0d5a..feb52a49df5 100644 --- a/test/jdk/java/util/Locale/bcp47u/CurrencyFormatTests.java +++ b/test/jdk/java/util/Locale/bcp47u/CurrencyFormatTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -27,7 +27,7 @@ * @bug 8215181 8230284 8231273 8284840 * @summary Tests the "u-cf" extension * @modules jdk.localedata - * @run testng/othervm -Djava.locale.providers=CLDR CurrencyFormatTests + * @run testng CurrencyFormatTests */ import static org.testng.Assert.assertEquals; diff --git a/test/jdk/java/util/Locale/bcp47u/DisplayNameTests.java b/test/jdk/java/util/Locale/bcp47u/DisplayNameTests.java index 11c4272f160..1f1a4d3e8ca 100644 --- a/test/jdk/java/util/Locale/bcp47u/DisplayNameTests.java +++ b/test/jdk/java/util/Locale/bcp47u/DisplayNameTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -27,7 +27,7 @@ * @bug 8176841 8202537 * @summary Tests the display names for BCP 47 U extensions * @modules jdk.localedata - * @run testng/othervm -Djava.locale.providers=CLDR DisplayNameTests + * @run testng DisplayNameTests */ import static org.testng.Assert.assertEquals; diff --git a/test/jdk/java/util/Locale/bcp47u/FormatTests.java b/test/jdk/java/util/Locale/bcp47u/FormatTests.java index 774d1e3dee1..3e4b0bf5ea0 100644 --- a/test/jdk/java/util/Locale/bcp47u/FormatTests.java +++ b/test/jdk/java/util/Locale/bcp47u/FormatTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -28,7 +28,7 @@ * @summary Tests *Format class deals with Unicode extensions * correctly. * @modules jdk.localedata - * @run testng/othervm -Djava.locale.providers=CLDR FormatTests + * @run testng FormatTests */ import static org.testng.Assert.assertEquals; diff --git a/test/jdk/java/util/Locale/bcp47u/SymbolsTests.java b/test/jdk/java/util/Locale/bcp47u/SymbolsTests.java index 2a8e8bf8e1e..451eac4c050 100644 --- a/test/jdk/java/util/Locale/bcp47u/SymbolsTests.java +++ b/test/jdk/java/util/Locale/bcp47u/SymbolsTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -28,7 +28,7 @@ * @summary Tests *FormatSymbols class deals with Unicode extensions * correctly. * @modules jdk.localedata - * @run testng/othervm -Djava.locale.providers=CLDR SymbolsTests + * @run testng SymbolsTests */ import static org.testng.Assert.assertEquals; diff --git a/test/jdk/java/util/TimeZone/Bug8167143.java b/test/jdk/java/util/TimeZone/Bug8167143.java index b8ce459a5de..fb94c94214f 100644 --- a/test/jdk/java/util/TimeZone/Bug8167143.java +++ b/test/jdk/java/util/TimeZone/Bug8167143.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -35,7 +35,7 @@ * java.base/sun.util.spi * jdk.localedata * @run main Bug8167143 testTimeZone - * @run main/othervm -Djava.locale.providers=CLDR Bug8167143 testCldr + * @run main Bug8167143 testCldr * @run main Bug8167143 testCache * @run main Bug8167143 testCandidateLocales */ @@ -236,40 +236,40 @@ public class Bug8167143 { private static void testImplicitCompatLocales() { LocaleProviderAdapter jre = LocaleProviderAdapter.forJRE(); checkPresenceCompat("BreakIteratorProvider", - jre.getBreakIteratorProvider().getAvailableLocales()); + jre.getBreakIteratorProvider().getAvailableLocales()); checkPresenceCompat("CollatorProvider", - jre.getCollatorProvider().getAvailableLocales()); + jre.getCollatorProvider().getAvailableLocales()); checkPresenceCompat("DateFormatProvider", - jre.getDateFormatProvider().getAvailableLocales()); + jre.getDateFormatProvider().getAvailableLocales()); checkPresenceCompat("DateFormatSymbolsProvider", - jre.getDateFormatSymbolsProvider().getAvailableLocales()); + jre.getDateFormatSymbolsProvider().getAvailableLocales()); checkPresenceCompat("DecimalFormatSymbolsProvider", - jre.getDecimalFormatSymbolsProvider().getAvailableLocales()); + jre.getDecimalFormatSymbolsProvider().getAvailableLocales()); checkPresenceCompat("NumberFormatProvider", - jre.getNumberFormatProvider().getAvailableLocales()); + jre.getNumberFormatProvider().getAvailableLocales()); checkPresenceCompat("CurrencyNameProvider", - jre.getCurrencyNameProvider().getAvailableLocales()); + jre.getCurrencyNameProvider().getAvailableLocales()); checkPresenceCompat("LocaleNameProvider", - jre.getLocaleNameProvider().getAvailableLocales()); + jre.getLocaleNameProvider().getAvailableLocales()); checkPresenceCompat("TimeZoneNameProvider", - jre.getTimeZoneNameProvider().getAvailableLocales()); + jre.getTimeZoneNameProvider().getAvailableLocales()); checkPresenceCompat("CalendarDataProvider", - jre.getCalendarDataProvider().getAvailableLocales()); + jre.getCalendarDataProvider().getAvailableLocales()); checkPresenceCompat("CalendarNameProvider", - jre.getCalendarNameProvider().getAvailableLocales()); + jre.getCalendarNameProvider().getAvailableLocales()); checkPresenceCompat("CalendarProvider", - jre.getCalendarProvider().getAvailableLocales()); + jre.getCalendarProvider().getAvailableLocales()); } private static void checkPresenceCompat(String testName, Locale[] got) { List gotLocalesList = Arrays.asList(got); List gotList = new ArrayList<>(gotLocalesList); - if (!gotList.removeAll(COMPAT_IMPLICIT_LOCS)) { - // check which Implicit locale are not present in retrievedLocales List. - List implicitLocales = new ArrayList<>(COMPAT_IMPLICIT_LOCS); - implicitLocales.removeAll(gotList); - throw new RuntimeException("Locales those not correctly reflected are " - + implicitLocales + " for test " + testName); - } + if (!gotList.removeAll(COMPAT_IMPLICIT_LOCS)) { + // check which Implicit locale are not present in retrievedLocales List. + List implicitLocales = new ArrayList<>(COMPAT_IMPLICIT_LOCS); + implicitLocales.removeAll(gotList); + throw new RuntimeException("Locales those not correctly reflected are " + + implicitLocales + " for test " + testName); + } } } diff --git a/test/jdk/java/util/TimeZone/CLDRDisplayNamesTest.java b/test/jdk/java/util/TimeZone/CLDRDisplayNamesTest.java index c1200445407..b6c80b6d7d0 100644 --- a/test/jdk/java/util/TimeZone/CLDRDisplayNamesTest.java +++ b/test/jdk/java/util/TimeZone/CLDRDisplayNamesTest.java @@ -26,7 +26,7 @@ * @bug 8005471 8008577 8129881 8130845 8136518 8181157 8210490 8220037 * 8234347 8236548 8317979 * @modules jdk.localedata - * @run main/othervm -Djava.locale.providers=CLDR CLDRDisplayNamesTest + * @run main CLDRDisplayNamesTest * @summary Make sure that localized time zone names of CLDR are used * if specified. */ diff --git a/test/jdk/sun/util/resources/TimeZone/ChineseTimeZoneNameTest.java b/test/jdk/sun/util/resources/TimeZone/ChineseTimeZoneNameTest.java index f874da18df3..335d147f9ab 100644 --- a/test/jdk/sun/util/resources/TimeZone/ChineseTimeZoneNameTest.java +++ b/test/jdk/sun/util/resources/TimeZone/ChineseTimeZoneNameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -26,7 +26,7 @@ * @bug 8275721 8174269 * @modules jdk.localedata * @summary Checks Chinese time zone names for `UTC` using CLDR are consistent - * @run testng/othervm -Djava.locale.providers=CLDR ChineseTimeZoneNameTest + * @run testng ChineseTimeZoneNameTest */ import java.time.Instant; diff --git a/test/jdk/sun/util/resources/cldr/Bug8134250.java b/test/jdk/sun/util/resources/cldr/Bug8134250.java index d38fce53352..bbc385cb870 100644 --- a/test/jdk/sun/util/resources/cldr/Bug8134250.java +++ b/test/jdk/sun/util/resources/cldr/Bug8134250.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, 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 @@ -26,7 +26,7 @@ * @bug 8134250 8134520 * @modules jdk.localedata * @summary Tests CLDR/LDML features are correctly reflected in JDK. - * @run main/othervm -Djava.locale.providers=CLDR Bug8134250 + * @run main Bug8134250 */ // Note this test highly depends on a particular version of CLDR. Results diff --git a/test/jdk/sun/util/resources/cldr/Bug8134384.java b/test/jdk/sun/util/resources/cldr/Bug8134384.java index f27b587ca98..17f41ba4c8d 100644 --- a/test/jdk/sun/util/resources/cldr/Bug8134384.java +++ b/test/jdk/sun/util/resources/cldr/Bug8134384.java @@ -25,7 +25,7 @@ * @test * @bug 8134384 8234347 8236548 8347841 * @summary Tests CLDR TimeZoneNames has English names for all tzids - * @run main/othervm -Djava.locale.providers=CLDR Bug8134384 + * @run main Bug8134384 */ import java.text.*; diff --git a/test/jdk/sun/util/resources/cldr/LikelySubtagLocalesTest.java b/test/jdk/sun/util/resources/cldr/LikelySubtagLocalesTest.java index 0e66db5c5da..c717e099f06 100644 --- a/test/jdk/sun/util/resources/cldr/LikelySubtagLocalesTest.java +++ b/test/jdk/sun/util/resources/cldr/LikelySubtagLocalesTest.java @@ -26,7 +26,7 @@ * @bug 8145136 8202537 8221432 8251317 8258794 8265315 8306116 8346948 * @modules jdk.localedata * @summary Tests LikelySubtags is correctly reflected in Locale.getAvailableLocales(). - * @run junit/othervm -Djava.locale.providers=CLDR LikelySubtagLocalesTest + * @run junit LikelySubtagLocalesTest */ import java.util.Arrays; import java.util.List; diff --git a/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java b/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java index 4fe1a7d25a9..ed6fb2a58d8 100644 --- a/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java +++ b/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java @@ -27,7 +27,7 @@ * @modules jdk.localedata * @summary Checks CLDR time zone names are generated correctly at * either build or runtime - * @run testng/othervm -Djava.locale.providers=CLDR TimeZoneNamesTest + * @run testng TimeZoneNamesTest */ import java.text.DateFormatSymbols; From 57862005f9914ce60aa389a6e35d2e0cd38f8c35 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 3 Jun 2025 16:32:12 +0000 Subject: [PATCH 007/143] 8354450: A File should be invalid if an element of its name sequence ends with a space Reviewed-by: alanb --- .../classes/java/io/WinNTFileSystem.java | 25 +++--- test/jdk/java/io/File/WinTrailingSpace.java | 83 +++++++++++++++++++ 2 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 test/jdk/java/io/File/WinTrailingSpace.java diff --git a/src/java.base/windows/classes/java/io/WinNTFileSystem.java b/src/java.base/windows/classes/java/io/WinNTFileSystem.java index 56aa27c004d..e053f7f004c 100644 --- a/src/java.base/windows/classes/java/io/WinNTFileSystem.java +++ b/src/java.base/windows/classes/java/io/WinNTFileSystem.java @@ -365,27 +365,28 @@ final class WinNTFileSystem extends FileSystem { @Override public boolean isInvalid(File f) { - if (f.getPath().indexOf('\u0000') >= 0) + final String pathname = f.getPath(); + + // Invalid if the pathname string contains a null character or if + // any name in the pathname's name sequence ends with a space + if (pathname.indexOf('\u0000') >= 0 || pathname.endsWith(" ") + || pathname.contains(" \\")) return true; + // The remaining checks are irrelevant for alternate data streams (ADS) if (ENABLE_ADS) return false; - // Invalid if there is a ":" at a position greater than 1, or if there + // Invalid if there is a ":" at a position other than 1, or if there // is a ":" at position 1 and the first character is not a letter - String pathname = f.getPath(); int lastColon = pathname.lastIndexOf(":"); + if (lastColon >= 0 && + (lastColon != 1 || !isLetter(pathname.charAt(0)))) + return true; - // Valid if there is no ":" present or if the last ":" present is - // at index 1 and the first character is a latter - if (lastColon < 0 || - (lastColon == 1 && isLetter(pathname.charAt(0)))) - return false; - - // Invalid if path creation fails - Path path = null; + // Invalid if the path string cannot be converted to a Path try { - path = sun.nio.fs.DefaultFileSystemProvider.theFileSystem().getPath(pathname); + Path path = sun.nio.fs.DefaultFileSystemProvider.theFileSystem().getPath(pathname); return false; } catch (InvalidPathException ignored) { } diff --git a/test/jdk/java/io/File/WinTrailingSpace.java b/test/jdk/java/io/File/WinTrailingSpace.java new file mode 100644 index 00000000000..1997a867195 --- /dev/null +++ b/test/jdk/java/io/File/WinTrailingSpace.java @@ -0,0 +1,83 @@ +/* + * 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. + */ + +import java.io.File; +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* @test + * @bug 8354450 + * @requires os.family == "windows" + * @summary Verify behavior for file names with a trailing space + * @run junit WinTrailingSpace + */ +public class WinTrailingSpace { + private static final String FILENAME_NO_TRAILING_SPACE = "foobargus"; + private static final String FILENAME_TRAILING_SPACE = "foobargus "; + private static final String DIRNAME_TRAILING_SPACE = "foo \\bar\\gus"; + + @Test + public void noTrailingSpace() throws IOException { + File f = null; + try { + f = new File(".", FILENAME_NO_TRAILING_SPACE); + f.delete(); + f.createNewFile(); + assertTrue(f.exists()); + } finally { + if (f != null) + f.delete(); + } + } + + @Test + public void trailingSpace() throws IOException { + File f = null; + try { + f = new File(".", FILENAME_TRAILING_SPACE); + f.delete(); + f.createNewFile(); + assertFalse(f.exists(), "Creation of " + f + " should have failed"); + } catch (IOException expected) { + } finally { + if (f != null) + f.delete(); + } + } + + @Test + public void dirTrailingSpace() throws IOException { + File f = null; + try { + f = new File(".", DIRNAME_TRAILING_SPACE); + f.delete(); + assertFalse(f.mkdirs(), "Creation of " + f + " should have failed"); + } finally { + if (f != null) + f.delete(); + } + } +} From d7e58ac480b06c6340a65e67731d8f6dc179acfb Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Tue, 3 Jun 2025 17:00:54 +0000 Subject: [PATCH 008/143] 8351635: C2 ROR/ROL: assert failed: Long constant expected Reviewed-by: thartmann, chagedorn --- src/hotspot/share/opto/vectornode.cpp | 3 +- .../TestVectorRotateScalarCount.java | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestVectorRotateScalarCount.java diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 05ef64af704..2b40ca77198 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1722,8 +1722,7 @@ Node* VectorNode::degenerate_vector_rotate(Node* src, Node* cnt, bool is_rotate_ if (cnt->Opcode() == Op_ConvI2L) { cnt = cnt->in(1); } else { - assert(cnt->bottom_type()->isa_long() && - cnt->bottom_type()->is_long()->is_con(), "Long constant expected"); + assert(cnt->bottom_type()->isa_long(), "long type shift count expected"); cnt = phase->transform(new ConvL2INode(cnt)); } } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorRotateScalarCount.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorRotateScalarCount.java new file mode 100644 index 00000000000..f9b14b9f130 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorRotateScalarCount.java @@ -0,0 +1,127 @@ +/* + * 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 compiler.vectorapi; + +import compiler.lib.ir_framework.*; +import compiler.lib.verify.Verify; +import jdk.incubator.vector.*; + +/* + * @test + * @bug 8351635 + * @summary Test missing pattern in vector rotate generation + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver compiler.vectorapi.TestVectorRotateScalarCount + */ +public class TestVectorRotateScalarCount { + public static void main(String[] args) { + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", "-XX:+IgnoreUnrecognizedVMOptions", "-XX:UseAVX=2"); + } + + public static long long_shift = 31L; + public static int int_shift = 12; + + static final Object GOLD_PATTERN1a = pattern1a(); + static final Object GOLD_PATTERN1b = pattern1b(); + static final Object GOLD_PATTERN2 = pattern2(); + static final Object GOLD_PATTERN3 = pattern3(); + static final Object GOLD_PATTERN4 = pattern4(); + + @Test + @IR(counts = {IRNode.URSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.LSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.OR_VL, IRNode.VECTOR_SIZE_2, "1"}, + applyIfCPUFeatureAnd = {"avx2", "true", "avx512f", "false", "avx512vl", "false"}) + public static Object pattern1a() { + LongVector lv1 = LongVector.broadcast(LongVector.SPECIES_128, 1); + long x = Long.divideUnsigned(long_shift, Long.MIN_VALUE); + return lv1.lanewise(VectorOperators.ROL, x); + } + + @Check(test = "pattern1a") + public static void check_pattern1a(Object param) { + Verify.checkEQ(GOLD_PATTERN1a, param); + } + + @Test + @IR(counts = {IRNode.URSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.LSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.OR_VL, IRNode.VECTOR_SIZE_2, "1"}, + applyIfCPUFeatureAnd = {"avx2", "true", "avx512f", "false", "avx512vl", "false"}) + public static Object pattern1b() { + LongVector lv1 = LongVector.broadcast(LongVector.SPECIES_128, 1); + long x = Long.min(32, Long.max(Long.reverse(long_shift), 0)); + return lv1.lanewise(VectorOperators.ROR, x); + } + + @Check(test = "pattern1b") + public static void check_pattern1b(Object param) { + Verify.checkEQ(GOLD_PATTERN1b, param); + } + + @Test + @IR(counts = {IRNode.URSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.LSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.OR_VL, IRNode.VECTOR_SIZE_2, "1"}, + applyIfCPUFeatureAnd = {"avx2", "true", "avx512f", "false", "avx512vl", "false"}) + public static Object pattern2() { + LongVector lv1 = LongVector.broadcast(LongVector.SPECIES_128, 1); + return lv1.lanewise(VectorOperators.ROL, int_shift); + } + + @Check(test = "pattern2") + public static void check_pattern2(Object param) { + Verify.checkEQ(GOLD_PATTERN2, param); + } + + @Test + @IR(counts = {IRNode.URSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.LSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.OR_VL, IRNode.VECTOR_SIZE_2, "1"}, + applyIfCPUFeatureAnd = {"avx2", "true", "avx512f", "false", "avx512vl", "false"}) + public static Object pattern3() { + LongVector lv1 = LongVector.broadcast(LongVector.SPECIES_128, 1); + return lv1.lanewise(VectorOperators.ROL, lv1); + } + + @Check(test = "pattern3") + public static void check_pattern3(Object param) { + Verify.checkEQ(GOLD_PATTERN3, param); + } + @Test + @IR(counts = {IRNode.URSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.LSHIFT_VL, IRNode.VECTOR_SIZE_2, "1", + IRNode.OR_VL, IRNode.VECTOR_SIZE_2, "1"}, + applyIfCPUFeatureAnd = {"avx2", "true", "avx512f", "false", "avx512vl", "false"}) + public static Object pattern4() { + LongVector lv1 = LongVector.broadcast(LongVector.SPECIES_128, 1); + return lv1.lanewise(VectorOperators.ROL, 15L); + } + + @Check(test = "pattern4") + public static void check_pattern4(Object param) { + Verify.checkEQ(GOLD_PATTERN4, param); + } +} From 44d62c8e21fb09381f9f86a081f70549cc321b1e Mon Sep 17 00:00:00 2001 From: Larry Cable Date: Tue, 3 Jun 2025 17:13:22 +0000 Subject: [PATCH 009/143] 8358077: sun.tools.attach.VirtualMachineImpl::checkCatchesAndSendQuitTo on Linux leaks file handles after JDK-8327114 Reviewed-by: kevinw, sspitsyn, syan --- .../classes/sun/tools/attach/VirtualMachineImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java index 820af57ec37..af8870ecf64 100644 --- a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java @@ -35,6 +35,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; + import java.util.regex.Pattern; import static java.nio.charset.StandardCharsets.UTF_8; @@ -358,7 +360,11 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { if (okToSendQuit) { sendQuitTo(pid); } else if (throwIfNotReady) { - final var cmdline = Files.lines(procPid.resolve("cmdline")).findFirst(); + Optional cmdline = Optional.empty(); + + try (final var clf = Files.lines(procPid.resolve("cmdline"))) { + cmdline = clf.findFirst(); + } var cmd = "null"; // default From c382da579884c28f2765b2c6ba68c0ad4fdcb2ce Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Tue, 3 Jun 2025 17:19:31 +0000 Subject: [PATCH 010/143] 8358178: Some nsk/jdi tests should be run with includevirtualthreads=y even though they pass without Reviewed-by: sspitsyn, lmesnik --- .../nsk/jdi/EventSet/resume/resume001/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume002/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume003/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume004/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume005/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume006/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume007/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume008/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume009/TestDescription.java | 3 ++- .../nsk/jdi/EventSet/resume/resume010/TestDescription.java | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume001/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume001/TestDescription.java index 5c86dc74fbc..87d47222fc1 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume001/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume001/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -66,6 +66,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume001 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume002/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume002/TestDescription.java index 5d79a05338e..b2bafcff62d 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume002/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume002/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume002 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume003/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume003/TestDescription.java index d19aff7885e..7e36d18e21e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume003/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume003/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume003 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume004/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume004/TestDescription.java index d0c405c19e8..e435cd78796 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume004/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume004/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume004 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume005/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume005/TestDescription.java index eef8e00a5d4..6a41ca7ebff 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume005/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume005/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -66,6 +66,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume005 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume006/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume006/TestDescription.java index 67536af919a..74a92b22a7c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume006/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume006/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume006 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume007/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume007/TestDescription.java index 61239470b7d..fc708b63343 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume007/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume007/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume007 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume008/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume008/TestDescription.java index 3a331640279..498fe9ba1da 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume008/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume008/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -66,6 +66,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume008 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume009/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume009/TestDescription.java index bd8e7cf0977..e030264986e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume009/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume009/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -66,6 +66,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume009 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume010/TestDescription.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume010/TestDescription.java index 15abd8db247..ed2a97533e3 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume010/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventSet/resume/resume010/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -65,6 +65,7 @@ * @run driver * nsk.jdi.EventSet.resume.resume010 * -verbose + * -includevirtualthreads * -arch=${os.family}-${os.simpleArch} * -waittime=5 * -debugee.vmkind=java From 461cb84277b40d01c5d04be3c74f25d8667a207c Mon Sep 17 00:00:00 2001 From: Alisen Chung Date: Tue, 3 Jun 2025 18:02:47 +0000 Subject: [PATCH 011/143] 8345538: Robot.mouseMove doesn't clamp bounds on macOS when trying to move mouse off screen Reviewed-by: honkar, prr --- .../classes/sun/lwawt/macosx/CRobot.java | 40 ++++++++- .../java/awt/Robot/MouseMoveOffScreen.java | 83 +++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/awt/Robot/MouseMoveOffScreen.java diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java index 5dba06cb612..036da04a7cb 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CRobot.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -25,6 +25,8 @@ package sun.lwawt.macosx; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.peer.RobotPeer; @@ -63,7 +65,41 @@ final class CRobot implements RobotPeer { mouseLastX = x; mouseLastY = y; - mouseEvent(mouseLastX, mouseLastY, mouseButtonsState, true, true); + int leastDiff = Integer.MAX_VALUE; + int finX = x; + int finY = y; + + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] gs = ge.getScreenDevices(); + Rectangle[] allScreenBounds = new Rectangle[gs.length]; + + for (int i = 0; i < gs.length; i++) { + allScreenBounds[i] = gs[i].getDefaultConfiguration().getBounds(); + } + + for (Rectangle screenBounds : allScreenBounds) { + Point cP = calcClosestPoint(x, y, screenBounds); + + int currXDiff = Math.abs(x - cP.x); + int currYDiff = Math.abs(y - cP.y); + int currDiff = (int) Math.round(Math.hypot(currXDiff, currYDiff)); + + if (currDiff == 0) { + mouseEvent(mouseLastX, mouseLastY, mouseButtonsState, true, true); + return; + } if (currDiff < leastDiff) { + finX = cP.x; + finY = cP.y; + leastDiff = currDiff; + } + } + + mouseEvent(finX, finY, mouseButtonsState, true, true); + } + + private Point calcClosestPoint(int x, int y, Rectangle screenBounds) { + return new Point(Math.min(Math.max(x, screenBounds.x), screenBounds.x + screenBounds.width - 1), + Math.min(Math.max(y, screenBounds.y), screenBounds.y + screenBounds.height - 1)); } /** diff --git a/test/jdk/java/awt/Robot/MouseMoveOffScreen.java b/test/jdk/java/awt/Robot/MouseMoveOffScreen.java new file mode 100644 index 00000000000..809e3dd1ff1 --- /dev/null +++ b/test/jdk/java/awt/Robot/MouseMoveOffScreen.java @@ -0,0 +1,83 @@ +/* + * 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. + */ + +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; + +/* + * @test + * @bug 8345538 + * @summary Tests mouseMove clamping to screen bounds when set to move offscreen + * @requires (os.family == "mac") + * @key headful + * @run main MouseMoveOffScreen + */ + +public class MouseMoveOffScreen { + private static final Point STARTING_LOC = new Point(200, 200); + private static final Point OFF_SCREEN_LOC = new Point(20000, 200); + private static Rectangle[] r; + + public static void main(String[] args) throws Exception { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] gs = ge.getScreenDevices(); + r = new Rectangle[gs.length]; + + for (int i = 0; i < gs.length; i++) { + r[i] = gs[i].getDefaultConfiguration().getBounds(); + System.out.println("Screen: "+ gs[i].getIDstring() + " Bounds: " + r[i]); + } + + Point offsc = validateOffScreen(OFF_SCREEN_LOC); + Robot robot = new Robot(); + robot.mouseMove(STARTING_LOC.x, STARTING_LOC.y); + robot.delay(500); + robot.mouseMove(offsc.x, offsc.y); + robot.delay(500); + + Point currLoc = MouseInfo.getPointerInfo().getLocation(); + + if (currLoc == null) { + throw new RuntimeException("Test Failed, getLocation returned null."); + } + + System.out.println("Current mouse location: " + currLoc); + if (currLoc.equals(OFF_SCREEN_LOC)) { + throw new RuntimeException("Test Failed, robot moved mouse off screen."); + } + } + + private static Point validateOffScreen(Point p) { + for (Rectangle rect : r) { + if (rect.contains(p)) { + return validateOffScreen(new Point(p.x * 2, p.y)); + } + } + return p; + } +} From e984fa7997dda922708edf556d1839b866e44e55 Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Tue, 3 Jun 2025 18:41:05 +0000 Subject: [PATCH 012/143] 8358539: ProblemList jdk/jfr/api/consumer/TestRecordingFileWrite.java Reviewed-by: ayang, bpb --- test/jdk/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 28bb2a1f033..f44dd5c7a7d 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -774,6 +774,7 @@ jdk/jfr/event/compiler/TestCodeSweeper.java 8338127 generic- jdk/jfr/event/oldobject/TestShenandoah.java 8342951 generic-all jdk/jfr/event/runtime/TestResidentSetSizeEvent.java 8309846 aix-ppc64 jdk/jfr/jvm/TestWaste.java 8282427 generic-all +jdk/jfr/api/consumer/TestRecordingFileWrite.java 8358536 generic-all ############################################################################ From 406f1bc5b94408778063b885cdac807fd1501e44 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Tue, 3 Jun 2025 18:46:55 +0000 Subject: [PATCH 013/143] 8357650: ThreadSnapshot to take snapshot of thread for thread dumps Co-authored-by: Alan Bateman Co-authored-by: Alex Menkov Reviewed-by: sspitsyn, kevinw --- src/hotspot/share/classfile/javaClasses.cpp | 2 +- src/hotspot/share/classfile/vmSymbols.hpp | 6 + src/hotspot/share/include/jvm.h | 3 + src/hotspot/share/prims/jvm.cpp | 9 + src/hotspot/share/prims/jvmtiThreadState.hpp | 2 +- .../share/services/diagnosticCommand.cpp | 2 + src/hotspot/share/services/threadService.cpp | 436 +++++++++++++++++- src/hotspot/share/services/threadService.hpp | 7 + .../jdk/internal/vm/ThreadSnapshot.java | 217 +++++++++ .../share/native/libjava/ThreadSnapshot.c | 36 ++ 10 files changed, 716 insertions(+), 4 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java create mode 100644 src/java.base/share/native/libjava/ThreadSnapshot.c diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 3b34edf416e..8d32d73fb47 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1872,7 +1872,7 @@ ByteSize java_lang_Thread::thread_id_offset() { } oop java_lang_Thread::park_blocker(oop java_thread) { - return java_thread->obj_field(_park_blocker_offset); + return java_thread->obj_field_access(_park_blocker_offset); } oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index f9f6bd07254..dc9ce61627b 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -742,6 +742,12 @@ class SerializeClosure; template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(dumpThreads_name, "dumpThreads") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \ + template(jdk_internal_vm_ThreadSnapshot, "jdk/internal/vm/ThreadSnapshot") \ + template(jdk_internal_vm_ThreadLock, "jdk/internal/vm/ThreadSnapshot$ThreadLock") \ + template(jdk_internal_vm_ThreadLock_signature, "Ljdk/internal/vm/ThreadSnapshot$ThreadLock;") \ + template(jdk_internal_vm_ThreadLock_array, "[Ljdk/internal/vm/ThreadSnapshot$ThreadLock;") \ + template(java_lang_StackTraceElement_of_name, "of") \ + template(java_lang_StackTraceElement_of_signature, "([Ljava/lang/StackTraceElement;)[Ljava/lang/StackTraceElement;") \ \ /* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \ template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 61857ac3ccd..73f60765a70 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -301,6 +301,9 @@ JVM_HoldsLock(JNIEnv *env, jclass threadClass, jobject obj); JNIEXPORT jobject JNICALL JVM_GetStackTrace(JNIEnv *env, jobject thread); +JNIEXPORT jobject JNICALL +JVM_CreateThreadSnapshot(JNIEnv* env, jobject thread); + JNIEXPORT jobjectArray JNICALL JVM_GetAllThreads(JNIEnv *env, jclass dummy); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index e50318c90b6..d669d7bf5ec 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -2961,6 +2961,15 @@ JVM_ENTRY(jobject, JVM_GetStackTrace(JNIEnv *env, jobject jthread)) return JNIHandles::make_local(THREAD, trace); JVM_END +JVM_ENTRY(jobject, JVM_CreateThreadSnapshot(JNIEnv* env, jobject jthread)) +#if INCLUDE_JVMTI + oop snapshot = ThreadSnapshotFactory::get_thread_snapshot(jthread, THREAD); + return JNIHandles::make_local(THREAD, snapshot); +#else + return nullptr; +#endif +JVM_END + JVM_ENTRY(void, JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring name)) // We don't use a ThreadsListHandle here because the current thread // must be alive. diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index cb0a779bd5a..62d0c45e337 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -77,7 +77,7 @@ class JvmtiEnvThreadStateIterator : public StackObj { // // Virtual Thread Mount State Transition (VTMS transition) mechanism // -class JvmtiVTMSTransitionDisabler { +class JvmtiVTMSTransitionDisabler : public AnyObj { private: static volatile int _VTMS_transition_disable_for_one_count; // transitions for one virtual thread are disabled while it is positive static volatile int _VTMS_transition_disable_for_all_count; // transitions for all virtual threads are disabled while it is positive diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 26a762fa109..8b6fa392c95 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -127,7 +127,9 @@ void DCmd::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); +#if INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); +#endif // INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index d320e17fafb..8e0c955bff8 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -34,21 +34,25 @@ #include "nmt/memTag.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "prims/jvmtiRawMonitor.hpp" +#include "prims/jvmtiThreadState.hpp" #include "runtime/atomic.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" +#include "runtime/javaCalls.hpp" #include "runtime/javaThread.inline.hpp" +#include "runtime/jniHandles.inline.hpp" #include "runtime/objectMonitor.inline.hpp" -#include "runtime/synchronizer.hpp" +#include "runtime/synchronizer.inline.hpp" #include "runtime/thread.inline.hpp" #include "runtime/threads.hpp" #include "runtime/threadSMR.inline.hpp" -#include "runtime/vframe.hpp" +#include "runtime/vframe.inline.hpp" #include "runtime/vmThread.hpp" #include "runtime/vmOperations.hpp" #include "services/threadService.hpp" @@ -1115,3 +1119,431 @@ ThreadsListEnumerator::ThreadsListEnumerator(Thread* cur_thread, _threads_array->append(h); } } + + +// jdk.internal.vm.ThreadSnapshot support +#if INCLUDE_JVMTI + +class GetThreadSnapshotClosure: public HandshakeClosure { +private: + static OopStorage* oop_storage() { + assert(_thread_service_storage != nullptr, "sanity"); + return _thread_service_storage; + } + +public: + struct OwnedLock { + // should be synced with ordinals of jdk.internal.vm.ThreadSnapshot.OwnedLockType enum + enum Type { + NOTHING = -1, + LOCKED = 0, + ELIMINATED = 1, + }; + + int _frame_depth; + Type _type; + // synchronization object (when type == LOCKED) or its klass (type == ELIMINATED) + OopHandle _obj; + + OwnedLock(int depth, Type type, OopHandle obj): _frame_depth(depth), _type(type), _obj(obj) {} + OwnedLock(): _frame_depth(0), _type(NOTHING), _obj(nullptr) {} + }; + + struct Blocker { + // should be synced with ordinals of jdk.internal.vm.ThreadSnapshot.BlockerLockType enum + enum Type { + NOTHING = -1, + PARK_BLOCKER = 0, + WAITING_TO_LOCK = 1, + WAITING_ON = 2, + }; + + Type _type; + // park blocker or an object the thread waiting on/trying to lock + OopHandle _obj; + + Blocker(Type type, OopHandle obj): _type(type), _obj(obj) {} + Blocker(): _type(NOTHING), _obj(nullptr) {} + + bool is_empty() const { + return _type == NOTHING; + } + }; + + Handle _thread_h; + JavaThread* _java_thread; + int _frame_count; // length of _methods and _bcis arrays + GrowableArray* _methods; + GrowableArray* _bcis; + JavaThreadStatus _thread_status; + OopHandle _thread_name; + GrowableArray* _locks; + Blocker _blocker; + + GetThreadSnapshotClosure(Handle thread_h, JavaThread* java_thread): + HandshakeClosure("GetThreadSnapshotClosure"), + _thread_h(thread_h), _java_thread(java_thread), + _frame_count(0), _methods(nullptr), _bcis(nullptr), + _thread_status(), _thread_name(nullptr), + _locks(nullptr), _blocker() { + } + virtual ~GetThreadSnapshotClosure() { + delete _methods; + delete _bcis; + _thread_name.release(oop_storage()); + if (_locks != nullptr) { + for (int i = 0; i < _locks->length(); i++) { + _locks->at(i)._obj.release(oop_storage()); + } + delete _locks; + } + _blocker._obj.release(oop_storage()); + } + +private: + void detect_locks(javaVFrame* jvf, int depth) { + Thread* current = Thread::current(); + + if (depth == 0 && _blocker.is_empty()) { + // If this is the first frame and it is java.lang.Object.wait(...) + // then print out the receiver. + if (jvf->method()->name() == vmSymbols::wait_name() && + jvf->method()->method_holder()->name() == vmSymbols::java_lang_Object()) { + OopHandle lock_object; + StackValueCollection* locs = jvf->locals(); + if (!locs->is_empty()) { + StackValue* sv = locs->at(0); + if (sv->type() == T_OBJECT) { + Handle o = locs->at(0)->get_obj(); + lock_object = OopHandle(oop_storage(), o()); + } + } + _blocker = Blocker(Blocker::WAITING_ON, lock_object); + } + } + + GrowableArray* mons = jvf->monitors(); + if (!mons->is_empty()) { + for (int index = (mons->length() - 1); index >= 0; index--) { + MonitorInfo* monitor = mons->at(index); + if (monitor->eliminated() && jvf->is_compiled_frame()) { // Eliminated in compiled code + if (monitor->owner_is_scalar_replaced()) { + Klass* k = java_lang_Class::as_Klass(monitor->owner_klass()); + _locks->push(OwnedLock(depth, OwnedLock::ELIMINATED, OopHandle(oop_storage(), k->klass_holder()))); + } else { + Handle owner(current, monitor->owner()); + if (owner.not_null()) { + Klass* k = owner->klass(); + _locks->push(OwnedLock(depth, OwnedLock::ELIMINATED, OopHandle(oop_storage(), k->klass_holder()))); + } + } + continue; + } + if (monitor->owner() != nullptr) { + // the monitor is associated with an object, i.e., it is locked + + if (depth == 0 && _blocker.is_empty()) { + ObjectMonitor* pending_moninor = java_lang_VirtualThread::is_instance(_thread_h()) + ? java_lang_VirtualThread::current_pending_monitor(_thread_h()) + : jvf->thread()->current_pending_monitor(); + + markWord mark = monitor->owner()->mark(); + // The first stage of async deflation does not affect any field + // used by this comparison so the ObjectMonitor* is usable here. + if (mark.has_monitor()) { + ObjectMonitor* mon = ObjectSynchronizer::read_monitor(current, monitor->owner(), mark); + if (// if the monitor is null we must be in the process of locking + mon == nullptr || + // we have marked ourself as pending on this monitor + mon == pending_moninor || + // we are not the owner of this monitor + (_java_thread != nullptr && !mon->is_entered(_java_thread))) { + _blocker = Blocker(Blocker::WAITING_TO_LOCK, OopHandle(oop_storage(), monitor->owner())); + continue; // go to next monitor + } + } + } + _locks->push(OwnedLock(depth, OwnedLock::LOCKED, OopHandle(oop_storage(), monitor->owner()))); + } + } + } + } + +public: + void do_thread(Thread* th) override { + Thread* current = Thread::current(); + + bool is_virtual = java_lang_VirtualThread::is_instance(_thread_h()); + if (_java_thread != nullptr) { + if (is_virtual) { + // mounted vthread, use carrier thread state + oop carrier_thread = java_lang_VirtualThread::carrier_thread(_thread_h()); + _thread_status = java_lang_Thread::get_thread_status(carrier_thread); + } else { + _thread_status = java_lang_Thread::get_thread_status(_thread_h()); + } + } else { + // unmounted vthread + int vt_state = java_lang_VirtualThread::state(_thread_h()); + _thread_status = java_lang_VirtualThread::map_state_to_thread_status(vt_state); + } + _thread_name = OopHandle(oop_storage(), java_lang_Thread::name(_thread_h())); + + if (_java_thread != nullptr && !_java_thread->has_last_Java_frame()) { + // stack trace is empty + return; + } + + bool vthread_carrier = !is_virtual && (_java_thread != nullptr) && (_java_thread->vthread_continuation() != nullptr); + + oop park_blocker = java_lang_Thread::park_blocker(_thread_h()); + if (park_blocker != nullptr) { + _blocker = Blocker(Blocker::PARK_BLOCKER, OopHandle(oop_storage(), park_blocker)); + } + + ResourceMark rm(current); + HandleMark hm(current); + + const int max_depth = MaxJavaStackTraceDepth; + const bool skip_hidden = !ShowHiddenFrames; + + // Pick minimum length that will cover most cases + int init_length = 64; + _methods = new (mtInternal) GrowableArray(init_length, mtInternal); + _bcis = new (mtInternal) GrowableArray(init_length, mtInternal); + _locks = new (mtInternal) GrowableArray(init_length, mtInternal); + int total_count = 0; + + vframeStream vfst(_java_thread != nullptr + ? vframeStream(_java_thread, false, true, vthread_carrier) + : vframeStream(java_lang_VirtualThread::continuation(_thread_h()))); + + for (; + !vfst.at_end() && (max_depth == 0 || max_depth != total_count); + vfst.next()) { + + detect_locks(vfst.asJavaVFrame(), total_count); + + if (skip_hidden && (vfst.method()->is_hidden() || + vfst.method()->is_continuation_enter_intrinsic())) { + continue; + } + _methods->push(vfst.method()); + _bcis->push(vfst.bci()); + total_count++; + } + + _frame_count = total_count; + } +}; + +class jdk_internal_vm_ThreadLock: AllStatic { + static bool _inited; + static int _depth_offset; + static int _typeOrdinal_offset; + static int _obj_offset; + + static void compute_offsets(InstanceKlass* klass, TRAPS) { + JavaClasses::compute_offset(_depth_offset, klass, "depth", vmSymbols::int_signature(), false); + JavaClasses::compute_offset(_typeOrdinal_offset, klass, "typeOrdinal", vmSymbols::int_signature(), false); + JavaClasses::compute_offset(_obj_offset, klass, "obj", vmSymbols::object_signature(), false); + } +public: + static void init(InstanceKlass* klass, TRAPS) { + if (!_inited) { + compute_offsets(klass, CHECK); + _inited = true; + } + } + + static Handle create(InstanceKlass* klass, int depth, int type_ordinal, OopHandle obj, TRAPS) { + init(klass, CHECK_NH); + Handle result = klass->allocate_instance_handle(CHECK_NH); + result->int_field_put(_depth_offset, depth); + result->int_field_put(_typeOrdinal_offset, type_ordinal); + result->obj_field_put(_obj_offset, obj.resolve()); + return result; + } +}; + +bool jdk_internal_vm_ThreadLock::_inited = false; +int jdk_internal_vm_ThreadLock::_depth_offset; +int jdk_internal_vm_ThreadLock::_typeOrdinal_offset; +int jdk_internal_vm_ThreadLock::_obj_offset; + +class jdk_internal_vm_ThreadSnapshot: AllStatic { + static bool _inited; + static int _name_offset; + static int _threadStatus_offset; + static int _carrierThread_offset; + static int _stackTrace_offset; + static int _locks_offset; + static int _blockerTypeOrdinal_offset; + static int _blockerObject_offset; + + static void compute_offsets(InstanceKlass* klass, TRAPS) { + JavaClasses::compute_offset(_name_offset, klass, "name", vmSymbols::string_signature(), false); + JavaClasses::compute_offset(_threadStatus_offset, klass, "threadStatus", vmSymbols::int_signature(), false); + JavaClasses::compute_offset(_carrierThread_offset, klass, "carrierThread", vmSymbols::thread_signature(), false); + JavaClasses::compute_offset(_stackTrace_offset, klass, "stackTrace", vmSymbols::java_lang_StackTraceElement_array(), false); + JavaClasses::compute_offset(_locks_offset, klass, "locks", vmSymbols::jdk_internal_vm_ThreadLock_array(), false); + JavaClasses::compute_offset(_blockerTypeOrdinal_offset, klass, "blockerTypeOrdinal", vmSymbols::int_signature(), false); + JavaClasses::compute_offset(_blockerObject_offset, klass, "blockerObject", vmSymbols::object_signature(), false); + } +public: + static void init(InstanceKlass* klass, TRAPS) { + if (!_inited) { + compute_offsets(klass, CHECK); + _inited = true; + } + } + + static Handle allocate(InstanceKlass* klass, TRAPS) { + init(klass, CHECK_NH); + Handle h_k = klass->allocate_instance_handle(CHECK_NH); + return h_k; + } + + static void set_name(oop snapshot, oop name) { + snapshot->obj_field_put(_name_offset, name); + } + static void set_thread_status(oop snapshot, int status) { + snapshot->int_field_put(_threadStatus_offset, status); + } + static void set_carrier_thread(oop snapshot, oop carrier_thread) { + snapshot->obj_field_put(_carrierThread_offset, carrier_thread); + } + static void set_stack_trace(oop snapshot, oop trace) { + snapshot->obj_field_put(_stackTrace_offset, trace); + } + static void set_locks(oop snapshot, oop locks) { + snapshot->obj_field_put(_locks_offset, locks); + } + static void set_blocker(oop snapshot, int type_ordinal, oop lock) { + snapshot->int_field_put(_blockerTypeOrdinal_offset, type_ordinal); + snapshot->obj_field_put(_blockerObject_offset, lock); + } +}; + +bool jdk_internal_vm_ThreadSnapshot::_inited = false; +int jdk_internal_vm_ThreadSnapshot::_name_offset; +int jdk_internal_vm_ThreadSnapshot::_threadStatus_offset; +int jdk_internal_vm_ThreadSnapshot::_carrierThread_offset; +int jdk_internal_vm_ThreadSnapshot::_stackTrace_offset; +int jdk_internal_vm_ThreadSnapshot::_locks_offset; +int jdk_internal_vm_ThreadSnapshot::_blockerTypeOrdinal_offset; +int jdk_internal_vm_ThreadSnapshot::_blockerObject_offset; + +oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { + ThreadsListHandle tlh(THREAD); + + ResourceMark rm(THREAD); + HandleMark hm(THREAD); + Handle thread_h(THREAD, JNIHandles::resolve(jthread)); + + // wrapper to auto delete JvmtiVTMSTransitionDisabler + class TransitionDisabler { + JvmtiVTMSTransitionDisabler* _transition_disabler; + public: + TransitionDisabler(): _transition_disabler(nullptr) {} + ~TransitionDisabler() { + reset(); + } + void init(jobject jthread) { + _transition_disabler = new (mtInternal) JvmtiVTMSTransitionDisabler(jthread); + } + void reset() { + if (_transition_disabler != nullptr) { + delete _transition_disabler; + _transition_disabler = nullptr; + } + } + } transition_disabler; + + JavaThread* java_thread = nullptr; + bool is_virtual = java_lang_VirtualThread::is_instance(thread_h()); + Handle carrier_thread; + if (is_virtual) { + // 1st need to disable mount/unmount transitions + transition_disabler.init(jthread); + + carrier_thread = Handle(THREAD, java_lang_VirtualThread::carrier_thread(thread_h())); + if (carrier_thread != nullptr) { + java_thread = java_lang_Thread::thread(carrier_thread()); + } + } else { + java_thread = java_lang_Thread::thread(thread_h()); + } + + // Handshake with target + GetThreadSnapshotClosure cl(thread_h, java_thread); + if (java_thread == nullptr) { + // unmounted vthread, execute on the current thread + cl.do_thread(nullptr); + } else { + Handshake::execute(&cl, &tlh, java_thread); + } + + // all info is collected, can enable transitions. + transition_disabler.reset(); + + // StackTrace + InstanceKlass* ste_klass = vmClasses::StackTraceElement_klass(); + assert(ste_klass != nullptr, "must be loaded"); + + objArrayHandle trace = oopFactory::new_objArray_handle(ste_klass, cl._frame_count, CHECK_NULL); + + for (int i = 0; i < cl._frame_count; i++) { + methodHandle method(THREAD, cl._methods->at(i)); + oop element = java_lang_StackTraceElement::create(method, cl._bcis->at(i), CHECK_NULL); + trace->obj_at_put(i, element); + } + + // Locks + Symbol* lock_sym = vmSymbols::jdk_internal_vm_ThreadLock(); + Klass* lock_k = SystemDictionary::resolve_or_fail(lock_sym, true, CHECK_NULL); + InstanceKlass* lock_klass = InstanceKlass::cast(lock_k); + + objArrayHandle locks; + if (cl._locks != nullptr && cl._locks->length() > 0) { + locks = oopFactory::new_objArray_handle(lock_klass, cl._locks->length(), CHECK_NULL); + for (int n = 0; n < cl._locks->length(); n++) { + GetThreadSnapshotClosure::OwnedLock* lock_info = cl._locks->adr_at(n); + + Handle lock = jdk_internal_vm_ThreadLock::create(lock_klass, + lock_info->_frame_depth, lock_info->_type, lock_info->_obj, CHECK_NULL); + locks->obj_at_put(n, lock()); + } + } + + // call static StackTraceElement[] StackTraceElement.of(StackTraceElement[] stackTrace) + // to properly initialize STEs. + JavaValue result(T_OBJECT); + JavaCalls::call_static(&result, + ste_klass, + vmSymbols::java_lang_StackTraceElement_of_name(), + vmSymbols::java_lang_StackTraceElement_of_signature(), + trace, + CHECK_NULL); + // the method return the same trace array + + Symbol* snapshot_klass_name = vmSymbols::jdk_internal_vm_ThreadSnapshot(); + Klass* snapshot_klass = SystemDictionary::resolve_or_fail(snapshot_klass_name, true, CHECK_NULL); + if (snapshot_klass->should_be_initialized()) { + snapshot_klass->initialize(CHECK_NULL); + } + + Handle snapshot = jdk_internal_vm_ThreadSnapshot::allocate(InstanceKlass::cast(snapshot_klass), CHECK_NULL); + jdk_internal_vm_ThreadSnapshot::set_name(snapshot(), cl._thread_name.resolve()); + jdk_internal_vm_ThreadSnapshot::set_thread_status(snapshot(), (int)cl._thread_status); + jdk_internal_vm_ThreadSnapshot::set_carrier_thread(snapshot(), carrier_thread()); + jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace()); + jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks()); + if (!cl._blocker.is_empty()) { + jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), cl._blocker._type, cl._blocker._obj.resolve()); + } + return snapshot(); +} + +#endif // INCLUDE_JVMTI + diff --git a/src/hotspot/share/services/threadService.hpp b/src/hotspot/share/services/threadService.hpp index 17d8ba9b3cc..b1de4bc8703 100644 --- a/src/hotspot/share/services/threadService.hpp +++ b/src/hotspot/share/services/threadService.hpp @@ -631,4 +631,11 @@ class JavaThreadSleepState : public JavaThreadStatusChanger { } }; + +// jdk.internal.vm.ThreadSnapshot support +class ThreadSnapshotFactory: AllStatic { +public: + JVMTI_ONLY(static oop get_thread_snapshot(jobject jthread, TRAPS);) +}; + #endif // SHARE_SERVICES_THREADSERVICE_HPP diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java new file mode 100644 index 00000000000..205bfde5449 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -0,0 +1,217 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.internal.vm; + +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Represents a snapshot of information about a Thread. + */ +class ThreadSnapshot { + private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0]; + private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0]; + + // filled by VM + private String name; + private int threadStatus; + private Thread carrierThread; + private StackTraceElement[] stackTrace; + // owned monitors + private ThreadLock[] locks; + // an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of() + private int blockerTypeOrdinal; + private Object blockerObject; + + // set by ThreadSnapshot.of() + private ThreadBlocker blocker; + + private ThreadSnapshot() {} + + /** + * Take a snapshot of a Thread to get all information about the thread. + */ + static ThreadSnapshot of(Thread thread) { + ThreadSnapshot snapshot = create(thread); + if (snapshot.stackTrace == null) { + snapshot.stackTrace = EMPTY_STACK; + } + if (snapshot.locks != null) { + Arrays.stream(snapshot.locks).forEach(ThreadLock::finishInit); + } else { + snapshot.locks = EMPTY_LOCKS; + } + if (snapshot.blockerObject != null) { + snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject); + snapshot.blockerObject = null; // release + } + return snapshot; + } + + /** + * Returns the thread name. + */ + String threadName() { + return name; + } + + /** + * Returns the thread state. + */ + Thread.State threadState() { + return jdk.internal.misc.VM.toThreadState(threadStatus); + } + + /** + * Returns the thread stack trace. + */ + StackTraceElement[] stackTrace() { + return stackTrace; + } + + /** + * Returns the thread's parkBlocker. + */ + Object parkBlocker() { + return getBlocker(BlockerLockType.PARK_BLOCKER); + } + + /** + * Returns the object that the thread is blocked on. + * @throws IllegalStateException if not in the blocked state + */ + Object blockedOn() { + if (threadState() != Thread.State.BLOCKED) { + throw new IllegalStateException(); + } + return getBlocker(BlockerLockType.WAITING_TO_LOCK); + } + + /** + * Returns the object that the thread is waiting on. + * @throws IllegalStateException if not in the waiting state + */ + Object waitingOn() { + if (threadState() != Thread.State.WAITING + && threadState() != Thread.State.TIMED_WAITING) { + throw new IllegalStateException(); + } + return getBlocker(BlockerLockType.WAITING_ON); + } + + private Object getBlocker(BlockerLockType type) { + return (blocker != null && blocker.type == type) ? blocker.obj : null; + } + + /** + * Returns true if the thread owns any object monitors. + */ + boolean ownsMonitors() { + return locks.length > 0; + } + + /** + * Returns the objects that the thread locked at the given depth. The stream + * will contain a null element for a monitor that has been eliminated. + */ + Stream ownedMonitorsAt(int depth) { + return Arrays.stream(locks) + .filter(lock -> lock.depth() == depth) + .map(lock -> (lock.type == OwnedLockType.LOCKED) + ? lock.lockObject() + : /*eliminated*/ null); + } + + /** + * If the thread is a mounted virtual thread then return its carrier. + */ + Thread carrierThread() { + return carrierThread; + } + + /** + * Represents information about a locking operation. + */ + private enum OwnedLockType { + LOCKED, + // Lock object is a class of the eliminated monitor + ELIMINATED, + } + + private enum BlockerLockType { + // Park blocker + PARK_BLOCKER, + WAITING_TO_LOCK, + // Object.wait() + WAITING_ON, + } + + /** + * Represents a locking operation of a thread at a specific stack depth. + */ + private class ThreadLock { + private static final OwnedLockType[] lockTypeValues = OwnedLockType.values(); // cache + + // set by the VM + private int depth; + private int typeOrdinal; + private Object obj; + + // set by ThreadLock.of() + private OwnedLockType type; + + private ThreadLock() {} + + void finishInit() { + type = lockTypeValues[typeOrdinal]; + } + + int depth() { + return depth; + } + + OwnedLockType type() { + return type; + } + + Object lockObject() { + if (type == OwnedLockType.ELIMINATED) { + // we have no lock object, lock contains lock class + return null; + } + return obj; + } + } + + private record ThreadBlocker(BlockerLockType type, Object obj) { + private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache + + ThreadBlocker(int typeOrdinal, Object obj) { + this(lockTypeValues[typeOrdinal], obj); + } + } + + private static native ThreadSnapshot create(Thread thread); +} diff --git a/src/java.base/share/native/libjava/ThreadSnapshot.c b/src/java.base/share/native/libjava/ThreadSnapshot.c new file mode 100644 index 00000000000..feea629f4c7 --- /dev/null +++ b/src/java.base/share/native/libjava/ThreadSnapshot.c @@ -0,0 +1,36 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +#include "jni.h" +#include "jvm.h" + +#include "jdk_internal_vm_ThreadSnapshot.h" + + +JNIEXPORT jobject JNICALL +Java_jdk_internal_vm_ThreadSnapshot_create(JNIEnv *env, jclass cls, jobject thread) +{ + return JVM_CreateThreadSnapshot(env, thread); +} From cc11b7d1f560d42d1de123e650470362f1f2aabb Mon Sep 17 00:00:00 2001 From: Stefan Lobbenmeier Date: Tue, 3 Jun 2025 19:22:52 +0000 Subject: [PATCH 014/143] 8356128: Correct documentation for --linux-package-deps Reviewed-by: asemenyuk, almatvee --- .../jdk/jpackage/internal/resources/HelpResources.properties | 2 +- .../jdk/jpackage/internal/resources/HelpResources_de.properties | 2 +- .../jdk/jpackage/internal/resources/HelpResources_ja.properties | 2 +- .../jpackage/internal/resources/HelpResources_zh_CN.properties | 2 +- src/jdk.jpackage/share/man/jpackage.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties index c21daab30d0..0f0d9fd5a17 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties @@ -295,7 +295,7 @@ MSG_Help_linux_install=\ \ Maintainer for .deb package\n\ \ --linux-menu-group \n\ \ Menu group this application is placed in\n\ -\ --linux-package-deps\n\ +\ --linux-package-deps \n\ \ Required packages or capabilities for the application\n\ \ --linux-rpm-license-type \n\ \ Type of the license ("License: " of the RPM .spec)\n\ diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_de.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_de.properties index 31d6e11d70c..3c66e8bb151 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_de.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_de.properties @@ -33,7 +33,7 @@ MSG_Help_win_install=\ --win-dir-chooser\n Fügt ein Dialogfeld hinzu, MSG_Help_win_install_dir=Relativer Unterpfad unter dem Standardinstallationsverzeichnis\n MSG_Help_mac_install=\ --mac-dmg-content [,...]\n Nimmt den gesamten referenzierten Inhalt in die DMG-Datei auf.\n Diese Option kann mehrmals verwendet werden. \n MSG_Help_mac_launcher=\ --mac-package-identifier \n Eine ID, die die Anwendung für macOS eindeutig identifiziert\n Standardwert ist der Hauptklassenname.\n Es dürfen nur alphanumerische Zeichen (A-Z, a-z, 0-9), Bindestriche (-)\n und Punkte (.) verwendet werden.\n --mac-package-name \n Name der Anwendung, wie in der Menüleiste angezeigt\n Dieser kann vom Anwendungsnamen abweichen.\n Er darf maximal 15 Zeichen enthalten und muss für die Anzeige\n in der Menüleiste und im Infofenster der Anwendung geeignet sein.\n Standardwert: Anwendungsname.\n --mac-package-signing-prefix \n Beim Signieren des Anwendungspackages wird dieser Wert\n allen zu signierenden Komponenten ohne vorhandene\n Package-ID als Präfix vorangestellt.\n --mac-sign\n Anforderung zum Signieren des Packages oder des vordefinierten\nAnwendungsimages\n --mac-signing-keychain \n Name des Schlüsselbundes für die Suche nach der Signaturidentität\n Bei fehlender Angabe werden die Standardschlüsselbunde verwendet.\n --mac-signing-key-user-name \n Team- oder Benutzernamensteil der Apple-Signaturidentitäten. Um direkt zu steuern,\n welche Signaturidentität zum Signieren eines Anwendungsimages oder\n Installationsprogramms verwendet wird, verwenden Sie --mac-app-image-sign-identity und/oder\n --mac-installer-sign-identity. Diese Option kann nicht mit\n --mac-app-image-sign-identity oder --mac-installer-sign-identity kombiniert werden.\n --mac-app-image-sign-identity \n Zum Signieren des Anwendungsimages verwendete Identität. Dieser Wert wird\n direkt an die Option --sign des Tools "codesign" übergeben. Diese Option kann nicht\n mit --mac-signing-key-user-name kombiniert werden.\n --mac-installer-sign-identity \n Zum Signieren des Installationsprogramms "pkg" verwendete Identität. Dieser Wert wird\n direkt an die Option --sign des Tools "productbuild" übergeben. Diese Option\n kann nicht mit --mac-signing-key-user-name kombiniert werden.\n --mac-app-store\n Gibt an, dass die jpackage-Ausgabe für den\n Mac App Store bestimmt ist.\n --mac-entitlements \n Pfad zu einer Datei mit Berechtigungen, die beim Signieren\n von ausführbaren Dateien und Librarys im Bundle verwendet werden sollen.\n --mac-app-category \n Zeichenfolge für das Erstellen von LSApplicationCategoryType in\n Anwendungs-plist. Standardwert: "utilities".\n -MSG_Help_linux_install=\ --linux-package-name \n Name für das Linux-Package, Standardwert: Anwendungsname\n --linux-deb-maintainer \n Maintainer für .deb-Package\n --linux-menu-group \n Menügruppe, in der diese Anwendung abgelegt wird\n --linux-package-deps\n Erforderliche Packages oder Funktionen für die Anwendung\n --linux-rpm-license-type \n Typ der Lizenz ("License: " der RPM-SPEC-Datei)\n --linux-app-release \n Releasewert der RPM-SPEC-Datei oder \n Debian-Revisionswert der DEB-Kontrolldatei\n --linux-app-category \n Gruppenwert der RPM-SPEC-Datei oder \n Abschnittswert der DEB-Kontrolldatei\n --linux-shortcut\n Erstellt eine Verknüpfung für die Anwendung.\n +MSG_Help_linux_install=\ --linux-package-name \n Name für das Linux-Package, Standardwert: Anwendungsname\n --linux-deb-maintainer \n Maintainer für .deb-Package\n --linux-menu-group \n Menügruppe, in der diese Anwendung abgelegt wird\n --linux-package-deps \n Erforderliche Packages oder Funktionen für die Anwendung\n --linux-rpm-license-type \n Typ der Lizenz ("License: " der RPM-SPEC-Datei)\n --linux-app-release \n Releasewert der RPM-SPEC-Datei oder \n Debian-Revisionswert der DEB-Kontrolldatei\n --linux-app-category \n Gruppenwert der RPM-SPEC-Datei oder \n Abschnittswert der DEB-Kontrolldatei\n --linux-shortcut\n Erstellt eine Verknüpfung für die Anwendung.\n MSG_Help_mac_linux_install_dir=Absoluter Pfad des Installationsverzeichnisses der Anwendung\n MSG_Help_default_install_dir=Absoluter Pfad des Installationsverzeichnisses der Anwendung auf OS X\n oder Linux. Relativer Unterpfad des Installationsverzeichnisses der\n Anwendung wie "Programme" oder "AppData" unter Windows.\n MSG_Help_no_args=Verwendung: jpackage \nVerwenden Sie jpackage --help (oder -h), um eine Liste möglicher Optionen aufzurufen diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties index 39483b8f116..cae6ae216d3 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties @@ -36,7 +36,7 @@ MSG_Help_win_install_dir=デフォルトのインストール場所の下の相 MSG_Help_mac_install=\ --mac-dmg-content [,...]\n dmgに参照されたコンテンツをすべて含めます。\n このオプションは複数回使用できます。 \n MSG_Help_mac_launcher=\ --mac-package-identifier \n macOSのアプリケーションを一意に識別するID\n メイン・クラス名にデフォルト設定されています。\n 英数字(A-Z、a-z、0-9)、ハイフン(-)およびピリオド(.)文字のみ\n 使用できます。\n --mac-package-name \n メニュー・バーに表示されるアプリケーションの名前\n アプリケーション名とは異なります。\n この名前は16文字未満にする必要があり、メニュー・バーおよびアプリケーション情報ウィンドウに\n 表示するのに適している必要があります。\n アプリケーション名にデフォルト設定されています。\n --mac-package-signing-prefix \n アプリケーション・パッケージに署名する際、\n 既存のパッケージIDのない署名が必要なすべてのコンポーネントに、\n この値が接頭辞として付けられます。\n --mac-sign\n パッケージまたは事前定義済アプリケーション・イメージに署名するよう\n リクエストします。\n --mac-signing-keychain \n 署名アイデンティティを検索するキーチェーンの名前\n 指定しなかった場合、標準のキーチェーンが使用されます。\n --mac-signing-key-user-name \n Apple署名アイデンティティのチームまたはユーザー名部分。\n アプリケーション・イメージまたはインストーラの署名に使用する署名アイデンティティの\n 直接制御には、--mac-app-image-sign-identityまたは\n --mac-installer-sign-identity(あるいは両方)を使用します。このオプションは\n --mac-app-image-sign-identityまたは--mac-installer-sign-identityと組み合せることはできません。\n --mac-app-image-sign-identity \n アプリケーション・イメージの署名に使用するアイデンティティ。この値は直接\n "codesign"ツールの--signオプションに渡されます。このオプションは\n \ --mac-signing-key-user-nameと組み合せることはできません。\n --mac-installer-sign-identity \n "pkg"インストーラの署名に使用するアイデンティティ。この値は直接\n "productbuild"ツールの--signオプションに渡されます。このオプションは\n --mac-signing-key-user-nameと組み合せることはできません。\n --mac-app-store\n jpackage出力がMac App Store用であること\n を示します。\n --mac-entitlements \n バンドルの実行可能ファイルおよびライブラリの署名時に\n 使用する権限を含むファイルのパス。\n --mac-app-category \n アプリケーションのplistのLSApplicationCategoryTypeを生成する際に使用する文字列。\n デフォルト値は"utilities"です。\n -MSG_Help_linux_install=\ --linux-package-name \n Linuxパッケージの名前。アプリケーション名にデフォルト設定されています\n --linux-deb-maintainer \n .debパッケージのMaintainer\n --linux-menu-group \n このアプリケーションが配置されているメニュー・グループ\n --linux-package-deps\n アプリケーションに必要なパッケージまたは機能\n --linux-rpm-license-type \n ライセンスのタイプ(RPM .specの"License: ")\n --linux-app-release \n RPM .specファイルのリリース値または\n DEBコントロール・ファイルのDebianリビジョン値。\n --linux-app-category \n RPM .specファイルのグループ値または \n DEBコントロール・ファイルのセクション値\n --linux-shortcut\n アプリケーションのショートカットを作成します。\n +MSG_Help_linux_install=\ --linux-package-name \n Linuxパッケージの名前。アプリケーション名にデフォルト設定されています\n --linux-deb-maintainer \n .debパッケージのMaintainer\n --linux-menu-group \n このアプリケーションが配置されているメニュー・グループ\n --linux-package-deps \n アプリケーションに必要なパッケージまたは機能\n --linux-rpm-license-type \n ライセンスのタイプ(RPM .specの"License: ")\n --linux-app-release \n RPM .specファイルのリリース値または\n DEBコントロール・ファイルのDebianリビジョン値。\n --linux-app-category \n RPM .specファイルのグループ値または \n DEBコントロール・ファイルのセクション値\n --linux-shortcut\n アプリケーションのショートカットを作成します。\n MSG_Help_mac_linux_install_dir=アプリケーションのインストール・ディレクトリの絶対パス\n MSG_Help_default_install_dir=OS XまたはLinux上のアプリケーションのインストール・ディレクトリの絶対パス。\n "プログラム・ファイル"や"AppData"など、Windows上のアプリケーションの\n インストール場所の相対サブパス。\n MSG_Help_no_args=使用方法: jpackage \n利用可能なオプションのリストについては、jpackage --help (or -h)を使用します diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties index 482381dd624..6adf62ef1f8 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties @@ -34,7 +34,7 @@ MSG_Help_win_install=\ --win-dir-chooser\n 添加一个对话框以允 MSG_Help_win_install_dir=默认安装位置下面的相对子路径\n MSG_Help_mac_install=\ --mac-dmg-content [,...]\n 包括 DMG 中引用的所有内容。\n 此选项可以使用多次。\n MSG_Help_mac_launcher=\ --mac-package-identifier \n 用来唯一地标识 macOS 应用程序的标识符\n 默认为主类名称。\n 只能使用字母数字 (A-Z,a-z,0-9)、连字符 (-) 和\n 句点 (.) 字符。\n --mac-package-name \n 出现在菜单栏中的应用程序名称\n 这可以与应用程序名称不同。\n 此名称的长度必须小于 16 个字符,适合\n 显示在菜单栏中和应用程序“信息”窗口中。\n 默认为应用程序名称。\n --mac-package-signing-prefix \n 在对应用程序包签名时,会在所有需要签名\n 但当前没有程序包标识符的组件的\n 前面加上此值。\n --mac-sign\n 请求对程序包或预定义的应用程序映像\n 进行签名。\n --mac-signing-keychain \n 要用来搜索签名身份的密钥链的名称\n 如果未指定,则使用标准的密钥链。\n --mac-signing-key-user-name \n Apple 签名身份的团队或用户名称部分。为了直接\n 控制用于对应用程序映像或安装程序进行签名的\n 签名身份,请使用 --mac-app-image-sign-identity 和/或\n --mac-installer-sign-identity。此选项不能与\n --mac-app-image-sign-identity 或 --mac-installer-sign-identity 结合使用。\n --mac-app-image-sign-identity \n 用于对应用程序映像进行签名的身份。此值将直接\n 传递至 "codesign" 工具的 --sign 选项。此选项不能\n 与 --mac-signing-key-user-name 结合使用。\n --mac-installer-sign-identity \n 用于对 "pkg" 安装程序进行签名的身份。此值将直接\n 传递至 "productbuild" 工具的 --sign 选项。此选项不能\n 与 --mac-signing-key-user-name 结合使用。\n --mac-app-store\n 指示 jpackage 输出面向\n Mac App Store。\n --mac-entitlements \n 包含一些权利的文件的路径,在对捆绑包中的可执行文件\n 和库进行签名时会使用这些权利。\n --mac-app-category \n 用于构造应用程序 plist 中 LSApplicationCategoryType 的\n 字符串。默认值为 "utilities"。\n -MSG_Help_linux_install=\ --linux-package-name \n Linux 程序包的名称,默认为应用程序名称\n --linux-deb-maintainer \n .deb 程序包的维护程序\n --linux-menu-group \n 此应用程序所在的菜单组\n --linux-package-deps\n 应用程序所需的程序包或功能\n --linux-rpm-license-type \n 许可证的类型(RPM .spec 的 "License: ")\n --linux-app-release \n RPM .spec 文件的发行版值或 \n DEB 控制文件的 Debian 修订版值\n --linux-app-category \n RPM .spec 文件的组值或 \n DEB 控制文件的节值\n --linux-shortcut\n 为应用程序创建快捷方式。\n +MSG_Help_linux_install=\ --linux-package-name \n Linux 程序包的名称,默认为应用程序名称\n --linux-deb-maintainer \n .deb 程序包的维护程序\n --linux-menu-group \n 此应用程序所在的菜单组\n --linux-package-deps \n 应用程序所需的程序包或功能\n --linux-rpm-license-type \n 许可证的类型(RPM .spec 的 "License: ")\n --linux-app-release \n RPM .spec 文件的发行版值或 \n DEB 控制文件的 Debian 修订版值\n --linux-app-category \n RPM .spec 文件的组值或 \n DEB 控制文件的节值\n --linux-shortcut\n 为应用程序创建快捷方式。\n MSG_Help_mac_linux_install_dir=应用程序安装目录的绝对路径\n MSG_Help_default_install_dir=OS X 或 Linux 上应用程序安装目录的绝对路径。\n Windows 上应用程序安装位置的相对子路径\n (如 "Program Files" 或 "AppData")。\n MSG_Help_no_args=用法:jpackage \n使用 jpackage --help(或 -h)可获取可能选项的列表 diff --git a/src/jdk.jpackage/share/man/jpackage.md b/src/jdk.jpackage/share/man/jpackage.md index 34e524f9eee..e49b04e204e 100644 --- a/src/jdk.jpackage/share/man/jpackage.md +++ b/src/jdk.jpackage/share/man/jpackage.md @@ -446,7 +446,7 @@ The `jpackage` tool will take as input a Java application and a Java run-time im : Menu group this application is placed in -`--linux-package-deps` +`--linux-package-deps` *package-dep-string* : Required packages or capabilities for the application From a44a470052aff3b17fa53165f043ccce36c1ef9b Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Tue, 3 Jun 2025 19:23:29 +0000 Subject: [PATCH 015/143] 8358515: make cmp-baseline is broken after JDK-8349665 Reviewed-by: erikj --- make/Init.gmk | 22 ++++++++++++++-------- make/Main.gmk | 8 -------- make/PreInit.gmk | 3 ++- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/make/Init.gmk b/make/Init.gmk index 32624d7dee6..38959323628 100644 --- a/make/Init.gmk +++ b/make/Init.gmk @@ -110,7 +110,18 @@ reconfigure: CUSTOM_CONFIG_DIR="$(CUSTOM_CONFIG_DIR)" \ $(RECONFIGURE_COMMAND) ) -.PHONY: print-modules print-targets print-tests print-configuration reconfigure +# Create files that are needed to run most targets in Main.gmk +create-make-helpers: + ( cd $(TOPDIR) && \ + $(MAKE) $(MAKE_ARGS) -j 1 -f make/GenerateFindTests.gmk \ + $(USER_MAKE_VARS) ) + ( cd $(TOPDIR) && \ + $(MAKE) $(MAKE_ARGS) -j 1 -f make/Main.gmk $(USER_MAKE_VARS) \ + UPDATE_MODULE_DEPS=true NO_RECIPES=true \ + create-main-targets-include ) + +.PHONY: print-modules print-targets print-tests print-configuration \ + reconfigure create-make-helpers ############################################################################## # The main target. This will delegate all other targets into Main.gmk. @@ -130,7 +141,7 @@ TARGET_DESCRIPTION := target$(if $(word 2, $(MAIN_TARGETS)),s) \ # variables are explicitly propagated using $(USER_MAKE_VARS). main: MAKEOVERRIDES := -main: $(INIT_TARGETS) +main: $(INIT_TARGETS) create-make-helpers ifneq ($(SEQUENTIAL_TARGETS)$(PARALLEL_TARGETS), ) $(call RotateLogFiles) $(ECHO) "Building $(TARGET_DESCRIPTION)" $(BUILD_LOG_PIPE_SIMPLE) @@ -142,12 +153,7 @@ main: $(INIT_TARGETS) $(SEQUENTIAL_TARGETS) ) # We might have cleaned away essential files, recreate them. ( cd $(TOPDIR) && \ - $(MAKE) $(MAKE_ARGS) -j 1 -f make/GenerateFindTests.gmk \ - $(USER_MAKE_VARS) ) - ( cd $(TOPDIR) && \ - $(MAKE) $(MAKE_ARGS) -j 1 -f make/Main.gmk $(USER_MAKE_VARS) \ - UPDATE_MODULE_DEPS=true NO_RECIPES=true \ - create-main-targets-include ) + $(MAKE) $(MAKE_ARGS) -j 1 -f make/Init.gmk create-make-helpers ) endif ifneq ($(PARALLEL_TARGETS), ) $(call PrepareFailureLogs) diff --git a/make/Main.gmk b/make/Main.gmk index 386bd226842..d0568509a4e 100644 --- a/make/Main.gmk +++ b/make/Main.gmk @@ -423,14 +423,6 @@ bootcycle-images: ifneq ($(COMPILE_TYPE), cross) $(call LogWarn, Boot cycle build step 2: Building a new JDK image using previously built image) $(call MakeDir, $(OUTPUTDIR)/bootcycle-build) - # We need to create essential files for the bootcycle spec dir - ( cd $(TOPDIR) && \ - $(MAKE) $(MAKE_ARGS) -f make/GenerateFindTests.gmk \ - SPEC=$(BOOTCYCLE_SPEC)) - ( cd $(TOPDIR) && \ - $(MAKE) $(MAKE_ARGS) -f $(TOPDIR)/make/Main.gmk \ - SPEC=$(BOOTCYCLE_SPEC) UPDATE_MODULE_DEPS=true NO_RECIPES=true \ - create-main-targets-include ) +$(MAKE) $(MAKE_ARGS) -f $(TOPDIR)/make/Init.gmk PARALLEL_TARGETS=$(BOOTCYCLE_TARGET) \ LOG_PREFIX="[bootcycle] " JOBS= SPEC=$(BOOTCYCLE_SPEC) main else diff --git a/make/PreInit.gmk b/make/PreInit.gmk index bce61ccde5f..3df44308dd9 100644 --- a/make/PreInit.gmk +++ b/make/PreInit.gmk @@ -50,7 +50,8 @@ include $(TOPDIR)/make/Global.gmk # Targets provided by Init.gmk. ALL_INIT_TARGETS := print-modules print-targets print-configuration \ - print-tests reconfigure pre-compare-build post-compare-build + print-tests reconfigure pre-compare-build post-compare-build \ + create-make-helpers # CALLED_TARGETS is the list of targets that the user provided, # or "default" if unspecified. From e235b61a8bb70462921c09d197adc4b60267d327 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Tue, 3 Jun 2025 19:38:58 +0000 Subject: [PATCH 016/143] 8357987: [JVMCI] Add support for retrieving all methods of a ResolvedJavaType Reviewed-by: dnsimon, yzheng, never --- src/hotspot/share/jvmci/jvmciCompilerToVM.cpp | 21 +++++++++++++++++++ src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 1 + .../jdk/vm/ci/hotspot/CompilerToVM.java | 12 ++++++++++- .../HotSpotResolvedJavaMethodImpl.java | 13 ++++++++++++ .../HotSpotResolvedObjectTypeImpl.java | 8 +++++++ .../hotspot/HotSpotResolvedPrimitiveType.java | 5 +++++ .../jdk/vm/ci/hotspot/HotSpotVMConfig.java | 1 + .../jdk/vm/ci/meta/ResolvedJavaMethod.java | 6 ++++++ .../jdk/vm/ci/meta/ResolvedJavaType.java | 12 +++++++++++ .../runtime/test/TestResolvedJavaMethod.java | 13 ++++++++++++ .../ci/runtime/test/TestResolvedJavaType.java | 12 +++++++++++ 11 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index 2256a0edc99..4f6d4aaa099 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -2224,6 +2224,26 @@ C2V_VMENTRY_NULL(jobjectArray, getDeclaredMethods, (JNIEnv* env, jobject, ARGUME return JVMCIENV->get_jobjectArray(methods); C2V_END +C2V_VMENTRY_NULL(jobjectArray, getAllMethods, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass))) + Klass* klass = UNPACK_PAIR(Klass, klass); + if (klass == nullptr) { + JVMCI_THROW_NULL(NullPointerException); + } + if (!klass->is_instance_klass()) { + JVMCIObjectArray methods = JVMCIENV->new_ResolvedJavaMethod_array(0, JVMCI_CHECK_NULL); + return JVMCIENV->get_jobjectArray(methods); + } + + InstanceKlass* iklass = InstanceKlass::cast(klass); + JVMCIObjectArray methods = JVMCIENV->new_ResolvedJavaMethod_array(iklass->methods()->length(), JVMCI_CHECK_NULL); + for (int i = 0; i < iklass->methods()->length(); i++) { + methodHandle mh(THREAD, iklass->methods()->at(i)); + JVMCIObject method = JVMCIENV->get_jvmci_method(mh, JVMCI_CHECK_NULL); + JVMCIENV->put_object_at(methods, i, method); + } + return JVMCIENV->get_jobjectArray(methods); +C2V_END + C2V_VMENTRY_NULL(jobjectArray, getDeclaredFieldsInfo, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass))) Klass* klass = UNPACK_PAIR(Klass, klass); if (klass == nullptr) { @@ -3359,6 +3379,7 @@ JNINativeMethod CompilerToVM::methods[] = { {CC "boxPrimitive", CC "(" OBJECT ")" OBJECTCONSTANT, FN_PTR(boxPrimitive)}, {CC "getDeclaredConstructors", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getDeclaredConstructors)}, {CC "getDeclaredMethods", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getDeclaredMethods)}, + {CC "getAllMethods", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getAllMethods)}, {CC "getDeclaredFieldsInfo", CC "(" HS_KLASS2 ")[" FIELDINFO, FN_PTR(getDeclaredFieldsInfo)}, {CC "readStaticFieldValue", CC "(" HS_KLASS2 "JC)" JAVACONSTANT, FN_PTR(readStaticFieldValue)}, {CC "readFieldValue", CC "(" OBJECTCONSTANT HS_KLASS2 "JC)" JAVACONSTANT, FN_PTR(readFieldValue)}, diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 4302c0ce6ad..e26c815946d 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -693,6 +693,7 @@ declare_constant(ConstMethodFlags::_misc_reserved_stack_access) \ declare_constant(ConstMethodFlags::_misc_changes_current_thread) \ declare_constant(ConstMethodFlags::_misc_is_scoped) \ + declare_constant(ConstMethodFlags::_misc_is_overpass) \ \ declare_constant(CounterData::count_off) \ \ diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index d7356659296..045b1f0fc12 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -1148,7 +1148,8 @@ final class CompilerToVM { native ResolvedJavaMethod[] getDeclaredConstructors(HotSpotResolvedObjectTypeImpl klass, long klassPointer); /** - * Gets the {@link ResolvedJavaMethod}s for all the non-constructor methods of {@code klass}. + * Gets the {@link ResolvedJavaMethod}s for all non-overpass and non-initializer + * methods of {@code klass}. */ ResolvedJavaMethod[] getDeclaredMethods(HotSpotResolvedObjectTypeImpl klass) { return getDeclaredMethods(klass, klass.getKlassPointer()); @@ -1156,6 +1157,15 @@ final class CompilerToVM { native ResolvedJavaMethod[] getDeclaredMethods(HotSpotResolvedObjectTypeImpl klass, long klassPointer); + /** + * Gets the {@link ResolvedJavaMethod}s for all methods of {@code klass}. + */ + ResolvedJavaMethod[] getAllMethods(HotSpotResolvedObjectTypeImpl klass) { + return getAllMethods(klass, klass.getKlassPointer()); + } + + native ResolvedJavaMethod[] getAllMethods(HotSpotResolvedObjectTypeImpl klass, long klassPointer); + HotSpotResolvedObjectTypeImpl.FieldInfo[] getDeclaredFieldsInfo(HotSpotResolvedObjectTypeImpl klass) { return getDeclaredFieldsInfo(klass, klass.getKlassPointer()); } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java index 59f5320cc54..9440a719dd4 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedJavaMethodImpl.java @@ -573,6 +573,19 @@ final class HotSpotResolvedJavaMethodImpl extends HotSpotMethod implements HotSp return ((getModifiers() & mask) == Modifier.PUBLIC) && getDeclaringClass().isInterface(); } + /* + * Currently in hotspot a method can either be a "normal" or an "overpass" + * method. Overpass methods are instance methods which are created when + * otherwise a valid candidate for method resolution would not be found. + */ + @Override + public boolean isDeclared() { + if (isConstructor() || isClassInitializer()) { + return false; + } + return (getConstMethodFlags() & config().constMethodFlagsIsOverpass) == 0; + } + @Override public Type[] getGenericParameterTypes() { if (isClassInitializer()) { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java index 8d3b9f48b4c..17eede6f490 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl.java @@ -1067,6 +1067,14 @@ final class HotSpotResolvedObjectTypeImpl extends HotSpotResolvedJavaType implem return runtime().compilerToVm.getDeclaredMethods(this); } + @Override + public List getAllMethods(boolean forceLink) { + if (forceLink) { + link(); + } + return List.of(runtime().compilerToVm.getAllMethods(this)); + } + @Override public ResolvedJavaMethod getClassInitializer() { if (!isArray()) { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java index 4425ade0880..b707906af08 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotResolvedPrimitiveType.java @@ -296,6 +296,11 @@ public final class HotSpotResolvedPrimitiveType extends HotSpotResolvedJavaType return new ResolvedJavaMethod[0]; } + @Override + public List getAllMethods(boolean forceLink) { + return List.of(); + } + @Override public ResolvedJavaMethod getClassInitializer() { return null; diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index e93b98042b6..bc2e121fe90 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java @@ -196,6 +196,7 @@ class HotSpotVMConfig extends HotSpotVMConfigAccess { final int constMethodFlagsCallerSensitive = getConstant("ConstMethodFlags::_misc_caller_sensitive", Integer.class); final int constMethodFlagsIntrinsicCandidate = getConstant("ConstMethodFlags::_misc_intrinsic_candidate", Integer.class); final int constMethodFlagsIsScoped = getConstant("ConstMethodFlags::_misc_is_scoped", Integer.class); + final int constMethodFlagsIsOverpass = getConstant("ConstMethodFlags::_misc_is_overpass", Integer.class); final int constMethodHasLineNumberTable = getConstant("ConstMethodFlags::_misc_has_linenumber_table", Integer.class); final int constMethodHasLocalVariableTable = getConstant("ConstMethodFlags::_misc_has_localvariable_table", Integer.class); final int constMethodHasMethodAnnotations = getConstant("ConstMethodFlags::_misc_has_method_annotations", Integer.class); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java index 8758dbb3bb6..f401bc30f83 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java @@ -114,6 +114,12 @@ public interface ResolvedJavaMethod extends JavaMethod, InvokeTarget, ModifiersP */ boolean isDefault(); + /** + * Returns {@code true} if this method is contained in the array returned by + * {@code getDeclaringClass().getDeclaredMethods()} + */ + boolean isDeclared(); + /** * Checks whether this method is a class initializer. * diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java index b5d29733a69..929a4713330 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaType.java @@ -23,6 +23,7 @@ package jdk.vm.ci.meta; import java.lang.reflect.AnnotatedElement; +import java.util.List; import jdk.vm.ci.meta.Assumptions.AssumptionResult; @@ -365,6 +366,17 @@ public interface ResolvedJavaType extends JavaType, ModifiersProvider, Annotated throw new UnsupportedOperationException(); } + /** + * Returns a list containing all methods present within this type. This list can + * include methods implicitly created and used by the VM that are not present in + * {@link #getDeclaredMethods}. The returned List is unmodifiable; calls to any + * mutator method will always cause {@code UnsupportedOperationException} to be + * thrown. + * + * @param forceLink if {@code true}, forces this type to be {@link #link linked} + */ + List getAllMethods(boolean forceLink); + /** * Returns the {@code } method for this class if there is one. */ diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java index 75f074ad09d..6151ae68ce7 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaMethod.java @@ -425,6 +425,19 @@ public class TestResolvedJavaMethod extends MethodUniverse { } } + @Test + public void isDeclaredTest() { + for (Map.Entry e : methods.entrySet()) { + ResolvedJavaMethod m = e.getValue(); + boolean expectedDeclared = Arrays.stream(m.getDeclaringClass().getDeclaredMethods()).anyMatch(i -> i.equals(m)); + assertEquals(expectedDeclared, m.isDeclared()); + } + for (Map.Entry, ResolvedJavaMethod> e : constructors.entrySet()) { + ResolvedJavaMethod m = e.getValue(); + assertFalse(m.isDeclared()); + } + } + @Test public void hasReceiverTest() { for (Map.Entry e : methods.entrySet()) { diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java index 456c4a5fd88..ee48724b71d 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java @@ -1019,6 +1019,18 @@ public class TestResolvedJavaType extends TypeUniverse { } } + @Test + public void getAllMethodsTest() { + for (Class c : classes) { + ResolvedJavaType type = metaAccess.lookupJavaType(c); + Set allMethods = new HashSet<>(type.getAllMethods(true)); + Stream allKnownMethods = Stream.concat(Arrays.stream(type.getDeclaredMethods()), Arrays.stream(type.getDeclaredConstructors())); + allKnownMethods = Stream.concat(allKnownMethods, Stream.ofNullable(type.getClassInitializer())); + List missingMethods = allKnownMethods.filter(m -> !allMethods.contains(m)).toList(); + assertTrue(missingMethods.toString(), missingMethods.isEmpty()); + } + } + static class A { static String name = "foo"; } From 704b5990a750719ca927e156553db7982637e590 Mon Sep 17 00:00:00 2001 From: Cesar Soares Lucas Date: Tue, 3 Jun 2025 20:15:20 +0000 Subject: [PATCH 017/143] 8358534: Bailout in Conv2B::Ideal when type of cmp input is not supported Reviewed-by: shade --- src/hotspot/share/opto/convertnode.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index 5df79a36edb..c468c66fdfd 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -79,6 +79,11 @@ Node* Conv2BNode::Ideal(PhaseGVN* phase, bool can_reshape) { assert(false, "Unrecognized comparison for Conv2B: %s", NodeClassNames[in(1)->Opcode()]); } + // Skip the transformation if input is unexpected. + if (cmp == nullptr) { + return nullptr; + } + // Replace Conv2B with the cmove Node* bol = phase->transform(new BoolNode(cmp, BoolTest::eq)); return new CMoveINode(bol, phase->intcon(1), phase->intcon(0), TypeInt::BOOL); From da49fa5e15b137c086ad8fd438bf448da42121cb Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Tue, 3 Jun 2025 20:47:17 +0000 Subject: [PATCH 018/143] 8354460: Streaming output for attach API should be turned on by default Reviewed-by: sspitsyn, kevinw --- src/hotspot/share/services/attachListener.cpp | 4 ++-- .../share/classes/sun/tools/attach/HotSpotVirtualMachine.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/services/attachListener.cpp b/src/hotspot/share/services/attachListener.cpp index d13a2e70b2b..92d3c302ded 100644 --- a/src/hotspot/share/services/attachListener.cpp +++ b/src/hotspot/share/services/attachListener.cpp @@ -172,8 +172,8 @@ volatile AttachListenerState AttachListener::_state = AL_NOT_INITIALIZED; AttachAPIVersion AttachListener::_supported_version = ATTACH_API_V1; -// Default is false (if jdk.attach.vm.streaming property is not set). -bool AttachListener::_default_streaming_output = false; +// Default is true (if jdk.attach.vm.streaming property is not set). +bool AttachListener::_default_streaming_output = true; static bool get_bool_sys_prop(const char* name, bool default_value, TRAPS) { ResourceMark rm(THREAD); diff --git a/src/jdk.attach/share/classes/sun/tools/attach/HotSpotVirtualMachine.java b/src/jdk.attach/share/classes/sun/tools/attach/HotSpotVirtualMachine.java index e0ac0edda51..97139a5a206 100644 --- a/src/jdk.attach/share/classes/sun/tools/attach/HotSpotVirtualMachine.java +++ b/src/jdk.attach/share/classes/sun/tools/attach/HotSpotVirtualMachine.java @@ -63,9 +63,8 @@ public abstract class HotSpotVirtualMachine extends VirtualMachine { static { String s = VM.getSavedProperty("jdk.attach.allowAttachSelf"); ALLOW_ATTACH_SELF = "".equals(s) || Boolean.parseBoolean(s); - // For now the default is false. String s2 = VM.getSavedProperty("jdk.attach.allowStreamingOutput"); - ALLOW_STREAMING_OUTPUT = "".equals(s2) || Boolean.parseBoolean(s2); + ALLOW_STREAMING_OUTPUT = !("false".equals(s2)); } private final boolean selfAttach; From 939753579b80d2910c78d8bbb1c97bd56afa21ba Mon Sep 17 00:00:00 2001 From: Daniel Gredler Date: Tue, 3 Jun 2025 23:27:44 +0000 Subject: [PATCH 019/143] 8356803: Test TextLayout/TestControls fails on windows & linux: line and paragraph separator show non-zero advance 8356812: Create an automated version of TextLayout/TestControls Reviewed-by: prr, honkar --- .../share/classes/sun/font/FontUtilities.java | 12 +- .../TextLayout/FormatCharAdvanceTest.java | 56 ++++-- .../awt/font/TextLayout/TestControls.java | 161 ------------------ 3 files changed, 54 insertions(+), 175 deletions(-) delete mode 100644 test/jdk/java/awt/font/TextLayout/TestControls.java diff --git a/src/java.desktop/share/classes/sun/font/FontUtilities.java b/src/java.desktop/share/classes/sun/font/FontUtilities.java index c2e3137e1b5..a14214ab751 100644 --- a/src/java.desktop/share/classes/sun/font/FontUtilities.java +++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java @@ -370,17 +370,25 @@ public final class FontUtilities { } /** - * Checks whether or not the specified codepoint is whitespace which is + *

Checks whether or not the specified codepoint is whitespace which is * ignorable at the shaping stage of text rendering. These ignorable * whitespace characters should be used prior to text shaping and * rendering to determine the position of the text, but are not themselves * rendered. * + *

Includes 0x0009 (horizontal tab / TAB), 0x000A (line feed / LF), + * 0x000B (vertical tab / VT), 0x000C (form feed / FF), + * 0x000D (carriage return / CR), 0x0085 (next line / NEL), + * 0x2028 (line separator / LS), 0x2029 (paragraph separator / PS). + * * @param ch the codepoint to check * @return whether the specified codepoint is ignorable whitespace */ public static boolean isIgnorableWhitespace(int ch) { - return ch == 0x0009 || ch == 0x000a || ch == 0x000d; + return (ch >= 0x0009 && ch <= 0x000d) + || ch == 0x0085 + || ch == 0x2028 + || ch == 0x2029; } public static PlatformLogger getLogger() { diff --git a/test/jdk/java/awt/font/TextLayout/FormatCharAdvanceTest.java b/test/jdk/java/awt/font/TextLayout/FormatCharAdvanceTest.java index b37f7e02707..63cd890f1db 100644 --- a/test/jdk/java/awt/font/TextLayout/FormatCharAdvanceTest.java +++ b/test/jdk/java/awt/font/TextLayout/FormatCharAdvanceTest.java @@ -21,10 +21,11 @@ * questions. */ -/** +/* * @test - * @bug 8208377 6562489 8270265 - * @summary Confirm that format-category glyphs are not rendered or measured. + * @bug 8208377 6562489 8270265 8356803 8356812 4517298 + * @summary Confirm that default-ignorable and ignorable-whitespace + * glyphs are not rendered or measured. */ import java.awt.Color; @@ -48,9 +49,11 @@ import java.util.Map; public class FormatCharAdvanceTest { /** - *

Font created for this test which contains glyphs for 0-9, a-z, A-Z, space, and most - * characters with Unicode general category = Format (Cf); the tests will pass if these - * format glyphs and their advances are ignored during text measurement and text drawing. + *

Font created for this test which contains glyphs for 0-9, a-z, A-Z, + * some whitespace characters, and most characters with Unicode "Format" + * (Cf) general category; the tests will pass if the font glyphs and + * advances for default-ignorable and ignorable-whitespace characters + * are ignored during text measurement and text drawing. * *

The following FontForge Python script was used to generate this font: * @@ -87,9 +90,11 @@ public class FormatCharAdvanceTest { * chars = 'bcdefghijklmnopqrstuvwxyz' \ * 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ * '1234567890' \ - * '\u00AD\u0600\u0601\u0602\u0603\u0604\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E' \ - * '\u200B\u200C\u200D\u200E\u200F\u202A\u202B\u202C\u202D\u202E\u2060\u2061\u2062\u2063\u2064' \ - * '\u2066\u2067\u2068\u2069\u206A\u206B\u206C\u206D\u206E\u206F\uFEFF\uFFF9\uFFFA\uFFFB' \ + * '\u0009\u000A\u000B\u000C\u000D\u0085\u00AD' \ + * '\u0600\u0601\u0602\u0603\u0604\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E' \ + * '\u200B\u200C\u200D\u200E\u200F\u2028\u2029\u202A\u202B\u202C\u202D\u202E\u202F' \ + * '\u2060\u2061\u2062\u2063\u2064\u2065\u2066\u2067\u2068\u2069\u206A\u206B\u206C' \ + * '\u206D\u206E\u206F\uFEFF\uFFF9\uFFFA\uFFFB' \ * '\U000110BD\U000110CD\U00013430\U00013431\U00013432\U00013433\U00013434\U00013435\U00013436' \ * '\U00013437\U00013438\U00013439\U0001343A\U0001343B\U0001343C\U0001343D\U0001343E\U0001343F' \ * '\U0001BCA0\U0001BCA1\U0001BCA2\U0001BCA3\U0001D173\U0001D174\U0001D175\U0001D176\U0001D177' \ @@ -119,12 +124,12 @@ public class FormatCharAdvanceTest { * f4.write(encoded) * */ - private static final String TTF_BYTES = "AAEAAAANAIAAAwBQRkZUTaj5NMoAABWEAAAAHE9TLzKEBfqWAAABWAAAAGBjbWFw6BKIbQAAAuQAAAMoY3Z0IABEBREAAAYMAAAABGdhc3D//wADAAAVfAAAAAhnbHlmcb+r/AAABzQAAAksaGVhZCi9kzQAAADcAAAANmhoZWEIcgJeAAABFAAAACRobXR4JX8bnAAAAbgAAAEqbG9jYam4p4gAAAYQAAABIm1heHAA1wBCAAABOAAAACBuYW1lImUC5wAAEGAAAAGJcG9zdBd/2qEAABHsAAADjwABAAAAAQAAEAez8l8PPPUACwgAAAAAAON7pHEAAAAA43ukcQBEAAACZAVVAAAACAACAAAAAAAAAAEAAAVVAAAAuAJYAAAAAAJkAAEAAAAAAAAAAAAAAAAAAAAFAAEAAACQAAgAAgAIAAIAAgAAAAEAAQAAAEAALgABAAEABAJXAZAABQAABTMFmQAAAR4FMwWZAAAD1wBmAhIAAAIABQMAAAAAAACAACADAgAAABECAKgAAAAAUGZFZACAACD//wZm/mYAuAVVAAAAAAABAAAAAADIAMgAAAAgAAEC7ABEAAAAAAKqAAACOQAAAlgAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAAAAAAAFAAAAAwAAACwAAAAEAAAA2gABAAAAAAIiAAMAAQAAACwAAwAKAAAA2gAEAK4AAAAmACAABAAGACAAOQBaAHoArQYFBhwG3QcPCJEI4hgOIA8gLiBkIG/+///7//8AAAAgADAAQQBhAK0GAAYcBt0HDwiQCOIYDiALICogYCBm/v//+f///+P/1P/N/8f/lfpD+i35bfk897z3bOhB4EXgK9/63/kBagAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABqAGsAbAAMAAAAAAFIAAAAAAAAABoAAAAgAAAAIAAAAAMAAAAwAAAAOQAAAAQAAABBAAAAWgAAAA4AAABhAAAAegAAACgAAACtAAAArQAAAEIAAAYAAAAGBQAAAEMAAAYcAAAGHAAAAEkAAAbdAAAG3QAAAEoAAAcPAAAHDwAAAEsAAAiQAAAIkQAAAEwAAAjiAAAI4gAAAE4AABgOAAAYDgAAAE8AACALAAAgDwAAAFAAACAqAAAgLgAAAFUAACBgAAAgZAAAAFoAACBmAAAgbwAAAF8AAP7/AAD+/wAAAGkAAP/5AAD/+wAAAGoAARC9AAEQvQAAAG0AARDNAAEQzQAAAG4AATQwAAE0PwAAAG8AAbygAAG8owAAAH8AAdFzAAHRegAAAIMADgABAA4AAQAAAIsADgAgAA4AIQAAAIwADgB+AA4AfwAAAI4AAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMAAAAAAAAAAAAAAAAAAAAEBQYHCAkKCwwNAAAAAAAAAA4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnAAAAAAAAKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQFEQAAACwALAAsACwANAA8AEQATABUAFwAZABsAHQAfACEAIwAlACcAKQArAC0ALwAxADMANQA3ADkAOwA9AD8AQQBDAEUARwBJAEsATQBPAFEAUwBXgFmAW4BdgF+AYYBjgGWAZ4BpgGuAbYBvgHGAc4B1gHeAeYB7gH2Af4CBgIOAhYCHgImAi4CNgI+AkYCTgJWAl4CZgJuAnYCfgKGAo4ClgKeAqYCrgK2Ar4CxgLOAtYC3gLmAu4C9gL+AwYDDgMWAx4DJgMuAzYDPgNGA04DVgNeA2YDbgN2A34DhgOOA5YDngOmA64DtgO+A8YDzgPWA94D5gPuA/YD/gQGBA4EFgQeBCYELgQ2BD4ERgROBFYEXgRmBG4EdgR+BIYEjgSWAAAAAgBEAAACZAVVAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIRElIREhRAIg/iQBmP5oBVX6q0QEzQAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAAAAIAZABkAfQAyAADAAcAADc1IRUhNSEVZAGQ/nABkGRkZGRk//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAP//AGQAZAH0AMgSBgAoAAD//wBkAGQB9ADIEgYAKAAA//8AZABkAfQAyBIGACgAAAAAAA4ArgABAAAAAAAAAAAAAgABAAAAAAABAAQADQABAAAAAAACAAcAIgABAAAAAAADACAAbAABAAAAAAAEAAQAlwABAAAAAAAFAA8AvAABAAAAAAAGAAQA1gADAAEECQAAAAAAAAADAAEECQABAAgAAwADAAEECQACAA4AEgADAAEECQADAEAAKgADAAEECQAEAAgAjQADAAEECQAFAB4AnAADAAEECQAGAAgAzAAAAABUAGUAcwB0AABUZXN0AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAFQAZQBzAHQAIAA6ACAAOAAtADEAMgAtADIAMAAyADQAAEZvbnRGb3JnZSAyLjAgOiBUZXN0IDogOC0xMi0yMDI0AABUAGUAcwB0AABUZXN0AABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAAVmVyc2lvbiAwMDEuMDAwAABUAGUAcwB0AABUZXN0AAAAAAACAAAAAAAA/2cAZgAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAABAAIAAwATABQAFQAWABcAGAAZABoAGwAcACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0BAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8BIAEhASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQBNQE2ATcBOAE5AToBOwE8AT0BPgE/AUABQQFCAUMBRAFFAUYBRwFIAUkBSgFLAUwBTQFOAU8HdW5pMDBBRAd1bmkwNjAwB3VuaTA2MDEHdW5pMDYwMgd1bmkwNjAzB3VuaTA2MDQHdW5pMDYwNQd1bmkwNjFDB3VuaTA2REQHdW5pMDcwRgd1bmkwODkwB3VuaTA4OTEHdW5pMDhFMgd1bmkxODBFB3VuaTIwMEIHdW5pMjAwQwd1bmkyMDBEB3VuaTIwMEUHdW5pMjAwRgd1bmkyMDJBB3VuaTIwMkIHdW5pMjAyQwd1bmkyMDJEB3VuaTIwMkUHdW5pMjA2MAd1bmkyMDYxB3VuaTIwNjIHdW5pMjA2Mwd1bmkyMDY0B3VuaTIwNjYHdW5pMjA2Nwd1bmkyMDY4B3VuaTIwNjkHdW5pMjA2QQd1bmkyMDZCB3VuaTIwNkMHdW5pMjA2RAd1bmkyMDZFB3VuaTIwNkYHdW5pRkVGRgd1bmlGRkY5B3VuaUZGRkEHdW5pRkZGQgZ1MTEwQkQGdTExMENEBnUxMzQzMAZ1MTM0MzEGdTEzNDMyBnUxMzQzMwZ1MTM0MzQGdTEzNDM1BnUxMzQzNgZ1MTM0MzcGdTEzNDM4BnUxMzQzOQZ1MTM0M0EGdTEzNDNCBnUxMzQzQwZ1MTM0M0QGdTEzNDNFBnUxMzQzRgZ1MUJDQTAGdTFCQ0ExBnUxQkNBMgZ1MUJDQTMGdTFEMTczBnUxRDE3NAZ1MUQxNzUGdTFEMTc2BnUxRDE3NwZ1MUQxNzgGdTFEMTc5BnUxRDE3QQZ1RTAwMDEGdUUwMDIwBnVFMDAyMQZ1RTAwN0UGdUUwMDdGAAAAAAH//wACAAAAAQAAAADiAevnAAAAAON7pHEAAAAA43ukcQ=="; + private static final String TTF_BYTES = "AAEAAAANAIAAAwBQRkZUTarBS1AAABbcAAAAHE9TLzKD7vqWAAABWAAAAGBjbWFw11zF/AAAAvwAAANSY3Z0IABEBREAAAZQAAAABGdhc3D//wADAAAW1AAAAAhnbHlmgVJ3qAAAB4gAAAnMaGVhZCqFqboAAADcAAAANmhoZWEIcgJiAAABFAAAACRobXR4L1UevAAAAbgAAAFEbG9jYb8EwZoAAAZUAAABNG1heHAA4ABCAAABOAAAACBuYW1lJWcF2wAAEVQAAAGJcG9zdBSfZd0AABLgAAAD8QABAAAAAQAAzMHptF8PPPUACwgAAAAAAORfr7QAAAAA5F+vtABEAAACZAVVAAAACAACAAAAAAAAAAEAAAVVAAAAuAJYAAAAAAJkAAEAAAAAAAAAAAAAAAAAAAAJAAEAAACZAAgAAgAIAAIAAgAAAAEAAQAAAEAALgABAAEABAJXAZAABQAABTMFmQAAAR4FMwWZAAAD1wBmAhIAAAIABQMAAAAAAACAACADAgAAABECAKgAAAAAUGZFZACAAAn//wZm/mYAuAVVAAAAAAABAAAAAADIAMgAAAAgAAEC7ABEAAAAAAJYAGQCWABkAlgAZAJYAGQCWABkAjkAAAJYAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAZABkAGQAAAAFAAAAAwAAACwAAAAEAAAA7AABAAAAAAJMAAMAAQAAACwAAwAKAAAA7AAEAMAAAAAoACAABAAIAA0AIAA5AFoAegCFAK0GBQYcBt0HDwiRCOIYDiAPIC8gb/7///v//wAAAAkAIAAwAEEAYQCFAK0GAAYcBt0HDwiQCOIYDiALICggYP7///n//wAA/+f/2P/R/8v/wf+a+kj6Mvly+UH3wfdx6EbgSuAy4AIBcwAAAAEAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAMABAAFAAYAAgBzAHQAdQAMAAAAAAFgAAAAAAAAABwAAAAJAAAADAAAAAMAAAANAAAADQAAAAIAAAAgAAAAIAAAAAcAAAAwAAAAOQAAAAgAAABBAAAAWgAAABIAAABhAAAAegAAACwAAACFAAAAhQAAAEYAAACtAAAArQAAAEcAAAYAAAAGBQAAAEgAAAYcAAAGHAAAAE4AAAbdAAAG3QAAAE8AAAcPAAAHDwAAAFAAAAiQAAAIkQAAAFEAAAjiAAAI4gAAAFMAABgOAAAYDgAAAFQAACALAAAgDwAAAFUAACAoAAAgLwAAAFoAACBgAAAgbwAAAGIAAP7/AAD+/wAAAHIAAP/5AAD/+wAAAHMAARC9AAEQvQAAAHYAARDNAAEQzQAAAHcAATQwAAE0PwAAAHgAAbygAAG8owAAAIgAAdFzAAHRegAAAIwADgABAA4AAQAAAJQADgAgAA4AIQAAAJUADgB+AA4AfwAAAJcAAAEGAAABAAAAAAAAAAEDBAUGAgAAAAAAAAAAAAAAAAAAAAEAAAcAAAAAAAAAAAAAAAAAAAAICQoLDA0ODxARAAAAAAAAABITFBUWFxgZGhscHR4fICEiIyQlJicoKSorAAAAAAAALC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAURAAAALAAsADQAPABEAEwAVABUAFwAZABsAHQAfACEAIwAlACcAKQArAC0ALwAxADMANQA3ADkAOwA9AD8AQQBDAEUARwBJAEsATQBPAFEAUwBVAFcAWQBbAF0AYYBjgGWAZ4BpgGuAbYBvgHGAc4B1gHeAeYB7gH2Af4CBgIOAhYCHgImAi4CNgI+AkYCTgJWAl4CZgJuAnYCfgKGAo4ClgKeAqYCrgK2Ar4CxgLOAtYC3gLmAu4C9gL+AwYDDgMWAx4DJgMuAzYDPgNGA04DVgNeA2YDbgN2A34DhgOOA5YDngOmA64DtgO+A8YDzgPWA94D5gPuA/YD/gQGBA4EFgQeBCYELgQ2BD4ERgROBFYEXgRmBG4EdgR+BIYEjgSWBJ4EpgSuBLYEvgTGBM4E1gTeBOYAAgBEAAACZAVVAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIRElIREhRAIg/iQBmP5oBVX6q0QEzQAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAAAAgBkAGQB9ADIAAMABwAANzUhFSE1IRVkAZD+cAGQZGRkZGT//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAD//wBkAGQB9ADIEgYALAAA//8AZABkAfQAyBIGACwAAP//AGQAZAH0AMgSBgAsAAAAAAAOAK4AAQAAAAAAAAAAAAIAAQAAAAAAAQAEAA0AAQAAAAAAAgAHACIAAQAAAAAAAwAgAGwAAQAAAAAABAAEAJcAAQAAAAAABQAPALwAAQAAAAAABgAEANYAAwABBAkAAAAAAAAAAwABBAkAAQAIAAMAAwABBAkAAgAOABIAAwABBAkAAwBAACoAAwABBAkABAAIAI0AAwABBAkABQAeAJwAAwABBAkABgAIAMwAAAAAVABlAHMAdAAAVGVzdAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAARgBvAG4AdABGAG8AcgBnAGUAIAAyAC4AMAAgADoAIABUAGUAcwB0ACAAOgAgADMAMAAtADUALQAyADAAMgA1AABGb250Rm9yZ2UgMi4wIDogVGVzdCA6IDMwLTUtMjAyNQAAVABlAHMAdAAAVGVzdAAAVgBlAHIAcwBpAG8AbgAgADAAMAAxAC4AMAAwADAAAFZlcnNpb24gMDAxLjAwMAAAVABlAHMAdAAAVGVzdAAAAAAAAgAAAAAAAP9nAGYAAAAAAAAAAAAAAAAAAAAAAAAAAACZAAAAAQECAQMBBAEFAQYAAwATABQAFQAWABcAGAAZABoAGwAcACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0BBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZB3VuaTAwMEQHdW5pMDAwOQd1bmkwMDBBB3VuaTAwMEIHdW5pMDAwQwd1bmkwMDg1B3VuaTAwQUQHdW5pMDYwMAd1bmkwNjAxB3VuaTA2MDIHdW5pMDYwMwd1bmkwNjA0B3VuaTA2MDUHdW5pMDYxQwd1bmkwNkREB3VuaTA3MEYHdW5pMDg5MAd1bmkwODkxB3VuaTA4RTIHdW5pMTgwRQd1bmkyMDBCB3VuaTIwMEMHdW5pMjAwRAd1bmkyMDBFB3VuaTIwMEYHdW5pMjAyOAd1bmkyMDI5B3VuaTIwMkEHdW5pMjAyQgd1bmkyMDJDB3VuaTIwMkQHdW5pMjAyRQd1bmkyMDJGB3VuaTIwNjAHdW5pMjA2MQd1bmkyMDYyB3VuaTIwNjMHdW5pMjA2NAd1bmkyMDY1B3VuaTIwNjYHdW5pMjA2Nwd1bmkyMDY4B3VuaTIwNjkHdW5pMjA2QQd1bmkyMDZCB3VuaTIwNkMHdW5pMjA2RAd1bmkyMDZFB3VuaTIwNkYHdW5pRkVGRgd1bmlGRkY5B3VuaUZGRkEHdW5pRkZGQgZ1MTEwQkQGdTExMENEBnUxMzQzMAZ1MTM0MzEGdTEzNDMyBnUxMzQzMwZ1MTM0MzQGdTEzNDM1BnUxMzQzNgZ1MTM0MzcGdTEzNDM4BnUxMzQzOQZ1MTM0M0EGdTEzNDNCBnUxMzQzQwZ1MTM0M0QGdTEzNDNFBnUxMzQzRgZ1MUJDQTAGdTFCQ0ExBnUxQkNBMgZ1MUJDQTMGdTFEMTczBnUxRDE3NAZ1MUQxNzUGdTFEMTc2BnUxRDE3NwZ1MUQxNzgGdTFEMTc5BnUxRDE3QQZ1RTAwMDEGdUUwMDIwBnVFMDAyMQZ1RTAwN0UGdUUwMDdGAAAAAAAAAf//AAIAAAABAAAAAOIB6+cAAAAA5F+vtAAAAADkX6+0"; /** * Same font as above, but in PostScript Type1 (PFB) format. */ - private static final String TYPE1_BYTES = "gAHhBgAAJSFQUy1BZG9iZUZvbnQtMS4wOiBUZXN0IDAwMS4wMDAKJSVUaXRsZTogVGVzdAolVmVyc2lvbjogMDAxLjAwMAolJUNyZWF0aW9uRGF0ZTogVHVlIERlYyAxMCAwMDo0Mzo1MCAyMDI0CiUlQ3JlYXRvcjogRGFuaWVsIEdyZWRsZXIKJSAyMDI0LTEyLTEwOiBDcmVhdGVkIHdpdGggRm9udEZvcmdlIChodHRwOi8vZm9udGZvcmdlLm9yZykKJSBHZW5lcmF0ZWQgYnkgRm9udEZvcmdlIDIwMjMwMTAxIChodHRwOi8vZm9udGZvcmdlLnNmLm5ldC8pCiUlRW5kQ29tbWVudHMKCjEwIGRpY3QgYmVnaW4KL0ZvbnRUeXBlIDEgZGVmCi9Gb250TWF0cml4IFswLjAwMDQ4ODI4MSAwIDAgMC4wMDA0ODgyODEgMCAwIF1yZWFkb25seSBkZWYKL0ZvbnROYW1lIC9UZXN0IGRlZgovRm9udEJCb3ggezEwMCAxMDAgNTAwIDIwMCB9cmVhZG9ubHkgZGVmCi9QYWludFR5cGUgMCBkZWYKL0ZvbnRJbmZvIDEwIGRpY3QgZHVwIGJlZ2luCiAvdmVyc2lvbiAoMDAxLjAwMCkgcmVhZG9ubHkgZGVmCiAvTm90aWNlICgpIHJlYWRvbmx5IGRlZgogL0Z1bGxOYW1lIChUZXN0KSByZWFkb25seSBkZWYKIC9GYW1pbHlOYW1lIChUZXN0KSByZWFkb25seSBkZWYKIC9XZWlnaHQgKFJlZ3VsYXIpIHJlYWRvbmx5IGRlZgogL0ZTVHlwZSAwIGRlZgogL0l0YWxpY0FuZ2xlIDAgZGVmCiAvaXNGaXhlZFBpdGNoIGZhbHNlIGRlZgogL1VuZGVybGluZVBvc2l0aW9uIC0yMDQuOCBkZWYKIC9VbmRlcmxpbmVUaGlja25lc3MgMTAyLjQgZGVmCmVuZCByZWFkb25seSBkZWYKL0VuY29kaW5nIDI1NiBhcnJheQogMCAxIDI1NSB7IDEgaW5kZXggZXhjaCAvLm5vdGRlZiBwdXR9IGZvcgpkdXAgMzIvc3BhY2UgcHV0CmR1cCA0OC96ZXJvIHB1dApkdXAgNDkvb25lIHB1dApkdXAgNTAvdHdvIHB1dApkdXAgNTEvdGhyZWUgcHV0CmR1cCA1Mi9mb3VyIHB1dApkdXAgNTMvZml2ZSBwdXQKZHVwIDU0L3NpeCBwdXQKZHVwIDU1L3NldmVuIHB1dApkdXAgNTYvZWlnaHQgcHV0CmR1cCA1Ny9uaW5lIHB1dApkdXAgNjUvQSBwdXQKZHVwIDY2L0IgcHV0CmR1cCA2Ny9DIHB1dApkdXAgNjgvRCBwdXQKZHVwIDY5L0UgcHV0CmR1cCA3MC9GIHB1dApkdXAgNzEvRyBwdXQKZHVwIDcyL0ggcHV0CmR1cCA3My9JIHB1dApkdXAgNzQvSiBwdXQKZHVwIDc1L0sgcHV0CmR1cCA3Ni9MIHB1dApkdXAgNzcvTSBwdXQKZHVwIDc4L04gcHV0CmR1cCA3OS9PIHB1dApkdXAgODAvUCBwdXQKZHVwIDgxL1EgcHV0CmR1cCA4Mi9SIHB1dApkdXAgODMvUyBwdXQKZHVwIDg0L1QgcHV0CmR1cCA4NS9VIHB1dApkdXAgODYvViBwdXQKZHVwIDg3L1cgcHV0CmR1cCA4OC9YIHB1dApkdXAgODkvWSBwdXQKZHVwIDkwL1ogcHV0CmR1cCA5Ny9hIHB1dApkdXAgOTgvYiBwdXQKZHVwIDk5L2MgcHV0CmR1cCAxMDAvZCBwdXQKZHVwIDEwMS9lIHB1dApkdXAgMTAyL2YgcHV0CmR1cCAxMDMvZyBwdXQKZHVwIDEwNC9oIHB1dApkdXAgMTA1L2kgcHV0CmR1cCAxMDYvaiBwdXQKZHVwIDEwNy9rIHB1dApkdXAgMTA4L2wgcHV0CmR1cCAxMDkvbSBwdXQKZHVwIDExMC9uIHB1dApkdXAgMTExL28gcHV0CmR1cCAxMTIvcCBwdXQKZHVwIDExMy9xIHB1dApkdXAgMTE0L3IgcHV0CmR1cCAxMTUvcyBwdXQKZHVwIDExNi90IHB1dApkdXAgMTE3L3UgcHV0CmR1cCAxMTgvdiBwdXQKZHVwIDExOS93IHB1dApkdXAgMTIwL3ggcHV0CmR1cCAxMjEveSBwdXQKZHVwIDEyMi96IHB1dApkdXAgMTczL3VuaTAwQUQgcHV0CnJlYWRvbmx5IGRlZgpjdXJyZW50ZGljdCBlbmQKY3VycmVudGZpbGUgZWV4ZWMKgAI5FQAAdD+EE/NjbKhan/77ULS7JzAqXMCrbi+Vm/INMgw3PCEo0KDOcHKx9nKgqjjDwOzrBLRMsXShwxYS1x/6IMkJVCVjeDcveVsL8pQfQ38Fn0GuBZjABRX+8YczNVfzLOMqnufUurZdpTQ/knB+LPzz6M5Eblraw5/Dfs5ktos1bODXEPRbHn8s12iryhQ2CDZodhoQCUZBYtBUwfa/LEajGbPGZFKHYxbBOpJkVtNmKVj9VnQNWFZzbmEE+BPtMZFWd3k07rHFRFrmv27lnlsZcItCh9tLm6+YgpGrwG/ed/zDHuFzCpT7xs7MdEpZqfajjiw9JKNtvO6XgdQ7y2rxwhv69q6/DIarClaEsha9+rFXfYTEkK8BcaUY1RI7beRwxnGx8Mt8pafG9aKKUpeQEJnqFv6Bk9Kz6/t+fAPkHdv8KUIErVw3AxMUFg63Ti+yeRa+1QQ7vDTTkV3O3Obim9olUKuQDhfmH2uWx+K3iBXFqwCLQTC/S/W/LDn9UCsxsphc4HA1txBMdO2NO8rPec0i5GLWOZZI5tOxhUsjPXLloUXLWOkU3I0wBTXW8TlAV2yBn+EPJ4XV14tyCoWHanYChLFqYcChHUHj+0GKluGz0gfPNQ4xf3u6+jyJLmvDFxiNxtSNAud0yBZx12ouXbVSeHGYXWk84KA/Sus2DDbG/E6H+kx2MfnEVMz2yZm/lW6a4w5Pevvdoxfj9BMLm1dBkDCxHQsjw1dbv7yBz3j6kibdv2I4JFxLoEW8qsxcH2SXXgJpM3KrNQlPRIqq8IKmxH3ZllsL/QQ8YJFg+KJjygCl9QOmrL/1FXx/JXmx0m6ynXaUfF1PwtiP7e1ISNfaExaiL1C9OtTK2T4h2WhRPW57ycPj/dYRfjKD7OvXXYQ+Co+s+jCWDRpbwGTLlDP0XQFMBNxx4cBogjdx6ouFfm8lXgfeQf/GC0CtQolv6YH1O06mdXeZ1hqAFwHALulGBr2WdAoV9wBD5SBSeMeIV3waNCP5P0xB7rP7uXTZv/oscgXK/61/3tsjyAXyZASQxvMf8uyWsThQA9TTEOM1o28z9bj6Yhs0Snwl2CxemFy3xgyidcNyBDRsCDFwYKjsejtr9sZGIXAFJK2djcN8aUa549kahghsM9NAB65fQFOwjWjPAZEnunfOsU/LXlWK30nIkQBl+0U0yDsCS6VOi3PKI5fFU4AoRKo71Ax77FoD2fNw16GFRgRagDsfvPWGHutBKIxwbPNTOks4YU8I+pzT3AGdID63P/cLQNDKDfhj2MX+jsr9J7cbAr9GipMFwap2Jls4jSt7wmKqfH/3aQYFwr0OL4EWYKcPGngbKXZU8/q4CkEc2Udvd+jwSDZGPSrMD/dd1b1WuZJfach8jzjYSrqBYqw9fJ1TN/TAr6bltKUFoIHGMArP5qH2RSGq289zzT5GO1vVBHCpBHHGdEYyKBGnYtcOJ+HXNzynHxP7pT+CbBux+zWpg681ryr4TTuVQj2nnBDxfoFmuWIx1wnANhjGXI3Hm/H/X031UQHeONr6mjapeuElW7P85UsunaWeI7Kaz20CH3jEZSq0Olc/qrT6pY5rb7Dtg0Hiue+VXSZquOKbTBMlbWL9zEkrTWWApXDmKjSlRkbf9IlVFebl/KO14wADGp5Q6BjOkl57wNPS/JI/amh72Acy5LEfYHXGMFvMfZ0ZElZLNlstV9YMGD5hPz4M/ZA2O9TMIhj7cVMacHAmV/GTyFIGkONjEM7Z6Cz6NOVBFmkolHa5SHOZMZ2NIy6RVaVi+agAZWSMz6Gt+h1zb7+B9feaArb92rJNha/+T5CR/0p+1fW01vDHSwbX8tkR9CS87ijM0VB/HV9ZHV5OsOqU5NbaIKESihbglj772nnG0iLqt55ZY34M818JpnnCU1oTG26tcbLOGcnfg1z9gSCcD05uG5oI1QmTLlY8iOlbbDf5sZeSTcQp/mWobcEXsTzx3tLH9nk6FZSnUp7Os67Eb3zhe69rLsEBsQrOAV/uKm+Iq4YUQQPjtj3vClIyIMT3i9Be9Yug4qZbl1BiTRLeLICi29JrbQvDBb2t17fIU1f6QmeNVnoT9UuvTBxsUOc2TgmO6SjHP56iAhwfdzIL8RWyMTfR/gaoQ+nwABwi0Z5+9+/hJAzTQby9VRd46WnCVTe+JuUjNt+P1gBa8UfpKlsW9D0PxMtBmY2k4POt3bYDzWnotzEyqBfTksuH7xhZJE5l96VnsBmmJU0qdUFymA6rHunbcaxZXYWa5KlnQvAqM2/Osz5YkNwWUheteEOc8czbDaL7PFZW8kKSAWKBVbCU17oU1sidYJhQ7hy1Bn39pJJcJi7zHnYs9E1Qn0IDcP2YylXdU6i685jTKaLA6dWcFuoHhhHF8tiEcWFyxj+P7wx15mbl1B6kwh3Omot4fA9sPtxbbATz0ElHXM7eTB8jsQBuH8CShTwM7X6wG+M0W0aT4MBE5IjD0n5et7A3cJD1vxo9Td149OWBycfpsmcCwyQfwrvpo6p4dCgju+x5GJ/E3DjzU1lKqmdcEl+wuTP/7Cl2vE60lg8hyeVf99lhycgB85UD0ZDQY+H5zHMF0sH4PJ9CXJFDZdaaeSJTCYpFc73Ttk/syOd91qMjfNFOXua+3pzJnwoz/iis54sbgLCfnGX3yGivMOrgGb2HcOWoVWOhGhzjE3fna21bAS2O5mMsvJGl1vpB0g9qZqSE0afF9KLi+SvoH9yu/Ui9NE3fgz4oxawafRJpoPubIG3ZBDTSxuSIcLfSfMu4eUXTsfB7n2/8DXdb6GqkH1du8ZaJ/KrfgHxa8z+AUaXDfKfJC6RT3f9AgKNMX+Dhzlc71ly1Lcmr6toErG32VKnvdIwMjStMTDxvLiMExCkpIkz1LI1zNPHThh4dgCpYOYmcGBr0tQ/sbw+Bj9Ra1z7maiQs09OoI5bAwkkGQhK8cMVL/DR6tIfcxxAUzjo7Bb706q2qr5TrVGF7/DbQbbDFCj/+OeUBA6NyuMO26mA7+T9KA22UVP5r05R5z2HA6yVZ3o/zCBD3p0HvlpiV3MnIhppxgShfOKXzQRoGcCmhxbHJtsFgs+/BMUPrXdwvArl882UbAyNWTXM/N99crf24ATi7mekTfYrupH+imr6ay1JLqVsbb5V3TJNibiRsVoJI8RG4Ymqu+pJHvjXU7aIuX/OHLMGznB7JP6LV2O0IAk4MItP7q2N6u3I3MIRhbtyrE3N2NkvLwEi2oK7inxqDzwbWlX4L5RalxBO4PLgQUjX01RxykUBsSg2GRbHxX3vT1Qxiyi1C/N5WP/fu8jMzT/X3Akx1yEinQFK9JvuC0f2U/paJamt3kab0Lap4HVnoga+emmxIkjz3UzC+zsGg0cLwEMY7dR8bFivfroAMgynqfgCYwSeFYCWbOrA8Wo12UNGSl3oE+xCMfz6vLvkotp0Woegf5XKshlqLJslQjSBxnjks/k/nGt2cABNZ38L4C8J1IZn7kHnCcIWn6ALn7D0AC5//Mjc+NH+TxAAJ9xnCrgwSf3xIpMt3nQkjXkkT/Rvh1Hk/v7RwYHlU4s5b3acCDJWN3HsMKKjESakM4+5i4+I3WNjnVGUqS4x17pX0KkHLfywKVg5IKjqW7eM6Kz6lG36xOgbG78vySxSiu+9xh97Bv6Ev2e0nYjtTRBwZuwPKGUymrlmKBgwU/kvi4SapC/xXsVWqaS+RM0CstLvu00UbuVNhYRfuEO7bYN3lgQicxK1pXqf6bz1lYZ3fMm0kr9wNetnMl+/wsQV3qVfoC7Z3ykW4mMxtYz6IkfJ6i5lTGytU3UGyQEXvaKb0WvywSiJt6ieXXpJN/z9/m+AHFAPq7OzGqDkri+Ce8NxLU3vHl8QpaJtX/4zZp1Q6/SKGcWe+rAriU5aIy6WYYogaJfgRfYXO4kddrDBEfy09PpeI+nMDTWO/HD/IeXHY1z2HpyZJsGo9SM7CaE/Z+ovSXjIjh3YlPSMG+apFiNXe1Aq66s/1dk3f+3YO1rRGNsTYhiLSFVWQSvzE/xM2UOeoOC5k3nAKB38SoveuX8EvbdVP8YgEkMClJUjinSjZvZY3esyLQjMPd45/1ZcoX6u7EnUNMP+9hyUZ2Owb7YV4RTmPAf9hkXE7bK7qVnFsTgLoarRfeYjgXLf9kez7Z/GQi/HqJRyX3Voh6RhEN8fJ61d8tAMC2vy5IoQLyPqyVnq3L5qeA6vKMSEeLF/i9zBqUJA4T1rfEfWMOPjXydg/VRoj3zS+91gtXM6bshSIHUKUrrHjK/bq9Oa4mEtP9bfoWKcybuZgENB/uatiUGtYFB8nFbO2qgSuJ8YdmHXhABG+mt1QZvHD0EstfjbC8zaT1o9GOPqwbv/F60lRDbIOXRjn9IENptVRZI/ZeYHB6pyOxDmFGlQcxs3axa1bX5yBlmYvr96Gm9gIL0rCw7kO8NHB7aTOUQm3j8MBTe54h1TgDSpsK+W60t/DDX0WDlXcqxev422fFEm3URF9Cipw/NJSXSV4ELwnT6ZjV0xnI7ooz6PBPcfVTEJtymuYd5oWBAGK3QgpNg41c09DuiAST797/UOO8HK8zhK06nF/jWzyPAtbXXCgvqSH2yk8zJDKk/d+Xncb5UsH6VUmEzZbiie38gysjOevISf3d7yi7hu/YEgiwGND2+AlLYV+5d/pMWthxGpHWmEAfBVY4b83VTNDlD6z51akm+RY06HkKM+Mn0byZlopP2MIxZluvfxVK/SiOm5TM+7T0SBlkCEBQEhx5X4h2W1lfIGOHA8RLHfXWLjX7b/PS0rIOZsjplYwg7SA7n/HzukyYassHdDP67wDmMG3bGFYYCsB+9M5JumEGhDpCoYC5SOd6lST+1YGyNs9xiF9hYRtp1Q+7blpsx5OVpdQwctuLEJnciPc5F5hJ7D1qWLFLStTbCPnNxqUtTUAD8mGbBvve0ic+YlKrM8ZdPIzmX0Y0R8GqcEw5wHXoOxedf6kpppOEO+9J1xCKNdAxFbTZqkRplSc96M4rNn8JRTP7g7hecqGaMkL/7rS+fCJj7oBIggyGK9iKvzO9/qx/WCcXf39LgzzT3ZyQa61S0T4z+bNe0nnXzrQmaDMSXyuzNpCR8woWEq5u5dh3ZY/ygwB953URXyjyO9g6RfDaZ7TXPdzrbeQ8Oo9avQTLapa77v3fGyF2mrrz2KgBF+AW99iDh/0pVCtwsRvA6QZMkCBl54O1TEb3Lmrd9dlGFLkRpRfap3Mi1zFqyjRl+c5ENgyfiqVwo/omZS/kjVqQqtAXRw6p/cIaEQRffxvQ3DVJwPplyujMqHb8oiE/fLYVY/qSFTAh5vs60AiZAVpq7h5vqtQtXdiEDs0auIupihC/uxKajYHQ/DohM1QHE+M4DF2itDJf9Go8gy/mRY47vK6IwwNy9TSFZCioIKOlR3LOde+6GcGwYn3i5mSCknfQVydkPHxkHWXIj4gg78y1RUBFgdV3/b7wJ2c+FCQHu/+4RZ8FwlbLBa+zotTpMtU2XGJbQ3KDYYkmjDvXkurqevIZMqjuBOF30b0xqxjC/kNgaczjsj3maTG5yjvCqov98LQIZN7IimQanoM6RMfof3+eZLIV6Uay/xQwl4Hr/LualgYI68MCRKr/h8J/V/o+0OtfLgbTp+UGn2949BtVwxNbR1Few28a6cRrNKTvCFC/AnRTPmUoXgYM2z7/P67ZAJusnLkrTfNoavXFOVgM1hmxCeTtSsBeN/S4cVCUPjg5/UX5uEaIdssVtQk6rTwABW88l6SRsBr1y57hSAXxqCbzvmNi1vbe0icG0MPR/3FuipycvW8w8dxpp86gAGGBG9n/wa0/jWNc8P3kbWbILfMGt8iCjMaAm495oK9cQHrRs6Yp9IJikCzPQk8GaqiZ6V2i9tsOjxyUXuG8MJegMH+3xwncHTfIU/Xw4Y9XmfrNTw1DtcmRgfY0MqkcNa/IwBjB/U7Xx9GLcflIF2T6VcsYFyJiyW3FDLNLOqWkVTG/t82TAs1jzIBz9ZL6lxRm/vcEQql5ApmAU69DCulf1FciksWVGuvpSplApgSeRlfDvRgj0OZ1ViV/nFZJUok9n2V3Qi+BFM3qUWE6s5jD4rl+RaaM+dSo3yKi3ykpuqs0lhvPZMyFYwP8OeQH7rKf+oP3N4LMmAj7Aw+9PcdxarZYQFWY1X93oY7vWm5OIUYHE1lTT8fD4e317PuJnkAh5Mf40fVMJdiMkFU6L4lWK0T1cLWanSG57RaLugCV51wz/LwkliHf5BQkg5yWdm7kDUYj5YIjFyOPtnA23yLoQG6lkAG/G132Y5lh3yJ+8wfQjJVxRO4eTnMNr5shz+2RmDvVVgvGcWXnjLkin8tx12F+8hjgCDnHFJGarimNsyLXAmzH4k5jAaYUBN+soYtTZNTkKScuIcGpcjK9+9gkTTkU0Y18trJfoof94R9kE2s6pTa+QmVYhY2AuI1Po1xmAtdkwjO6VhJl3dHDC/8MVsF2qgbNasaaXFnjvRRZCoszskgYDgQAOiySLt4j7wkaR4m8OzQb2h9DOEMHg4opCHf+ac3+T7l53E8ZBb6teOCHsOVHr9uPwTuhcs5/whJmrDypKUKWVC1rfpFE7HBuiPIftm2OYmgszWMlSWQDJRRWdec/R39T8N1cLo8rAkGF89/wIPasWhgGrv6hjS95LlB/z4NhVDK3/IvsNvDei8H2eCFWZzWBdM49CfcEmzSJKLOrN4053kkFM87XskTJIxBQOVKnX700J3K+ZERkyjS03t4xPCgaE6A3mzxNC3nsbXk5GHUE3C7k2esm7VZeixJQg6VUAkED8enaP3y0Jzz+qVLfuNrg+kzJoyaKlyTId6lAoftXv58qn+TuLJbPONek+Vda36Da6UxrcHjhEnN9QklI9e/RXUt58RUiVXrOKncsPvSQZTvCKKZWEK/6J66oE0h5JBbNIU0U18SqzmN/dWAV/2yBemo0YQ2lptRJ2ayS83C8xSnuQH3q31jaxwnC1hxXmRIjWWe5Lo4QIGnuZbKnoZNkffZ0k+c3Z8epK/V1W+EqUyxIw1CqymYlBYB5PDYqTe527xNVpn51EE6VeyhQpepKYACPNQb9VCHjwT5kjX4xdxKCqfkE27HKYn9g2EsU+f9B1upDybnpzyXbhA23JBHrbonaBrF6vXzCi8KVur+/3eGTZXRhy+xTtzpHDMjpK51SoEiLIntftx+LFsw/C/HgAEVAgAACjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAowMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAowMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMApjbGVhcnRvbWFyawqAAw=="; + private static final String TYPE1_BYTES = "gAFSBwAAJSFQUy1BZG9iZUZvbnQtMS4wOiBUZXN0IDAwMS4wMDAKJSVUaXRsZTogVGVzdAolVmVyc2lvbjogMDAxLjAwMAolJUNyZWF0aW9uRGF0ZTogRnJpIE1heSAzMCAyMDo1NTo0OCAyMDI1CiUlQ3JlYXRvcjogRGFuaWVsIEdyZWRsZXIKJSAyMDI1LTUtMzA6IENyZWF0ZWQgd2l0aCBGb250Rm9yZ2UgKGh0dHA6Ly9mb250Zm9yZ2Uub3JnKQolIEdlbmVyYXRlZCBieSBGb250Rm9yZ2UgMjAyMzAxMDEgKGh0dHA6Ly9mb250Zm9yZ2Uuc2YubmV0LykKJSVFbmRDb21tZW50cwoKMTAgZGljdCBiZWdpbgovRm9udFR5cGUgMSBkZWYKL0ZvbnRNYXRyaXggWzAuMDAwNDg4MjgxIDAgMCAwLjAwMDQ4ODI4MSAwIDAgXXJlYWRvbmx5IGRlZgovRm9udE5hbWUgL1Rlc3QgZGVmCi9Gb250QkJveCB7MTAwIDEwMCA1MDAgMjAwIH1yZWFkb25seSBkZWYKL1BhaW50VHlwZSAwIGRlZgovRm9udEluZm8gMTAgZGljdCBkdXAgYmVnaW4KIC92ZXJzaW9uICgwMDEuMDAwKSByZWFkb25seSBkZWYKIC9Ob3RpY2UgKCkgcmVhZG9ubHkgZGVmCiAvRnVsbE5hbWUgKFRlc3QpIHJlYWRvbmx5IGRlZgogL0ZhbWlseU5hbWUgKFRlc3QpIHJlYWRvbmx5IGRlZgogL1dlaWdodCAoUmVndWxhcikgcmVhZG9ubHkgZGVmCiAvRlNUeXBlIDAgZGVmCiAvSXRhbGljQW5nbGUgMCBkZWYKIC9pc0ZpeGVkUGl0Y2ggZmFsc2UgZGVmCiAvVW5kZXJsaW5lUG9zaXRpb24gLTIwNC44IGRlZgogL1VuZGVybGluZVRoaWNrbmVzcyAxMDIuNCBkZWYKZW5kIHJlYWRvbmx5IGRlZgovRW5jb2RpbmcgMjU2IGFycmF5CiAwIDEgMjU1IHsgMSBpbmRleCBleGNoIC8ubm90ZGVmIHB1dH0gZm9yCmR1cCA5L3VuaTAwMDkgcHV0CmR1cCAxMC91bmkwMDBBIHB1dApkdXAgMTEvdW5pMDAwQiBwdXQKZHVwIDEyL3VuaTAwMEMgcHV0CmR1cCAxMy91bmkwMDBEIHB1dApkdXAgMzIvc3BhY2UgcHV0CmR1cCA0OC96ZXJvIHB1dApkdXAgNDkvb25lIHB1dApkdXAgNTAvdHdvIHB1dApkdXAgNTEvdGhyZWUgcHV0CmR1cCA1Mi9mb3VyIHB1dApkdXAgNTMvZml2ZSBwdXQKZHVwIDU0L3NpeCBwdXQKZHVwIDU1L3NldmVuIHB1dApkdXAgNTYvZWlnaHQgcHV0CmR1cCA1Ny9uaW5lIHB1dApkdXAgNjUvQSBwdXQKZHVwIDY2L0IgcHV0CmR1cCA2Ny9DIHB1dApkdXAgNjgvRCBwdXQKZHVwIDY5L0UgcHV0CmR1cCA3MC9GIHB1dApkdXAgNzEvRyBwdXQKZHVwIDcyL0ggcHV0CmR1cCA3My9JIHB1dApkdXAgNzQvSiBwdXQKZHVwIDc1L0sgcHV0CmR1cCA3Ni9MIHB1dApkdXAgNzcvTSBwdXQKZHVwIDc4L04gcHV0CmR1cCA3OS9PIHB1dApkdXAgODAvUCBwdXQKZHVwIDgxL1EgcHV0CmR1cCA4Mi9SIHB1dApkdXAgODMvUyBwdXQKZHVwIDg0L1QgcHV0CmR1cCA4NS9VIHB1dApkdXAgODYvViBwdXQKZHVwIDg3L1cgcHV0CmR1cCA4OC9YIHB1dApkdXAgODkvWSBwdXQKZHVwIDkwL1ogcHV0CmR1cCA5Ny9hIHB1dApkdXAgOTgvYiBwdXQKZHVwIDk5L2MgcHV0CmR1cCAxMDAvZCBwdXQKZHVwIDEwMS9lIHB1dApkdXAgMTAyL2YgcHV0CmR1cCAxMDMvZyBwdXQKZHVwIDEwNC9oIHB1dApkdXAgMTA1L2kgcHV0CmR1cCAxMDYvaiBwdXQKZHVwIDEwNy9rIHB1dApkdXAgMTA4L2wgcHV0CmR1cCAxMDkvbSBwdXQKZHVwIDExMC9uIHB1dApkdXAgMTExL28gcHV0CmR1cCAxMTIvcCBwdXQKZHVwIDExMy9xIHB1dApkdXAgMTE0L3IgcHV0CmR1cCAxMTUvcyBwdXQKZHVwIDExNi90IHB1dApkdXAgMTE3L3UgcHV0CmR1cCAxMTgvdiBwdXQKZHVwIDExOS93IHB1dApkdXAgMTIwL3ggcHV0CmR1cCAxMjEveSBwdXQKZHVwIDEyMi96IHB1dApkdXAgMTMzL3VuaTAwODUgcHV0CmR1cCAxNzMvdW5pMDBBRCBwdXQKcmVhZG9ubHkgZGVmCmN1cnJlbnRkaWN0IGVuZApjdXJyZW50ZmlsZSBlZXhlYwqAAo0WAAB0P4QT82NsqFqf/vtQtLsnMCpcwKtuL5Wb8g0yDDc8ISjQoM5wcrH2cqCqOMPA7OsEtEyxdKHDFhLXH/ogyQlUJWN4Ny95WwvylB9DfwWfQa4FmMAFFf7xhzM1V/Ms4yqe59S6tl2lND+ScH4s/PPozkRuWtrDn8N+zmS2izVs4NcQ9FsefyzXaKvKFDYINmh2GhAJRkFi0FTB9r8sRqMZs8ZkUodjFsE6kmRW02YpWP1WdA1YVnNuYQT4E+0xkVZ3eTTuscVEWua/buWeWxlwi0KH20ubr5iCkavAb953/MMe4XMKlPvGzsx0Slmp9qOOLD0ko2287peB1DvLavHCG/r2rr8MhqsKVoSyFr36sVd9hMSQrwFxpRjVEjtt5HDGcbHwy3ylp8b1oopSl5AQmeoW/oGT0rPr+358A+Qd2/wpQgStXDcDExQWDrdOL7J5Fr7VBDu8NNORXc7c5uKb2iVQq5AOF+Yfa5bH4reIFcWrAItBML9L9b8sOf1QKzGymFzgcDW3EEx07Y07ys95zSLkYtY5lkjm07GFSyM9cuWhRctY6RTcjTAFNdbxOUBXbIGf4Q8nhdXXi3IKhYdqdgKEsWphwKEdQeP7QYqW4bPSB881DjF/e7r6PIkua8MXGI3G1I0C53TIFnHXai5dtVJ4cZhdaTzgoD9K6zYMNsb8Tof6THYx+cRUzPbJmb+VbprjDk96+92jF+P0EwubV0GQMLEdCyPDV1u/vIHPePqSJt2/YjgkXEugRbyqzFwfZJdeAmkzcqs1CU9EiqrwgqbEfdmWWwv9BDxgkWD4omPKAKX1A6asv/UVfH8lebHSbrKddpR8XU/C2I/t7UhI19oTFqIvUL061MrZPiHZaFE9bnvJw+P91hF+MoPs69ddhD4Kj6z6MJYNGlvAZMuUM/RdAUwE3HHhwGiCN3Hqi4V+byVeB95B/8YLQK1CiW/pgfU7TqZ1d5nWGoAXAcAu6UYGvZZ0ChX3AEPlIFJ4x4hXfBo0I/k/TEHus/u5dNm/+ixyBcr/rX/e2yPIBfJkBJDG8x/y7JaxOFAD1NMQ4zWjbzP1uPpiGzRKfCXYLF6YXLfGDKJ1w3IENGwIMXBgqOx6O2v2xkYhcAUkrZ2Nw3xpRrnj2RqGCGwz00AHrl9AU7CNaM8BkSe6d86xT8teVYuOk926Xj4/+Kx6rfu526Ylz91kxebopcMvql6ysRVzzSGsW2ec9ZPJo1Q/WKb+tCvBm8OdUnRi+DldIajpytyl08TmlS+IRUcHZbxoIjxb7ZCvF8hd03yDhs9bsUSO6h7jbhegenLIiQPX7RtsGAg3logZLD0NUjcAm2tKBieMHhMxAD89lVmuMkNj5r6EaixXvkvhgqzhjPExdu30Knife5IEnlCvIlMCh1EXQsY9KRkzeTRTKfnZTJ/idze+cX0nCEGcFrvCCjZRfNuaLRHx73o2KlDHmoYBm1mFEEvfUReQxS987AiVfSF7cs8IrLEqV9qe2mCAv7ATbVUFbnvYxTBYvHL9sc4892rLrjSu/yP5RZmmIMOpTH8CStLw1zGAcXH3W3ZfPjQkEA4jzvo1U5Oi9EkBKqlgqjj2pFLWepwuNG/L3Iyr36frW6NFtqCxyVQYcNwOTnmQjY0LXPAKxyz2l+xyo/gcDY8nqtWms5aqINgemqM0pG2wBt3GawARbEVkkKllyB/WJbRw0GGHktmj73ixkTulD6nHZ+vMd+aS8iKXPa2ASEOPpxp515DZxg+VYZIrM0h4mDdRff4To/8NKs7Kx4IZx2qzlhIKS2zHpLwuY/U2Wt80e9nqMCFEtnS8sKpcy1rrE9FzXsFUNtYE6LdIat4ygQxoq24Y6D3bfDpl3E8wMvECaVCJX8lfBhQcGGU6+Jba1qfQ7jonjQM0zfoOwHrApk/dkPkSdjQy7UM+dZNMtgZf2N46UuWC2YCtcqCN1X+o5SpojTgXNmTjUy3KOy6L4GNYOxutsDhWiah6rJ+FGVL9D51MJvT3l02T1+LpNt8ZeZQSkZpx7NdFFTFA4/SE8zycfBLNm71Q9pjQKa30TnCY9ZkMEH9rnArLV0w2Gi6LoJoWpT7GoAB2Cdvi3x9wGDdt60K6oABWiD5mpAv4KfrJMBn5dRDJVyRPARXxwBUROAc3BxY8St3WuWKqDqqef0t0rlqAXGynPrcAjqFMXc/GBWORQ7W/dTvbu/eZb+apjKEX7szjaY4cSRsNge4gTyCEsfhLrdBkncM7up9jj206tIaqZQILxHAnrVwYplJ5D2EvSSLYvxFEVHSrI19aNJEvDK64tzXCV+P4yOCOuXpBmc15PDlrMQEauqrD+xneCO+CrCbudHXOJsbmvQq56+ovLp5SNBfdEPQpqo5tBgQaNHcEjg/iVt++uLxT3vAEFW5d7WXPMYdy+XqoMmdRDOzey0Mdx5BZ92rNM/LthQa6F54nvzkypM64HdxCYlri8cO3k0G6bp2eWdseP0P4zMc5QikuXc/Dr4NztBYe2yJLbLLesWSB8nOWcl+gd0GMX5PY4P5mn5WJDNStLWPdD9NapzMsyx6lZwR9Tfv/XSmay9LEo9YaaxysWfllDIasUCdvhZVi7LJvPu/0GbuHLOz9mP6prkF8h8KmjBYMlMktouA23G74M+Pkdfbj9KAM8zOYCgQFZUaXx3iZ4m3uBtDPeKjUcdFZSnHdW11eSv5aCz2fMbExV/qnPOSMbkR5rvpR71WwVZP6j3tNWr8jlPQ8d60k8jXPy7d8wjw9obnDEpvKzKreEzjfA0wCovNt7FLH9oxWmO5TF8JMg/U7+ToEv0fPAW475dXTXdue98/k/c+3+LCJNbHzBBx14CzpteKXKqGNA3jwgJUhfDsTISiN4gF222zIi1deY9BspjPl7jergbwh+ZPdfk2BLaPTDeNUycKdeiFoJEd8gKLvjMrnPa9IX4HwR9gJSjsQ0UWhnYuaXdk12zk8sVkGJAJLrphh5tHcd4LwG2rWcVXwM/2zw1YXQR+jffC1uxUEPVaq1fSf9RB6iEb8LgIollE+gDEybc+Dw3Q/gxVe5/Nu0vHHur6B+4YEcxvz3O5cYKUQTxDgG4mXJhqZ6z7etSWINChQyP6PUFFbsUIDXjfuHZ9oF4ce1NNOnn/RIwm/u/+AduDTtqloUooZjqak4VbvmBEZcW75liKm0MggeilL0nl5arItzTwE8OudJDBkJ/xnf1yZIgeFKSODIz5aQrIiSMJX3BGRxHEEQrm1xtwCfjtMPpFhFtG+o5fjMlCnowJ6+/HrG3FdZKn4/gv7qCxF2/3NW2J4OWuXbjEsUlSHu57DFvhrVl/q8jv81OfXhZKutYQWjaDWwOQHR+ym6wvMyQ9YfiiJpBXa+Ig9uGAbERyApc8wsCLcy0d8Msxx9PAfhrDa+Gf2yhYGA57t8iGxEcoI0A4B8zKVqYXKf4AdsrVzE9cUpfRN/HH8OGWyv9mKMTJyGJQql/OH5hYHnvJVKZXIz6l3nL/7gnpYaGtASiV1q+JwO7WSk1pfwT9SE4sFl7uS7f3pu4BwPhcz/wyN82f144yav11mrGqypOMaMDR27/L0sW1c3hWBBa8BMU/EuwF7ldclS9nxmFHOc35RLruyQ26cp1niukcpK42wg3ozqGQLoQRIoin0AWnUwnOmkdBEkILqFCiWgERDZuCB7G3pZJW/32HPgD3byO0lH8JF6OiINsUmM+75Y3qdvuepTHxXwpDa9Isero3x/f+UWGGUO12s+Ou2yQKtLsQaBNSTaGEBVCAtpJ58TgZ+KfVyhgRQdINr7GuGq9iRPQBY00anspxvsrhfffggqOariHO8h0jdEZPJarJJAufQbYgKiACtJ8iG6jlIs3GfWRI0jRYDw+kHPc2/A6fS5XGF360aX1I6iy5J1pZpN+6stJq5MQ8QRHopTJWQ0DW1Pq46BglmHH9av/X4tvLFFyyC4X6OB2jDb9+XwwcuJ2t/352IcJC06IsWKDsr6E6vF3hC3GUBUy9I7JbqlAh7zlYK52MITQzGINZD2p8+TJjmDycEr/UM/JHqBzbJniUzxg8vo2/FjpZggU8NuBsTZ5P/YZSgRsZcVKkhkGYAn+euvrQIiZ2LMwOygD0gW/zy7v+Z1aHfxKig7kkOR1ZuVCFE8FWx1unCnFHtkjFIx9JY4cYHboveIlF2IzNhfnKJncBt7DuZORqfAT3gQDZlIFNx06tSnTe1v5l7u7VSKMXRhc3bbeCqfvO/NSOqfLz1QYlj2b0C5sm0AcUe1RpC7rOu6muPLeJ38DOIhJHzs+wpqBZTHJr9zdnjZl+OyXaAAbgCsxamEQW91iGdRmVHqyT+0+XQxssYOF795eGYpsXy9Bik6Z4apt/AcuZQFu3XthVXvjyVCi0Rj3gXjOkFdT1YaiRe6yPRdBB2GorcxCzQQvjZXTuh5P4a7MD/6W0mJYoh0BzQHYC0O/T9v3d8GepAbQTytL7MWIWk9C2qZ0pPciuIfXNmGRWBUkCCts1BAsW39l4giNBCwDQ6CauWkz8iWaxx2krBQpKl0WyBBSd/SIJUFM4psvYNaGNk+xR18s0OrPqvxQvc3NNgVwpAljSM/wt3IzIcIsJHF/u0T60hXgEv4H0f44/SzROL0iGUZIfRZK6sJRGBQH2ZVrFCJ6ageJX7WTBLVwKAtxgIUK2dXAemsYgGm/CIe3JCmwL/YmEh8I8W4LrWzXq4vNF6/Sq1hQ2r4dndAO+HEZ8nYpPr+9pUCUgSvnN7aVWZpc/xJaj5L4U6W28hVZ2mb+UUKDpq0bSxKdkVr2SC/7Q/8WimMNDYCaExzXQ+RzZyv/4227pn/k3y1avulXGz3ujy+lqdciVVWHmPYyJPhqeoyDEkM6g46KL9kg912jsB1SGP7Yc2l7XENW59mh5RE+fNiW8wWjxW0Oa+xSe2+RTOgRA+Ojq2L8+g9sL7jKw7BPAPjnYTo8gMat4DurAKbJrtaRLZMzXkFyE7heHr9NsLJl/d1JLRwNoGDpnvLjyPNBUjW/2CTEEIyI6yq3oOJivAwe1yiJib7IUTkh5MCBSnEssvxOYJRUaR/ILAHMJpAoEV/VOCfBlh/I55gVYZWa+Y/5/AMs5Po1w9/18Q6V4ichv80Qr+Up7+2s8sV6zM7m78WMmQp3IR6uTJasqI2Vnr99KAY31BJVpBelRq2xjIruSHx/BAr2kSHA+Kuk1I9+Y1dEaSbfUpjcly0WDUh9x1VTkrUu1u6XN/i/SkFeO1k8o2o7s0rVXNI+VipXQt69H+yVDea7AYuvy9Rp2kKuq7o7mgcFCVq1Vbeyv++vDSKdYmKbyQ2N7ubU6u41Td59YNfRldl56ec0bl7hgyMUb/7/ifjdRXOVl1sTTMUiETnT+44vuAiiUeOHRgKmRlLgHp9iMnPPZVF3DqExLV1IPraZO/olA9QIc4bM2g6SPebITaH94rZn80oQzWzBAFUzK/B2vY39XGdPSTAZTFn1yAYcRHIhqWZs9+FtJvBN9f9lUxPFHUs3vyKBC8sFph/yee7KNIKyTinMNAs1+jcnVS4LPqR4L4cn3ancasZ6wBgexKIjjwf8KPkgUcMwZqk+KwDeJbmyGblqsRzSuhrwPq0U+n4n1ktcqI11Y4D/u93UrU04LgZwWPEZPrgjGUgciPrRa3BIJb6oT1s5VSfgiupwHaFYgF5sJ3VD0ZcT1zwdet8tPprxRq00SIZMZKZeVSIWD0ai82pPxH4PYLUGwUbW3ocaYouILRf9sF9DP1gaVfd4SDRUuKUtZRiG6lpSopmFO24N1vWq90caknzY91uUWX2v4+C/JHDd47e4h/g1Lf13euI6csefzsnQDTDsyrh5zSJDo26B81kY5RyKv+AnsvTnTl8uroRsh1KqeIUywpWImmlnnWT8rpNg822BjraGaWc9NHFzkUSmOmD/iJRdfRjno1JaeDVk8KBniY0vnMqdBG1BWRMsnUAhWxHJ4Jn5fXCltO2C5OV+x+jYocNhWw4Mgu3CVZp9erAEPsDEUsY4qaMitjRq6yJPn3nwkQRpFHGbXv187fPU/z8+BQ5/W4Qfg/qEmJKcp1T5VcrwLAlZQTomKAt/7xNlJIxlx9bCMVXnvk3Y8hB7kCTaSQmvjC8IXCHbJyMCFQV7qAKdmaihyV7zmpqWcDqfELQChm2KxmQJ1dPYJ1+jR+aYm3an5bPEoCdbsZauFF8qXHIhx8JKvRGgxPrTGHi8N5FcfuNsCA3xOkoSGX9Sxg6h5KEtoeFdkwf5BKqhJj5pcsBewAHkWng86BzAqt7R+a9TnyvLMWvmpI/0fkpRkbg731aWOnWOvbpz6f+nXSxGgrvs/B6xSgFPDo6Ty3ivL+CGitfK6XwdHA07TV/eK5vd5oyNe/Ay5wMdXiypEUWukN+jiuGmrVMkke/0DmXmtqqY3T45iLrqfgwRhXsT9kEdzcWVqM4OkAEqEzpjI0NHG74cslhQvDPbX/ZRp57/bcOuarHpCDakvzajblF2NXf0Wp0dO0NltExHFoY8myAlBu/SMn3VN6GF5faMq370MevoNi4eNRD8XiGUYh+QzVphqtLIeqyyt6W4MzTjwSLgAB0kITRYcG0KVi89iUgZMQdwIOfMUBmS/q1GpBtTFKqI2TwC3dz9oBnNzwi3Ru9yyNuX1mw1p0Sd8UNY0yVbdb7UrLrd0ldVrx2FWNVCEedkn0npe9xstIJvgMuRLA0DNN7vex3hfvGvV7qCuQi5ssNVYUkoyEjlFR3xCThuVCgwzK77JmvWMpeB0eR0O3SH9KK/1qaAkXK/bhyazFdT+WHrexsFYZuO5I1MiQWJzNDQ9r3act5mt0senky0dQ+Sb233mB+RX2AWxOf9SUcRtkKjiTvfSdcKHmLx7ns2F8MQdwFoKz7j4a7sPsuruL+BMHWuDj/VTfnVmG6nlzIjs92LVWKnZ010UOv156HJYQvhlSV+WzPv8EC/2A2LhBx0g1ZJ3vYQIu2G1AuhcZurtEOfbhcIsS6cq9P2YoRYTYTHeiXHNzKot/25uSAFs85GQhWyMz+2L3HBiszTmSdwtf0ZzHgGEBc90JD6yNfjwqF4FJ7AcuLYw5euNz/VEON3z2C021shibn6LGd4RjflA8XmBGSakJ0nkpplflYaQ9Awcv2eKYR4ebAVjhyErm4zHikUC5Vgtz/0UbghbNcX0RIEESW2GaGtFNYIYNPHJNtj4QMYLK7BrUOnzITF+Kx/FZG6oIeySnU+TJoZIkJBIqGLOYSkw+QuEbCbf1BM5nERddA8z+IFxYz7Goxg4XMpc3+L/Hx29UZzkNSY55Y2Swii1qxBWgBaRGpzI66cKfr6mHES+cbb9vNAdFPuBgJttyT/t3Jf+W5yRYt8M4VyIlEiUjAGqL3bqakDP5IwywMSpYeS1PmqWBsbUcV9bAavsLMnnEHvUue8GboIr+EeBMCk6XPBKFahrRZBTQq802IGDYOi++VjG7805Tleu2pX6H/IY/LDQVLnqVhxDaVonTvQqMffVvSwXp7EFNvR/XKxh+IJxO/W3tQeAi8Jn77E3itRU+tQEl8QQ65psW7FBQXujG6YYWK9XbPUPI/AkdVmaAuQQt7RPni0MhxB0Q8k6BqzhwbpyLMPa/XCkHdJLrO2xGupnrwYvt0OcF7Ia/hN9+HAekLtq8l+HcNGIY/JA2RPYOYGSEgAEVAgAACjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAowMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAowMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMApjbGVhcnRvbWFyawqAAw=="; public static void main(String[] args) throws Exception { @@ -162,12 +167,20 @@ public class FormatCharAdvanceTest { } private static void testChars(BufferedImage image, Graphics2D g2d, Font font) { + testChar('\t', image, g2d, font); // horizontal tab (TAB) + testChar('\n', image, g2d, font); // line feed (LF) + testChar('\u000B', image, g2d, font); // vertical tab (VT) + testChar('\u000C', image, g2d, font); // form feed (FF) + testChar('\r', image, g2d, font); // carriage return (CR) + testChar('\u0085', image, g2d, font); // next line (NEL) testChar('\u00AD', image, g2d, font); // soft hyphen (SHY) testChar('\u200B', image, g2d, font); // zero width space (ZWSP) testChar('\u200C', image, g2d, font); // zero width non-joiner (ZWNJ) testChar('\u200D', image, g2d, font); // zero width joiner (ZWJ) testChar('\u200E', image, g2d, font); // left-to-right mark (LRM) testChar('\u200F', image, g2d, font); // right-to-left mark (RLM) + testChar('\u2028', image, g2d, font); // line separator (LS) + testChar('\u2029', image, g2d, font); // paragraph separator (PS) testChar('\u202A', image, g2d, font); // left-to-right embedding (LRE) testChar('\u202B', image, g2d, font); // right-to-left embedding (RLE) testChar('\u202C', image, g2d, font); // pop directional formatting (PDF) @@ -181,6 +194,12 @@ public class FormatCharAdvanceTest { testChar('\u2067', image, g2d, font); // right-to-left isolate (RLI) testChar('\u2068', image, g2d, font); // first strong isolate (FSI) testChar('\u2069', image, g2d, font); // pop directional isolate (PDI) + testChar('\u206A', image, g2d, font); // inhibit symmetric swapping + testChar('\u206B', image, g2d, font); // activate symmetric swapping + testChar('\u206C', image, g2d, font); // inhibit arabic form shaping + testChar('\u206D', image, g2d, font); // activate arabic form shaping + testChar('\u206E', image, g2d, font); // national digit shapes + testChar('\u206F', image, g2d, font); // nominal digit shapes testChar('\uFEFF', image, g2d, font); // zero width no-break space (ZWNBSP/BOM) } @@ -191,8 +210,9 @@ public class FormatCharAdvanceTest { int h = image.getHeight(); FontRenderContext frc = g2d.getFontRenderContext(); FontMetrics metrics = g2d.getFontMetrics(font); - String c5 = String.valueOf(c).repeat(5); + assertEqual(0, metrics.charWidth(c), "charWidth", c, font); + String c5 = String.valueOf(c).repeat(5); int ab1 = metrics.stringWidth("AB"); int ab2 = metrics.stringWidth("A" + c5 + "B"); assertEqual(ab1, ab2, "stringWidth", c, font); @@ -226,6 +246,18 @@ public class FormatCharAdvanceTest { ab2 = (int) layout2.getAdvance(); assertEqual(ab1, ab2, "getAdvance", c, font); + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, w, h); + g2d.setColor(Color.BLACK); + layout1.draw(g2d, w / 2, h / 2); + ab1 = findTextBoundingBox(image).width; + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, w, h); + g2d.setColor(Color.BLACK); + layout2.draw(g2d, w / 2, h / 2); + ab2 = findTextBoundingBox(image).width; + assertEqual(ab1, ab2, "TextLayout.draw", c, font); + g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, w, h); g2d.setColor(Color.BLACK); diff --git a/test/jdk/java/awt/font/TextLayout/TestControls.java b/test/jdk/java/awt/font/TextLayout/TestControls.java deleted file mode 100644 index ee15038a845..00000000000 --- a/test/jdk/java/awt/font/TextLayout/TestControls.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2001, 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. - */ - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.Panel; -import java.awt.ScrollPane; -import java.awt.font.FontRenderContext; -import java.awt.font.TextLayout; - -/* - * @test - * @bug 4517298 - * @summary Display special control characters using both TextLayout.draw and - * Graphics.drawString. In no case should a missing glyph appear. - * Also display the advance of the control characters, in all cases - * these should be 0. The space character is also displayed as a reference. - * Note, the character is rendered between '><' but owing to the directional - * properties of two of the characters, the second '<' is rendered as '>'. - * This is correct behavior. - * @library /java/awt/regtesthelpers - * @build PassFailJFrame - * @run main/manual TestControls - */ - -public class TestControls { - private static String fontName = Font.DIALOG; - - public static void main(String[] args) throws Exception { - final String INSTRUCTIONS = """ - A number of control characters are displayed, one per line. - Each line displays the hex value of the character, the character - between '><' as rendered by TextLayout, the character between '><' - as rendered by drawString, and the advance of the character. - The first line renders the space character, as a reference. - The following lines all render the controls. - All controls should not render (even as space) and report a zero advance. - - Pass the test if this is true. - - Note: two of the control characters have the effect of changing the '<' - following the control character so that it renders as '>'. - This is not an error."""; - - PassFailJFrame.builder() - .title("TestControls Instruction") - .instructions(INSTRUCTIONS) - .columns(45) - .testUI(TestControls::createUI) - .build() - .awaitAndCheck(); - } - - private static Frame createUI() { - Frame f = new Frame("TestControls Test UI"); - Panel panel = new ControlPanel(fontName); - ScrollPane sp = new ScrollPane(); - sp.add("Center", panel); - f.add(sp); - f.setSize(450, 400); - return f; - } - - static class ControlPanel extends Panel { - - static final char[] chars = { - (char)0x0020, (char)0x0009, - (char)0x000A, (char)0x000D, (char)0x200C, (char)0x200D, (char)0x200E, - (char)0x200F, (char)0x2028, (char)0x2029, (char)0x202A, (char)0x202B, - (char)0x202C, (char)0x202D, (char)0x202E, (char)0x206A, (char)0x206B, - (char)0x206C, (char)0x206D, (char)0x206E, (char)0x206F - }; - - ControlPanel(String fontName) { - Font font = new Font(fontName, Font.PLAIN, 24); - System.out.println("using font: " + font); - setFont(font); - setForeground(Color.BLACK); - setBackground(Color.WHITE); - } - - @Override - public Dimension getPreferredSize() { - return new Dimension(400, 750); - } - - @Override - public Dimension getMaximumSize() { - return getPreferredSize(); - } - - @Override - public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D)g; - FontRenderContext frc = g2d.getFontRenderContext(); - Font font = g2d.getFont(); - FontMetrics fm = g2d.getFontMetrics(); - Insets insets = getInsets(); - - String jvmString = System.getProperty("java.version"); - String osString = System.getProperty("os.name") + " / " + - System.getProperty("os.arch") + " / " + - System.getProperty("os.version"); - - int x = insets.left + 10; - int y = insets.top; - - y += 30; - g2d.drawString("jvm: " + jvmString, x, y); - - y += 30; - g2d.drawString("os: " + osString, x, y); - - y += 30; - g2d.drawString("font: " + font.getFontName(), x, y); - - for (int i = 0; i < chars.length; ++i) { - String s = ">" + chars[i] + "<"; - x = insets.left + 10; - y += 30; - - g2d.drawString(Integer.toHexString(chars[i]), x, y); - x += 100; - - new TextLayout(s, font, frc).draw(g2d, x, y); - x += 100; - - g2d.drawString(s, x, y); - x += 100; - - g2d.drawString(Integer.toString(fm.charWidth(chars[i])), x, y); - } - } - } -} From 9c74d545147c2eeec187df552037a12b6b476a61 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 3 Jun 2025 23:28:00 +0000 Subject: [PATCH 020/143] 8358158: test/jdk/java/io/Console/CharsetTest.java failing with NoClassDefFoundError: jtreg/SkippedException Reviewed-by: joehw, jlu, iris --- test/jdk/java/io/Console/CharsetTest.java | 73 ------------------ .../java/io/Console/ConsolePromptTest.java | 5 +- test/jdk/java/io/Console/RestoreEchoTest.java | 5 +- .../java/io/Console/StdinEncodingTest.java | 4 +- .../java/io/Console/StdoutEncodingTest.java | 75 +++++++++++++++++++ .../{script.exp => stdoutEncoding.exp} | 6 +- 6 files changed, 83 insertions(+), 85 deletions(-) delete mode 100644 test/jdk/java/io/Console/CharsetTest.java create mode 100644 test/jdk/java/io/Console/StdoutEncodingTest.java rename test/jdk/java/io/Console/{script.exp => stdoutEncoding.exp} (88%) diff --git a/test/jdk/java/io/Console/CharsetTest.java b/test/jdk/java/io/Console/CharsetTest.java deleted file mode 100644 index c8c756def81..00000000000 --- a/test/jdk/java/io/Console/CharsetTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2021, 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. - */ - -import java.io.Console; -import java.nio.file.Files; -import java.nio.file.Paths; - -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; -import static jdk.test.lib.Utils.*; - -/** - * @test - * @bug 8264208 8265918 8356985 - * @summary Tests Console.charset() method. "expect" command in Windows/Cygwin - * does not work as expected. Ignoring tests on Windows. - * @requires (os.family == "linux") | (os.family == "mac") - * @library /test/lib - * @run main CharsetTest en_US.ISO8859-1 ISO-8859-1 - * @run main CharsetTest en_US.US-ASCII US-ASCII - * @run main CharsetTest en_US.UTF-8 UTF-8 - */ -public class CharsetTest { - public static void main(String... args) throws Throwable { - if (args.length == 0) { - // no arg means child java process being tested. - Console con = System.console(); - System.out.println(con.charset()); - return; - } else { - // check "expect" command availability - var expect = Paths.get("/usr/bin/expect"); - if (!Files.exists(expect) || !Files.isExecutable(expect)) { - throw new jtreg.SkippedException("'expect' command not found. Test ignored."); - } - - // invoking "expect" command - OutputAnalyzer output = ProcessTools.executeProcess( - "expect", - "-n", - TEST_SRC + "/script.exp", - TEST_JDK + "/bin/java", - args[0], - args[1], - TEST_CLASSES); - output.reportDiagnosticSummary(); - var eval = output.getExitValue(); - if (eval != 0) { - throw new RuntimeException("Test failed. Exit value from 'expect' command: " + eval); - } - } - } -} diff --git a/test/jdk/java/io/Console/ConsolePromptTest.java b/test/jdk/java/io/Console/ConsolePromptTest.java index 1e4f73b65d2..0c59aaccac4 100644 --- a/test/jdk/java/io/Console/ConsolePromptTest.java +++ b/test/jdk/java/io/Console/ConsolePromptTest.java @@ -104,10 +104,7 @@ public class ConsolePromptTest { OutputAnalyzer output = ProcessTools.executeProcess(command.toArray(String[]::new)); output.reportDiagnosticSummary(); - var eval = output.getExitValue(); - if (eval != 0) { - throw new RuntimeException("Test failed. Exit value from 'expect' command: " + eval); - } + output.shouldHaveExitValue(0); } public static class ConsoleTest { diff --git a/test/jdk/java/io/Console/RestoreEchoTest.java b/test/jdk/java/io/Console/RestoreEchoTest.java index 78d36faa537..6ccc434e7ad 100644 --- a/test/jdk/java/io/Console/RestoreEchoTest.java +++ b/test/jdk/java/io/Console/RestoreEchoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -32,7 +32,6 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; -import static org.junit.jupiter.api.Assertions.*; /** @@ -72,7 +71,7 @@ public class RestoreEchoTest { "-classpath", testClasses, "RestoreEchoTest"); output.reportDiagnosticSummary(); - assertEquals(0, output.getExitValue()); + output.shouldHaveExitValue(0); } public static void main(String... args) throws Throwable { diff --git a/test/jdk/java/io/Console/StdinEncodingTest.java b/test/jdk/java/io/Console/StdinEncodingTest.java index 1f2ea225f96..55811ac912a 100644 --- a/test/jdk/java/io/Console/StdinEncodingTest.java +++ b/test/jdk/java/io/Console/StdinEncodingTest.java @@ -31,7 +31,6 @@ import static jdk.test.lib.Utils.*; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; /** * @test @@ -64,8 +63,7 @@ public class StdinEncodingTest { "-Dstdin.encoding=Uppercasing", // <- gist of this test "StdinEncodingTest"); output.reportDiagnosticSummary(); - var eval = output.getExitValue(); - assertEquals(0, eval, "Test failed. Exit value from 'expect' command: " + eval); + output.shouldHaveExitValue(0); } public static void main(String... args) throws Throwable { diff --git a/test/jdk/java/io/Console/StdoutEncodingTest.java b/test/jdk/java/io/Console/StdoutEncodingTest.java new file mode 100644 index 00000000000..38deddae8b6 --- /dev/null +++ b/test/jdk/java/io/Console/StdoutEncodingTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, 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. + */ + +import java.nio.file.Files; +import java.nio.file.Paths; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import static jdk.test.lib.Utils.*; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * @test + * @bug 8264208 8265918 8356985 8358158 + * @summary Tests if "stdout.encoding" property is reflected in + * Console.charset() method. "expect" command in Windows/Cygwin + * does not work as expected. Ignoring tests on Windows. + * @requires (os.family == "linux") | (os.family == "mac") + * @library /test/lib + * @run junit StdoutEncodingTest + */ +public class StdoutEncodingTest { + + @ParameterizedTest + @CsvSource({ + "en_US.ISO8859-1, ISO-8859-1", + "en_US.US-ASCII, US-ASCII", + "en_US.UTF-8, UTF-8" + }) + void testCharset(String locale, String expectedCharset) throws Exception { + // check "expect" command availability + var expect = Paths.get("/usr/bin/expect"); + Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect), + "'" + expect + "' not found. Test ignored."); + + // invoking "expect" command + OutputAnalyzer output = ProcessTools.executeProcess( + "expect", + "-n", + TEST_SRC + "/stdoutEncoding.exp", + TEST_JDK + "/bin/java", + locale, + expectedCharset, + TEST_CLASSES); + output.reportDiagnosticSummary(); + output.shouldHaveExitValue(0); + } + + public static void main(String... args) { + System.out.println(System.console().charset()); + } +} diff --git a/test/jdk/java/io/Console/script.exp b/test/jdk/java/io/Console/stdoutEncoding.exp similarity index 88% rename from test/jdk/java/io/Console/script.exp rename to test/jdk/java/io/Console/stdoutEncoding.exp index c416990cbe9..4e23fd5fcd8 100644 --- a/test/jdk/java/io/Console/script.exp +++ b/test/jdk/java/io/Console/stdoutEncoding.exp @@ -21,12 +21,14 @@ # questions. # +# `expect` script for StdoutEncodingTest + set java [lrange $argv 0 0] set locale [lrange $argv 1 1] set expected [lrange $argv 2 2] -set args [lrange $argv 3 end] +set classpath [lrange $argv 3 end] regexp {([a-zA-Z_]*).([a-zA-Z0-9\-]*)} $locale dummy lang_region encoding -eval spawn $java -Dstdout.encoding=$encoding -classpath $args CharsetTest +eval spawn $java -Dstdout.encoding=$encoding -classpath $classpath StdoutEncodingTest expect $expected expect eof From 2345065166c56a958365a6362af356e7c95fcaff Mon Sep 17 00:00:00 2001 From: Cesar Soares Lucas Date: Tue, 3 Jun 2025 23:39:32 +0000 Subject: [PATCH 021/143] 8357600: Patch nmethod flushing message to include more details Reviewed-by: shade, kvn --- src/hotspot/share/code/nmethod.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 83afc02cdfc..d82e31fd855 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -2143,10 +2143,19 @@ void nmethod::purge(bool unregister_nmethod) { // completely deallocate this method Events::log_nmethod_flush(Thread::current(), "flushing %s nmethod " INTPTR_FORMAT, is_osr_method() ? "osr" : "", p2i(this)); - log_debug(codecache)("*flushing %s nmethod %3d/" INTPTR_FORMAT ". Live blobs:" UINT32_FORMAT - "/Free CodeCache:%zuKb", - is_osr_method() ? "osr" : "",_compile_id, p2i(this), CodeCache::blob_count(), - CodeCache::unallocated_capacity(CodeCache::get_code_blob_type(this))/1024); + + LogTarget(Debug, codecache) lt; + if (lt.is_enabled()) { + ResourceMark rm; + LogStream ls(lt); + const char* method_name = method()->name()->as_C_string(); + const size_t codecache_capacity = CodeCache::capacity()/1024; + const size_t codecache_free_space = CodeCache::unallocated_capacity(CodeCache::get_code_blob_type(this))/1024; + ls.print("Flushing nmethod %6d/" INTPTR_FORMAT ", level=%d, osr=%d, cold=%d, epoch=" UINT64_FORMAT ", cold_count=" UINT64_FORMAT ". " + "Cache capacity: %zuKb, free space: %zuKb. method %s (%s)", + _compile_id, p2i(this), _comp_level, is_osr_method(), is_cold(), _gc_epoch, CodeCache::cold_gc_count(), + codecache_capacity, codecache_free_space, method_name, compiler_name()); + } // We need to deallocate any ExceptionCache data. // Note that we do not need to grab the nmethod lock for this, it From 939521b8e4120357108220d177228b683af3334f Mon Sep 17 00:00:00 2001 From: Anjian Wen Date: Wed, 4 Jun 2025 02:03:22 +0000 Subject: [PATCH 022/143] 8358105: RISC-V: Optimize interpreter profile updates Reviewed-by: fjiang, fyang --- src/hotspot/cpu/riscv/interp_masm_riscv.cpp | 38 ++++++--------------- src/hotspot/cpu/riscv/interp_masm_riscv.hpp | 7 ++-- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 2a01a6d209d..fae34a9c770 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -955,47 +955,29 @@ void InterpreterMacroAssembler::set_mdp_data_at(Register mdp_in, void InterpreterMacroAssembler::increment_mdp_data_at(Register mdp_in, - int constant, - bool decrement) { - increment_mdp_data_at(mdp_in, noreg, constant, decrement); + int constant) { + increment_mdp_data_at(mdp_in, noreg, constant); } void InterpreterMacroAssembler::increment_mdp_data_at(Register mdp_in, - Register reg, - int constant, - bool decrement) { + Register index, + int constant) { assert(ProfileInterpreter, "must be profiling interpreter"); - // %%% this does 64bit counters at best it is wasting space - // at worst it is a rare bug when counters overflow - assert_different_registers(t1, t0, mdp_in, reg); + assert_different_registers(t1, t0, mdp_in, index); Address addr1(mdp_in, constant); Address addr2(t1, 0); Address &addr = addr1; - if (reg != noreg) { + if (index != noreg) { la(t1, addr1); - add(t1, t1, reg); + add(t1, t1, index); addr = addr2; } - if (decrement) { - ld(t0, addr); - subi(t0, t0, DataLayout::counter_increment); - Label L; - bltz(t0, L); // skip store if counter underflow - sd(t0, addr); - bind(L); - } else { - assert(DataLayout::counter_increment == 1, - "flow-free idiom only works with 1"); - ld(t0, addr); - addi(t0, t0, DataLayout::counter_increment); - Label L; - blez(t0, L); // skip store if counter overflow - sd(t0, addr); - bind(L); - } + ld(t0, addr); + addi(t0, t0, DataLayout::counter_increment); + sd(t0, addr); } void InterpreterMacroAssembler::set_mdp_flag_at(Register mdp_in, diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.hpp b/src/hotspot/cpu/riscv/interp_masm_riscv.hpp index 7a3e6764aaa..891db16b243 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.hpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.hpp @@ -233,11 +233,8 @@ class InterpreterMacroAssembler: public MacroAssembler { void verify_method_data_pointer(); void set_mdp_data_at(Register mdp_in, int constant, Register value); - void increment_mdp_data_at(Address data, bool decrement = false); - void increment_mdp_data_at(Register mdp_in, int constant, - bool decrement = false); - void increment_mdp_data_at(Register mdp_in, Register reg, int constant, - bool decrement = false); + void increment_mdp_data_at(Register mdp_in, int constant); + void increment_mdp_data_at(Register mdp_in, Register index, int constant); void increment_mask_and_jump(Address counter_addr, int increment, Address mask, Register tmp1, Register tmp2, From ebd85288ce309b7dc7ff8b36558dd9f2a2300209 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Wed, 4 Jun 2025 02:14:17 +0000 Subject: [PATCH 023/143] 8358289: [asan] runtime/cds/appcds/aotCode/AOTCodeFlags.java reports heap-buffer-overflow in ArchiveBuilder Reviewed-by: shade, iklam, asmehra --- src/hotspot/share/runtime/sharedRuntime.cpp | 23 ++++++++++++--------- src/hotspot/share/runtime/sharedRuntime.hpp | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index edb33b3500a..9c710fc98e4 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -2199,10 +2199,11 @@ class AdapterFingerPrint : public MetaspaceObj { } // Private construtor. Use allocate() to get an instance. - AdapterFingerPrint(int total_args_passed, BasicType* sig_bt) { + AdapterFingerPrint(int total_args_passed, BasicType* sig_bt, int len) { int* data = data_pointer(); // Pack the BasicTypes with 8 per int - _length = length(total_args_passed); + assert(len == length(total_args_passed), "sanity"); + _length = len; int sig_index = 0; for (int index = 0; index < _length; index++) { int value = 0; @@ -2217,16 +2218,15 @@ class AdapterFingerPrint : public MetaspaceObj { // Call deallocate instead ~AdapterFingerPrint() { - FreeHeap(this); + ShouldNotCallThis(); } static int length(int total_args) { return (total_args + (_basic_types_per_int-1)) / _basic_types_per_int; } - static int compute_size(int total_args_passed, BasicType* sig_bt) { - int len = length(total_args_passed); - return sizeof(AdapterFingerPrint) + (len * sizeof(int)); + static int compute_size_in_words(int len) { + return (int)heap_word_size(sizeof(AdapterFingerPrint) + (len * sizeof(int))); } // Remap BasicTypes that are handled equivalently by the adapters. @@ -2289,12 +2289,15 @@ class AdapterFingerPrint : public MetaspaceObj { public: static AdapterFingerPrint* allocate(int total_args_passed, BasicType* sig_bt) { - int size_in_bytes = compute_size(total_args_passed, sig_bt); - return new (size_in_bytes) AdapterFingerPrint(total_args_passed, sig_bt); + int len = length(total_args_passed); + int size_in_bytes = BytesPerWord * compute_size_in_words(len); + AdapterFingerPrint* afp = new (size_in_bytes) AdapterFingerPrint(total_args_passed, sig_bt, len); + assert((afp->size() * BytesPerWord) == size_in_bytes, "should match"); + return afp; } static void deallocate(AdapterFingerPrint* fp) { - fp->~AdapterFingerPrint(); + FreeHeap(fp); } int value(int index) { @@ -2418,7 +2421,7 @@ class AdapterFingerPrint : public MetaspaceObj { // methods required by virtue of being a MetaspaceObj void metaspace_pointers_do(MetaspaceClosure* it) { return; /* nothing to do here */ } - int size() const { return (int)heap_word_size(sizeof(AdapterFingerPrint) + (_length * sizeof(int))); } + int size() const { return compute_size_in_words(_length); } MetaspaceObj::Type type() const { return AdapterFingerPrintType; } static bool equals(AdapterFingerPrint* const& fp1, AdapterFingerPrint* const& fp2) { diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index 86074ac7aca..eb2ffb82e5a 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -711,6 +711,7 @@ class AdapterHandlerEntry : public MetaspaceObj { // Dummy argument is used to avoid C++ warning about using // deleted opearator MetaspaceObj::delete(). void* operator new(size_t size, size_t dummy) throw() { + assert(size == BytesPerWord * heap_word_size(sizeof(AdapterHandlerEntry)), "should match"); void* p = AllocateHeap(size, mtCode); memset(p, 0, size); return p; From f17b2bc06ad358933481c0e2cffd57c842bc0e76 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Wed, 4 Jun 2025 04:10:10 +0000 Subject: [PATCH 024/143] 8356870: HotSpotDiagnosticMXBean.dumpThreads and jcmd Thread.dump_to_file updates Reviewed-by: sspitsyn, kevinw --- .../classes/jdk/internal/vm/ThreadDumper.java | 587 ++++++++++---- .../jdk/internal/vm/ThreadSnapshot.java | 4 + .../management/HotSpotDiagnosticMXBean.java | 10 +- .../doc-files/threadDump.schema.json | 171 ++++ .../internal/HotSpotDiagnostic.java | 4 +- .../dcmd/thread/ThreadDumpToFileTest.java | 27 +- .../HotSpotDiagnosticMXBean/DumpThreads.java | 750 ++++++++++++++---- .../DumpThreadsWithEliminatedLock.java | 171 ++++ .../jdk/test/lib/threaddump/ThreadDump.java | 280 ++++--- 9 files changed, 1565 insertions(+), 439 deletions(-) create mode 100644 src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json create mode 100644 test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreadsWithEliminatedLock.java diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java index d0705bc2457..58729774f24 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -24,11 +24,13 @@ */ package jdk.internal.vm; -import java.io.BufferedOutputStream; +import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; @@ -36,15 +38,19 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.time.Instant; -import java.util.ArrayList; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; import java.util.Iterator; import java.util.List; +import java.util.Objects; /** * Thread dump support. * - * This class defines methods to dump threads to an output stream or file in plain - * text or JSON format. + * This class defines static methods to support the Thread.dump_to_file diagnostic command + * and the HotSpotDiagnosticMXBean.dumpThreads API. It defines methods to generate a + * thread dump to a file or byte array in plain text or JSON format. */ public class ThreadDumper { private ThreadDumper() { } @@ -53,13 +59,12 @@ public class ThreadDumper { private static final int MAX_BYTE_ARRAY_SIZE = 16_000; /** - * Generate a thread dump in plain text format to a byte array or file, UTF-8 encoded. - * + * Generate a thread dump in plain text format to a file or byte array, UTF-8 encoded. * This method is invoked by the VM for the Thread.dump_to_file diagnostic command. * * @param file the file path to the file, null or "-" to return a byte array * @param okayToOverwrite true to overwrite an existing file - * @return the UTF-8 encoded thread dump or message to return to the user + * @return the UTF-8 encoded thread dump or message to return to the tool user */ public static byte[] dumpThreads(String file, boolean okayToOverwrite) { if (file == null || file.equals("-")) { @@ -70,13 +75,12 @@ public class ThreadDumper { } /** - * Generate a thread dump in JSON format to a byte array or file, UTF-8 encoded. - * + * Generate a thread dump in JSON format to a file or byte array, UTF-8 encoded. * This method is invoked by the VM for the Thread.dump_to_file diagnostic command. * * @param file the file path to the file, null or "-" to return a byte array * @param okayToOverwrite true to overwrite an existing file - * @return the UTF-8 encoded thread dump or message to return to the user + * @return the UTF-8 encoded thread dump or message to return to the tool user */ public static byte[] dumpThreadsToJson(String file, boolean okayToOverwrite) { if (file == null || file.equals("-")) { @@ -88,21 +92,32 @@ public class ThreadDumper { /** * Generate a thread dump in plain text or JSON format to a byte array, UTF-8 encoded. + * This method is the implementation of the Thread.dump_to_file diagnostic command + * when a file path is not specified. It returns the thread dump and/or message to + * send to the tool user. */ private static byte[] dumpThreadsToByteArray(boolean json, int maxSize) { - try (var out = new BoundedByteArrayOutputStream(maxSize); - PrintStream ps = new PrintStream(out, true, StandardCharsets.UTF_8)) { + var out = new BoundedByteArrayOutputStream(maxSize); + try (out; var writer = new TextWriter(out)) { if (json) { - dumpThreadsToJson(ps); + dumpThreadsToJson(writer); } else { - dumpThreads(ps); + dumpThreads(writer); } - return out.toByteArray(); + } catch (Exception ex) { + if (ex instanceof UncheckedIOException ioe) { + ex = ioe.getCause(); + } + String reply = String.format("Failed: %s%n", ex); + return reply.getBytes(StandardCharsets.UTF_8); } + return out.toByteArray(); } /** * Generate a thread dump in plain text or JSON format to the given file, UTF-8 encoded. + * This method is the implementation of the Thread.dump_to_file diagnostic command. + * It returns the thread dump and/or message to send to the tool user. */ private static byte[] dumpThreadsToFile(String file, boolean okayToOverwrite, boolean json) { Path path = Path.of(file).toAbsolutePath(); @@ -110,224 +125,412 @@ public class ThreadDumper { ? new OpenOption[0] : new OpenOption[] { StandardOpenOption.CREATE_NEW }; String reply; - try (OutputStream out = Files.newOutputStream(path, options); - BufferedOutputStream bos = new BufferedOutputStream(out); - PrintStream ps = new PrintStream(bos, false, StandardCharsets.UTF_8)) { - if (json) { - dumpThreadsToJson(ps); - } else { - dumpThreads(ps); + try (OutputStream out = Files.newOutputStream(path, options)) { + try (var writer = new TextWriter(out)) { + if (json) { + dumpThreadsToJson(writer); + } else { + dumpThreads(writer); + } + reply = String.format("Created %s%n", path); + } catch (UncheckedIOException e) { + reply = String.format("Failed: %s%n", e.getCause()); } - reply = String.format("Created %s%n", path); - } catch (FileAlreadyExistsException e) { + } catch (FileAlreadyExistsException _) { reply = String.format("%s exists, use -overwrite to overwrite%n", path); - } catch (IOException ioe) { - reply = String.format("Failed: %s%n", ioe); + } catch (Exception ex) { + reply = String.format("Failed: %s%n", ex); } return reply.getBytes(StandardCharsets.UTF_8); } /** - * Generate a thread dump in plain text format to the given output stream, - * UTF-8 encoded. - * - * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads. + * Generate a thread dump in plain text format to the given output stream, UTF-8 + * encoded. This method is invoked by HotSpotDiagnosticMXBean.dumpThreads. + * @throws IOException if an I/O error occurs */ - public static void dumpThreads(OutputStream out) { - BufferedOutputStream bos = new BufferedOutputStream(out); - PrintStream ps = new PrintStream(bos, false, StandardCharsets.UTF_8); + public static void dumpThreads(OutputStream out) throws IOException { + var writer = new TextWriter(out); try { - dumpThreads(ps); - } finally { - ps.flush(); // flushes underlying stream + dumpThreads(writer); + writer.flush(); + } catch (UncheckedIOException e) { + IOException ioe = e.getCause(); + throw ioe; } } /** - * Generate a thread dump in plain text format to the given print stream. + * Generate a thread dump in plain text format to the given text stream. + * @throws UncheckedIOException if an I/O error occurs */ - private static void dumpThreads(PrintStream ps) { - ps.println(processId()); - ps.println(Instant.now()); - ps.println(Runtime.version()); - ps.println(); - dumpThreads(ThreadContainers.root(), ps); + private static void dumpThreads(TextWriter writer) { + writer.println(processId()); + writer.println(Instant.now()); + writer.println(Runtime.version()); + writer.println(); + dumpThreads(ThreadContainers.root(), writer); } - private static void dumpThreads(ThreadContainer container, PrintStream ps) { - container.threads().forEach(t -> dumpThread(t, ps)); - container.children().forEach(c -> dumpThreads(c, ps)); + private static void dumpThreads(ThreadContainer container, TextWriter writer) { + container.threads().forEach(t -> dumpThread(t, writer)); + container.children().forEach(c -> dumpThreads(c, writer)); } - private static void dumpThread(Thread thread, PrintStream ps) { - String suffix = thread.isVirtual() ? " virtual" : ""; - ps.println("#" + thread.threadId() + " \"" + thread.getName() + "\"" + suffix); - for (StackTraceElement ste : thread.getStackTrace()) { - ps.print(" "); - ps.println(ste); + private static void dumpThread(Thread thread, TextWriter writer) { + ThreadSnapshot snapshot = ThreadSnapshot.of(thread); + Instant now = Instant.now(); + Thread.State state = snapshot.threadState(); + writer.println("#" + thread.threadId() + " \"" + snapshot.threadName() + + "\" " + (thread.isVirtual() ? "virtual " : "") + state + " " + now); + + StackTraceElement[] stackTrace = snapshot.stackTrace(); + int depth = 0; + while (depth < stackTrace.length) { + writer.print(" at "); + writer.println(stackTrace[depth]); + snapshot.ownedMonitorsAt(depth).forEach(o -> { + if (o != null) { + writer.println(" - locked " + decorateObject(o)); + } else { + writer.println(" - lock is eliminated"); + } + }); + + // if parkBlocker set, or blocked/waiting on monitor, then print after top frame + if (depth == 0) { + // park blocker + Object parkBlocker = snapshot.parkBlocker(); + if (parkBlocker != null) { + writer.println(" - parking to wait for " + decorateObject(parkBlocker)); + } + + // blocked on monitor enter or Object.wait + if (state == Thread.State.BLOCKED && snapshot.blockedOn() instanceof Object obj) { + writer.println(" - waiting to lock " + decorateObject(obj)); + } else if ((state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) + && snapshot.waitingOn() instanceof Object obj) { + writer.println(" - waiting on " + decorateObject(obj)); + } + } + + depth++; } - ps.println(); + writer.println(); + } + + /** + * Returns the identity string for the given object in a form suitable for the plain + * text format thread dump. + */ + private static String decorateObject(Object obj) { + return "<" + Objects.toIdentityString(obj) + ">"; } /** * Generate a thread dump in JSON format to the given output stream, UTF-8 encoded. - * * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads. + * @throws IOException if an I/O error occurs */ - public static void dumpThreadsToJson(OutputStream out) { - BufferedOutputStream bos = new BufferedOutputStream(out); - PrintStream ps = new PrintStream(bos, false, StandardCharsets.UTF_8); + public static void dumpThreadsToJson(OutputStream out) throws IOException { + var writer = new TextWriter(out); try { - dumpThreadsToJson(ps); - } finally { - ps.flush(); // flushes underlying stream + dumpThreadsToJson(writer); + writer.flush(); + } catch (UncheckedIOException e) { + IOException ioe = e.getCause(); + throw ioe; } } /** - * Generate a thread dump to the given print stream in JSON format. + * Generate a thread dump to the given text stream in JSON format. + * @throws UncheckedIOException if an I/O error occurs */ - private static void dumpThreadsToJson(PrintStream out) { - out.println("{"); - out.println(" \"threadDump\": {"); + private static void dumpThreadsToJson(TextWriter textWriter) { + var jsonWriter = new JsonWriter(textWriter); - String now = Instant.now().toString(); - String runtimeVersion = Runtime.version().toString(); - out.format(" \"processId\": \"%d\",%n", processId()); - out.format(" \"time\": \"%s\",%n", escape(now)); - out.format(" \"runtimeVersion\": \"%s\",%n", escape(runtimeVersion)); + jsonWriter.startObject(); // top-level object - out.println(" \"threadContainers\": ["); - List containers = allContainers(); - Iterator iterator = containers.iterator(); - while (iterator.hasNext()) { - ThreadContainer container = iterator.next(); - boolean more = iterator.hasNext(); - dumpThreadsToJson(container, out, more); - } - out.println(" ]"); // end of threadContainers + jsonWriter.startObject("threadDump"); - out.println(" }"); // end threadDump - out.println("}"); // end object + jsonWriter.writeProperty("processId", processId()); + jsonWriter.writeProperty("time", Instant.now()); + jsonWriter.writeProperty("runtimeVersion", Runtime.version()); + + jsonWriter.startArray("threadContainers"); + dumpThreads(ThreadContainers.root(), jsonWriter); + jsonWriter.endArray(); + + jsonWriter.endObject(); // threadDump + + jsonWriter.endObject(); // end of top-level object } /** - * Dump the given thread container to the print stream in JSON format. + * Write a thread container to the given JSON writer. + * @throws UncheckedIOException if an I/O error occurs */ - private static void dumpThreadsToJson(ThreadContainer container, - PrintStream out, - boolean more) { - out.println(" {"); - out.format(" \"container\": \"%s\",%n", escape(container.toString())); - - ThreadContainer parent = container.parent(); - if (parent == null) { - out.format(" \"parent\": null,%n"); - } else { - out.format(" \"parent\": \"%s\",%n", escape(parent.toString())); - } + private static void dumpThreads(ThreadContainer container, JsonWriter jsonWriter) { + jsonWriter.startObject(); + jsonWriter.writeProperty("container", container); + jsonWriter.writeProperty("parent", container.parent()); Thread owner = container.owner(); - if (owner == null) { - out.format(" \"owner\": null,%n"); - } else { - out.format(" \"owner\": \"%d\",%n", owner.threadId()); - } + jsonWriter.writeProperty("owner", (owner != null) ? owner.threadId() : null); long threadCount = 0; - out.println(" \"threads\": ["); + jsonWriter.startArray("threads"); Iterator threads = container.threads().iterator(); while (threads.hasNext()) { Thread thread = threads.next(); - dumpThreadToJson(thread, out, threads.hasNext()); + dumpThread(thread, jsonWriter); threadCount++; } - out.println(" ],"); // end of threads + jsonWriter.endArray(); // threads // thread count if (!ThreadContainers.trackAllThreads()) { threadCount = Long.max(threadCount, container.threadCount()); } - out.format(" \"threadCount\": \"%d\"%n", threadCount); + jsonWriter.writeProperty("threadCount", threadCount); - if (more) { - out.println(" },"); - } else { - out.println(" }"); // last container, no trailing comma - } + jsonWriter.endObject(); + + // the children of the thread container follow + container.children().forEach(c -> dumpThreads(c, jsonWriter)); } /** - * Dump the given thread and its stack trace to the print stream in JSON format. + * Write a thread to the given JSON writer. + * @throws UncheckedIOException if an I/O error occurs */ - private static void dumpThreadToJson(Thread thread, PrintStream out, boolean more) { - out.println(" {"); - out.println(" \"tid\": \"" + thread.threadId() + "\","); - out.println(" \"name\": \"" + escape(thread.getName()) + "\","); - out.println(" \"stack\": ["); + private static void dumpThread(Thread thread, JsonWriter jsonWriter) { + Instant now = Instant.now(); + ThreadSnapshot snapshot = ThreadSnapshot.of(thread); + Thread.State state = snapshot.threadState(); + StackTraceElement[] stackTrace = snapshot.stackTrace(); - int i = 0; - StackTraceElement[] stackTrace = thread.getStackTrace(); - while (i < stackTrace.length) { - out.print(" \""); - out.print(escape(stackTrace[i].toString())); - out.print("\""); - i++; - if (i < stackTrace.length) { - out.println(","); - } else { - out.println(); // last element, no trailing comma + jsonWriter.startObject(); + jsonWriter.writeProperty("tid", thread.threadId()); + jsonWriter.writeProperty("time", now); + if (thread.isVirtual()) { + jsonWriter.writeProperty("virtual", Boolean.TRUE); + } + jsonWriter.writeProperty("name", snapshot.threadName()); + jsonWriter.writeProperty("state", state); + + // park blocker + Object parkBlocker = snapshot.parkBlocker(); + if (parkBlocker != null) { + // parkBlocker is an object to allow for exclusiveOwnerThread in the future + jsonWriter.startObject("parkBlocker"); + jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker)); + jsonWriter.endObject(); + } + + // blocked on monitor enter or Object.wait + if (state == Thread.State.BLOCKED && snapshot.blockedOn() instanceof Object obj) { + jsonWriter.writeProperty("blockedOn", Objects.toIdentityString(obj)); + } else if ((state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) + && snapshot.waitingOn() instanceof Object obj) { + jsonWriter.writeProperty("waitingOn", Objects.toIdentityString(obj)); + } + + // stack trace + jsonWriter.startArray("stack"); + Arrays.stream(stackTrace).forEach(jsonWriter::writeProperty); + jsonWriter.endArray(); + + // monitors owned, skip if none + if (snapshot.ownsMonitors()) { + jsonWriter.startArray("monitorsOwned"); + int depth = 0; + while (depth < stackTrace.length) { + List objs = snapshot.ownedMonitorsAt(depth).toList(); + if (!objs.isEmpty()) { + jsonWriter.startObject(); + jsonWriter.writeProperty("depth", depth); + jsonWriter.startArray("locks"); + snapshot.ownedMonitorsAt(depth) + .map(o -> (o != null) ? Objects.toIdentityString(o) : null) + .forEach(jsonWriter::writeProperty); + jsonWriter.endArray(); + jsonWriter.endObject(); + } + depth++; + } + jsonWriter.endArray(); + } + + // thread identifier of carrier, when mounted + if (thread.isVirtual() && snapshot.carrierThread() instanceof Thread carrier) { + jsonWriter.writeProperty("carrier", carrier.threadId()); + } + + jsonWriter.endObject(); + } + + /** + * Simple JSON writer to stream objects/arrays to a TextWriter with formatting. + * This class is not intended to be a fully featured JSON writer. + */ + private static class JsonWriter { + private static class Node { + final boolean isArray; + int propertyCount; + Node(boolean isArray) { + this.isArray = isArray; + } + boolean isArray() { + return isArray; + } + int propertyCount() { + return propertyCount; + } + int getAndIncrementPropertyCount() { + int old = propertyCount; + propertyCount++; + return old; } } - out.println(" ]"); - if (more) { - out.println(" },"); - } else { - out.println(" }"); // last thread, no trailing comma + private final Deque stack = new ArrayDeque<>(); + private final TextWriter writer; + + JsonWriter(TextWriter writer) { + this.writer = writer; } - } - /** - * Returns a list of all thread containers that are "reachable" from - * the root container. - */ - private static List allContainers() { - List containers = new ArrayList<>(); - collect(ThreadContainers.root(), containers); - return containers; - } + private void indent() { + int indent = stack.size() * 2; + writer.print(" ".repeat(indent)); + } - private static void collect(ThreadContainer container, List containers) { - containers.add(container); - container.children().forEach(c -> collect(c, containers)); - } + /** + * Start of object or array. + */ + private void startObject(String name, boolean isArray) { + if (!stack.isEmpty()) { + Node node = stack.peek(); + if (node.getAndIncrementPropertyCount() > 0) { + writer.println(","); + } + } + indent(); + if (name != null) { + writer.print("\"" + name + "\": "); + } + writer.println(isArray ? "[" : "{"); + stack.push(new Node(isArray)); + } - /** - * Escape any characters that need to be escape in the JSON output. - */ - private static String escape(String value) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (c) { - case '"' -> sb.append("\\\""); - case '\\' -> sb.append("\\\\"); - case '/' -> sb.append("\\/"); - case '\b' -> sb.append("\\b"); - case '\f' -> sb.append("\\f"); - case '\n' -> sb.append("\\n"); - case '\r' -> sb.append("\\r"); - case '\t' -> sb.append("\\t"); - default -> { - if (c <= 0x1f) { - sb.append(String.format("\\u%04x", c)); - } else { - sb.append(c); + /** + * End of object or array. + */ + private void endObject(boolean isArray) { + Node node = stack.pop(); + if (node.isArray() != isArray) + throw new IllegalStateException(); + if (node.propertyCount() > 0) { + writer.println(); + } + indent(); + writer.print(isArray ? "]" : "}"); + } + + /** + * Write a property. + * @param name the property name, null for an unnamed property + * @param obj the value or null + */ + void writeProperty(String name, Object obj) { + Node node = stack.peek(); + if (node.getAndIncrementPropertyCount() > 0) { + writer.println(","); + } + indent(); + if (name != null) { + writer.print("\"" + name + "\": "); + } + switch (obj) { + // Long may be larger than safe range of JSON integer value + case Long _ -> writer.print("\"" + obj + "\""); + case Number _ -> writer.print(obj); + case Boolean _ -> writer.print(obj); + case null -> writer.print("null"); + default -> writer.print("\"" + escape(obj.toString()) + "\""); + } + } + + /** + * Write an unnamed property. + */ + void writeProperty(Object obj) { + writeProperty(null, obj); + } + + /** + * Start named object. + */ + void startObject(String name) { + startObject(name, false); + } + + /** + * Start unnamed object. + */ + void startObject() { + startObject(null); + } + + /** + * End of object. + */ + void endObject() { + endObject(false); + } + + /** + * Start named array. + */ + void startArray(String name) { + startObject(name, true); + } + + /** + * End of array. + */ + void endArray() { + endObject(true); + } + + /** + * Escape any characters that need to be escape in the JSON output. + */ + private static String escape(String value) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '"' -> sb.append("\\\""); + case '\\' -> sb.append("\\\\"); + case '/' -> sb.append("\\/"); + case '\b' -> sb.append("\\b"); + case '\f' -> sb.append("\\f"); + case '\n' -> sb.append("\\n"); + case '\r' -> sb.append("\\r"); + case '\t' -> sb.append("\\t"); + default -> { + if (c <= 0x1f) { + sb.append(String.format("\\u%04x", c)); + } else { + sb.append(c); + } } } } + return sb.toString(); } - return sb.toString(); } /** @@ -357,6 +560,56 @@ public class ThreadDumper { } } + /** + * Simple Writer implementation for printing text. The print/println methods + * throw UncheckedIOException if an I/O error occurs. + */ + private static class TextWriter extends Writer { + private final Writer delegate; + + TextWriter(OutputStream out) { + delegate = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + delegate.write(cbuf, off, len); + } + + void print(Object obj) { + String s = String.valueOf(obj); + try { + write(s, 0, s.length()); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + void println() { + print(System.lineSeparator()); + } + + void println(String s) { + print(s); + println(); + } + + void println(Object obj) { + print(obj); + println(); + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + } + /** * Returns the process ID or -1 if not supported. */ diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java index 205bfde5449..b5607059abc 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -52,9 +52,13 @@ class ThreadSnapshot { /** * Take a snapshot of a Thread to get all information about the thread. + * @throws UnsupportedOperationException if not supported by VM */ static ThreadSnapshot of(Thread thread) { ThreadSnapshot snapshot = create(thread); + if (snapshot == null) { + throw new UnsupportedOperationException(); + } if (snapshot.stackTrace == null) { snapshot.stackTrace = EMPTY_STACK; } diff --git a/src/jdk.management/share/classes/com/sun/management/HotSpotDiagnosticMXBean.java b/src/jdk.management/share/classes/com/sun/management/HotSpotDiagnosticMXBean.java index fba64846155..067cfffb158 100644 --- a/src/jdk.management/share/classes/com/sun/management/HotSpotDiagnosticMXBean.java +++ b/src/jdk.management/share/classes/com/sun/management/HotSpotDiagnosticMXBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -116,6 +116,13 @@ public interface HotSpotDiagnosticMXBean extends PlatformManagedObject { * {@code outputFile} parameter must be an absolute path to a file that * does not exist. * + *

When the format is specified as {@link ThreadDumpFormat#JSON JSON}, the + * thread dump is generated in JavaScript Object Notation. + * threadDump.schema.json + * describes the thread dump format in draft + * + * JSON Schema Language version 2. + * *

The thread dump will include output for all platform threads. It may * include output for some or all virtual threads. * @@ -151,6 +158,7 @@ public interface HotSpotDiagnosticMXBean extends PlatformManagedObject { TEXT_PLAIN, /** * JSON (JavaScript Object Notation) format. + * @spec https://datatracker.ietf.org/doc/html/rfc8259 JavaScript Object Notation */ JSON, } diff --git a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json new file mode 100644 index 00000000000..57ef5c8b859 --- /dev/null +++ b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json @@ -0,0 +1,171 @@ +{ + "type": "object", + "properties": { + "threadDump": { + "type": "object", + "properties": { + "processId": { + "type": "string", + "description": "The native process id of the Java virtual machine." + }, + "time": { + "type": "string", + "description": "The time in ISO 8601 format when the thread dump was generated." + }, + "runtimeVersion": { + "type": "string", + "description": "The runtime version, see java.lang.Runtime.Version" + }, + "threadContainers": { + "type": "array", + "description": "The array of thread containers (thread groupings).", + "items": [ + { + "type": "object", + "properties": { + "container": { + "type": "string", + "description": "The container name. The container name is unique." + }, + "parent": { + "type": [ + "string", + "null" + ], + "description": "The parent container name or null for the root container." + }, + "owner": { + "type": [ + "string", + "null" + ], + "description": "The thread identifier of the owner thread if owned." + }, + "threads": { + "type": "array", + "description": "The array of threads in the thread container.", + "items": [ + { + "type": "object", + "properties": { + "tid": { + "type": "string", + "description": "The thread identifier." + }, + "time": { + "type": "string", + "description": "The time in ISO 8601 format that the thread was sampled." + }, + "name": { + "type": "string", + "description": "The thread name." + }, + "state": { + "type": "string", + "description": "The thread state (Thread::getState)." + }, + "virtual" : { + "type": "boolean", + "description": "true for a virtual thread." + }, + "parkBlocker": { + "type": [ + "object" + ], + "properties": { + "object": { + "type": "string", + "description": "The blocker object responsible for the thread parking." + } + }, + "required": [ + "object" + ] + }, + "blockedOn": { + "type": "string", + "description": "The object that the thread is blocked on waiting to enter/re-enter a synchronization block/method." + }, + "waitingOn": { + "type": "string", + "description": "The object that the thread is waiting to be notified (Object.wait)." + }, + "stack": { + "type": "array", + "description": "The thread stack. The first element is the top of the stack.", + "items": [ + { + "type": "string", + "description": "A stack trace element (java.lang.StackTraceElement)." + } + ] + }, + "monitorsOwned": { + "type": "array", + "description": "The objects for which monitors are owned by the thread.", + "items": { + "type": "object", + "properties": { + "depth": { + "type": "integer", + "description": "The stack depth at which the monitors are owned." + }, + "locks": { + "type": "array", + "items": { + "type": [ + "string", + null + ], + "description": "The object for which the monitor is owned by the thread, null if eliminated" + } + } + }, + "required": [ + "depth", + "locks" + ] + } + }, + "carrier": { + "type": "string", + "description": "The thread identifier of the carrier thread if mounted." + } + }, + "required": [ + "tid", + "time", + "name", + "state", + "stack" + ] + } + ] + }, + "threadCount": { + "type": "string", + "description": "The number of threads in the thread container." + } + }, + "required": [ + "container", + "parent", + "owner", + "threads" + ] + } + ] + } + }, + "required": [ + "processId", + "time", + "runtimeVersion", + "threadContainers" + ] + } + }, + "required": [ + "threadDump" + ] +} diff --git a/src/jdk.management/share/classes/com/sun/management/internal/HotSpotDiagnostic.java b/src/jdk.management/share/classes/com/sun/management/internal/HotSpotDiagnostic.java index 855e800a794..4e5f20fa141 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/HotSpotDiagnostic.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/HotSpotDiagnostic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -153,7 +153,7 @@ public class HotSpotDiagnostic implements HotSpotDiagnosticMXBean { throw new IllegalArgumentException("'outputFile' not absolute path"); try (OutputStream out = Files.newOutputStream(file, StandardOpenOption.CREATE_NEW)) { - dumpThreads(out, format); + dumpThreads(out, format); } } diff --git a/test/hotspot/jtreg/serviceability/dcmd/thread/ThreadDumpToFileTest.java b/test/hotspot/jtreg/serviceability/dcmd/thread/ThreadDumpToFileTest.java index f6002a32968..1fcc609114a 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/thread/ThreadDumpToFileTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/thread/ThreadDumpToFileTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -25,6 +25,7 @@ * @test * @bug 8284161 8287008 * @summary Basic test for jcmd Thread.dump_to_file + * @modules jdk.jcmd * @library /test/lib * @run junit/othervm ThreadDumpToFileTest */ @@ -66,7 +67,8 @@ class ThreadDumpToFileTest { @Test void testJsonThreadDump() throws IOException { Path file = genThreadDumpPath(".json"); - jcmdThreadDumpToFile(file, "-format=json").shouldMatch("Created"); + jcmdThreadDumpToFile(file, "-format=json") + .shouldMatch("Created"); // parse the JSON text String jsonText = Files.readString(file); @@ -89,7 +91,8 @@ class ThreadDumpToFileTest { Path file = genThreadDumpPath(".txt"); Files.writeString(file, "xxx"); - jcmdThreadDumpToFile(file, "").shouldMatch("exists"); + jcmdThreadDumpToFile(file, "") + .shouldMatch("exists"); // file should not be overridden assertEquals("xxx", Files.readString(file)); @@ -102,7 +105,23 @@ class ThreadDumpToFileTest { void testOverwriteFile() throws IOException { Path file = genThreadDumpPath(".txt"); Files.writeString(file, "xxx"); - jcmdThreadDumpToFile(file, "-overwrite"); + jcmdThreadDumpToFile(file, "-overwrite") + .shouldMatch("Created"); + } + + /** + * Test output file cannot be created. + */ + @Test + void testFileCreateFails() throws IOException { + Path badFile = Path.of(".").toAbsolutePath() + .resolve("does-not-exist") + .resolve("does-not-exist") + .resolve("threads.bad"); + jcmdThreadDumpToFile(badFile, "-format=plain") + .shouldMatch("Failed"); + jcmdThreadDumpToFile(badFile, "-format=json") + .shouldMatch("Failed"); } /** diff --git a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java index a4072f2f7ff..adf643749c7 100644 --- a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java +++ b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -21,41 +21,64 @@ * questions. */ -/** +/* * @test - * @bug 8284161 8287008 8309406 + * @bug 8284161 8287008 8309406 8356870 * @summary Basic test for com.sun.management.HotSpotDiagnosticMXBean.dumpThreads * @requires vm.continuations - * @modules jdk.management + * @modules java.base/jdk.internal.vm jdk.management * @library /test/lib - * @run junit/othervm DumpThreads - * @run junit/othervm -Djdk.trackAllThreads DumpThreads - * @run junit/othervm -Djdk.trackAllThreads=true DumpThreads - * @run junit/othervm -Djdk.trackAllThreads=false DumpThreads + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run junit/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * --enable-native-access=ALL-UNNAMED DumpThreads + * @run junit/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * --enable-native-access=ALL-UNNAMED -Djdk.trackAllThreads DumpThreads + * @run junit/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * --enable-native-access=ALL-UNNAMED -Djdk.trackAllThreads=true DumpThreads + * @run junit/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * --enable-native-access=ALL-UNNAMED -Djdk.trackAllThreads=false DumpThreads */ import java.lang.management.ManagementFactory; +import java.lang.reflect.Method; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Path; -import java.util.Objects; import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.regex.Pattern; +import java.util.regex.Matcher; import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat; import jdk.test.lib.threaddump.ThreadDump; +import jdk.test.lib.thread.VThreadPinner; +import jdk.test.lib.thread.VThreadRunner; +import jdk.test.whitebox.WhiteBox; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; class DumpThreads { private static boolean trackAllThreads; @@ -64,6 +87,56 @@ class DumpThreads { static void setup() throws Exception { String s = System.getProperty("jdk.trackAllThreads"); trackAllThreads = (s == null) || s.isEmpty() || Boolean.parseBoolean(s); + + // need >=2 carriers for testing pinning + VThreadRunner.ensureParallelism(2); + } + + /** + * Test thread dump in plain text format. + */ + @Test + void testPlainText() throws Exception { + List lines = dumpThreadsToPlainText(); + + // pid should be on the first line + String pid = Long.toString(ProcessHandle.current().pid()); + assertEquals(pid, lines.get(0)); + + // timestamp should be on the second line + String secondLine = lines.get(1); + ZonedDateTime.parse(secondLine); + + // runtime version should be on third line + String vs = Runtime.version().toString(); + assertEquals(vs, lines.get(2)); + + // dump should include current thread + Thread currentThread = Thread.currentThread(); + if (trackAllThreads || !currentThread.isVirtual()) { + ThreadFields fields = findThread(currentThread.threadId(), lines); + assertNotNull(fields, "current thread not found"); + assertEquals(currentThread.getName(), fields.name()); + assertEquals(currentThread.isVirtual(), fields.isVirtual()); + } + } + + /** + * Test thread dump in JSON format. + */ + @Test + void testJsonFormat() throws Exception { + ThreadDump threadDump = dumpThreadsToJson(); + + // dump should include current thread in the root container + Thread currentThread = Thread.currentThread(); + if (trackAllThreads || !currentThread.isVirtual()) { + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(currentThread.threadId()) + .orElse(null); + assertNotNull(ti, "current thread not found"); + assertEquals(currentThread.isVirtual(), ti.isVirtual()); + } } /** @@ -78,180 +151,438 @@ class DumpThreads { } /** - * Test thread dump in plain text format contains information about the current - * thread and a virtual thread created directly with the Thread API. - */ - @Test - void testRootContainerPlainTextFormat() throws Exception { - Thread vthread = Thread.ofVirtual().start(LockSupport::park); - try { - testDumpThreadsPlainText(vthread, trackAllThreads); - } finally { - LockSupport.unpark(vthread); - } - } - - /** - * Test thread dump in JSON format contains information about the current - * thread and a virtual thread created directly with the Thread API. - */ - @Test - void testRootContainerJsonFormat() throws Exception { - Thread vthread = Thread.ofVirtual().start(LockSupport::park); - try { - testDumpThreadsJson(null, vthread, trackAllThreads); - } finally { - LockSupport.unpark(vthread); - } - } - - /** - * Test thread dump in plain text format includes a thread executing a task in the - * given ExecutorService. + * Test that a thread container for an executor service is in the JSON format thread dump. */ @ParameterizedTest @MethodSource("executors") - void testExecutorServicePlainTextFormat(ExecutorService executor) throws Exception { + void testThreadContainer(ExecutorService executor) throws Exception { try (executor) { - Thread thread = forkParker(executor); - try { - testDumpThreadsPlainText(thread, true); - } finally { - LockSupport.unpark(thread); - } + testThreadContainer(executor, Objects.toIdentityString(executor)); } } /** - * Test thread dump in JSON format includes a thread executing a task in the - * given ExecutorService. - */ - @ParameterizedTest - @MethodSource("executors") - void testExecutorServiceJsonFormat(ExecutorService executor) throws Exception { - try (executor) { - Thread thread = forkParker(executor); - try { - testDumpThreadsJson(Objects.toIdentityString(executor), thread, true); - } finally { - LockSupport.unpark(thread); - } - } - } - - /** - * Test thread dump in JSON format includes a thread executing a task in the - * fork-join common pool. + * Test that a thread container for the common pool is in the JSON format thread dump. */ @Test - void testForkJoinPool() throws Exception { - ForkJoinPool pool = ForkJoinPool.commonPool(); - Thread thread = forkParker(pool); + void testCommonPool() throws Exception { + testThreadContainer(ForkJoinPool.commonPool(), "ForkJoinPool.commonPool"); + } + + /** + * Test that the JSON thread dump has a thread container for the given executor. + */ + private void testThreadContainer(ExecutorService executor, String name) throws Exception { + var threadRef = new AtomicReference(); + + executor.submit(() -> { + threadRef.set(Thread.currentThread()); + LockSupport.park(); + }); + + // capture Thread + Thread thread; + while ((thread = threadRef.get()) == null) { + Thread.sleep(20); + } + try { - testDumpThreadsJson("ForkJoinPool.commonPool", thread, true); + // dump threads to file and parse as JSON object + ThreadDump threadDump = dumpThreadsToJson(); + + // find the thread container corresponding to the executor + var container = threadDump.findThreadContainer(name).orElse(null); + assertNotNull(container, name + " not found"); + assertFalse(container.owner().isPresent()); + var parent = container.parent().orElse(null); + assertEquals(threadDump.rootThreadContainer(), parent); + + // find the thread in the thread container + ThreadDump.ThreadInfo ti = container.findThread(thread.threadId()).orElse(null); + assertNotNull(ti, "thread not found"); + } finally { LockSupport.unpark(thread); } } /** - * Invoke HotSpotDiagnosticMXBean.dumpThreads to create a thread dump in plain text - * format, then sanity check that the thread dump includes expected strings, the - * current thread, and maybe the given thread. - * @param thread the thread to test if included - * @param expectInDump true if the thread is expected to be included + * ThreadFactory implementations for tests. */ - private void testDumpThreadsPlainText(Thread thread, boolean expectInDump) throws Exception { - Path file = genOutputPath(".txt"); - var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); - mbean.dumpThreads(file.toString(), ThreadDumpFormat.TEXT_PLAIN); - System.err.format("Dumped to %s%n", file); - - // pid should be on the first line - String line1 = line(file, 0); - String pid = Long.toString(ProcessHandle.current().pid()); - assertTrue(line1.contains(pid)); - - // timestamp should be on the second line - String line2 = line(file, 1); - ZonedDateTime.parse(line2); - - // runtime version should be on third line - String line3 = line(file, 2); - String vs = Runtime.version().toString(); - assertTrue(line3.contains(vs)); - - // test if thread is included in thread dump - assertEquals(expectInDump, isPresent(file, thread)); - - // current thread should be included if platform thread or tracking all threads - Thread currentThread = Thread.currentThread(); - boolean currentThreadExpected = trackAllThreads || !currentThread.isVirtual(); - assertEquals(currentThreadExpected, isPresent(file, currentThread)); + static Stream threadFactories() { + Stream s = Stream.of(Thread.ofPlatform().factory()); + if (trackAllThreads) { + return Stream.concat(s, Stream.of(Thread.ofVirtual().factory())); + } else { + return s; + } } /** - * Invoke HotSpotDiagnosticMXBean.dumpThreads to create a thread dump in JSON format. - * The thread dump is parsed as a JSON object and checked to ensure that it contains - * expected data, the current thread, and maybe the given thread. - * @param containerName the name of the container or null for the root container - * @param thread the thread to test if included - * @param expect true if the thread is expected to be included + * Test thread dump with a thread blocked on monitor enter. */ - private void testDumpThreadsJson(String containerName, - Thread thread, - boolean expectInDump) throws Exception { - Path file = genOutputPath(".json"); - var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); - mbean.dumpThreads(file.toString(), ThreadDumpFormat.JSON); - System.err.format("Dumped to %s%n", file); + @ParameterizedTest + @MethodSource("threadFactories") + void testBlockedThread(ThreadFactory factory) throws Exception { + testBlockedThread(factory, false); + } - // parse the JSON text - String jsonText = Files.readString(file); - ThreadDump threadDump = ThreadDump.parse(jsonText); + /** + * Test thread dump with a thread blocked on monitor enter when pinned. + */ + @Test + void testBlockedThreadWhenPinned() throws Exception { + assumeTrue(trackAllThreads, "This test requires all threads to be tracked"); + testBlockedThread(Thread.ofVirtual().factory(), true); + } - // test threadDump/processId - assertTrue(threadDump.processId() == ProcessHandle.current().pid()); + void testBlockedThread(ThreadFactory factory, boolean pinned) throws Exception { + var lock = new Object(); + var started = new CountDownLatch(1); - // test threadDump/time can be parsed - ZonedDateTime.parse(threadDump.time()); + Thread thread = factory.newThread(() -> { + if (pinned) { + VThreadPinner.runPinned(() -> { + started.countDown(); + synchronized (lock) { } // blocks + }); + } else { + started.countDown(); + synchronized (lock) { } // blocks + } + }); - // test threadDump/runtimeVersion - assertEquals(Runtime.version().toString(), threadDump.runtimeVersion()); + try { + synchronized (lock) { + // start thread and wait for it to block + thread.start(); + started.await(); + await(thread, Thread.State.BLOCKED); - // test root container, has no parent and no owner - var rootContainer = threadDump.rootThreadContainer(); - assertFalse(rootContainer.owner().isPresent()); - assertFalse(rootContainer.parent().isPresent()); + long tid = thread.threadId(); + String lockAsString = Objects.toIdentityString(lock); - // test that the container contains the given thread - ThreadDump.ThreadContainer container; - if (containerName == null) { - // root container, the thread should be found if trackAllThreads is true - container = rootContainer; - } else { - // find the container - container = threadDump.findThreadContainer(containerName).orElse(null); - assertNotNull(container, containerName + " not found"); - assertFalse(container.owner().isPresent()); - assertTrue(container.parent().get() == rootContainer); + // thread dump in plain text should include thread + List lines = dumpThreadsToPlainText(); + ThreadFields fields = findThread(tid, lines); + assertNotNull(fields, "thread not found"); + assertEquals("BLOCKED", fields.state()); + assertTrue(contains(lines, "- waiting to lock <" + lockAsString)); + // thread dump in JSON format should include thread in root container + ThreadDump threadDump = dumpThreadsToJson(); + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(tid) + .orElse(null); + assertNotNull(ti, "thread not found"); + assertEquals("BLOCKED", ti.state()); + assertEquals(lockAsString, ti.blockedOn()); + if (pinned) { + long carrierTid = ti.carrier().orElse(-1L); + assertNotEquals(-1L, carrierTid, "carrier not found"); + assertForkJoinWorkerThread(carrierTid); + } + } + } finally { + thread.join(); } - boolean found = container.findThread(thread.threadId()).isPresent(); - assertEquals(expectInDump, found); + } - // current thread should be in root container if platform thread or tracking all threads - Thread currentThread = Thread.currentThread(); - boolean currentThreadExpected = trackAllThreads || !currentThread.isVirtual(); - found = rootContainer.findThread(currentThread.threadId()).isPresent(); - assertEquals(currentThreadExpected, found); + /** + * Test thread dump with a thread waiting in Object.wait. + */ + @ParameterizedTest + @MethodSource("threadFactories") + void testWaitingThread(ThreadFactory factory) throws Exception { + testWaitingThread(factory, false); + } + + /** + * Test thread dump with a thread waiting in Object.wait when pinned. + */ + @Test + void testWaitingThreadWhenPinned() throws Exception { + assumeTrue(trackAllThreads, "This test requires all threads to be tracked"); + testWaitingThread(Thread.ofVirtual().factory(), true); + } + + void testWaitingThread(ThreadFactory factory, boolean pinned) throws Exception { + var lock = new Object(); + var started = new CountDownLatch(1); + + Thread thread = factory.newThread(() -> { + try { + synchronized (lock) { + if (pinned) { + VThreadPinner.runPinned(() -> { + started.countDown(); + lock.wait(); + }); + } else { + started.countDown(); + lock.wait(); + } + } + } catch (InterruptedException e) { } + }); + + try { + // start thread and wait for it to wait in Object.wait + thread.start(); + started.await(); + await(thread, Thread.State.WAITING); + + long tid = thread.threadId(); + String lockAsString = Objects.toIdentityString(lock); + + // thread dump in plain text should include thread + List lines = dumpThreadsToPlainText(); + ThreadFields fields = findThread(tid, lines); + assertNotNull(fields, "thread not found"); + assertEquals("WAITING", fields.state()); + + // thread dump in JSON format should include thread in root container + ThreadDump threadDump = dumpThreadsToJson(); + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(thread.threadId()) + .orElse(null); + assertNotNull(ti, "thread not found"); + assertEquals(ti.isVirtual(), thread.isVirtual()); + assertEquals("WAITING", ti.state()); + if (pinned) { + long carrierTid = ti.carrier().orElse(-1L); + assertNotEquals(-1L, carrierTid, "carrier not found"); + assertForkJoinWorkerThread(carrierTid); + } + + // Compiled native frames have no locals. If Object.wait0 has been compiled + // then we don't have the object that the thread is waiting on + Method wait0 = Object.class.getDeclaredMethod("wait0", long.class); + boolean expectWaitingOn = !WhiteBox.getWhiteBox().isMethodCompiled(wait0); + if (expectWaitingOn) { + // plain text dump should have "waiting on" line + assertTrue(contains(lines, "- waiting on <" + lockAsString)); + + // JSON thread dump should have waitingOn property + assertEquals(lockAsString, ti.waitingOn()); + } + + } finally { + synchronized (lock) { + lock.notifyAll(); + } + thread.join(); + } + } + + /** + * Test thread dump with a thread parked on a j.u.c. lock. + */ + @ParameterizedTest + @MethodSource("threadFactories") + void testParkedThread(ThreadFactory factory) throws Exception { + testParkedThread(factory, false); + } + + /** + * Test thread dump with a thread parked on a j.u.c. lock and pinned. + */ + @Test + void testParkedThreadWhenPinned() throws Exception { + assumeTrue(trackAllThreads, "This test requires all threads to be tracked"); + testParkedThread(Thread.ofVirtual().factory(), true); + } + + void testParkedThread(ThreadFactory factory, boolean pinned) throws Exception { + var lock = new ReentrantLock(); + var started = new CountDownLatch(1); + + Thread thread = factory.newThread(() -> { + if (pinned) { + VThreadPinner.runPinned(() -> { + started.countDown(); + lock.lock(); + lock.unlock(); + }); + } else { + started.countDown(); + lock.lock(); + lock.unlock(); + } + }); + + lock.lock(); + try { + // start thread and wait for it to park + thread.start(); + started.await(); + await(thread, Thread.State.WAITING); + + long tid = thread.threadId(); + + // thread dump in plain text should include thread + List lines = dumpThreadsToPlainText(); + ThreadFields fields = findThread(tid, lines); + assertNotNull(fields, "thread not found"); + assertEquals("WAITING", fields.state()); + assertTrue(contains(lines, "- parking to wait for { + synchronized (lock) { + if (pinned) { + VThreadPinner.runPinned(() -> { + started.countDown(); + LockSupport.park(); + }); + } else { + started.countDown(); + LockSupport.park(); + } + } + }); + + try { + // start thread and wait for it to park + thread.start(); + started.await(); + await(thread, Thread.State.WAITING); + + long tid = thread.threadId(); + String lockAsString = Objects.toIdentityString(lock); + + // thread dump in plain text should include thread + List lines = dumpThreadsToPlainText(); + ThreadFields fields = findThread(tid, lines); + assertNotNull(fields, "thread not found"); + assertEquals("WAITING", fields.state()); + assertTrue(contains(lines, "- locked <" + lockAsString)); + + // thread dump in JSON format should include thread in root container + ThreadDump threadDump = dumpThreadsToJson(); + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(tid) + .orElse(null); + assertNotNull(ti, "thread not found"); + assertEquals(ti.isVirtual(), thread.isVirtual()); + + // the lock should be in the ownedMonitors array + Set ownedMonitors = ti.ownedMonitors().values() + .stream() + .flatMap(List::stream) + .collect(Collectors.toSet()); + assertTrue(ownedMonitors.contains(lockAsString), lockAsString + " not found"); + } finally { + LockSupport.unpark(thread); + thread.join(); + } + } + + /** + * Test mounted virtual thread. + */ + @Test + void testMountedVirtualThread() throws Exception { + assumeTrue(trackAllThreads, "This test requires all threads to be tracked"); + + // start virtual thread that spins until done + var started = new AtomicBoolean(); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + started.set(true); + while (!done.get()) { + Thread.onSpinWait(); + } + }); + + try { + // wait for thread to start + awaitTrue(started); + long tid = thread.threadId(); + + // thread dump in plain text should include thread + List lines = dumpThreadsToPlainText(); + ThreadFields fields = findThread(tid, lines); + assertNotNull(fields, "thread not found"); + assertTrue(fields.isVirtual()); + + // thread dump in JSON format should include thread in root container + ThreadDump threadDump = dumpThreadsToJson(); + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(tid) + .orElse(null); + assertNotNull(ti, "thread not found"); + assertTrue(ti.isVirtual()); + long carrierTid = ti.carrier().orElse(-1L); + assertNotEquals(-1L, carrierTid, "carrier not found"); + assertForkJoinWorkerThread(carrierTid); + } finally { + done.set(true); + thread.join(); + } + } + + /** + * Asserts that the given thread identifier is a ForkJoinWorkerThread. + */ + private void assertForkJoinWorkerThread(long tid) { + Thread thread = Thread.getAllStackTraces() + .keySet() + .stream() + .filter(t -> t.threadId() == tid) + .findAny() + .orElse(null); + assertNotNull(thread, "thread " + tid + " not found"); + assertTrue(thread instanceof ForkJoinWorkerThread, "not a ForkJoinWorkerThread"); } /** * Test that dumpThreads throws if the output file already exists. */ @Test - void testFileAlreadyExsists() throws Exception { + void testFileAlreadyExists() throws Exception { var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); String file = Files.createFile(genOutputPath("txt")).toString(); assertThrows(FileAlreadyExistsException.class, @@ -260,11 +591,44 @@ class DumpThreads { () -> mbean.dumpThreads(file, ThreadDumpFormat.JSON)); } + /** + * Test that dumpThreads throws IOException when the output file cannot be created. + */ + @Test + void testFileCreateFails() { + var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); + String badFile = Path.of(".").toAbsolutePath() + .resolve("does-not-exist") + .resolve("does-not-exist") + .resolve("threads.bad") + .toString(); + assertThrows(IOException.class, + () -> mbean.dumpThreads(badFile, ThreadDumpFormat.TEXT_PLAIN)); + assertThrows(IOException.class, + () -> mbean.dumpThreads(badFile, ThreadDumpFormat.JSON)); + } + + /** + * Test that dumpThreads throws IOException if writing to output file fails. + */ + @Test + void testFileWriteFails() { + var out = new OutputStream() { + @Override + public void write(int b) throws IOException { + throw new IOException("There is not enough space on the disk"); + } + }; + // need to invoke internal API directly to test this + assertThrows(IOException.class, () -> jdk.internal.vm.ThreadDumper.dumpThreads(out)); + assertThrows(IOException.class, () -> jdk.internal.vm.ThreadDumper.dumpThreadsToJson(out)); + } + /** * Test that dumpThreads throws if the file path is relative. */ @Test - void testRelativePath() throws Exception { + void testRelativePath() { var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); assertThrows(IllegalArgumentException.class, () -> mbean.dumpThreads("threads.txt", ThreadDumpFormat.TEXT_PLAIN)); @@ -276,7 +640,7 @@ class DumpThreads { * Test that dumpThreads throws with null parameters. */ @Test - void testNull() throws Exception { + void testNull() { var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); assertThrows(NullPointerException.class, () -> mbean.dumpThreads(null, ThreadDumpFormat.TEXT_PLAIN)); @@ -285,31 +649,63 @@ class DumpThreads { } /** - * Submits a parking task to the given executor, returns the Thread object of - * the parked thread. + * Represents the data for a thread found in a plain text thread dump. */ - private static Thread forkParker(ExecutorService executor) { - class Box { static volatile Thread thread;} - var latch = new CountDownLatch(1); - executor.submit(() -> { - Box.thread = Thread.currentThread(); - latch.countDown(); - LockSupport.park(); - }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + private record ThreadFields(long tid, String name, boolean isVirtual, String state) { } + + /** + * Find a thread in the lines of a plain text thread dump. + */ + private ThreadFields findThread(long tid, List lines) { + String line = lines.stream() + .filter(l -> l.startsWith("#" + tid + " ")) + .findFirst() + .orElse(null); + if (line == null) { + return null; } - return Box.thread; + + // #3 "main" RUNNABLE 2025-04-18T15:22:12.012450Z + // #36 "" virtual WAITING 2025-04-18T15:22:12.012450Z + Pattern pattern = Pattern.compile("#(\\d+)\\s+\"([^\"]*)\"\\s+(virtual\\s+)?(\\w+)\\s+(.*)"); + Matcher matcher = pattern.matcher(line); + assertTrue(matcher.matches()); + String name = matcher.group(2); + boolean isVirtual = "virtual ".equals(matcher.group(3)); + String state = matcher.group(4); + return new ThreadFields(tid, name, isVirtual, state); } /** - * Returns true if a Thread is present in a plain text thread dump. + * Returns true if lines of a plain text thread dump contain the given text. */ - private static boolean isPresent(Path file, Thread thread) throws Exception { - String expect = "#" + thread.threadId(); - return count(file, expect) > 0; + private boolean contains(List lines, String text) { + return lines.stream().map(String::trim) + .anyMatch(l -> l.contains(text)); + } + + /** + * Dump threads to a file in plain text format, return the lines in the file. + */ + private List dumpThreadsToPlainText() throws Exception { + Path file = genOutputPath(".txt"); + var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); + mbean.dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN); + System.err.format("Dumped to %s%n", file.getFileName()); + List lines = Files.readAllLines(file); + return lines; + } + + /** + * Dump threads to a file in JSON format, parse and return as JSON object. + */ + private static ThreadDump dumpThreadsToJson() throws Exception { + Path file = genOutputPath(".json"); + var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); + mbean.dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON); + System.err.format("Dumped to %s%n", file.getFileName()); + String jsonText = Files.readString(file); + return ThreadDump.parse(jsonText); } /** @@ -323,21 +719,23 @@ class DumpThreads { } /** - * Return the count of the number of files in the given file that contain - * the given character sequence. + * Waits for the given thread to get to a given state. */ - static long count(Path file, CharSequence cs) throws Exception { - try (Stream stream = Files.lines(file)) { - return stream.filter(line -> line.contains(cs)).count(); + private void await(Thread thread, Thread.State expectedState) throws InterruptedException { + Thread.State state = thread.getState(); + while (state != expectedState) { + assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); + Thread.sleep(10); + state = thread.getState(); } } /** - * Return line $n of the given file. + * Waits for the boolean value to become true. */ - private String line(Path file, long n) throws Exception { - try (Stream stream = Files.lines(file)) { - return stream.skip(n).findFirst().orElseThrow(); + private static void awaitTrue(AtomicBoolean ref) throws Exception { + while (!ref.get()) { + Thread.sleep(20); } } } diff --git a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreadsWithEliminatedLock.java b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreadsWithEliminatedLock.java new file mode 100644 index 00000000000..a2dce62792b --- /dev/null +++ b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreadsWithEliminatedLock.java @@ -0,0 +1,171 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8356870 + * @summary Test HotSpotDiagnosticMXBean.dumpThreads with a thread owning a monitor for + * an object that is scalar replaced + * @requires !vm.debug & (vm.compMode != "Xcomp") + * @requires (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) + * @modules jdk.management + * @library /test/lib + * @run main/othervm DumpThreadsWithEliminatedLock plain platform + * @run main/othervm DumpThreadsWithEliminatedLock plain virtual + * @run main/othervm DumpThreadsWithEliminatedLock json platform + * @run main/othervm DumpThreadsWithEliminatedLock json virtual + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import com.sun.management.HotSpotDiagnosticMXBean; +import jdk.test.lib.threaddump.ThreadDump; +import jdk.test.lib.thread.VThreadRunner; + +public class DumpThreadsWithEliminatedLock { + + public static void main(String[] args) throws Exception { + boolean plain = switch (args[0]) { + case "plain" -> true; + case "json" -> false; + default -> throw new RuntimeException("Unknown dump format"); + }; + + ThreadFactory factory = switch (args[1]) { + case "platform" -> Thread.ofPlatform().factory(); + case "virtual" -> Thread.ofVirtual().factory(); + default -> throw new RuntimeException("Unknown thread kind"); + }; + + // need at least two carriers for JTREG_TEST_THREAD_FACTORY=Virtual + if (Thread.currentThread().isVirtual()) { + VThreadRunner.ensureParallelism(2); + } + + // A thread that spins creating and adding to a StringBuffer. StringBuffer is + // synchronized, assume object will be scalar replaced and the lock eliminated. + var done = new AtomicBoolean(); + var ref = new AtomicReference(); + Thread thread = factory.newThread(() -> { + while (!done.get()) { + StringBuffer sb = new StringBuffer(); + sb.append(System.currentTimeMillis()); + String s = sb.toString(); + ref.set(s); + } + }); + try { + thread.start(); + if (plain) { + testPlainFormat(); + } else { + testJsonFormat(thread.threadId()); + } + } finally { + done.set(true); + thread.join(); + } + } + + /** + * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in plain text + * format until "lock is eliminated" is found in the output. + */ + private static void testPlainFormat() { + try { + Path file = genOutputPath(".txt"); + boolean found = false; + int attempts = 0; + while (!found) { + attempts++; + Files.deleteIfExists(file); + ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) + .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN); + try (Stream stream = Files.lines(file)) { + found = stream.map(String::trim) + .anyMatch(l -> l.contains("- lock is eliminated")); + } + System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found); + } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + /** + * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in JSON format + * until the monitorsOwned.locks array for the given thread has a null lock. + */ + private static void testJsonFormat(long tid) { + try { + Path file = genOutputPath(".json"); + boolean found = false; + int attempts = 0; + while (!found) { + attempts++; + Files.deleteIfExists(file); + ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) + .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON); + + // parse thread dump as JSON and find thread + String jsonText = Files.readString(file); + ThreadDump threadDump = ThreadDump.parse(jsonText); + ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() + .findThread(tid) + .orElse(null); + if (ti == null) { + throw new RuntimeException("Thread " + tid + " not found in thread dump"); + } + + // look for null element in ownedMonitors/locks array + found = ti.ownedMonitors() + .values() + .stream() + .flatMap(List::stream) + .anyMatch(o -> o == null); + System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found); + } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + /** + * Generate a file path with the given suffix to use as an output file. + */ + private static Path genOutputPath(String suffix) throws IOException { + Path dir = Path.of(".").toAbsolutePath(); + Path file = Files.createTempFile(dir, "dump", suffix); + Files.delete(file); + return file; + } +} \ No newline at end of file diff --git a/test/lib/jdk/test/lib/threaddump/ThreadDump.java b/test/lib/jdk/test/lib/threaddump/ThreadDump.java index 5e4f6ebc10f..f4964a9521f 100644 --- a/test/lib/jdk/test/lib/threaddump/ThreadDump.java +++ b/test/lib/jdk/test/lib/threaddump/ThreadDump.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -26,6 +26,7 @@ package jdk.test.lib.threaddump; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -62,6 +63,7 @@ import jdk.test.lib.json.JSONValue; * { * "tid": "8", * "name": "Reference Handler", + * "state": "RUNNABLE", * "stack": [ * "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", * "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:245)", @@ -113,23 +115,46 @@ import jdk.test.lib.json.JSONValue; * } */ public final class ThreadDump { - private final long processId; - private final String time; - private final String runtimeVersion; - private ThreadContainer rootThreadContainer; + private final ThreadContainer rootThreadContainer; + private final Map nameToThreadContainer; + private final JSONValue threadDumpObj; + + private ThreadDump(ThreadContainer rootThreadContainer, + Map nameToThreadContainer, + JSONValue threadDumpObj) { + this.rootThreadContainer = rootThreadContainer; + this.nameToThreadContainer = nameToThreadContainer; + this.threadDumpObj = threadDumpObj; + } /** * Represents an element in the threadDump/threadContainers array. */ public static class ThreadContainer { private final String name; - private long owner; - private ThreadContainer parent; - private Set threads; + private final ThreadContainer parent; private final Set children = new HashSet<>(); + private final JSONValue containerObj; - ThreadContainer(String name) { + ThreadContainer(String name, ThreadContainer parent, JSONValue containerObj) { this.name = name; + this.parent = parent; + this.containerObj = containerObj; + } + + /** + * Add a child thread container. + */ + void addChild(ThreadContainer container) { + children.add(container); + } + + /** + * Returns the value of a property of this thread container, as a string. + */ + private String getStringProperty(String propertyName) { + JSONValue value = containerObj.get(propertyName); + return (value != null) ? value.asString() : null; } /** @@ -143,7 +168,10 @@ public final class ThreadDump { * Return the thread identifier of the owner or empty OptionalLong if not owned. */ public OptionalLong owner() { - return (owner != 0) ? OptionalLong.of(owner) : OptionalLong.empty(); + String owner = getStringProperty("owner"); + return (owner != null) + ? OptionalLong.of(Long.parseLong(owner)) + : OptionalLong.empty(); } /** @@ -164,7 +192,12 @@ public final class ThreadDump { * Returns a stream of {@code ThreadInfo} objects for the threads in this container. */ public Stream threads() { - return threads.stream(); + JSONValue.JSONArray threadsObj = containerObj.get("threads").asArray(); + Set threadInfos = new HashSet<>(); + for (JSONValue threadObj : threadsObj) { + threadInfos.add(new ThreadInfo(threadObj)); + } + return threadInfos.stream(); } /** @@ -176,21 +209,6 @@ public final class ThreadDump { .findAny(); } - /** - * Helper method to recursively find a container with the given name. - */ - ThreadContainer findThreadContainer(String name) { - if (name().equals(name)) - return this; - if (name().startsWith(name + "/")) - return this; - return children() - .map(c -> c.findThreadContainer(name)) - .filter(c -> c != null) - .findAny() - .orElse(null); - } - @Override public int hashCode() { return name.hashCode(); @@ -216,13 +234,30 @@ public final class ThreadDump { */ public static final class ThreadInfo { private final long tid; - private final String name; - private final List stack; + private final JSONValue threadObj; - ThreadInfo(long tid, String name, List stack) { - this.tid = tid; - this.name = name; - this.stack = stack; + ThreadInfo(JSONValue threadObj) { + this.tid = Long.parseLong(threadObj.get("tid").asString()); + this.threadObj = threadObj; + } + + /** + * Returns the value of a property of this thread object, as a string. + */ + private String getStringProperty(String propertyName) { + JSONValue value = threadObj.get(propertyName); + return (value != null) ? value.asString() : null; + } + + /** + * Returns the value of a property of an object in this thread object, as a string. + */ + private String getStringProperty(String objectName, String propertyName) { + if (threadObj.get(objectName) instanceof JSONValue.JSONObject obj + && obj.get(propertyName) instanceof JSONValue value) { + return value.asString(); + } + return null; } /** @@ -236,16 +271,86 @@ public final class ThreadDump { * Returns the thread name. */ public String name() { - return name; + return getStringProperty("name"); + } + + /** + * Returns the thread state. + */ + public String state() { + return getStringProperty("state"); + } + + /** + * Returns true if virtual thread. + */ + public boolean isVirtual() { + String s = getStringProperty("virtual"); + return (s != null) ? Boolean.parseBoolean(s) : false; + } + + /** + * Returns the thread's parkBlocker. + */ + public String parkBlocker() { + return getStringProperty("parkBlocker", "object"); + } + + /** + * Returns the object that the thread is blocked entering its monitor. + */ + public String blockedOn() { + return getStringProperty("blockedOn"); + } + + /** + * Return the object that is the therad is waiting on with Object.wait. + */ + public String waitingOn() { + return getStringProperty("waitingOn"); } /** * Returns the thread stack. */ public Stream stack() { + JSONValue.JSONArray stackObj = threadObj.get("stack").asArray(); + List stack = new ArrayList<>(); + for (JSONValue steObject : stackObj) { + stack.add(steObject.asString()); + } return stack.stream(); } + /** + * Return a map of monitors owned. + */ + public Map> ownedMonitors() { + Map> ownedMonitors = new HashMap<>(); + JSONValue monitorsOwnedObj = threadObj.get("monitorsOwned"); + if (monitorsOwnedObj != null) { + for (JSONValue obj : monitorsOwnedObj.asArray()) { + int depth = Integer.parseInt(obj.get("depth").asString()); + for (JSONValue lock : obj.get("locks").asArray()) { + ownedMonitors.computeIfAbsent(depth, _ -> new ArrayList<>()) + .add(lock.asString()); + } + } + } + return ownedMonitors; + } + + /** + * If the thread is a mounted virtual thread, return the thread identifier of + * its carrier. + */ + public OptionalLong carrier() { + String s = getStringProperty("carrier"); + return (s != null) + ? OptionalLong.of(Long.parseLong(s)) + : OptionalLong.empty(); + } + @Override public int hashCode() { return Long.hashCode(tid); @@ -264,84 +369,42 @@ public final class ThreadDump { public String toString() { StringBuilder sb = new StringBuilder("#"); sb.append(tid); + String name = name(); if (name.length() > 0) { - sb.append(","); - sb.append(name); + sb.append(",") + .append(name); } return sb.toString(); } } /** - * Parses the given JSON text as a thread dump. + * Returns the value of a property of this thread dump, as a string. */ - private ThreadDump(String json) { - JSONValue threadDumpObj = JSONValue.parse(json).get("threadDump"); - - // maps container name to ThreadContainer - Map map = new HashMap<>(); - - // threadContainers array - JSONValue threadContainersObj = threadDumpObj.get("threadContainers"); - for (JSONValue containerObj : threadContainersObj.asArray()) { - String name = containerObj.get("container").asString(); - String parentName = containerObj.get("parent").asString(); - String owner = containerObj.get("owner").asString(); - JSONValue.JSONArray threadsObj = containerObj.get("threads").asArray(); - - // threads array - Set threadInfos = new HashSet<>(); - for (JSONValue threadObj : threadsObj) { - long tid = Long.parseLong(threadObj.get("tid").asString()); - String threadName = threadObj.get("name").asString(); - JSONValue.JSONArray stackObj = threadObj.get("stack").asArray(); - List stack = new ArrayList<>(); - for (JSONValue steObject : stackObj) { - stack.add(steObject.asString()); - } - threadInfos.add(new ThreadInfo(tid, threadName, stack)); - } - - // add to map if not already encountered - var container = map.computeIfAbsent(name, k -> new ThreadContainer(name)); - if (owner != null) - container.owner = Long.parseLong(owner); - container.threads = threadInfos; - - if (parentName == null) { - rootThreadContainer = container; - } else { - // add parent to map if not already encountered and add to its set of children - var parent = map.computeIfAbsent(parentName, k -> new ThreadContainer(parentName)); - container.parent = parent; - parent.children.add(container); - } - } - - this.processId = Long.parseLong(threadDumpObj.get("processId").asString()); - this.time = threadDumpObj.get("time").asString(); - this.runtimeVersion = threadDumpObj.get("runtimeVersion").asString(); + private String getStringProperty(String propertyName) { + JSONValue value = threadDumpObj.get(propertyName); + return (value != null) ? value.asString() : null; } /** * Returns the value of threadDump/processId. */ public long processId() { - return processId; + return Long.parseLong(getStringProperty("processId")); } /** * Returns the value of threadDump/time. */ public String time() { - return time; + return getStringProperty("time"); } /** * Returns the value of threadDump/runtimeVersion. */ public String runtimeVersion() { - return runtimeVersion; + return getStringProperty("runtimeVersion"); } /** @@ -355,8 +418,17 @@ public final class ThreadDump { * Finds a container in the threadDump/threadContainers array with the given name. */ public Optional findThreadContainer(String name) { - ThreadContainer container = rootThreadContainer.findThreadContainer(name); - return Optional.ofNullable(container); + ThreadContainer container = nameToThreadContainer.get(name); + if (container == null) { + // may be name/identity format + container = nameToThreadContainer.entrySet() + .stream() + .filter(e -> e.getKey().startsWith(name + "/")) + .map(e -> e.getValue()) + .findAny() + .orElse(null); + } + return Optional.of(container); } /** @@ -364,6 +436,36 @@ public final class ThreadDump { * @throws RuntimeException if an error occurs */ public static ThreadDump parse(String json) { - return new ThreadDump(json); + JSONValue threadDumpObj = JSONValue.parse(json).get("threadDump"); + + // threadContainers array, preserve insertion order (parents are added before children) + Map containerObjs = new LinkedHashMap<>(); + JSONValue threadContainersObj = threadDumpObj.get("threadContainers"); + for (JSONValue containerObj : threadContainersObj.asArray()) { + String name = containerObj.get("container").asString(); + containerObjs.put(name, containerObj); + } + + // find root and create tree of thread containers + ThreadContainer root = null; + Map map = new HashMap<>(); + for (String name : containerObjs.keySet()) { + JSONValue containerObj = containerObjs.get(name); + String parentName = containerObj.get("parent").asString(); + if (parentName == null) { + root = new ThreadContainer(name, null, containerObj); + map.put(name, root); + } else { + var parent = map.get(parentName); + if (parent == null) { + throw new RuntimeException("Thread container " + name + " found before " + parentName); + } + var container = new ThreadContainer(name, parent, containerObj); + parent.addChild(container); + map.put(name, container); + } + } + + return new ThreadDump(root, map, threadDumpObj); } -} +} \ No newline at end of file From b918dc84ec8364321a5a6d9f6835edcb1d9ad62f Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 4 Jun 2025 06:02:49 +0000 Subject: [PATCH 025/143] 8357434: x86: Simplify Interpreter::profile_taken_branch Reviewed-by: kvn, vlivanov --- src/hotspot/cpu/x86/interp_masm_x86.cpp | 16 +++------------- src/hotspot/cpu/x86/interp_masm_x86.hpp | 2 +- src/hotspot/cpu/x86/templateTable_x86.cpp | 4 +--- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index 6d638ab67ef..92233ee0d07 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -1355,25 +1355,15 @@ void InterpreterMacroAssembler::update_mdp_for_ret(Register return_bci) { } -void InterpreterMacroAssembler::profile_taken_branch(Register mdp, - Register bumped_count) { +void InterpreterMacroAssembler::profile_taken_branch(Register mdp) { if (ProfileInterpreter) { Label profile_continue; // If no method data exists, go to profile_continue. - // Otherwise, assign to mdp test_method_data_pointer(mdp, profile_continue); // We are taking a branch. Increment the taken count. - // We inline increment_mdp_data_at to return bumped_count in a register - //increment_mdp_data_at(mdp, in_bytes(JumpData::taken_offset())); - Address data(mdp, in_bytes(JumpData::taken_offset())); - movptr(bumped_count, data); - assert(DataLayout::counter_increment == 1, - "flow-free idiom only works with 1"); - addptr(bumped_count, DataLayout::counter_increment); - sbbptr(bumped_count, 0); - movptr(data, bumped_count); // Store back out + increment_mdp_data_at(mdp, in_bytes(JumpData::taken_offset())); // The method data pointer needs to be updated to reflect the new target. update_mdp_by_offset(mdp, in_bytes(JumpData::displacement_offset())); @@ -1389,7 +1379,7 @@ void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp) { // If no method data exists, go to profile_continue. test_method_data_pointer(mdp, profile_continue); - // We are taking a branch. Increment the not taken count. + // We are not taking a branch. Increment the not taken count. increment_mdp_data_at(mdp, in_bytes(BranchData::not_taken_offset())); // The method data pointer needs to be updated to correspond to diff --git a/src/hotspot/cpu/x86/interp_masm_x86.hpp b/src/hotspot/cpu/x86/interp_masm_x86.hpp index 47d54b54d7f..a36a697eebf 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.hpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.hpp @@ -236,7 +236,7 @@ class InterpreterMacroAssembler: public MacroAssembler { void update_mdp_by_constant(Register mdp_in, int constant); void update_mdp_for_ret(Register return_bci); - void profile_taken_branch(Register mdp, Register bumped_count); + void profile_taken_branch(Register mdp); void profile_not_taken_branch(Register mdp); void profile_call(Register mdp); void profile_final_call(Register mdp); diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 9f568904ae2..82ca18d8a1f 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -1687,8 +1687,7 @@ void TemplateTable::float_cmp(bool is_float, int unordered_result) { void TemplateTable::branch(bool is_jsr, bool is_wide) { __ get_method(rcx); // rcx holds method - __ profile_taken_branch(rax, rbx); // rax holds updated MDP, rbx - // holds bumped taken count + __ profile_taken_branch(rax); // rax holds updated MDP const ByteSize be_offset = MethodCounters::backedge_counter_offset() + InvocationCounter::counter_offset(); @@ -1739,7 +1738,6 @@ void TemplateTable::branch(bool is_jsr, bool is_wide) { if (UseLoopCounter) { // increment backedge counter for backward branches // rax: MDO - // rbx: MDO bumped taken-count // rcx: method // rdx: target offset // r13: target bcp From 683319f25cbea83e28b9a0ad22e1c3e781e78165 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 4 Jun 2025 06:04:05 +0000 Subject: [PATCH 026/143] 8357798: ReverseOrderListView uses Boolean boxes after JDK-8356080 Reviewed-by: liach, smarks --- .../share/classes/java/util/ReverseOrderListView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index 75d69f38aaf..6ecbac66ed8 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -42,8 +42,7 @@ class ReverseOrderListView implements List { @Stable final List base; - @Stable - final Boolean modifiable; + final boolean modifiable; public static List of(List list, boolean modifiable) { if (list instanceof RandomAccess) { From b5cfd76c047392788b6a5c25ebadc463b2c8ce90 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 4 Jun 2025 06:38:06 +0000 Subject: [PATCH 027/143] 8358456: ZipFile.getInputStream(ZipEntry) throws unspecified IllegalArgumentException Reviewed-by: lancea --- .../share/classes/java/util/zip/ZipFile.java | 16 +-- .../ZipFile/InvalidCompressedSizeTest.java | 124 ++++++++++++++++++ 2 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 test/jdk/java/util/zip/ZipFile/InvalidCompressedSizeTest.java diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index b7d6f1ff714..17382181876 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -351,11 +351,11 @@ public class ZipFile implements ZipConstants, Closeable { case DEFLATED: // Inflater likes a bit of slack // MORE: Compute good size for inflater stream: - long size = CENSIZ(zsrc.cen, pos); - if (size > 65536) { - size = 8192; + long inputBufSize = CENSIZ(zsrc.cen, pos); + if (inputBufSize > 65536 || inputBufSize <= 0) { + inputBufSize = 8192; } - InputStream is = new ZipFileInflaterInputStream(in, res, (int) size); + InputStream is = new ZipFileInflaterInputStream(in, res, (int) inputBufSize); synchronized (istreams) { istreams.add(is); } @@ -416,14 +416,14 @@ public class ZipFile implements ZipConstants, Closeable { private final Cleanable cleanable; ZipFileInflaterInputStream(ZipFileInputStream zfin, - CleanableResource res, int size) { - this(zfin, res, res.getInflater(), size); + CleanableResource res, int inputBufSize) { + this(zfin, res, res.getInflater(), inputBufSize); } private ZipFileInflaterInputStream(ZipFileInputStream zfin, CleanableResource res, - Inflater inf, int size) { - super(zfin, inf, size); + Inflater inf, int inputBufSize) { + super(zfin, inf, inputBufSize); this.cleanable = CleanerFactory.cleaner().register(this, new InflaterCleanupAction(inf, res)); } diff --git a/test/jdk/java/util/zip/ZipFile/InvalidCompressedSizeTest.java b/test/jdk/java/util/zip/ZipFile/InvalidCompressedSizeTest.java new file mode 100644 index 00000000000..37c9916ee5e --- /dev/null +++ b/test/jdk/java/util/zip/ZipFile/InvalidCompressedSizeTest.java @@ -0,0 +1,124 @@ +/* + * 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. + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HexFormat; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.junit.jupiter.api.Test; +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/* + * @test + * @bug 8358456 + * @summary verify that ZipFile.getInputStream(ZipFile) doesn't throw an unspecified exception + * for invalid compressed size of an entry + * @run junit InvalidCompressedSizeTest + */ +class InvalidCompressedSizeTest { + + private static final String ENTRY_NAME = "foo-bar"; + private static final byte[] ENTRY_CONTENT = new byte[]{0x42, 0x42}; + + // created through a call to createZIPContent() + private static final String ZIP_CONTENT_HEX = """ + 504b03041400080808005053c35a00000000000000000000000007000000666f6f2d6261727 + 3720200504b0708c41f441b0400000002000000504b010214001400080808005053c35ac41f + 441b0400000002000000070000000000000000000000000000000000666f6f2d626172504b0 + 506000000000100010035000000390000000000 + """; + + + // 0039 CENTRAL HEADER #1 02014B50 + // ... + // 0043 Compression Method 0008 'Deflated' + // ... + // 004D Compressed Length 00000004 + // 0051 Uncompressed Length 00000002 + // ... + // 0067 Filename 'foo-bar' + // this is the offset in the ZIP content stream for the compressed size field + // for the entry of interest + private static final int COMP_SIZE_OFFSET = 0x004D; + + // intentionally unused but left here to allow for constructing newer/updated + // ZIP_CONTENT_HEX, when necessary + private static String createZIPContent() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (final ZipOutputStream zos = new ZipOutputStream(baos)) { + final ZipEntry ze = new ZipEntry(ENTRY_NAME); + zos.putNextEntry(ze); + zos.write(ENTRY_CONTENT); + zos.closeEntry(); + } + return HexFormat.of().formatHex(baos.toByteArray()); + } + + /* + * Calls ZipFile.getInputStream(ZipEntry) on a ZIP entry whose compressed size is + * intentionally set to 0. The test then verifies that the call to getInputStream() + * doesn't throw an unspecified exception. + */ + @Test + void testInvalidCompressedSize() throws Exception { + final byte[] originalZIPContent = HexFormat.of().parseHex(ZIP_CONTENT_HEX.replace("\n", "")); + final ByteBuffer zipContent = ByteBuffer.wrap(originalZIPContent).order(LITTLE_ENDIAN); + + // overwrite the compressed size value in the entry's CEN to an invalid value of 0 + zipContent.position(COMP_SIZE_OFFSET); + final int invalidCompressedSize = 0; + zipContent.putInt(invalidCompressedSize); + zipContent.rewind(); + + // write out the ZIP content so that it can be read through ZipFile + final Path zip = Files.createTempFile(Path.of("."), "8358456-", ".zip"); + Files.write(zip, zipContent.array()); + System.out.println("created ZIP " + zip + " with an invalid compressed size for entry"); + + try (final ZipFile zf = new ZipFile(zip.toFile())) { + final ZipEntry entry = zf.getEntry(ENTRY_NAME); + assertNotNull(entry, "missing entry " + ENTRY_NAME + " in ZIP file " + zip); + // verify that we are indeed testing a ZIP file with an invalid + // compressed size for the entry + assertEquals(0, entry.getCompressedSize(), "unexpected compressed size"); + // merely open and close the InputStream to exercise the code which + // would incorrectly raise an exception. we don't read the contents + // of the stream because we have (intentionally) corrupted the metadata + // of the ZIP and that will cause the reading to fail. + try (final InputStream is = zf.getInputStream(entry)) { + System.out.println("successfully opened input stream " + is + + " for entry " + entry.getName()); + } + } + } +} From edf92721c2db4cfba091cf4901af603db8486951 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Wed, 4 Jun 2025 08:10:42 +0000 Subject: [PATCH 028/143] 8356977: UTF-8 cleanups Reviewed-by: naoto, prr --- src/java.base/share/classes/java/util/Locale.java | 4 ++-- src/java.base/share/classes/java/util/LocaleISOData.java | 2 +- .../share/classes/java/util/PropertyResourceBundle.java | 4 ++-- src/java.base/share/classes/sun/security/util/DomainName.java | 2 +- src/java.desktop/share/classes/javax/swing/Action.java | 2 +- test/jdk/java/awt/event/KeyEvent/KeyTyped/EscapeKeyTyped.java | 2 +- .../RemotePrinterStatusRefresh.java | 2 +- test/jdk/java/nio/file/Path/UriImportExport.java | 2 +- test/jdk/java/util/Currency/ValidateISO4217.java | 2 +- test/jdk/java/util/Locale/LocaleProvidersFormat.java | 2 +- .../jpackage/helpers/jdk/jpackage/test/FileAssociations.java | 2 +- .../langtools/jdk/javadoc/doclet/testRelativeLinks/pkg/C.java | 2 +- test/langtools/tools/javac/api/guide/Test.java | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index a5ac0ae5bf7..9c745c1731b 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -385,10 +385,10 @@ import sun.util.locale.provider.TimeZoneNameUtility; * {@snippet lang = java: * var number = 1000; * NumberFormat.getCurrencyInstance(Locale.US).format(number); // returns "$1,000.00" - * NumberFormat.getCurrencyInstance(Locale.JAPAN).format(number); // returns "\u00A51,000"" + * NumberFormat.getCurrencyInstance(Locale.JAPAN).format(number); // returns "¥1,000"" * var date = LocalDate.of(2024, 1, 1); * DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).localizedBy(Locale.US).format(date); // returns "January 1, 2024" - * DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).localizedBy(Locale.JAPAN).format(date); // returns "2024\u5e741\u67081\u65e5" + * DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).localizedBy(Locale.JAPAN).format(date); // returns "2024年1月1日" * } * *

Locale Matching

diff --git a/src/java.base/share/classes/java/util/LocaleISOData.java b/src/java.base/share/classes/java/util/LocaleISOData.java index 403e3f90bea..c2090d3be19 100644 --- a/src/java.base/share/classes/java/util/LocaleISOData.java +++ b/src/java.base/share/classes/java/util/LocaleISOData.java @@ -239,7 +239,7 @@ class LocaleISOData { + "AT" + "AUT" // Austria, Republic of + "AU" + "AUS" // Australia, Commonwealth of + "AW" + "ABW" // Aruba - + "AX" + "ALA" // \u00c5land Islands + + "AX" + "ALA" // Åland Islands + "AZ" + "AZE" // Azerbaijan, Republic of + "BA" + "BIH" // Bosnia and Herzegovina + "BB" + "BRB" // Barbados diff --git a/src/java.base/share/classes/java/util/PropertyResourceBundle.java b/src/java.base/share/classes/java/util/PropertyResourceBundle.java index d8cc1fac198..9b832a70577 100644 --- a/src/java.base/share/classes/java/util/PropertyResourceBundle.java +++ b/src/java.base/share/classes/java/util/PropertyResourceBundle.java @@ -81,7 +81,7 @@ import sun.util.ResourceBundleEnumeration; * Keys are case-sensitive. * {@snippet lang=properties : * # MessageFormat pattern - * s1=Die Platte \"{1}\" enth\u00E4lt {0}. + * s1=Die Platte \"{1}\" enthält {0}. * # location of {0} in pattern * s2=1 * # sample disk name @@ -93,7 +93,7 @@ import sun.util.ResourceBundleEnumeration; * # third ChoiceFormat choice * s6={0,number} Dateien * # sample date - * s7=3. M\u00E4rz 1996 + * s7=3. März 1996 * } * * @apiNote diff --git a/src/java.base/share/classes/sun/security/util/DomainName.java b/src/java.base/share/classes/sun/security/util/DomainName.java index 53a646c8102..4f577f1114c 100644 --- a/src/java.base/share/classes/sun/security/util/DomainName.java +++ b/src/java.base/share/classes/sun/security/util/DomainName.java @@ -61,7 +61,7 @@ import sun.security.ssl.SSLLogger; * co.uk * k12.ak.us * com.tw - * \u7db2\u8def.tw + * 網路.tw * * Public suffixes effectively denote registration authorities. * diff --git a/src/java.desktop/share/classes/javax/swing/Action.java b/src/java.desktop/share/classes/javax/swing/Action.java index 3942d3309d4..b1b092e2104 100644 --- a/src/java.desktop/share/classes/javax/swing/Action.java +++ b/src/java.desktop/share/classes/javax/swing/Action.java @@ -263,7 +263,7 @@ public interface Action extends ActionListener { * commonly used to specify a mnemonic. For example: * myAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A) * sets the mnemonic of myAction to 'a', while - * myAction.putValue(Action.MNEMONIC_KEY, KeyEvent.getExtendedKeyCodeForChar('\u0444')) + * myAction.putValue(Action.MNEMONIC_KEY, KeyEvent.getExtendedKeyCodeForChar('ф')) * sets the mnemonic of myAction to Cyrillic letter "Ef". * * @since 1.3 diff --git a/test/jdk/java/awt/event/KeyEvent/KeyTyped/EscapeKeyTyped.java b/test/jdk/java/awt/event/KeyEvent/KeyTyped/EscapeKeyTyped.java index 6410fc4bad8..51fb3fedaba 100644 --- a/test/jdk/java/awt/event/KeyEvent/KeyTyped/EscapeKeyTyped.java +++ b/test/jdk/java/awt/event/KeyEvent/KeyTyped/EscapeKeyTyped.java @@ -87,7 +87,7 @@ public class EscapeKeyTyped { public void keyPressed(KeyEvent e) { printKey(e); int keychar = e.getKeyChar(); - if (keychar == 27) { // Escape character is 27 or \u0021 + if (keychar == 27) { // Escape character is 27 or \u001b escapeKeyTypedReceived = true; } } diff --git a/test/jdk/java/awt/print/RemotePrinterStatusRefresh/RemotePrinterStatusRefresh.java b/test/jdk/java/awt/print/RemotePrinterStatusRefresh/RemotePrinterStatusRefresh.java index 7768c54481a..33fe9fc86d3 100644 --- a/test/jdk/java/awt/print/RemotePrinterStatusRefresh/RemotePrinterStatusRefresh.java +++ b/test/jdk/java/awt/print/RemotePrinterStatusRefresh/RemotePrinterStatusRefresh.java @@ -185,7 +185,7 @@ public class RemotePrinterStatusRefresh extends WindowAdapter { + "Step 3: Compare the list of printers in \"Before\" and " + "\"After\" lists.\n" + " Added printers are highlighted with " - + "green color, removed ones \u2014 with " + + "green color, removed ones with " + "red color.\n" + "Step 4: Click Pass if the list of printers is correctly " + "updated.\n" diff --git a/test/jdk/java/nio/file/Path/UriImportExport.java b/test/jdk/java/nio/file/Path/UriImportExport.java index 934098096b6..2ab93ee399e 100644 --- a/test/jdk/java/nio/file/Path/UriImportExport.java +++ b/test/jdk/java/nio/file/Path/UriImportExport.java @@ -128,7 +128,7 @@ public class UriImportExport { testUri("file:///foo/bar/doesnotexist"); testUri("file:/foo/bar/doesnotexist"); - // file:///foo/bar/\u0440\u0443\u0441\u0441\u043A\u0438\u0439 (Russian) + // file:///foo/bar/русский (Russian) testUri("file:///foo/bar/%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9"); // invalid diff --git a/test/jdk/java/util/Currency/ValidateISO4217.java b/test/jdk/java/util/Currency/ValidateISO4217.java index 6996045fc3a..67793dcbecb 100644 --- a/test/jdk/java/util/Currency/ValidateISO4217.java +++ b/test/jdk/java/util/Currency/ValidateISO4217.java @@ -131,7 +131,7 @@ public class ValidateISO4217 { */ {"GS", "GBP", "826", "2"}, // South Georgia And The South Sandwich Islands /* Not defined in ISO 4217 list, but defined in .properties file. */ - {"AX", "EUR", "978", "2"}, // \u00c5LAND ISLANDS + {"AX", "EUR", "978", "2"}, // ÅLAND ISLANDS {"PS", "ILS", "376", "2"}, // Palestinian Territory, Occupied /* Not defined in ISO 4217 list, but added in ISO 3166 country code list */ {"JE", "GBP", "826", "2"}, // Jersey diff --git a/test/jdk/java/util/Locale/LocaleProvidersFormat.java b/test/jdk/java/util/Locale/LocaleProvidersFormat.java index 5d3f1fc2e2e..157e92ce8a3 100644 --- a/test/jdk/java/util/Locale/LocaleProvidersFormat.java +++ b/test/jdk/java/util/Locale/LocaleProvidersFormat.java @@ -85,7 +85,7 @@ public class LocaleProvidersFormat { /* * 8027289: Ensure if underlying system format locale is zh_CN, the Window's currency - * symbol under HOST provider is \u00A5, the yen (yuan) sign. + * symbol under HOST provider is ¥, the yen (yuan) sign. */ @Test @EnabledOnOs(WINDOWS) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java index f8dc10c3dd0..ebdbb474006 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java @@ -135,7 +135,7 @@ public final class FileAssociations { // To test unicode arguments on Windows manually: // 1. add the following argument ("Hello" in Bulgarian) to the - // additionalArgs list: "\u0417\u0434\u0440\u0430\u0432\u0435\u0439\u0442\u0435" + // additionalArgs list: "Здравейте" // 2. in Control Panel -> Region -> Administrative -> Language for non-Unicode programs // change the system locale to "Bulgarian (Bulgaria)" // 3. reboot Windows and re-run the test diff --git a/test/langtools/jdk/javadoc/doclet/testRelativeLinks/pkg/C.java b/test/langtools/jdk/javadoc/doclet/testRelativeLinks/pkg/C.java index e2f2d1f6f43..79a09b4f240 100644 --- a/test/langtools/jdk/javadoc/doclet/testRelativeLinks/pkg/C.java +++ b/test/langtools/jdk/javadoc/doclet/testRelativeLinks/pkg/C.java @@ -33,7 +33,7 @@ package pkg; public class C { /** - * Here is a relative link in a field:\u0130 + * Here is a relative link in a field: * relative field link. */ public C field = null; diff --git a/test/langtools/tools/javac/api/guide/Test.java b/test/langtools/tools/javac/api/guide/Test.java index 64b2a304016..5bf881a590c 100644 --- a/test/langtools/tools/javac/api/guide/Test.java +++ b/test/langtools/tools/javac/api/guide/Test.java @@ -25,7 +25,7 @@ * @test * @bug 6427274 6347778 6469079 * @summary Various bugs fixed while writing Compiler API Guide - * @author Peter von der Ah\u0081 + * @author Peter von der Ahé * @library ../lib * @modules java.compiler * jdk.compiler From 955bfcd5502b3555c2c91db876be8e7535f2289a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Wed, 4 Jun 2025 08:19:24 +0000 Subject: [PATCH 029/143] 8357671: JFR: Remove JfrTraceIdEpoch synchronizing Reviewed-by: egahlin --- .../checkpoint/jfrCheckpointManager.cpp | 9 ++----- .../checkpoint/jfrCheckpointManager.hpp | 3 +-- .../types/traceid/jfrTraceIdEpoch.cpp | 21 +++------------ .../types/traceid/jfrTraceIdEpoch.hpp | 26 +++++++++---------- .../recorder/service/jfrRecorderService.cpp | 6 ++--- .../jfr/recorder/stringpool/jfrStringPool.cpp | 5 +--- .../classes/jdk/jfr/internal/StringPool.java | 4 +-- 7 files changed, 23 insertions(+), 51 deletions(-) diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp index 7736d3f4565..b0f4461a82c 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp @@ -497,15 +497,10 @@ typedef CompositeOperation WriteRelease typedef VirtualThreadLocalCheckpointWriteOp VirtualThreadLocalCheckpointOperation; typedef MutexedWriteOp VirtualThreadLocalWriteOperation; -void JfrCheckpointManager::begin_epoch_shift() { - assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - JfrTraceIdEpoch::begin_epoch_shift(); -} - -void JfrCheckpointManager::end_epoch_shift() { +void JfrCheckpointManager::shift_epoch() { assert(SafepointSynchronize::is_at_safepoint(), "invariant"); DEBUG_ONLY(const u1 current_epoch = JfrTraceIdEpoch::current();) - JfrTraceIdEpoch::end_epoch_shift(); + JfrTraceIdEpoch::shift_epoch(); assert(current_epoch != JfrTraceIdEpoch::current(), "invariant"); JfrStringPool::on_epoch_shift(); } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp index 53fa064e267..f9f8f1c26cf 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp @@ -96,8 +96,7 @@ class JfrCheckpointManager : public JfrCHeapObj { void clear_type_set(); void write_type_set(); - void begin_epoch_shift(); - void end_epoch_shift(); + void shift_epoch(); static void on_unloading_classes(); void on_rotation(); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp index 8c2b8cdd0ec..a4ada594700 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp @@ -31,7 +31,7 @@ /* * The epoch generation is the range [1-32767]. * - * When the epoch value is stored in a thread object, + * When the epoch value is stored in a vthread object, * the most significant bit of the u2 is used to denote * thread exclusion, i.e 1 << 15 == 32768 denotes exclusion. */ @@ -39,32 +39,17 @@ u2 JfrTraceIdEpoch::_generation = 0; JfrSignal JfrTraceIdEpoch::_tag_state; bool JfrTraceIdEpoch::_method_tracer_state = false; bool JfrTraceIdEpoch::_epoch_state = false; -bool JfrTraceIdEpoch::_synchronizing = false; static constexpr const u2 epoch_generation_overflow = excluded_bit; -void JfrTraceIdEpoch::begin_epoch_shift() { +void JfrTraceIdEpoch::shift_epoch() { assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - _synchronizing = true; - OrderAccess::fence(); -} - -void JfrTraceIdEpoch::end_epoch_shift() { - assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - assert(_synchronizing, "invariant"); _epoch_state = !_epoch_state; - ++_generation; - if (epoch_generation_overflow == _generation) { + if (++_generation == epoch_generation_overflow) { _generation = 1; } assert(_generation != 0, "invariant"); assert(_generation < epoch_generation_overflow, "invariant"); - OrderAccess::storestore(); - _synchronizing = false; -} - -bool JfrTraceIdEpoch::is_synchronizing() { - return Atomic::load_acquire(&_synchronizing); } void JfrTraceIdEpoch::set_method_tracer_tag_state() { diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp index 10ea9643971..9e2d2f0708a 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp @@ -26,7 +26,6 @@ #define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP #include "jfr/utilities/jfrSignal.hpp" -#include "jfr/utilities/jfrTypes.hpp" #include "memory/allStatic.hpp" #define BIT 1 @@ -41,16 +40,17 @@ #define EPOCH_0_METHOD_AND_CLASS_BITS (METHOD_AND_CLASS_BITS << EPOCH_0_SHIFT) #define EPOCH_1_METHOD_AND_CLASS_BITS (METHOD_AND_CLASS_BITS << EPOCH_1_SHIFT) - // Epoch alternation on each rotation allow for concurrent tagging. - // The epoch shift happens only during a safepoint. - // - // _synchronizing is a transition state, the purpose of which is to - // have JavaThreads that run _thread_in_native (i.e. Compiler threads) - // respect the current epoch shift in-progress during the safepoint. - // - // _changed_tag_state == true signals an incremental modification to artifact tagging - // (klasses, methods, CLDs, etc), purpose of which is to trigger collection of artifacts. - // +/* + * An epoch shift or alternation on each rotation enables concurrent tagging. + * The epoch shift happens only during a safepoint. + * + * _generation - mainly used with virtual threads, but also for the generational string pool in Java. + * _tag_state - signals an incremental modification to artifact tagging (klasses, methods, CLDs, etc) + * purpose of which is to trigger a collection of artifacts. + * _method_tracer_state - a special notification state only used with method timing and tracing. + * _epoch_state - the fundamental binary epoch state that shifts on each rotation during a safepoint. + */ + class JfrTraceIdEpoch : AllStatic { friend class JfrCheckpointManager; private: @@ -58,10 +58,8 @@ class JfrTraceIdEpoch : AllStatic { static JfrSignal _tag_state; static bool _method_tracer_state; static bool _epoch_state; - static bool _synchronizing; - static void begin_epoch_shift(); - static void end_epoch_shift(); + static void shift_epoch(); public: static bool epoch() { diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp index 07fe019c9af..7d1d7ac0a05 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp @@ -484,13 +484,12 @@ void JfrRecorderService::invoke_safepoint_clear() { void JfrRecorderService::safepoint_clear() { assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - _checkpoint_manager.begin_epoch_shift(); _storage.clear(); _checkpoint_manager.notify_threads(); _chunkwriter.set_time_stamp(); JfrDeprecationManager::on_safepoint_clear(); JfrStackTraceRepository::clear(); - _checkpoint_manager.end_epoch_shift(); + _checkpoint_manager.shift_epoch(); } void JfrRecorderService::post_safepoint_clear() { @@ -593,14 +592,13 @@ void JfrRecorderService::invoke_safepoint_write() { void JfrRecorderService::safepoint_write() { assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - _checkpoint_manager.begin_epoch_shift(); JfrStackTraceRepository::clear_leak_profiler(); _checkpoint_manager.on_rotation(); _storage.write_at_safepoint(); _chunkwriter.set_time_stamp(); JfrDeprecationManager::on_safepoint_write(); write_stacktrace(_stack_trace_repository, _chunkwriter, true); - _checkpoint_manager.end_epoch_shift(); + _checkpoint_manager.shift_epoch(); } void JfrRecorderService::post_safepoint_write() { diff --git a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp index a9e39094f79..dc28818b0f9 100644 --- a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp +++ b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp @@ -44,8 +44,6 @@ static int generation_offset = invalid_offset; static jobject string_pool = nullptr; -static unsigned short generation = 0; - static bool setup_string_pool_offsets(TRAPS) { const char class_name[] = "jdk/jfr/internal/StringPool"; Symbol* const k_sym = SymbolTable::new_symbol(class_name); @@ -281,9 +279,8 @@ void JfrStringPool::register_full(BufferPtr buffer, Thread* thread) { void JfrStringPool::on_epoch_shift() { assert(SafepointSynchronize::is_at_safepoint(), "invariant"); - assert(!JfrTraceIdEpoch::is_synchronizing(), "invariant"); assert(string_pool != nullptr, "invariant"); oop mirror = JfrJavaSupport::resolve_non_null(string_pool); assert(mirror != nullptr, "invariant"); - mirror->short_field_put(generation_offset, generation++); + mirror->short_field_put(generation_offset, JfrTraceIdEpoch::epoch_generation()); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java index c0dc4e6ba58..41e8a48cfe6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/StringPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -51,7 +51,7 @@ public final class StringPool { private static int preCacheOld = 0; /* max size bytes */ private static long currentSizeUTF16; - /* string pool generation (0-65535) set by the JVM on epoch shift. Not private to avoid being optimized away. */ + /* The string pool epoch generation is the range [1-32767] set by the JVM on epoch shift. Not private to avoid being optimized away. */ static short generation = 0; /* internalSid is a composite id [48-bit externalSid][16-bit generation]. */ From b6d60280e789436c7f9e3cd1447c8f77b77e77b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Wed, 4 Jun 2025 08:20:48 +0000 Subject: [PATCH 030/143] 8358429: JFR: minimize the time the Threads_lock is held for sampling Reviewed-by: egahlin --- .../periodic/sampling/jfrSampleMonitor.hpp | 74 +++++++++++++++ .../periodic/sampling/jfrSampleRequest.hpp | 9 +- .../periodic/sampling/jfrThreadSampler.cpp | 95 +++++++++++-------- .../periodic/sampling/jfrThreadSampling.cpp | 29 ++---- 4 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp new file mode 100644 index 00000000000..a9e0b1728a3 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp @@ -0,0 +1,74 @@ +/* + * 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. + * + */ + +#ifndef SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP +#define SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP + +#include "jfr/periodic/sampling/jfrSampleRequest.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "memory/allocation.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/mutex.hpp" + +class JfrSampleMonitor : public StackObj { + private: + JfrThreadLocal* const _tl; + Monitor* const _sample_monitor; + mutable bool _waiting; + public: + JfrSampleMonitor(JfrThreadLocal* tl) : + _tl(tl), _sample_monitor(tl->sample_monitor()), _waiting(false) { + assert(tl != nullptr, "invariant"); + assert(_sample_monitor != nullptr, "invariant"); + _sample_monitor->lock_without_safepoint_check(); + } + + bool is_waiting() const { + assert_lock_strong(_sample_monitor); + _waiting = _tl->sample_state() == WAITING_FOR_NATIVE_SAMPLE; + return _waiting; + } + + void install_java_sample_request() { + assert_lock_strong(_sample_monitor); + assert(_waiting, "invariant"); + assert(_tl->sample_state() == WAITING_FOR_NATIVE_SAMPLE, "invariant"); + JfrSampleRequest request; + request._sample_ticks = JfrTicks::now(); + _tl->set_sample_request(request); + _tl->set_sample_state(JAVA_SAMPLE); + _sample_monitor->notify_all(); + } + + ~JfrSampleMonitor() { + assert_lock_strong(_sample_monitor); + if (!_waiting) { + _tl->set_sample_state(NO_SAMPLE); + _sample_monitor->notify_all(); + } + _sample_monitor->unlock(); + } +}; + +#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp index 8cc2b66aa9e..6567e7f8bff 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp @@ -48,10 +48,11 @@ enum JfrSampleResult { }; enum JfrSampleRequestType { - NO_SAMPLE = 0, - NATIVE_SAMPLE = 1, - JAVA_SAMPLE = 2, - NOF_SAMPLE_TYPES + NO_SAMPLE, + JAVA_SAMPLE, + NATIVE_SAMPLE, + WAITING_FOR_NATIVE_SAMPLE, + NOF_SAMPLE_STATES }; struct JfrSampleRequest { diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp index 3efa0b0d581..4c44c43772d 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp @@ -24,6 +24,7 @@ #include "jfr/metadata/jfrSerializer.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/periodic/sampling/jfrSampleMonitor.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" #include "jfr/periodic/sampling/jfrThreadSampling.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" @@ -230,38 +231,41 @@ void JfrSamplerThread::task_stacktrace(JfrSampleRequestType type, JavaThread** l JavaThread* start = nullptr; elapsedTimer sample_time; sample_time.start(); - { - MutexLocker tlock(Threads_lock); - ThreadsListHandle tlh; - // Resolve a sample session relative start position index into the thread list array. - // In cases where the last sampled thread is null or not-null but stale, find_index() returns -1. - _cur_index = tlh.list()->find_index_of_JavaThread(*last_thread); - JavaThread* current = _cur_index != -1 ? *last_thread : nullptr; + ThreadsListHandle tlh; + // Resolve a sample session relative start position index into the thread list array. + // In cases where the last sampled thread is null or not-null but stale, find_index() returns -1. + _cur_index = tlh.list()->find_index_of_JavaThread(*last_thread); + JavaThread* current = _cur_index != -1 ? *last_thread : nullptr; - while (num_samples < sample_limit) { - current = next_thread(tlh.list(), start, current); - if (current == nullptr) { - break; - } - if (is_excluded(current)) { - continue; - } - if (start == nullptr) { - start = current; // remember the thread where we started to attempt sampling - } - bool success; - if (JAVA_SAMPLE == type) { - success = sample_java_thread(current); - } else { - assert(type == NATIVE_SAMPLE, "invariant"); - success = sample_native_thread(current); - } - if (success) { - num_samples++; - } + while (num_samples < sample_limit) { + current = next_thread(tlh.list(), start, current); + if (current == nullptr) { + break; + } + if (is_excluded(current)) { + continue; + } + if (start == nullptr) { + start = current; // remember the thread where we started to attempt sampling + } + bool success; + if (JAVA_SAMPLE == type) { + success = sample_java_thread(current); + } else { + assert(type == NATIVE_SAMPLE, "invariant"); + success = sample_native_thread(current); + } + if (success) { + num_samples++; + } + if (SafepointSynchronize::is_at_safepoint()) { + // For _thread_in_native, we cannot get the Threads_lock. + // For _thread_in_Java, well, there are none. + break; } - *last_thread = current; // remember the thread we last attempted to sample } + + *last_thread = current; // remember the thread we last attempted to sample sample_time.stop(); log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples", sample_time.seconds(), type == JAVA_SAMPLE ? num_samples : 0, type == NATIVE_SAMPLE ? num_samples : 0); @@ -338,17 +342,32 @@ bool JfrSamplerThread::sample_native_thread(JavaThread* jt) { SafepointMechanism::arm_local_poll_release(jt); - // Barriers needed to keep the next read of thread state from floating up. - if (UseSystemMemoryBarrier) { - SystemMemoryBarrier::emit(); - } else { - OrderAccess::storeload(); + // Take the Threads_lock for two purposes: + // 1) Avoid sampling through a safepoint which could result + // in touching oops in case of virtual threads. + // 2) Prevent JFR from issuing an epoch rotation while the sampler thread + // is actively processing a thread in native, as both threads are now + // outside the safepoint protocol. + + // OrderAccess::fence() as part of acquiring the lock prevents loads from floating up. + JfrMutexTryLock threads_lock(Threads_lock); + + if (!threads_lock.acquired() || !jt->has_last_Java_frame()) { + // Remove the native sample request and release the potentially waiting thread. + JfrSampleMonitor jsm(tl); + return false; } - if (jt->thread_state() != _thread_in_native || !jt->has_last_Java_frame()) { - MonitorLocker lock(tl->sample_monitor(), Monitor::_no_safepoint_check_flag); - tl->set_sample_state(NO_SAMPLE); - lock.notify_all(); + if (jt->thread_state() != _thread_in_native) { + assert_lock_strong(Threads_lock); + JfrSampleMonitor jsm(tl); + if (jsm.is_waiting()) { + // The thread has already returned from native, + // now in _thread_in_vm and is waiting to be sampled. + // Convert the native sample request into a java sample request + // and let the thread process the ljf on its own. + jsm.install_java_sample_request(); + } return false; } diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index 9bae25bcb3c..aa72c29cf50 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -28,6 +28,7 @@ #include "code/nmethod.hpp" #include "interpreter/interpreter.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrSampleMonitor.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" #include "jfr/periodic/sampling/jfrThreadSampling.hpp" #include "jfr/recorder/stacktrace/jfrStackTrace.hpp" @@ -307,24 +308,6 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav assert(!tl->has_enqueued_requests(), "invariant"); } -class SampleMonitor : public StackObj { - private: - JfrThreadLocal* const _tl; - Monitor* const _sample_monitor; - public: - SampleMonitor(JfrThreadLocal* tl) : _tl(tl), _sample_monitor(tl->sample_monitor()) { - assert(tl != nullptr, "invariant"); - assert(_sample_monitor != nullptr, "invariant"); - _sample_monitor->lock_without_safepoint_check(); - } - ~SampleMonitor() { - assert_lock_strong(_sample_monitor); - _tl->set_sample_state(NO_SAMPLE); - _sample_monitor->notify_all(); - _sample_monitor->unlock(); - } -}; - // Only entered by the JfrSampler thread. bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) { assert(tl != nullptr, "invairant"); @@ -334,7 +317,9 @@ bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaTh assert(tl == jt->jfr_thread_local(), "invariant"); assert(jt != sampler_thread, "only asynchronous processing of native samples"); assert(jt->has_last_Java_frame(), "invariant"); - assert(tl->sample_state() == NATIVE_SAMPLE, "invariant"); + assert(tl->sample_state() >= NATIVE_SAMPLE, "invariant"); + + assert_lock_strong(Threads_lock); const JfrTicks start_time = JfrTicks::now(); @@ -342,7 +327,7 @@ bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaTh traceid sid; { - SampleMonitor sm(tl); + JfrSampleMonitor sm(tl); // Because the thread was in native, it is in a walkable state, because // it will hit a safepoint poll on the way back from native. To ensure timely @@ -384,10 +369,14 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) { for (;;) { const int sample_state = tl->sample_state(); if (sample_state == NATIVE_SAMPLE) { + tl->set_sample_state(WAITING_FOR_NATIVE_SAMPLE); // Wait until stack trace is processed. ml.wait(); } else if (sample_state == JAVA_SAMPLE) { tl->enqueue_request(); + } else if (sample_state == WAITING_FOR_NATIVE_SAMPLE) { + // Handle spurious wakeups. Again wait until stack trace is processed. + ml.wait(); } else { // State has been processed. break; From f141674d1619d95053d38a9cd8f93a8959b4a211 Mon Sep 17 00:00:00 2001 From: "He-Pin(kerr)" Date: Wed, 4 Jun 2025 08:28:29 +0000 Subject: [PATCH 031/143] 8347491: IllegalArgumentationException thrown by ThreadPoolExecutor doesn't have a useful message Reviewed-by: vklang, liach, pminborg --- .../concurrent/AbstractExecutorService.java | 19 +- .../concurrent/ExecutorCompletionService.java | 13 +- .../util/concurrent/ThreadPoolExecutor.java | 46 +-- .../tck/ThreadPoolExecutorTest.java | 269 +++++++++++++----- 4 files changed, 248 insertions(+), 99 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/AbstractExecutorService.java b/src/java.base/share/classes/java/util/concurrent/AbstractExecutorService.java index 1b1ba4b29a0..0d26607591d 100644 --- a/src/java.base/share/classes/java/util/concurrent/AbstractExecutorService.java +++ b/src/java.base/share/classes/java/util/concurrent/AbstractExecutorService.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Objects; /** * Provides default implementations of {@link ExecutorService} @@ -119,7 +120,7 @@ public abstract class AbstractExecutorService implements ExecutorService { */ @Override public Future submit(Runnable task) { - if (task == null) throw new NullPointerException(); + Objects.requireNonNull(task, "task"); RunnableFuture ftask = newTaskFor(task, null); execute(ftask); return ftask; @@ -131,7 +132,7 @@ public abstract class AbstractExecutorService implements ExecutorService { */ @Override public Future submit(Runnable task, T result) { - if (task == null) throw new NullPointerException(); + Objects.requireNonNull(task, "task"); RunnableFuture ftask = newTaskFor(task, result); execute(ftask); return ftask; @@ -143,7 +144,7 @@ public abstract class AbstractExecutorService implements ExecutorService { */ @Override public Future submit(Callable task) { - if (task == null) throw new NullPointerException(); + Objects.requireNonNull(task, "task"); RunnableFuture ftask = newTaskFor(task); execute(ftask); return ftask; @@ -155,11 +156,10 @@ public abstract class AbstractExecutorService implements ExecutorService { private T doInvokeAny(Collection> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { - if (tasks == null) - throw new NullPointerException(); + Objects.requireNonNull(tasks, "tasks"); int ntasks = tasks.size(); if (ntasks == 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("tasks is empty"); ArrayList> futures = new ArrayList<>(ntasks); ExecutorCompletionService ecs = new ExecutorCompletionService(this); @@ -262,8 +262,7 @@ public abstract class AbstractExecutorService implements ExecutorService { @Override public List> invokeAll(Collection> tasks) throws InterruptedException { - if (tasks == null) - throw new NullPointerException(); + Objects.requireNonNull(tasks, "tasks"); ArrayList> futures = new ArrayList<>(tasks.size()); try { for (Callable t : tasks) { @@ -294,8 +293,8 @@ public abstract class AbstractExecutorService implements ExecutorService { public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { - if (tasks == null) - throw new NullPointerException(); + Objects.requireNonNull(tasks, "tasks"); + Objects.requireNonNull(unit, "unit"); final long nanos = unit.toNanos(timeout); final long deadline = System.nanoTime() + nanos; ArrayList> futures = new ArrayList<>(tasks.size()); diff --git a/src/java.base/share/classes/java/util/concurrent/ExecutorCompletionService.java b/src/java.base/share/classes/java/util/concurrent/ExecutorCompletionService.java index 3c09b4882d0..249c2ebf4d9 100644 --- a/src/java.base/share/classes/java/util/concurrent/ExecutorCompletionService.java +++ b/src/java.base/share/classes/java/util/concurrent/ExecutorCompletionService.java @@ -35,6 +35,8 @@ package java.util.concurrent; +import java.util.Objects; + /** * A {@link CompletionService} that uses a supplied {@link Executor} * to execute tasks. This class arranges that submitted tasks are, @@ -145,8 +147,7 @@ public class ExecutorCompletionService implements CompletionService { * @throws NullPointerException if executor is {@code null} */ public ExecutorCompletionService(Executor executor) { - if (executor == null) - throw new NullPointerException(); + Objects.requireNonNull(executor, "executor"); this.executor = executor; this.aes = (executor instanceof AbstractExecutorService) ? (AbstractExecutorService) executor : null; @@ -168,8 +169,8 @@ public class ExecutorCompletionService implements CompletionService { */ public ExecutorCompletionService(Executor executor, BlockingQueue> completionQueue) { - if (executor == null || completionQueue == null) - throw new NullPointerException(); + Objects.requireNonNull(executor, "executor"); + Objects.requireNonNull(completionQueue, "completionQueue"); this.executor = executor; this.aes = (executor instanceof AbstractExecutorService) ? (AbstractExecutorService) executor : null; @@ -181,7 +182,7 @@ public class ExecutorCompletionService implements CompletionService { * @throws NullPointerException {@inheritDoc} */ public Future submit(Callable task) { - if (task == null) throw new NullPointerException(); + Objects.requireNonNull(task, "task"); RunnableFuture f = newTaskFor(task); executor.execute(new QueueingFuture(f, completionQueue)); return f; @@ -192,7 +193,7 @@ public class ExecutorCompletionService implements CompletionService { * @throws NullPointerException {@inheritDoc} */ public Future submit(Runnable task, V result) { - if (task == null) throw new NullPointerException(); + Objects.requireNonNull(task, "task"); RunnableFuture f = newTaskFor(task, result); executor.execute(new QueueingFuture(f, completionQueue)); return f; diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java b/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java index 0bf5669b6e1..029e31ef9f6 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java @@ -1251,13 +1251,19 @@ public class ThreadPoolExecutor extends AbstractExecutorService { BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); + if (corePoolSize < 0) { + throw new IllegalArgumentException("corePoolSize must be non-negative"); + } else if (maximumPoolSize <= 0) { + throw new IllegalArgumentException("maximumPoolSize must be positive"); + } else if (maximumPoolSize < corePoolSize) { + throw new IllegalArgumentException("maximumPoolSize must be greater than or equal to corePoolSize"); + } else if (keepAliveTime < 0) { + throw new IllegalArgumentException("keepAliveTime must be non-negative"); + } + Objects.requireNonNull(unit, "unit"); + Objects.requireNonNull(workQueue, "workQueue"); + Objects.requireNonNull(threadFactory, "threadFactory"); + Objects.requireNonNull(handler, "handler"); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; @@ -1284,8 +1290,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { - if (command == null) - throw new NullPointerException(); + Objects.requireNonNull(command, "command"); /* * Proceed in 3 steps: * @@ -1446,8 +1451,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * @see #getThreadFactory */ public void setThreadFactory(ThreadFactory threadFactory) { - if (threadFactory == null) - throw new NullPointerException(); + Objects.requireNonNull(threadFactory, "threadFactory"); this.threadFactory = threadFactory; } @@ -1469,8 +1473,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * @see #getRejectedExecutionHandler */ public void setRejectedExecutionHandler(RejectedExecutionHandler handler) { - if (handler == null) - throw new NullPointerException(); + Objects.requireNonNull(handler, "handler"); this.handler = handler; } @@ -1498,8 +1501,11 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { - if (corePoolSize < 0 || maximumPoolSize < corePoolSize) - throw new IllegalArgumentException(); + if (corePoolSize < 0) { + throw new IllegalArgumentException("corePoolSize must be non-negative"); + } else if (corePoolSize > maximumPoolSize) { + throw new IllegalArgumentException("corePoolSize must be less than or equal to maximumPoolSize"); + } int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; if (workerCountOf(ctl.get()) > corePoolSize) @@ -1623,8 +1629,11 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * @see #getMaximumPoolSize */ public void setMaximumPoolSize(int maximumPoolSize) { - if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) - throw new IllegalArgumentException(); + if (maximumPoolSize <= 0) { + throw new IllegalArgumentException("maximumPoolSize must be positive"); + } else if (maximumPoolSize < corePoolSize) { + throw new IllegalArgumentException("maximumPoolSize must be greater than or equal to corePoolSize"); + } this.maximumPoolSize = maximumPoolSize; if (workerCountOf(ctl.get()) > maximumPoolSize) interruptIdleWorkers(); @@ -1658,9 +1667,10 @@ public class ThreadPoolExecutor extends AbstractExecutorService { */ public void setKeepAliveTime(long time, TimeUnit unit) { if (time < 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("time must be non-negative"); if (time == 0 && allowsCoreThreadTimeOut()) throw new IllegalArgumentException("Core threads must have nonzero keep alive times"); + Objects.requireNonNull(unit, "unit"); long keepAliveTime = unit.toNanos(time); long delta = keepAliveTime - this.keepAliveTime; this.keepAliveTime = keepAliveTime; diff --git a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java index 73e6deda987..d9ce643a26d 100644 --- a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java +++ b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java @@ -41,22 +41,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.*; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; @@ -66,6 +51,7 @@ import java.util.concurrent.atomic.AtomicReference; import junit.framework.Test; import junit.framework.TestSuite; +import org.junit.Assert; public class ThreadPoolExecutorTest extends JSR166TestCase { public static void main(String[] args) { @@ -304,7 +290,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setThreadFactory(null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("threadFactory", success.getMessage()); + } } } @@ -364,7 +352,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setRejectedExecutionHandler(null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("handler", success.getMessage()); + } } } @@ -737,7 +727,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(-1, 1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("corePoolSize must be non-negative", success.getMessage()); + } } /** @@ -748,7 +740,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(1, -1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -759,7 +753,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(1, 0, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -770,7 +766,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(1, 2, -1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("keepAliveTime must be non-negative", success.getMessage()); + } } /** @@ -781,7 +779,12 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(2, 1, 1L, SECONDS, new ArrayBlockingQueue(10)); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } } /** @@ -792,7 +795,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ThreadPoolExecutor(1, 2, 1L, SECONDS, (BlockingQueue) null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("workQueue", success.getMessage()); + } } /** @@ -804,7 +809,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("corePoolSize must be non-negative", success.getMessage()); + } } /** @@ -816,7 +823,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -828,7 +837,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -840,7 +851,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("keepAliveTime must be non-negative", success.getMessage()); + } } /** @@ -852,7 +865,12 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new SimpleThreadFactory()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } } /** @@ -864,7 +882,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { (BlockingQueue) null, new SimpleThreadFactory()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("workQueue", success.getMessage()); + } } /** @@ -876,7 +896,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), (ThreadFactory) null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("threadFactory", success.getMessage()); + } } /** @@ -888,7 +910,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("corePoolSize must be non-negative", success.getMessage()); + } } /** @@ -900,7 +924,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -912,7 +938,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -924,7 +952,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("keepAliveTime must be non-negative", success.getMessage()); + } } /** @@ -936,7 +966,12 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } } /** @@ -948,7 +983,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { (BlockingQueue) null, new NoOpREHandler()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("workQueue", success.getMessage()); + } } /** @@ -960,7 +997,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new ArrayBlockingQueue(10), (RejectedExecutionHandler) null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("handler", success.getMessage()); + } } /** @@ -973,7 +1012,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("corePoolSize must be non-negative", success.getMessage()); + } } /** @@ -986,7 +1027,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -999,7 +1042,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } /** @@ -1012,7 +1057,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("keepAliveTime must be non-negative", success.getMessage()); + } } /** @@ -1025,7 +1072,12 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } } /** @@ -1038,7 +1090,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), new NoOpREHandler()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("workQueue", success.getMessage()); + } } /** @@ -1051,7 +1105,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { new SimpleThreadFactory(), (RejectedExecutionHandler) null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("handler", success.getMessage()); + } } /** @@ -1064,7 +1120,24 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { (ThreadFactory) null, new NoOpREHandler()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("threadFactory", success.getMessage()); + } + } + + /** + * Constructor throws if unit is null + */ + public void testConstructorNullPointerException9() { + try { + new ThreadPoolExecutor(1, 2, 1L, (TimeUnit) null, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) { + assertEquals("unit", success.getMessage()); + } } /** @@ -1228,7 +1301,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setCorePoolSize(-1); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("corePoolSize must be non-negative", success.getMessage()); + } } } @@ -1245,7 +1320,12 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setMaximumPoolSize(1); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } } } @@ -1262,7 +1342,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setMaximumPoolSize(-1); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("maximumPoolSize must be positive", success.getMessage()); + } } } @@ -1282,13 +1364,25 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setMaximumPoolSize(s - 1); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + s == 1 + ? "maximumPoolSize must be positive" + : "maximumPoolSize must be greater than or equal to corePoolSize", + success.getMessage() + ); + } assertEquals(s, p.getCorePoolSize()); assertEquals(s, p.getMaximumPoolSize()); try { p.setCorePoolSize(s + 1); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals( + "corePoolSize must be less than or equal to maximumPoolSize", + success.getMessage() + ); + } assertEquals(s, p.getCorePoolSize()); assertEquals(s, p.getMaximumPoolSize()); } @@ -1299,7 +1393,7 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { * setKeepAliveTime throws IllegalArgumentException * when given a negative value */ - public void testKeepAliveTimeIllegalArgumentException() { + public void testKeepAliveTimeInvalidLengthIllegalArgumentException() { final ThreadPoolExecutor p = new ThreadPoolExecutor(2, 3, LONG_DELAY_MS, MILLISECONDS, @@ -1308,7 +1402,28 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { p.setKeepAliveTime(-1, MILLISECONDS); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("time must be non-negative", success.getMessage()); + } + } + } + + /** + * setKeepAliveTime throws IllegalArgumentException + * when given a null unit + */ + public void testKeepAliveTimeNullTimeUnitIllegalArgumentException() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setKeepAliveTime(1, (TimeUnit) null); + shouldThrow(); + } catch (NullPointerException success) { + assertEquals("unit", success.getMessage()); + } } } @@ -1399,7 +1514,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("tasks", success.getMessage()); + } } } @@ -1415,7 +1532,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(new ArrayList>()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("tasks is empty", success.getMessage()); + } } } @@ -1435,7 +1554,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(l); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("task", success.getMessage()); + } latch.countDown(); } } @@ -1489,7 +1610,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAll(null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("tasks", success.getMessage()); + } } } @@ -1524,7 +1647,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAll(l); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals(null, success.getMessage()); + } } } @@ -1581,7 +1706,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(null, randomTimeout(), randomTimeUnit()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("tasks", success.getMessage()); + } } } @@ -1599,7 +1726,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(l, randomTimeout(), null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("Cannot invoke \"java.util.concurrent.TimeUnit.toNanos(long)\" because \"unit\" is null", success.getMessage()); + } } } @@ -1616,7 +1745,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { e.invokeAny(new ArrayList>(), randomTimeout(), randomTimeUnit()); shouldThrow(); - } catch (IllegalArgumentException success) {} + } catch (IllegalArgumentException success) { + assertEquals("tasks is empty", success.getMessage()); + } } } @@ -1636,7 +1767,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAny(l, randomTimeout(), randomTimeUnit()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("task", success.getMessage()); + } latch.countDown(); } } @@ -1694,7 +1827,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAll(null, randomTimeout(), randomTimeUnit()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("tasks", success.getMessage()); + } } } @@ -1712,7 +1847,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAll(l, randomTimeout(), null); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals("unit", success.getMessage()); + } } } @@ -1748,7 +1885,9 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { try { e.invokeAll(l, randomTimeout(), randomTimeUnit()); shouldThrow(); - } catch (NullPointerException success) {} + } catch (NullPointerException success) { + assertEquals(null, success.getMessage()); + } } } From ab235000349bfd268e80a7cb99bf07a229406119 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Wed, 4 Jun 2025 08:31:37 +0000 Subject: [PATCH 032/143] 8354636: [PPC64] Clean up comments regarding frame manager Reviewed-by: amitkumar, rrich --- src/hotspot/cpu/ppc/register_ppc.hpp | 4 ++-- src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp | 2 +- src/hotspot/cpu/ppc/stubGenerator_ppc.cpp | 18 ++++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/hotspot/cpu/ppc/register_ppc.hpp b/src/hotspot/cpu/ppc/register_ppc.hpp index 2b5b25f449e..b7949750dcc 100644 --- a/src/hotspot/cpu/ppc/register_ppc.hpp +++ b/src/hotspot/cpu/ppc/register_ppc.hpp @@ -523,7 +523,7 @@ constexpr FloatRegister F11_ARG11 = F11; // volatile constexpr FloatRegister F12_ARG12 = F12; // volatile constexpr FloatRegister F13_ARG13 = F13; // volatile -// Register declarations to be used in frame manager assembly code. +// Register declarations to be used in template interpreter assembly code. // Use only non-volatile registers in order to keep values across C-calls. constexpr Register R14_bcp = R14; constexpr Register R15_esp = R15; // slot below top of expression stack for ld/st with update @@ -533,7 +533,7 @@ constexpr Register R17_tos = R17; // The interpreter's top of (expres constexpr Register R18_locals = R18; // address of first param slot (receiver). constexpr Register R19_method = R19; // address of current method -// Temporary registers to be used within frame manager. We can use +// Temporary registers to be used within template interpreter. We can use // the non-volatiles because the call stub has saved them. // Use only non-volatile registers in order to keep values across C-calls. constexpr Register R21_tmp1 = R21; diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index a7e759d770b..4ec2483b267 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -2935,7 +2935,7 @@ static void push_skeleton_frames(MacroAssembler* masm, bool deopt, __ cmpdi(CR0, number_of_frames_reg, 0); __ bne(CR0, loop); - // Get the return address pointing into the frame manager. + // Get the return address pointing into the template interpreter. __ ld(R0, 0, pcs_reg); // Store it in the top interpreter frame. __ std(R0, _abi0(lr), R1_SP); diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp index 7fc807bb9ce..2624131033c 100644 --- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp @@ -86,7 +86,7 @@ class StubGenerator: public StubCodeGenerator { // R10 - thread : Thread* // address generate_call_stub(address& return_address) { - // Setup a new c frame, copy java arguments, call frame manager or + // Setup a new c frame, copy java arguments, call template interpreter or // native_entry, and process result. StubGenStubId stub_id = StubGenStubId::call_stub_id; @@ -215,11 +215,10 @@ class StubGenerator: public StubCodeGenerator { } { - BLOCK_COMMENT("Call frame manager or native entry."); - // Call frame manager or native entry. + BLOCK_COMMENT("Call template interpreter or native entry."); assert_different_registers(r_arg_entry, r_top_of_arguments_addr, r_arg_method, r_arg_thread); - // Register state on entry to frame manager / native entry: + // Register state on entry to template interpreter / native entry: // // tos - intptr_t* sender tos (prepushed) Lesp = (SP) + copied_arguments_offset - 8 // R19_method - Method @@ -242,7 +241,7 @@ class StubGenerator: public StubCodeGenerator { // Set R15_prev_state to 0 for simplifying checks in callee. __ load_const_optimized(R25_templateTableBase, (address)Interpreter::dispatch_table((TosState)0), R0); - // Stack on entry to frame manager / native entry: + // Stack on entry to template interpreter / native entry: // // F0 [TOP_IJAVA_FRAME_ABI] // alignment (optional) @@ -262,7 +261,7 @@ class StubGenerator: public StubCodeGenerator { __ mr(R21_sender_SP, R1_SP); // Do a light-weight C-call here, r_arg_entry holds the address - // of the interpreter entry point (frame manager or native entry) + // of the interpreter entry point (template interpreter or native entry) // and save runtime-value of LR in return_address. assert(r_arg_entry != tos && r_arg_entry != R19_method && r_arg_entry != R16_thread, "trashed r_arg_entry"); @@ -270,11 +269,10 @@ class StubGenerator: public StubCodeGenerator { } { - BLOCK_COMMENT("Returned from frame manager or native entry."); - // Returned from frame manager or native entry. + BLOCK_COMMENT("Returned from template interpreter or native entry."); // Now pop frame, process result, and return to caller. - // Stack on exit from frame manager / native entry: + // Stack on exit from template interpreter / native entry: // // F0 [ABI] // ... @@ -295,7 +293,7 @@ class StubGenerator: public StubCodeGenerator { Register r_cr = R12_scratch2; // Reload some volatile registers which we've spilled before the call - // to frame manager / native entry. + // to template interpreter / native entry. // Access all locals via frame pointer, because we know nothing about // the topmost frame's size. __ ld(r_entryframe_fp, _abi0(callers_sp), R1_SP); // restore after call From cd16b6896222a623dc99b9e63bb917a9d2980e88 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 4 Jun 2025 09:06:46 +0000 Subject: [PATCH 033/143] 8357155: [asan] ZGC does not work (x86_64 and ppc64) Co-authored-by: Axel Boldt-Christmas Reviewed-by: mdoerr, aboldtch --- src/hotspot/cpu/ppc/gc/z/zAddress_ppc.cpp | 5 +++++ src/hotspot/cpu/x86/gc/z/zAddress_x86.cpp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/hotspot/cpu/ppc/gc/z/zAddress_ppc.cpp b/src/hotspot/cpu/ppc/gc/z/zAddress_ppc.cpp index 89417c4691d..20d96f6e937 100644 --- a/src/hotspot/cpu/ppc/gc/z/zAddress_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/z/zAddress_ppc.cpp @@ -93,10 +93,15 @@ static size_t probe_valid_max_address_bit() { size_t ZPlatformAddressOffsetBits() { static const size_t valid_max_address_offset_bits = probe_valid_max_address_bit() + 1; const size_t max_address_offset_bits = valid_max_address_offset_bits - 3; +#ifdef ADDRESS_SANITIZER + // The max supported value is 44 because of other internal data structures. + return MIN2(valid_max_address_offset_bits, (size_t)44); +#else const size_t min_address_offset_bits = max_address_offset_bits - 2; const size_t address_offset = ZGlobalsPointers::min_address_offset_request(); const size_t address_offset_bits = log2i_exact(address_offset); return clamp(address_offset_bits, min_address_offset_bits, max_address_offset_bits); +#endif } size_t ZPlatformAddressHeapBaseShift() { diff --git a/src/hotspot/cpu/x86/gc/z/zAddress_x86.cpp b/src/hotspot/cpu/x86/gc/z/zAddress_x86.cpp index 6b5b64d3036..db35a4efe08 100644 --- a/src/hotspot/cpu/x86/gc/z/zAddress_x86.cpp +++ b/src/hotspot/cpu/x86/gc/z/zAddress_x86.cpp @@ -30,11 +30,15 @@ size_t ZPointerLoadShift; size_t ZPlatformAddressOffsetBits() { +#ifdef ADDRESS_SANITIZER + return 44; +#else const size_t min_address_offset_bits = 42; // 4TB const size_t max_address_offset_bits = 44; // 16TB const size_t address_offset = ZGlobalsPointers::min_address_offset_request(); const size_t address_offset_bits = log2i_exact(address_offset); return clamp(address_offset_bits, min_address_offset_bits, max_address_offset_bits); +#endif } size_t ZPlatformAddressHeapBaseShift() { From 42f48a39e867ae1683708dda3e158c24a6957180 Mon Sep 17 00:00:00 2001 From: Sean Coffey Date: Wed, 4 Jun 2025 09:41:51 +0000 Subject: [PATCH 034/143] 8350689: Turn on timestamp and thread metadata by default for java.security.debug Reviewed-by: mullan --- .../doc-files/debug-system-property.html | 18 +--- .../classes/sun/security/util/Debug.java | 81 +++-------------- .../security/krb5/auto/LoginModuleDebug.java | 20 ++--- .../SSLLogger/DebugPropertyValuesTest.java | 18 ++-- .../sun/security/util/Debug/DebugOptions.java | 88 ++++++------------- 5 files changed, 55 insertions(+), 170 deletions(-) diff --git a/src/java.base/share/classes/java/security/doc-files/debug-system-property.html b/src/java.base/share/classes/java/security/doc-files/debug-system-property.html index 4ea34252b3c..ac325b2ef62 100644 --- a/src/java.base/share/classes/java/security/doc-files/debug-system-property.html +++ b/src/java.base/share/classes/java/security/doc-files/debug-system-property.html @@ -52,24 +52,10 @@

To monitor security access, you can set the java.security.debug system property, which determines what trace messages are printed during execution. The value of the property is one or more options separated by a - comma. + comma. Each trace message includes the thread id, caller information, and + timestamp.

-

Printing Thread and Timestamp Information

-

- You can append the following strings to any option specified in the - java.security.debug system property to print additional - information: -

    -
  • +thread: Print thread and caller information
  • -
  • +timestamp: Print timestamp information
  • -
-

- For example, to add thread, caller, and timestamp information to all - debugging output, set the java.security.debug system property - on the command line as follows: -

java -Djava.security.debug=all+thread+timestamp MyApp
-

The following table lists the java.security.debug options:

diff --git a/src/java.base/share/classes/sun/security/util/Debug.java b/src/java.base/share/classes/sun/security/util/Debug.java index f6c0c523165..9f7649fc73c 100644 --- a/src/java.base/share/classes/sun/security/util/Debug.java +++ b/src/java.base/share/classes/sun/security/util/Debug.java @@ -41,14 +41,7 @@ import java.util.Locale; public class Debug { private String prefix; - private boolean printDateTime; - private boolean printThreadDetails; - private static String args; - private static boolean threadInfoAll; - private static boolean timeStampInfoAll; - private static final String TIMESTAMP_OPTION = "+timestamp"; - private static final String THREAD_OPTION = "+thread"; static { args = System.getProperty("java.security.debug"); @@ -66,16 +59,6 @@ public class Debug { args = args.toLowerCase(Locale.ENGLISH); if (args.equals("help")) { Help(); - } else if (args.contains("all")) { - // "all" option has special handling for decorator options - // If the thread or timestamp decorator option is detected - // with the "all" option, then it impacts decorator options - // for other categories - int beginIndex = args.lastIndexOf("all") + "all".length(); - int commaIndex = args.indexOf(',', beginIndex); - if (commaIndex == -1) commaIndex = args.length(); - threadInfoAll = args.substring(beginIndex, commaIndex).contains(THREAD_OPTION); - timeStampInfoAll = args.substring(beginIndex, commaIndex).contains(TIMESTAMP_OPTION); } } } @@ -106,11 +89,6 @@ public class Debug { System.err.println("ts timestamping"); System.err.println("x509 X.509 certificate debugging"); System.err.println(); - System.err.println("+timestamp can be appended to any of above options to print"); - System.err.println(" a timestamp for that debug option"); - System.err.println("+thread can be appended to any of above options to print"); - System.err.println(" thread and caller information for that debug option"); - System.err.println(); System.err.println("The following can be used with provider:"); System.err.println(); System.err.println("engine="); @@ -151,7 +129,6 @@ public class Debug { if (isOn(option)) { Debug d = new Debug(); d.prefix = prefix; - d.configureExtras(option); return d; } else { return null; @@ -166,32 +143,6 @@ public class Debug { .findFirst().orElse("unknown caller")); } - // parse an option string to determine if extra details, - // like thread and timestamp, should be printed - private void configureExtras(String option) { - // treat "all" as special case, only used for java.security.debug property - this.printDateTime = timeStampInfoAll; - this.printThreadDetails = threadInfoAll; - - if (printDateTime && printThreadDetails) { - // nothing left to configure - return; - } - - // args is converted to lower case for the most part via marshal method - int optionIndex = args.lastIndexOf(option); - if (optionIndex == -1) { - // option not in args list. Only here since "all" was present - // in debug property argument. "all" option already parsed - return; - } - int beginIndex = optionIndex + option.length(); - int commaIndex = args.indexOf(',', beginIndex); - if (commaIndex == -1) commaIndex = args.length(); - String subOpt = args.substring(beginIndex, commaIndex); - printDateTime = printDateTime || subOpt.contains(TIMESTAMP_OPTION); - printThreadDetails = printThreadDetails || subOpt.contains(THREAD_OPTION); - } /** * Get a Debug object corresponding to the given option on the given @@ -208,11 +159,6 @@ public class Debug { * Debug debug = Debug.of("login", property); * } * - * +timestamp string can be appended to property value - * to print timestamp information. (e.g. true+timestamp) - * +thread string can be appended to property value - * to print thread and caller information. (e.g. true+thread) - * * @param prefix the debug option name * @param property debug setting for this option * @return a new Debug object if the property is true @@ -221,8 +167,6 @@ public class Debug { if (property != null && property.toLowerCase(Locale.ROOT).startsWith("true")) { Debug d = new Debug(); d.prefix = prefix; - d.printThreadDetails = property.contains(THREAD_OPTION); - d.printDateTime = property.contains(TIMESTAMP_OPTION); return d; } return null; @@ -285,23 +229,18 @@ public class Debug { } /** - * If thread debug option enabled, include information containing - * hex value of threadId and the current thread name - * If timestamp debug option enabled, include timestamp string - * @return extra info if debug option enabled. + * Include information containing: + * - hex value of threadId + * - the current thread name + * - timestamp string + * @return String with above metadata */ private String extraInfo() { - String retString = ""; - if (printThreadDetails) { - retString = "0x" + Long.toHexString( - Thread.currentThread().threadId()).toUpperCase(Locale.ROOT) + - "|" + Thread.currentThread().getName() + "|" + formatCaller(); - } - if (printDateTime) { - retString += (retString.isEmpty() ? "" : "|") - + FormatHolder.DATE_TIME_FORMATTER.format(Instant.now()); - } - return retString.isEmpty() ? "" : "[" + retString + "]"; + return String.format("[0x%s|%s|%s|%s]", + Long.toHexString(Thread.currentThread().threadId()).toUpperCase(Locale.ROOT), + Thread.currentThread().getName(), + formatCaller(), + FormatHolder.DATE_TIME_FORMATTER.format(Instant.now())); } /** diff --git a/test/jdk/sun/security/krb5/auto/LoginModuleDebug.java b/test/jdk/sun/security/krb5/auto/LoginModuleDebug.java index 42ddf72ec50..b34e8b42282 100644 --- a/test/jdk/sun/security/krb5/auto/LoginModuleDebug.java +++ b/test/jdk/sun/security/krb5/auto/LoginModuleDebug.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -47,8 +47,8 @@ public class LoginModuleDebug { // debug option set to true - no extra info Arguments.of("debug", "true", - "krb5loginmodule:", - "krb5loginmodule\\["), + "krb5loginmodule\\[.*\\|main|" + DATE_REGEX + ".*\\]:", + "krb5loginmodule:"), // debug option set to false Arguments.of("debug", "false", @@ -59,19 +59,9 @@ public class LoginModuleDebug { "bar", "", "krb5loginmodule"), - // thread info only + // test for thread and timestamp info Arguments.of("debug", "true+thread", - "krb5loginmodule\\[.*\\|main|\\.*java.*]:", - "\\|" + DATE_REGEX + ".*\\]:"), - // timestamp info only - Arguments.of("debug", - "true+timestamp", - "krb5loginmodule\\[" + DATE_REGEX + ".*\\]", - "\\|main\\]:"), - // both thread and timestamp - Arguments.of("debug", - "true+timestamp+thread", "krb5loginmodule\\[.*\\|main|" + DATE_REGEX + ".*\\]:", "krb5loginmodule:") ); @@ -104,4 +94,4 @@ public class LoginModuleDebug { new Subject(), null, Map.of(), Map.of(args[0], args[1])); } } -} \ No newline at end of file +} diff --git a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java index c9ad335a45e..424a460914c 100644 --- a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java +++ b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java @@ -51,8 +51,11 @@ public class DebugPropertyValuesTest extends SSLSocketTemplate { private static final Path LOG_FILE = Path.of("logging.conf"); private static final HashMap> debugMessages = new HashMap<>(); + private static final String DATE_REGEX = "\\d{4}-\\d{2}-\\d{2}"; static { + + debugMessages.put("handshake", List.of("Produced ClientHello handshake message", "supported_versions")); @@ -74,10 +77,10 @@ public class DebugPropertyValuesTest extends SSLSocketTemplate { debugMessages.put("help", List.of("print the help messages", "debugging can be widened with:")); - debugMessages.put("javax.net.debug", - List.of("properties: Initial security property:", - "certpath: Cert path validation succeeded")); - debugMessages.put("logger", + debugMessages.put("java.security.debug", + List.of("properties\\[.*\\|main\\|.*" + DATE_REGEX + ".*\\]:", + "certpath\\[.*\\|main\\|.*" + DATE_REGEX + ".*\\]:")); + debugMessages.put("javax.net.debug.logger", List.of("FINE: adding as trusted certificates", "FINE: WRITE: TLSv1.3 application_data")); } @@ -151,14 +154,15 @@ public class DebugPropertyValuesTest extends SSLSocketTemplate { // add in javax.net.debug sanity test Arguments.of(List.of("-Djavax.net.debug=ssl:trustmanager", "-Djava.security.debug=all"), - List.of("handshake", "javax.net.debug", "keymanager", + List.of("handshake", "java.security.debug", "keymanager", "record", "session", "ssl", "sslctx", "trustmanager", "verbose")), // empty invokes System.Logger use Arguments.of(List.of("-Djavax.net.debug", "-Djava.util.logging.config.file=" + LOG_FILE), - List.of("handshake", "keymanager", "logger", "packet", - "plaintext", "record", "session", "ssl", + List.of("handshake", "javax.net.debug.logger", + "keymanager", "packet", "plaintext", + "record", "session", "ssl", "sslctx", "trustmanager", "verbose")) ); } diff --git a/test/jdk/sun/security/util/Debug/DebugOptions.java b/test/jdk/sun/security/util/Debug/DebugOptions.java index a52566e7aeb..5fa02af5112 100644 --- a/test/jdk/sun/security/util/Debug/DebugOptions.java +++ b/test/jdk/sun/security/util/Debug/DebugOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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,7 +23,7 @@ /* * @test - * @bug 8051959 + * @bug 8051959 8350689 * @summary Option to print extra information in java.security.debug output * @library /test/lib * @run junit DebugOptions @@ -43,75 +43,41 @@ import jdk.test.lib.process.ProcessTools; public class DebugOptions { static final String DATE_REGEX = "\\d{4}-\\d{2}-\\d{2}"; + static final String EXPECTED_PROP_REGEX = + "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:"; + static final String EXPECTED_PROP_KEYSTORE_REGEX = + "properties\\[.*\\|main|" + DATE_REGEX + + ".*\\Rkeystore\\[.*\\|main|" + DATE_REGEX + ".*\\]:"; + static final String EXPECTED_ALL_REGEX = + "properties\\[.*\\|main.*\\|" + DATE_REGEX + + ".*\\]((.*\\R)*)keystore\\[.*\\|main.*\\|" + + DATE_REGEX + ".*\\]:"; private static Stream patternMatches() { return Stream.of( - // no extra info present + // test for thread and timestamp info Arguments.of("properties", - "properties: Initial", - "properties\\["), - // thread info only + EXPECTED_PROP_REGEX, + "properties:"), + // test for thread and timestamp info Arguments.of("properties+thread", - "properties\\[.*\\|main\\|.*java.*]:", - "properties\\[" + DATE_REGEX), - // timestamp info only - Arguments.of("properties+timestamp", - "properties\\[" + DATE_REGEX + ".*\\]", - "\\|main\\]:"), - // both thread and timestamp - Arguments.of("properties+timestamp+thread", - "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:", + EXPECTED_PROP_REGEX, "properties:"), // flip the arguments of previous test Arguments.of("properties+thread+timestamp", - "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:", + EXPECTED_PROP_REGEX, "properties:"), - // comma not valid separator, ignore extra info printing request - Arguments.of("properties,thread,timestamp", - "properties:", - "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:"), - // no extra info for keystore debug prints - Arguments.of("properties+thread+timestamp,keystore", - "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:", - "keystore\\["), - // flip arguments around in last test - same outcome expected - Arguments.of("keystore,properties+thread+timestamp", - "properties\\[.*\\|main|" + DATE_REGEX + ".*\\]:", - "keystore\\["), - // turn on thread info for both keystore and properties components - Arguments.of("keystore+thread,properties+thread", - "properties\\[.*\\|main|.*\\Rkeystore\\[.*\\|main|.*\\]:", - "\\|" + DATE_REGEX + ".*\\]:"), - // same as above with erroneous comma at end of string. same output expected - Arguments.of("keystore+thread,properties+thread,", - "properties\\[.*\\|main|.*\\Rkeystore\\[.*\\|main|.*\\]:", - "\\|" + DATE_REGEX + ".*\\]:"), - // turn on thread info for properties and timestamp for keystore - Arguments.of("keystore+timestamp,properties+thread", - "properties\\[.*\\|main|.*\\Rkeystore\\[" + DATE_REGEX + ".*\\]:", - "properties\\[.*\\|" + DATE_REGEX + ".*\\]:"), - // turn on thread info for all components + // regular keystore,properties component string + Arguments.of("keystore,properties", + EXPECTED_PROP_KEYSTORE_REGEX, + "properties:"), + // turn on all + Arguments.of("all", + EXPECTED_ALL_REGEX, + "properties:"), + // expect thread and timestamp info Arguments.of("all+thread", - "properties\\[.*\\|main.*((.*\\R)*)keystore\\[.*\\|main.*java.*\\]:", - "properties\\[" + DATE_REGEX + ".*\\]:"), - // turn on thread info and timestamp for all components - Arguments.of("all+thread+timestamp", - "properties\\[.*\\|main.*\\|" + DATE_REGEX + - ".*\\]((.*\\R)*)keystore\\[.*\\|main.*\\|" + DATE_REGEX + ".*\\]:", - "properties:"), - // all decorator option should override other component options - Arguments.of("all+thread+timestamp,properties", - "properties\\[.*\\|main.*\\|" + DATE_REGEX + - ".*\\]((.*\\R)*)keystore\\[.*\\|main.*\\|" + DATE_REGEX + ".*\\]:", - "properties:"), - // thread details should only be printed for properties option - Arguments.of("properties+thread,all", - "properties\\[.*\\|main\\|.*\\]:", - "keystore\\[.*\\|main\\|.*\\]:"), - // thread details should be printed for all statements - Arguments.of("properties,all+thread", - "properties\\[.*\\|main.*java" + - ".*\\]((.*\\R)*)keystore\\[.*\\|main.*java.*\\]:", + EXPECTED_ALL_REGEX, "properties:") ); } From 7838321b74276e45b92c54904ea31ef70ed9e33f Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Wed, 4 Jun 2025 09:52:45 +0000 Subject: [PATCH 035/143] 8358496: Concurrent reading from Socket with timeout executes sequentially Reviewed-by: dfuchs --- .../classes/sun/nio/ch/NioSocketImpl.java | 23 +- test/jdk/java/net/Socket/Timeouts.java | 246 +++++++++++------- 2 files changed, 163 insertions(+), 106 deletions(-) diff --git a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java index 6705134648d..dd81b356738 100644 --- a/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java @@ -288,7 +288,7 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp * @throws SocketException if the socket is closed or a socket I/O error occurs * @throws SocketTimeoutException if the read timeout elapses */ - private int implRead(byte[] b, int off, int len) throws IOException { + private int implRead(byte[] b, int off, int len, long remainingNanos) throws IOException { int n = 0; FileDescriptor fd = beginRead(); try { @@ -296,11 +296,10 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp throw new SocketException("Connection reset"); if (isInputClosed) return -1; - int timeout = this.timeout; - configureNonBlockingIfNeeded(fd, timeout > 0); - if (timeout > 0) { + configureNonBlockingIfNeeded(fd, remainingNanos > 0); + if (remainingNanos > 0) { // read with timeout - n = timedRead(fd, b, off, len, MILLISECONDS.toNanos(timeout)); + n = timedRead(fd, b, off, len, remainingNanos); } else { // read, no timeout n = tryRead(fd, b, off, len); @@ -335,14 +334,24 @@ public final class NioSocketImpl extends SocketImpl implements PlatformSocketImp if (len == 0) { return 0; } else { - readLock.lock(); + long remainingNanos = 0; + int timeout = this.timeout; + if (timeout > 0) { + remainingNanos = tryLock(readLock, timeout, MILLISECONDS); + if (remainingNanos <= 0) { + assert !readLock.isHeldByCurrentThread(); + throw new SocketTimeoutException("Read timed out"); + } + } else { + readLock.lock(); + } try { // emulate legacy behavior to return -1, even if socket is closed if (readEOF) return -1; // read up to MAX_BUFFER_SIZE bytes int size = Math.min(len, MAX_BUFFER_SIZE); - int n = implRead(b, off, size); + int n = implRead(b, off, size, remainingNanos); if (n == -1) readEOF = true; return n; diff --git a/test/jdk/java/net/Socket/Timeouts.java b/test/jdk/java/net/Socket/Timeouts.java index 83bf01ebf50..f8fcfb86d0f 100644 --- a/test/jdk/java/net/Socket/Timeouts.java +++ b/test/jdk/java/net/Socket/Timeouts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,11 +23,10 @@ /* * @test - * @bug 8221481 + * @bug 8221481 8358496 * @library /test/lib * @build jdk.test.lib.Utils - * @compile Timeouts.java - * @run testng/othervm/timeout=180 Timeouts + * @run junit/othervm/timeout=180 Timeouts * @summary Test Socket timeouts */ @@ -43,25 +42,27 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.testng.SkipException; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; import jdk.test.lib.Utils; -@Test -public class Timeouts { +class Timeouts { /** - * Test timed connect where connection is established + * Test timed connect where connection is established. */ - public void testTimedConnect1() throws IOException { + @Test + void testTimedConnect1() throws IOException { try (ServerSocket ss = boundServerSocket()) { try (Socket s = new Socket()) { s.connect(ss.getLocalSocketAddress(), 2000); @@ -70,21 +71,21 @@ public class Timeouts { } /** - * Test timed connect where connection is refused + * Test timed connect where connection is refused. */ - public void testTimedConnect2() throws IOException { + @Test + void testTimedConnect2() throws IOException { try (Socket s = new Socket()) { SocketAddress remote = Utils.refusingEndpoint(); - try { - s.connect(remote, 10000); - } catch (ConnectException expected) { } + assertThrows(ConnectException.class, () -> s.connect(remote, 10000)); } } /** - * Test connect with a timeout of Integer.MAX_VALUE + * Test connect with a timeout of Integer.MAX_VALUE. */ - public void testTimedConnect3() throws IOException { + @Test + void testTimedConnect3() throws IOException { try (ServerSocket ss = boundServerSocket()) { try (Socket s = new Socket()) { s.connect(ss.getLocalSocketAddress(), Integer.MAX_VALUE); @@ -95,141 +96,183 @@ public class Timeouts { /** * Test connect with a negative timeout. */ - public void testTimedConnect4() throws IOException { + @Test + void testTimedConnect4() throws IOException { try (ServerSocket ss = boundServerSocket()) { try (Socket s = new Socket()) { - expectThrows(IllegalArgumentException.class, + assertThrows(IllegalArgumentException.class, () -> s.connect(ss.getLocalSocketAddress(), -1)); } } } /** - * Test timed read where the read succeeds immediately + * Test timed read where the read succeeds immediately. */ - public void testTimedRead1() throws IOException { + @Test + void testTimedRead1() throws IOException { withConnection((s1, s2) -> { s1.getOutputStream().write(99); s2.setSoTimeout(30*1000); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** - * Test timed read where the read succeeds after a delay + * Test timed read where the read succeeds after a delay. */ - public void testTimedRead2() throws IOException { + @Test + void testTimedRead2() throws IOException { withConnection((s1, s2) -> { scheduleWrite(s1.getOutputStream(), 99, 2000); s2.setSoTimeout(30*1000); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** - * Test timed read where the read times out + * Test timed read where the read times out. */ - public void testTimedRead3() throws IOException { + @Test + void testTimedRead3() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); long startMillis = millisTime(); - expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + assertThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); int timeout = s2.getSoTimeout(); checkDuration(startMillis, timeout-100, timeout+20_000); }); } /** - * Test timed read that succeeds after a previous read has timed out + * Test timed read that succeeds after a previous read has timed out. */ - public void testTimedRead4() throws IOException { + @Test + void testTimedRead4() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); - expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + assertThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); s1.getOutputStream().write(99); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** * Test timed read that succeeds after a previous read has timed out and - * after a short delay + * after a short delay. */ - public void testTimedRead5() throws IOException { + @Test + void testTimedRead5() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); - expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + assertThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); s2.setSoTimeout(30*3000); scheduleWrite(s1.getOutputStream(), 99, 2000); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** - * Test untimed read that succeeds after a previous read has timed out + * Test untimed read that succeeds after a previous read has timed out. */ - public void testTimedRead6() throws IOException { + @Test + void testTimedRead6() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); - expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + assertThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); s1.getOutputStream().write(99); s2.setSoTimeout(0); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** * Test untimed read that succeeds after a previous read has timed out and - * after a short delay + * after a short delay. */ - public void testTimedRead7() throws IOException { + @Test + void testTimedRead7() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(2000); - expectThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); + assertThrows(SocketTimeoutException.class, () -> s2.getInputStream().read()); scheduleWrite(s1.getOutputStream(), 99, 2000); s2.setSoTimeout(0); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } /** - * Test async close of timed read + * Test async close of timed read. */ - public void testTimedRead8() throws IOException { + @Test + void testTimedRead8() throws IOException { withConnection((s1, s2) -> { s2.setSoTimeout(30*1000); scheduleClose(s2, 2000); - expectThrows(SocketException.class, () -> s2.getInputStream().read()); + assertThrows(SocketException.class, () -> s2.getInputStream().read()); }); } /** - * Test read with a timeout of Integer.MAX_VALUE + * Test read with a timeout of Integer.MAX_VALUE. */ - public void testTimedRead9() throws IOException { + @Test + void testTimedRead9() throws IOException { withConnection((s1, s2) -> { scheduleWrite(s1.getOutputStream(), 99, 2000); s2.setSoTimeout(Integer.MAX_VALUE); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); }); } + /** + * Test 100 threads concurrently reading the same Socket with a timeout of 2s. + * Each read should throw SocketTimeoutException after 2s, not 2s for the first, + * 4s for the second, 6s for the third, up to 200s for the last thread. + */ + @Test + void testTimedRead10() throws Exception { + var futures = new ArrayList>(); + withConnection((_, s) -> { + s.setSoTimeout(2000); + Callable timedReadTask = () -> { + long startMillis = millisTime(); + assertThrows(SocketTimeoutException.class, + () -> s.getInputStream().read()); + int timeout = s.getSoTimeout(); + checkDuration(startMillis, timeout-100, timeout+20_000); + return null; + }; + // start 100 virtual threads to read from the socket + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < 100; i++) { + Future future = executor.submit(timedReadTask); + futures.add(future); + } + } + }); + for (Future future : futures) { + future.get(); + } + } + /** * Test writing after a timed read. */ - public void testTimedWrite1() throws IOException { + @Test + void testTimedWrite1() throws IOException { withConnection((s1, s2) -> { s1.getOutputStream().write(99); s2.setSoTimeout(3000); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); // schedule thread to read s1 to EOF scheduleReadToEOF(s1.getInputStream(), 3000); @@ -245,12 +288,13 @@ public class Timeouts { /** * Test async close of writer (after a timed read). */ - public void testTimedWrite2() throws IOException { + @Test + void testTimedWrite2() throws IOException { withConnection((s1, s2) -> { s1.getOutputStream().write(99); s2.setSoTimeout(3000); int b = s2.getInputStream().read(); - assertTrue(b == 99); + assertEquals(99, b); // schedule s2 to be closed scheduleClose(s2, 3000); @@ -266,9 +310,10 @@ public class Timeouts { } /** - * Test timed accept where a connection is established immediately + * Test timed accept where a connection is established immediately. */ - public void testTimedAccept1() throws IOException { + @Test + void testTimedAccept1() throws IOException { Socket s1 = null; Socket s2 = null; try (ServerSocket ss = boundServerSocket()) { @@ -283,9 +328,10 @@ public class Timeouts { } /** - * Test timed accept where a connection is established after a short delay + * Test timed accept where a connection is established after a short delay. */ - public void testTimedAccept2() throws IOException { + @Test + void testTimedAccept2() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(30*1000); scheduleConnect(ss.getLocalSocketAddress(), 2000); @@ -297,7 +343,8 @@ public class Timeouts { /** * Test timed accept where the accept times out */ - public void testTimedAccept3() throws IOException { + @Test + void testTimedAccept3() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); long startMillis = millisTime(); @@ -316,7 +363,8 @@ public class Timeouts { * Test timed accept where a connection is established immediately after a * previous accept timed out. */ - public void testTimedAccept4() throws IOException { + @Test + void testTimedAccept4() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { @@ -334,9 +382,10 @@ public class Timeouts { /** * Test untimed accept where a connection is established after a previous - * accept timed out + * accept timed out. */ - public void testTimedAccept5() throws IOException { + @Test + void testTimedAccept5() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { @@ -355,9 +404,10 @@ public class Timeouts { /** * Test untimed accept where a connection is established after a previous - * accept timed out and after a short delay + * accept timed out and after a short delay. */ - public void testTimedAccept6() throws IOException { + @Test + void testTimedAccept6() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); try { @@ -373,9 +423,10 @@ public class Timeouts { } /** - * Test async close of a timed accept + * Test async close of a timed accept. */ - public void testTimedAccept7() throws IOException { + @Test + void testTimedAccept7() throws IOException { try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(30*1000); long delay = 2000; @@ -393,9 +444,9 @@ public class Timeouts { /** * Test timed accept with the thread interrupt status set. */ - public void testTimedAccept8() throws IOException { - if (Thread.currentThread().isVirtual()) - throw new SkipException("Main test is a virtual thread"); + @Test + void testTimedAccept8() throws IOException { + assumeFalse(Thread.currentThread().isVirtual(), "Main test is a virtual thread"); try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(2000); Thread.currentThread().interrupt(); @@ -418,9 +469,9 @@ public class Timeouts { /** * Test interrupt of thread blocked in timed accept. */ - public void testTimedAccept9() throws IOException { - if (Thread.currentThread().isVirtual()) - throw new SkipException("Main test is a virtual thread"); + @Test + void testTimedAccept9() throws IOException { + assumeFalse(Thread.currentThread().isVirtual(), "Main test is a virtual thread"); try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(4000); // interrupt thread after 1 second @@ -445,7 +496,8 @@ public class Timeouts { /** * Test two threads blocked in timed accept where no connection is established. */ - public void testTimedAccept10() throws Exception { + @Test + void testTimedAccept10() throws Exception { ExecutorService pool = Executors.newFixedThreadPool(2); try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(4000); @@ -456,9 +508,9 @@ public class Timeouts { Future result2 = pool.submit(ss::accept); // both tasks should complete with SocketTimeoutException - Throwable e = expectThrows(ExecutionException.class, result1::get); + Throwable e = assertThrows(ExecutionException.class, result1::get); assertTrue(e.getCause() instanceof SocketTimeoutException); - e = expectThrows(ExecutionException.class, result2::get); + e = assertThrows(ExecutionException.class, result2::get); assertTrue(e.getCause() instanceof SocketTimeoutException); // should get here in 4 seconds, not 8 seconds @@ -472,7 +524,8 @@ public class Timeouts { /** * Test two threads blocked in timed accept where one connection is established. */ - public void testTimedAccept11() throws Exception { + @Test + void testTimedAccept11() throws Exception { ExecutorService pool = Executors.newFixedThreadPool(2); try (ServerSocket ss = boundServerSocket()) { ss.setSoTimeout(4000); @@ -514,25 +567,25 @@ public class Timeouts { /** * Test Socket setSoTimeout with a negative timeout. */ - @Test(expectedExceptions = { IllegalArgumentException.class }) - public void testBadTimeout1() throws IOException { + @Test + void testBadTimeout1() throws IOException { try (Socket s = new Socket()) { - s.setSoTimeout(-1); + assertThrows(IllegalArgumentException.class, () -> s.setSoTimeout(-1)); } } /** * Test ServerSocket setSoTimeout with a negative timeout. */ - @Test(expectedExceptions = { IllegalArgumentException.class }) - public void testBadTimeout2() throws IOException { + @Test + void testBadTimeout2() throws IOException { try (ServerSocket ss = new ServerSocket()) { - ss.setSoTimeout(-1); + assertThrows(IllegalArgumentException.class, () -> ss.setSoTimeout(-1)); } } /** - * Returns a ServerSocket bound to a port on the loopback address + * Returns a ServerSocket bound to a port on the loopback address. */ static ServerSocket boundServerSocket() throws IOException { var loopback = InetAddress.getLoopbackAddress(); @@ -542,14 +595,14 @@ public class Timeouts { } /** - * An operation that accepts two arguments and may throw IOException + * An operation that accepts two arguments and may throw IOException. */ interface ThrowingBiConsumer { void accept(T t, U u) throws IOException; } /** - * Invokes the consumer with a connected pair of sockets + * Invokes the consumer with a connected pair of sockets. */ static void withConnection(ThrowingBiConsumer consumer) throws IOException @@ -568,7 +621,7 @@ public class Timeouts { } /** - * Schedule c to be closed after a delay + * Schedule c to be closed after a delay. */ static void scheduleClose(Closeable c, long delay) { schedule(() -> { @@ -579,14 +632,14 @@ public class Timeouts { } /** - * Schedule thread to be interrupted after a delay + * Schedule thread to be interrupted after a delay. */ static Future scheduleInterrupt(Thread thread, long delay) { return schedule(() -> thread.interrupt(), delay); } /** - * Schedule a thread to connect to the given end point after a delay + * Schedule a thread to connect to the given end point after a delay. */ static void scheduleConnect(SocketAddress remote, long delay) { schedule(() -> { @@ -597,7 +650,7 @@ public class Timeouts { } /** - * Schedule a thread to read to EOF after a delay + * Schedule a thread to read to EOF after a delay. */ static void scheduleReadToEOF(InputStream in, long delay) { schedule(() -> { @@ -609,7 +662,7 @@ public class Timeouts { } /** - * Schedule a thread to write after a delay + * Schedule a thread to write after a delay. */ static void scheduleWrite(OutputStream out, byte[] data, long delay) { schedule(() -> { @@ -623,12 +676,7 @@ public class Timeouts { } static Future schedule(Runnable task, long delay) { - ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - try { - return executor.schedule(task, delay, TimeUnit.MILLISECONDS); - } finally { - executor.shutdown(); - } + return ForkJoinPool.commonPool().schedule(task, delay, TimeUnit.MILLISECONDS); } /** @@ -640,7 +688,7 @@ public class Timeouts { } /** - * Check the duration of a task + * Check the duration of a task. * @param start start time, in milliseconds * @param min minimum expected duration, in milliseconds * @param max maximum expected duration, in milliseconds From dc961609f84a38164d10852cb92c005c3eb077e4 Mon Sep 17 00:00:00 2001 From: Robbin Ehn Date: Wed, 4 Jun 2025 12:43:23 +0000 Subject: [PATCH 036/143] 8356159: RISC-V: Add Zabha Reviewed-by: fyang, fjiang --- src/hotspot/cpu/riscv/assembler_riscv.hpp | 292 +++++++--- src/hotspot/cpu/riscv/globals_riscv.hpp | 1 + .../cpu/riscv/macroAssembler_riscv.cpp | 40 +- .../cpu/riscv/macroAssembler_riscv.hpp | 16 +- src/hotspot/cpu/riscv/riscv.ad | 525 +++++++++++++----- .../gtest/riscv/test_assembler_riscv.cpp | 145 +++-- 6 files changed, 759 insertions(+), 260 deletions(-) diff --git a/src/hotspot/cpu/riscv/assembler_riscv.hpp b/src/hotspot/cpu/riscv/assembler_riscv.hpp index 3cdc9b70d52..3317ccc3b53 100644 --- a/src/hotspot/cpu/riscv/assembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/assembler_riscv.hpp @@ -961,81 +961,239 @@ protected: #undef INSN -enum Aqrl {relaxed = 0b00, rl = 0b01, aq = 0b10, aqrl = 0b11}; + enum Aqrl {relaxed = 0b00, rl = 0b01, aq = 0b10, aqrl = 0b11}; -#define INSN(NAME, op, funct3, funct7) \ - void NAME(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { \ - unsigned insn = 0; \ - patch((address)&insn, 6, 0, op); \ - patch((address)&insn, 14, 12, funct3); \ - patch_reg((address)&insn, 7, Rd); \ - patch_reg((address)&insn, 15, Rs1); \ - patch_reg((address)&insn, 20, Rs2); \ - patch((address)&insn, 31, 27, funct7); \ - patch((address)&insn, 26, 25, memory_order); \ - emit(insn); \ + private: + + enum AmoWidthFunct3 : uint8_t { + AMO_WIDTH_BYTE = 0b000, // Zabha extension + AMO_WIDTH_HALFWORD = 0b001, // Zabha extension + AMO_WIDTH_WORD = 0b010, + AMO_WIDTH_DOUBLEWORD = 0b011, + AMO_WIDTH_QUADWORD = 0b100, + // 0b101 to 0b111 are reserved + }; + + enum AmoOperationFunct5 : uint8_t { + AMO_ADD = 0b00000, + AMO_SWAP = 0b00001, + AMO_LR = 0b00010, + AMO_SC = 0b00011, + AMO_XOR = 0b00100, + AMO_OR = 0b01000, + AMO_AND = 0b01100, + AMO_MIN = 0b10000, + AMO_MAX = 0b10100, + AMO_MINU = 0b11000, + AMO_MAXU = 0b11100, + AMO_CAS = 0b00101 // Zacas + }; + + static constexpr uint32_t OP_AMO_MAJOR = 0b0101111; + + template + void amo_base(Register Rd, Register Rs1, uint8_t Rs2, Aqrl memory_order = aqrl) { + assert(width > AMO_WIDTH_HALFWORD || UseZabha, "Must be"); + assert(funct5 != AMO_CAS || UseZacas, "Must be"); + unsigned insn = 0; + patch((address)&insn, 6, 0, OP_AMO_MAJOR); + patch_reg((address)&insn, 7, Rd); + patch((address)&insn, 14, 12, width); + patch_reg((address)&insn, 15, Rs1); + patch((address)&insn, 24, 20, Rs2); + patch((address)&insn, 26, 25, memory_order); + patch((address)&insn, 31, 27, funct5); + emit(insn); } - INSN(amoswap_w, 0b0101111, 0b010, 0b00001); - INSN(amoadd_w, 0b0101111, 0b010, 0b00000); - INSN(amoxor_w, 0b0101111, 0b010, 0b00100); - INSN(amoand_w, 0b0101111, 0b010, 0b01100); - INSN(amoor_w, 0b0101111, 0b010, 0b01000); - INSN(amomin_w, 0b0101111, 0b010, 0b10000); - INSN(amomax_w, 0b0101111, 0b010, 0b10100); - INSN(amominu_w, 0b0101111, 0b010, 0b11000); - INSN(amomaxu_w, 0b0101111, 0b010, 0b11100); - INSN(amoswap_d, 0b0101111, 0b011, 0b00001); - INSN(amoadd_d, 0b0101111, 0b011, 0b00000); - INSN(amoxor_d, 0b0101111, 0b011, 0b00100); - INSN(amoand_d, 0b0101111, 0b011, 0b01100); - INSN(amoor_d, 0b0101111, 0b011, 0b01000); - INSN(amomin_d, 0b0101111, 0b011, 0b10000); - INSN(amomax_d , 0b0101111, 0b011, 0b10100); - INSN(amominu_d, 0b0101111, 0b011, 0b11000); - INSN(amomaxu_d, 0b0101111, 0b011, 0b11100); - INSN(amocas_w, 0b0101111, 0b010, 0b00101); - INSN(amocas_d, 0b0101111, 0b011, 0b00101); -#undef INSN - -enum operand_size { int8, int16, int32, uint32, int64 }; - -#define INSN(NAME, op, funct3, funct7) \ - void NAME(Register Rd, Register Rs1, Aqrl memory_order = relaxed) { \ - unsigned insn = 0; \ - uint32_t val = memory_order & 0x3; \ - patch((address)&insn, 6, 0, op); \ - patch((address)&insn, 14, 12, funct3); \ - patch_reg((address)&insn, 7, Rd); \ - patch_reg((address)&insn, 15, Rs1); \ - patch((address)&insn, 25, 20, 0b00000); \ - patch((address)&insn, 31, 27, funct7); \ - patch((address)&insn, 26, 25, val); \ - emit(insn); \ + template + void amo_base(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2->raw_encoding(), memory_order); } - INSN(lr_w, 0b0101111, 0b010, 0b00010); - INSN(lr_d, 0b0101111, 0b011, 0b00010); + public: -#undef INSN - -#define INSN(NAME, op, funct3, funct7) \ - void NAME(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = relaxed) { \ - unsigned insn = 0; \ - uint32_t val = memory_order & 0x3; \ - patch((address)&insn, 6, 0, op); \ - patch((address)&insn, 14, 12, funct3); \ - patch_reg((address)&insn, 7, Rd); \ - patch_reg((address)&insn, 15, Rs2); \ - patch_reg((address)&insn, 20, Rs1); \ - patch((address)&insn, 31, 27, funct7); \ - patch((address)&insn, 26, 25, val); \ - emit(insn); \ + void amoadd_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); } - INSN(sc_w, 0b0101111, 0b010, 0b00011); - INSN(sc_d, 0b0101111, 0b011, 0b00011); -#undef INSN + void amoadd_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoadd_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoadd_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoswap_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoswap_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoswap_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoswap_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoxor_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoxor_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoxor_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoxor_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoor_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoor_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoor_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoor_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoand_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoand_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoand_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amoand_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomin_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomin_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomin_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomin_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amominu_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amominu_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amominu_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amominu_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomax_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomax_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomax_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomax_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomaxu_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomaxu_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomaxu_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amomaxu_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + protected: + + void lr_w(Register Rd, Register Rs1, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, 0, memory_order); + } + + void lr_d(Register Rd, Register Rs1, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, 0, memory_order); + } + + void sc_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void sc_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amocas_b(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amocas_h(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amocas_w(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + void amocas_d(Register Rd, Register Rs1, Register Rs2, Aqrl memory_order = aqrl) { + amo_base(Rd, Rs1, Rs2, memory_order); + } + + public: + + enum operand_size { int8, int16, int32, uint32, int64 }; // Immediate Instruction #define INSN(NAME, op, funct3) \ diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index 3ef084d30fc..d67e05bbb6d 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -107,6 +107,7 @@ define_pd_global(intx, InlineSmallCode, 1000); product(bool, UseZfh, false, DIAGNOSTIC, "Use Zfh instructions") \ product(bool, UseZfhmin, false, DIAGNOSTIC, "Use Zfhmin instructions") \ product(bool, UseZacas, false, EXPERIMENTAL, "Use Zacas instructions") \ + product(bool, UseZabha, false, EXPERIMENTAL, "Use UseZabha instructions") \ product(bool, UseZcb, false, EXPERIMENTAL, "Use Zcb instructions") \ product(bool, UseZic64b, false, EXPERIMENTAL, "Use Zic64b instructions") \ product(bool, UseZicbom, false, EXPERIMENTAL, "Use Zicbom instructions") \ diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index bc6bf88d0ad..c755d9ae23d 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3798,7 +3798,7 @@ void MacroAssembler::cmpxchg_obj_header(Register oldv, Register newv, Register o void MacroAssembler::load_reserved(Register dst, Register addr, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire) { switch (size) { case int64: @@ -3819,15 +3819,15 @@ void MacroAssembler::load_reserved(Register dst, void MacroAssembler::store_conditional(Register dst, Register new_val, Register addr, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl release) { switch (size) { case int64: - sc_d(dst, new_val, addr, release); + sc_d(dst, addr, new_val, release); break; case int32: case uint32: - sc_w(dst, new_val, addr, release); + sc_w(dst, addr, new_val, release); break; default: ShouldNotReachHere(); @@ -3836,7 +3836,7 @@ void MacroAssembler::store_conditional(Register dst, void MacroAssembler::cmpxchg_narrow_value_helper(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Register shift, Register mask, Register aligned_addr) { assert(size == int8 || size == int16, "unsupported operand size"); @@ -3866,10 +3866,11 @@ void MacroAssembler::cmpxchg_narrow_value_helper(Register addr, Register expecte // which are forced to work with 4-byte aligned address. void MacroAssembler::cmpxchg_narrow_value(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, bool result_as_bool, Register tmp1, Register tmp2, Register tmp3) { + assert(!(UseZacas && UseZabha), "Use amocas"); assert_different_registers(addr, expected, new_val, result, tmp1, tmp2, tmp3, t0, t1); Register scratch0 = t0, aligned_addr = t1; @@ -3902,13 +3903,13 @@ void MacroAssembler::cmpxchg_narrow_value(Register addr, Register expected, notr(scratch1, mask); bind(retry); - lr_w(result, aligned_addr, acquire); + load_reserved(result, aligned_addr, operand_size::int32, acquire); andr(scratch0, result, mask); bne(scratch0, expected, fail); andr(scratch0, result, scratch1); // scratch1 is ~mask orr(scratch0, scratch0, new_val); - sc_w(scratch0, scratch0, aligned_addr, release); + store_conditional(scratch0, scratch0, aligned_addr, operand_size::int32, release); bnez(scratch0, retry); } @@ -3940,10 +3941,11 @@ void MacroAssembler::cmpxchg_narrow_value(Register addr, Register expected, // failed. void MacroAssembler::weak_cmpxchg_narrow_value(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, Register tmp1, Register tmp2, Register tmp3) { + assert(!(UseZacas && UseZabha), "Use amocas"); assert_different_registers(addr, expected, new_val, result, tmp1, tmp2, tmp3, t0, t1); Register scratch0 = t0, aligned_addr = t1; @@ -3974,13 +3976,13 @@ void MacroAssembler::weak_cmpxchg_narrow_value(Register addr, Register expected, } else { notr(scratch1, mask); - lr_w(result, aligned_addr, acquire); + load_reserved(result, aligned_addr, operand_size::int32, acquire); andr(scratch0, result, mask); bne(scratch0, expected, fail); andr(scratch0, result, scratch1); // scratch1 is ~mask orr(scratch0, scratch0, new_val); - sc_w(scratch0, scratch0, aligned_addr, release); + store_conditional(scratch0, scratch0, aligned_addr, operand_size::int32, release); bnez(scratch0, fail); } @@ -3997,10 +3999,10 @@ void MacroAssembler::weak_cmpxchg_narrow_value(Register addr, Register expected, void MacroAssembler::cmpxchg(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, bool result_as_bool) { - assert(size != int8 && size != int16, "unsupported operand size"); + assert((UseZacas && UseZabha) || (size != int8 && size != int16), "unsupported operand size"); assert_different_registers(addr, t0); assert_different_registers(expected, t0); assert_different_registers(new_val, t0); @@ -4058,10 +4060,10 @@ void MacroAssembler::cmpxchg(Register addr, Register expected, void MacroAssembler::weak_cmpxchg(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result) { - + assert((UseZacas && UseZabha) || (size != int8 && size != int16), "unsupported operand size"); assert_different_registers(addr, t0); assert_different_registers(expected, t0); assert_different_registers(new_val, t0); @@ -4134,7 +4136,7 @@ ATOMIC_XCHGU(xchgalwu, xchgalw) #undef ATOMIC_XCHGU void MacroAssembler::atomic_cas(Register prev, Register newv, Register addr, - enum operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release) { + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release) { switch (size) { case int64: amocas_d(prev, addr, newv, (Assembler::Aqrl)(acquire | release)); @@ -4146,6 +4148,12 @@ void MacroAssembler::atomic_cas(Register prev, Register newv, Register addr, amocas_w(prev, addr, newv, (Assembler::Aqrl)(acquire | release)); zext(prev, prev, 32); break; + case int16: + amocas_h(prev, addr, newv, (Assembler::Aqrl)(acquire | release)); + break; + case int8: + amocas_b(prev, addr, newv, (Assembler::Aqrl)(acquire | release)); + break; default: ShouldNotReachHere(); } diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 6f80a02ddc6..f302b3602ad 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -1187,26 +1187,26 @@ public: void cmpxchgptr(Register oldv, Register newv, Register addr, Register tmp, Label &succeed, Label *fail); void cmpxchg(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, bool result_as_bool = false); void weak_cmpxchg(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result); void cmpxchg_narrow_value_helper(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Register shift, Register mask, Register aligned_addr); void cmpxchg_narrow_value(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, bool result_as_bool, Register tmp1, Register tmp2, Register tmp3); void weak_cmpxchg_narrow_value(Register addr, Register expected, Register new_val, - enum operand_size size, + Assembler::operand_size size, Assembler::Aqrl acquire, Assembler::Aqrl release, Register result, Register tmp1, Register tmp2, Register tmp3); @@ -1223,7 +1223,7 @@ public: void atomic_xchgwu(Register prev, Register newv, Register addr); void atomic_xchgalwu(Register prev, Register newv, Register addr); - void atomic_cas(Register prev, Register newv, Register addr, enum operand_size size, + void atomic_cas(Register prev, Register newv, Register addr, Assembler::operand_size size, Assembler::Aqrl acquire = Assembler::relaxed, Assembler::Aqrl release = Assembler::relaxed); // Emit a far call/jump. Only invalidates the tmp register which @@ -1636,8 +1636,8 @@ private: int bitset_to_regs(unsigned int bitset, unsigned char* regs); Address add_memory_helper(const Address dst, Register tmp); - void load_reserved(Register dst, Register addr, enum operand_size size, Assembler::Aqrl acquire); - void store_conditional(Register dst, Register new_val, Register addr, enum operand_size size, Assembler::Aqrl release); + void load_reserved(Register dst, Register addr, Assembler::operand_size size, Assembler::Aqrl acquire); + void store_conditional(Register dst, Register new_val, Register addr, Assembler::operand_size size, Assembler::Aqrl release); public: void lightweight_lock(Register basic_lock, Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index c711d2db640..e838ee184fb 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2304,42 +2304,6 @@ encode %{ } %} - enc_class riscv_enc_cmpxchgw(iRegINoSp res, memory mem, iRegI oldval, iRegI newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - - enc_class riscv_enc_cmpxchgn(iRegINoSp res, memory mem, iRegI oldval, iRegI newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - - enc_class riscv_enc_cmpxchg(iRegINoSp res, memory mem, iRegL oldval, iRegL newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - - enc_class riscv_enc_cmpxchgw_acq(iRegINoSp res, memory mem, iRegI oldval, iRegI newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - - enc_class riscv_enc_cmpxchgn_acq(iRegINoSp res, memory mem, iRegI oldval, iRegI newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - - enc_class riscv_enc_cmpxchg_acq(iRegINoSp res, memory mem, iRegL oldval, iRegL newval) %{ - __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); - %} - // compare and branch instruction encodings enc_class riscv_enc_j(label lbl) %{ @@ -5250,18 +5214,20 @@ instruct prefetchalloc( memory mem ) %{ // standard CompareAndSwapX when we are using barriers // these have higher priority than the rules selected by a predicate -instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndSwapB_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ + predicate(!UseZabha || !UseZacas); + match(Set res (CompareAndSwapB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 10 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr); format %{ "cmpxchg $mem, $oldval, $newval\t# (byte) if $mem == $oldval then $mem <-- $newval\n\t" - "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapB" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapB_narrow" %} ins_encode %{ @@ -5273,18 +5239,42 @@ instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R1 ins_pipe(pipe_slow); %} -instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ + predicate(UseZabha && UseZacas); + + match(Set res (CompareAndSwapB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $mem, $oldval, $newval\t# (byte) if $mem == $oldval then $mem <-- $newval\n\t" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapB" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + Assembler::relaxed /* acquire */, Assembler::rl /* release */, $res$$Register, + true /* result as bool */); + %} + + ins_pipe(pipe_slow); +%} + +instruct compareAndSwapS_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate(!UseZabha || !UseZacas); + match(Set res (CompareAndSwapS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 11 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr); format %{ "cmpxchg $mem, $oldval, $newval\t# (short) if $mem == $oldval then $mem <-- $newval\n\t" - "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapS" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapS_narrow" %} ins_encode %{ @@ -5296,18 +5286,44 @@ instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R1 ins_pipe(pipe_slow); %} +instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate(UseZabha && UseZacas); + + match(Set res (CompareAndSwapS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $mem, $oldval, $newval\t# (short) if $mem == $oldval then $mem <-- $newval\n\t" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapS" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + Assembler::relaxed /* acquire */, Assembler::rl /* release */, $res$$Register, + true /* result as bool */); + %} + + ins_pipe(pipe_slow); +%} + instruct compareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ match(Set res (CompareAndSwapI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $mem, $oldval, $newval\t# (int) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapI" %} - ins_encode(riscv_enc_cmpxchgw(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5316,14 +5332,18 @@ instruct compareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL newval %{ match(Set res (CompareAndSwapL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $mem, $oldval, $newval\t# (long) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapL" %} - ins_encode(riscv_enc_cmpxchg(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5334,14 +5354,18 @@ instruct compareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval match(Set res (CompareAndSwapP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $mem, $oldval, $newval\t# (ptr) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapP" %} - ins_encode(riscv_enc_cmpxchg(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5349,35 +5373,40 @@ instruct compareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval instruct compareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN newval) %{ predicate(n->as_LoadStore()->barrier_data() == 0); + match(Set res (CompareAndSwapN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 8 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $mem, $oldval, $newval\t# (narrow oop) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapN" %} - ins_encode(riscv_enc_cmpxchgn(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} // alternative CompareAndSwapX when we are eliding barriers -instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndSwapBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (CompareAndSwapB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 10 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (byte) if $mem == $oldval then $mem <-- $newval\n\t" - "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapBAcq" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapBAcq_narrow" %} ins_encode %{ @@ -5389,20 +5418,42 @@ instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI ins_pipe(pipe_slow); %} -instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (CompareAndSwapB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $mem, $oldval, $newval\t# (byte) if $mem == $oldval then $mem <-- $newval\n\t" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapBAcq" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, + true /* result as bool */); + %} + + ins_pipe(pipe_slow); +%} + +instruct compareAndSwapSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (CompareAndSwapS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 11 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (short) if $mem == $oldval then $mem <-- $newval\n\t" - "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapSAcq" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapSAcq_narrow" %} ins_encode %{ @@ -5414,20 +5465,46 @@ instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI ins_pipe(pipe_slow); %} +instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (CompareAndSwapS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $mem, $oldval, $newval\t# (short) if $mem == $oldval then $mem <-- $newval\n\t" + "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapSAcq" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, + true /* result as bool */); + %} + + ins_pipe(pipe_slow); +%} + instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ predicate(needs_acquiring_load_reserved(n)); match(Set res (CompareAndSwapI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (int) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapIAcq" %} - ins_encode(riscv_enc_cmpxchgw_acq(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5438,14 +5515,18 @@ instruct compareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL new match(Set res (CompareAndSwapL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (long) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapLAcq" %} - ins_encode(riscv_enc_cmpxchg_acq(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5456,14 +5537,18 @@ instruct compareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP new match(Set res (CompareAndSwapP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 6 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (ptr) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapPAcq" %} - ins_encode(riscv_enc_cmpxchg_acq(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5474,14 +5559,18 @@ instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN new match(Set res (CompareAndSwapN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + ALU_COST * 8 + BRANCH_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $mem, $oldval, $newval\t# (narrow oop) if $mem == $oldval then $mem <-- $newval\n\t" "mv $res, $res == $oldval\t# $res <-- ($res == $oldval ? 1 : 0), #@compareAndSwapNAcq" %} - ins_encode(riscv_enc_cmpxchgn_acq(res, mem, oldval, newval)); + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + /*result as bool*/ true); + %} ins_pipe(pipe_slow); %} @@ -5492,17 +5581,19 @@ instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN new // no trailing StoreLoad barrier emitted by C2. Unfortunately we // can't check the type of memory ordering here, so we always emit a // sc_d(w) with rl bit set. -instruct compareAndExchangeB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndExchangeB_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ + predicate(!UseZabha || !UseZacas); + match(Set res (CompareAndExchangeB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST * 5); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ - "cmpxchg $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeB" + "cmpxchg $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeB_narrow" %} ins_encode %{ @@ -5514,17 +5605,39 @@ instruct compareAndExchangeB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iReg ins_pipe(pipe_slow); %} -instruct compareAndExchangeS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndExchangeB(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ + predicate(UseZabha && UseZacas); + + match(Set res (CompareAndExchangeB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeB" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + +instruct compareAndExchangeS_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate(!UseZabha || !UseZacas); + match(Set res (CompareAndExchangeS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST * 6); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ - "cmpxchg $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeS" + "cmpxchg $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeS_narrow" %} ins_encode %{ @@ -5536,13 +5649,31 @@ instruct compareAndExchangeS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iReg ins_pipe(pipe_slow); %} +instruct compareAndExchangeS(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate(UseZabha && UseZacas); + + match(Set res (CompareAndExchangeS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeS" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + instruct compareAndExchangeI(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ match(Set res (CompareAndExchangeI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $res = $mem, $oldval, $newval\t# (int, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeI" @@ -5560,9 +5691,7 @@ instruct compareAndExchangeL(iRegLNoSp res, indirect mem, iRegL oldval, iRegL ne %{ match(Set res (CompareAndExchangeL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $res = $mem, $oldval, $newval\t# (long, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeL" @@ -5579,11 +5708,10 @@ instruct compareAndExchangeL(iRegLNoSp res, indirect mem, iRegL oldval, iRegL ne instruct compareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN newval) %{ predicate(n->as_LoadStore()->barrier_data() == 0); + match(Set res (CompareAndExchangeN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST * 3); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $res = $mem, $oldval, $newval\t# (narrow oop, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeN" @@ -5600,11 +5728,10 @@ instruct compareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN ne instruct compareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP newval) %{ predicate(n->as_LoadStore()->barrier_data() == 0); + match(Set res (CompareAndExchangeP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg $res = $mem, $oldval, $newval\t# (ptr, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeP" @@ -5618,19 +5745,19 @@ instruct compareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP ne ins_pipe(pipe_slow); %} -instruct compareAndExchangeBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndExchangeBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (CompareAndExchangeB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST * 5); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ - "cmpxchg_acq $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeBAcq" + "cmpxchg_acq $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeBAcq_narrow" %} ins_encode %{ @@ -5642,19 +5769,39 @@ instruct compareAndExchangeBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, i ins_pipe(pipe_slow); %} -instruct compareAndExchangeSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct compareAndExchangeBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (CompareAndExchangeB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg_acq $res = $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeBAcq" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + +instruct compareAndExchangeSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (CompareAndExchangeS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST * 6); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ - "cmpxchg_acq $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeSAcq" + "cmpxchg_acq $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeSAcq_narrow" %} ins_encode %{ @@ -5666,15 +5813,33 @@ instruct compareAndExchangeSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, i ins_pipe(pipe_slow); %} +instruct compareAndExchangeSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (CompareAndExchangeS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "cmpxchg_acq $res = $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeSAcq" + %} + + ins_encode %{ + __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + instruct compareAndExchangeIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ predicate(needs_acquiring_load_reserved(n)); match(Set res (CompareAndExchangeI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $res = $mem, $oldval, $newval\t# (int, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeIAcq" @@ -5694,9 +5859,7 @@ instruct compareAndExchangeLAcq(iRegLNoSp res, indirect mem, iRegL oldval, iRegL match(Set res (CompareAndExchangeL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $res = $mem, $oldval, $newval\t# (long, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeLAcq" @@ -5716,9 +5879,7 @@ instruct compareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRegN match(Set res (CompareAndExchangeN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $res = $mem, $oldval, $newval\t# (narrow oop, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangeNAcq" @@ -5738,9 +5899,7 @@ instruct compareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRegP match(Set res (CompareAndExchangeP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 3 + ALU_COST); - - effect(TEMP_DEF res); + ins_cost(2 * VOLATILE_REF_COST); format %{ "cmpxchg_acq $res = $mem, $oldval, $newval\t# (ptr, weak) if $mem == $oldval then $mem <-- $newval, #@compareAndExchangePAcq" @@ -5754,18 +5913,20 @@ instruct compareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRegP ins_pipe(pipe_slow); %} -instruct weakCompareAndSwapB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct weakCompareAndSwapB_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ + predicate(!UseZabha || !UseZacas); + match(Set res (WeakCompareAndSwapB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 6); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval\n\t" - "# $res == 1 when success, #@weakCompareAndSwapB" + "# $res == 1 when success, #@weakCompareAndSwapB_narrow" %} ins_encode %{ @@ -5777,18 +5938,41 @@ instruct weakCompareAndSwapB(iRegINoSp res, indirect mem, iRegI_R12 oldval, iReg ins_pipe(pipe_slow); %} -instruct weakCompareAndSwapS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct weakCompareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ + predicate(UseZabha && UseZacas); + + match(Set res (WeakCompareAndSwapB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "weak_cmpxchg $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval\n\t" + "# $res == 1 when success, #@weakCompareAndSwapB" + %} + + ins_encode %{ + __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + +instruct weakCompareAndSwapS_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate(!UseZabha || !UseZacas); + match(Set res (WeakCompareAndSwapS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 7); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval\n\t" - "# $res == 1 when success, #@weakCompareAndSwapS" + "# $res == 1 when success, #@weakCompareAndSwapS_narrow" %} ins_encode %{ @@ -5800,11 +5984,32 @@ instruct weakCompareAndSwapS(iRegINoSp res, indirect mem, iRegI_R12 oldval, iReg ins_pipe(pipe_slow); %} +instruct weakCompareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate(UseZabha && UseZacas); + + match(Set res (WeakCompareAndSwapS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "weak_cmpxchg $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval\n\t" + "# $res == 1 when success, #@weakCompareAndSwapS" + %} + + ins_encode %{ + __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + instruct weakCompareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ match(Set res (WeakCompareAndSwapI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (int, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5823,7 +6028,7 @@ instruct weakCompareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL ne %{ match(Set res (WeakCompareAndSwapL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (long, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5841,9 +6046,10 @@ instruct weakCompareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL ne instruct weakCompareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN newval) %{ predicate(n->as_LoadStore()->barrier_data() == 0); + match(Set res (WeakCompareAndSwapN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (narrow oop, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5861,9 +6067,10 @@ instruct weakCompareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN ne instruct weakCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval) %{ predicate(n->as_LoadStore()->barrier_data() == 0); + match(Set res (WeakCompareAndSwapP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg $mem, $oldval, $newval\t# (ptr, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5878,20 +6085,20 @@ instruct weakCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP ne ins_pipe(pipe_slow); %} -instruct weakCompareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct weakCompareAndSwapBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (WeakCompareAndSwapB mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 6); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval\n\t" - "# $res == 1 when success, #@weakCompareAndSwapBAcq" + "# $res == 1 when success, #@weakCompareAndSwapBAcq_narrow" %} ins_encode %{ @@ -5903,20 +6110,41 @@ instruct weakCompareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, i ins_pipe(pipe_slow); %} -instruct weakCompareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, - iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +instruct weakCompareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ - predicate(needs_acquiring_load_reserved(n)); + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (WeakCompareAndSwapB mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "weak_cmpxchg_acq $mem, $oldval, $newval\t# (byte, weak) if $mem == $oldval then $mem <-- $newval\n\t" + "# $res == 1 when success, #@weakCompareAndSwapBAcq" + %} + + ins_encode %{ + __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + +instruct weakCompareAndSwapSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval, iRegI_R13 newval, + iRegINoSp tmp1, iRegINoSp tmp2, iRegINoSp tmp3, rFlagsReg cr) +%{ + predicate((!UseZabha || !UseZacas) && needs_acquiring_load_reserved(n)); match(Set res (WeakCompareAndSwapS mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 7); + ins_cost(2 * VOLATILE_REF_COST); effect(TEMP_DEF res, KILL cr, USE_KILL oldval, USE_KILL newval, TEMP tmp1, TEMP tmp2, TEMP tmp3); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval\n\t" - "# $res == 1 when success, #@weakCompareAndSwapSAcq" + "# $res == 1 when success, #@weakCompareAndSwapSAcq_narrow" %} ins_encode %{ @@ -5928,13 +6156,34 @@ instruct weakCompareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI_R12 oldval, i ins_pipe(pipe_slow); %} +instruct weakCompareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) +%{ + predicate((UseZabha && UseZacas) && needs_acquiring_load_reserved(n)); + + match(Set res (WeakCompareAndSwapS mem (Binary oldval newval))); + + ins_cost(2 * VOLATILE_REF_COST); + + format %{ + "weak_cmpxchg_acq $mem, $oldval, $newval\t# (short, weak) if $mem == $oldval then $mem <-- $newval\n\t" + "# $res == 1 when success, #@weakCompareAndSwapSAcq" + %} + + ins_encode %{ + __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, + /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + %} + + ins_pipe(pipe_slow); +%} + instruct weakCompareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval) %{ predicate(needs_acquiring_load_reserved(n)); match(Set res (WeakCompareAndSwapI mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (int, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5955,7 +6204,7 @@ instruct weakCompareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL match(Set res (WeakCompareAndSwapL mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (long, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5976,7 +6225,7 @@ instruct weakCompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN match(Set res (WeakCompareAndSwapN mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 4); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (narrow oop, weak) if $mem == $oldval then $mem <-- $newval\n\t" @@ -5997,7 +6246,7 @@ instruct weakCompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP match(Set res (WeakCompareAndSwapP mem (Binary oldval newval))); - ins_cost(LOAD_COST + STORE_COST + BRANCH_COST * 2 + ALU_COST * 2); + ins_cost(2 * VOLATILE_REF_COST); format %{ "weak_cmpxchg_acq $mem, $oldval, $newval\t# (ptr, weak) if $mem == $oldval then $mem <-- $newval\n\t" diff --git a/test/hotspot/gtest/riscv/test_assembler_riscv.cpp b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp index 45f0ed390bd..c6754eacae6 100644 --- a/test/hotspot/gtest/riscv/test_assembler_riscv.cpp +++ b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp @@ -107,6 +107,14 @@ TEST_VM(RiscV, cmov) { } } +template +bool using_narrow() { + if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) { + return !(UseZacas && UseZabha); + } + return false; +} + template class CmpxchgTester { // The functions expect arguments to be type represented, not C-ABI argument representation. @@ -126,7 +134,7 @@ class CmpxchgTester { CodeBuffer code(_bb); MacroAssembler _masm(&code); address entry = _masm.pc(); - if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) { + if (using_narrow()) { address entry = _masm.pc(); _masm.cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2, ASMSIZE, Assembler::relaxed, Assembler::relaxed, @@ -176,7 +184,7 @@ class CmpxchgTester { } TESTSIZE cmpxchg(intptr_t addr, TESTSIZE expected, TESTSIZE new_value) { - if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) { + if (using_narrow()) { return _narrow(addr, expected, new_value, /* dummy result */ 67, -1, -1, -1); } else { return _func(addr, expected, new_value, /* dummy result */ 67); @@ -197,7 +205,7 @@ template static void run_plain_cmpxchg_tests() { TESTSIZE max = std::numeric_limits::max(); TESTSIZE min = std::numeric_limits::min(); - TESTSIZE val[] = {1337, min, max}; + TESTSIZE val[] = {37, min, max}; for (int i = 0; i < 3; i++) { // Normal plain_cmpxchg_test( 0 /* variant */ , val[i] /* start value */, @@ -205,7 +213,7 @@ static void run_plain_cmpxchg_tests() { val[i] /* return */ , 42 /* end value*/, false /* boolean ret*/); plain_cmpxchg_test( 0 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, val[i] /* return */ , val[i] /* end value */, false /* boolean ret*/); plain_cmpxchg_test( 0 /* variant */ , val[i] /* start value */, @@ -213,7 +221,7 @@ static void run_plain_cmpxchg_tests() { 1 /* return */ , 42 /* end value*/, true /* boolean ret*/); plain_cmpxchg_test( 0 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, 0 /* return */ , val[i] /* end value */, true /* boolean ret*/); // result == expected register @@ -222,7 +230,7 @@ static void run_plain_cmpxchg_tests() { val[i] /* return */ , 42 /* end value*/, false /* boolean ret*/); plain_cmpxchg_test( 1 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, val[i] /* return */ , val[i] /* end value */, false /* boolean ret*/); plain_cmpxchg_test( 1 /* variant */ , val[i] /* start value */, @@ -230,7 +238,7 @@ static void run_plain_cmpxchg_tests() { 1 /* return */ , 42 /* end value*/, true /* boolean ret*/); plain_cmpxchg_test( 1 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, 0 /* return */ , val[i] /* end value */, true /* boolean ret*/); // new_value == result register @@ -239,7 +247,7 @@ static void run_plain_cmpxchg_tests() { val[i] /* return */ , 42 /* end value*/, false /* boolean ret*/); plain_cmpxchg_test( 2 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, val[i] /* return */ , val[i] /* end value */, false /* boolean ret*/); plain_cmpxchg_test( 2 /* variant */ , val[i] /* start value */, @@ -247,7 +255,7 @@ static void run_plain_cmpxchg_tests() { 1 /* return */ , 42 /* end value*/, true /* boolean ret*/); plain_cmpxchg_test( 2 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, 0 /* return */ , val[i] /* end value */, true /* boolean ret*/); // expected == new_value register @@ -256,7 +264,7 @@ static void run_plain_cmpxchg_tests() { val[i] /* return */ , val[i] /* end value */, false /* boolean ret*/); plain_cmpxchg_test( 3 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, val[i] /* return */ , val[i] /* end value */, false /* boolean ret*/); plain_cmpxchg_test( 3 /* variant */ , val[i] /* start value */, @@ -264,7 +272,7 @@ static void run_plain_cmpxchg_tests() { 1 /* return */ , val[i] /* end value */, true /* boolean ret*/); plain_cmpxchg_test( 3 /* variant */ , val[i] /* start value */, - 1336 /* expected */, 42 /* new value */, + 36 /* expected */, 42 /* new value */, 0 /* return */ , val[i] /* end value */, true /* boolean ret*/); } } @@ -308,6 +316,18 @@ TEST_VM(RiscV, cmpxchg_uint32_maybe_zacas) { } } +TEST_VM(RiscV, cmpxchg_int16_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { + run_plain_cmpxchg_tests(); + } +} + +TEST_VM(RiscV, cmpxchg_int8_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { + run_plain_cmpxchg_tests(); + } +} + template static void run_narrow_cmpxchg_tests() { CmpxchgTester cmpxchg(0, false); @@ -320,7 +340,6 @@ static void run_narrow_cmpxchg_tests() { TESTSIZE val[] = {121, min, max}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 7; j++) { - // printf("%lu %lX\n", (uint64_t)val[i], (uint64_t)val[i]); memset(data, -1, sizeof(data)); data[i] = val[i]; ret = cmpxchg.cmpxchg((intptr_t)&data[i], val[i], 42); @@ -345,29 +364,35 @@ static void run_narrow_cmpxchg_tests() { } } -TEST_VM(RiscV, cmpxchg_int16_lr_sc) { +TEST_VM(RiscV, cmpxchg_narrow_int16_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_narrow_cmpxchg_tests(); UseZacas = zacas; } -TEST_VM(RiscV, cmpxchg_int16_maybe_zacas) { +TEST_VM(RiscV, cmpxchg_narrow_int16_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; run_narrow_cmpxchg_tests(); + UseZabha = zabha; } } -TEST_VM(RiscV, cmpxchg_int8_lr_sc) { +TEST_VM(RiscV, cmpxchg_narrow_int8_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_narrow_cmpxchg_tests(); UseZacas = zacas; } -TEST_VM(RiscV, cmpxchg_int8_maybe_zacas) { +TEST_VM(RiscV, cmpxchg_narrow_int8_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; run_narrow_cmpxchg_tests(); + UseZabha = zabha; } } @@ -486,7 +511,7 @@ TEST_VM(RiscV, cmpxchg_uint32_concurrent_maybe_zacas) { } } -TEST_VM(RiscV, cmpxchg_int16_concurrent_lr_sc) { +TEST_VM(RiscV, cmpxchg_narrow_int16_concurrent_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_concurrent_cmpxchg_tests(); @@ -494,14 +519,17 @@ TEST_VM(RiscV, cmpxchg_int16_concurrent_lr_sc) { UseZacas = zacas; } -TEST_VM(RiscV, cmpxchg_int16_concurrent_maybe_zacas) { +TEST_VM(RiscV, cmpxchg_narrow_int16_concurrent_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; run_concurrent_cmpxchg_tests(); run_concurrent_alt_cmpxchg_tests(); + UseZabha = zabha; } } -TEST_VM(RiscV, cmpxchg_int8_concurrent_lr_sc) { +TEST_VM(RiscV, cmpxchg_narrow_int8_concurrent_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_concurrent_cmpxchg_tests(); @@ -509,8 +537,25 @@ TEST_VM(RiscV, cmpxchg_int8_concurrent_lr_sc) { UseZacas = zacas; } -TEST_VM(RiscV, cmpxchg_int8_concurrent_maybe_zacas) { +TEST_VM(RiscV, cmpxchg_narrow_int8_concurrent_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; + run_concurrent_cmpxchg_tests(); + run_concurrent_alt_cmpxchg_tests(); + UseZabha = zabha; + } +} + +TEST_VM(RiscV, cmpxchg_int16_concurrent_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { + run_concurrent_cmpxchg_tests(); + run_concurrent_alt_cmpxchg_tests(); + } +} + +TEST_VM(RiscV, cmpxchg_int8_concurrent_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { run_concurrent_cmpxchg_tests(); run_concurrent_alt_cmpxchg_tests(); } @@ -534,7 +579,7 @@ class WeakCmpxchgTester { _bb = BufferBlob::create("riscvTest", 128); CodeBuffer code(_bb); MacroAssembler _masm(&code); - if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) { + if (using_narrow()) { address entry = _masm.pc(); _masm.weak_cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg2, ASMSIZE, Assembler::relaxed, Assembler::relaxed, @@ -554,7 +599,7 @@ class WeakCmpxchgTester { } TESTSIZE weak_cmpxchg(intptr_t addr, TESTSIZE expected, TESTSIZE new_value) { - if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) { + if (using_narrow()) { return _narrow_weak(addr, expected, new_value, /* dummy result */ 67, -1, -1, -1); } else { return _weak(addr, expected, new_value, /* dummy result */ 67); @@ -624,28 +669,46 @@ TEST_VM(RiscV, weak_cmpxchg_uint32_maybe_zacas) { } } -TEST_VM(RiscV, weak_cmpxchg_int16_lr_sc) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int16_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_weak_cmpxchg_tests(); UseZacas = zacas; } -TEST_VM(RiscV, weak_cmpxchg_int8_lr_sc) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int8_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_weak_cmpxchg_tests(); UseZacas = zacas; } -TEST_VM(RiscV, weak_cmpxchg_int16_maybe_zacas) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int16_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; + run_weak_cmpxchg_tests(); + UseZabha = zabha; + } +} + +TEST_VM(RiscV, weak_cmpxchg_narrow_int8_maybe_zacas) { + if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; + run_weak_cmpxchg_tests(); + UseZabha = zabha; + } +} + +TEST_VM(RiscV, weak_cmpxchg_int16_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { run_weak_cmpxchg_tests(); } } -TEST_VM(RiscV, weak_cmpxchg_int8_maybe_zacas) { - if (UseZacas) { +TEST_VM(RiscV, weak_cmpxchg_int8_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { run_weak_cmpxchg_tests(); } } @@ -724,7 +787,7 @@ TEST_VM(RiscV, weak_cmpxchg_int32_concurrent_maybe_zacas) { } } -TEST_VM(RiscV, weak_cmpxchg_int16_concurrent_lr_sc) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int16_concurrent_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_concurrent_weak_cmpxchg_tests(); @@ -732,14 +795,17 @@ TEST_VM(RiscV, weak_cmpxchg_int16_concurrent_lr_sc) { UseZacas = zacas; } -TEST_VM(RiscV, weak_cmpxchg_int16_concurrent_maybe_zacas) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int16_concurrent_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; run_concurrent_weak_cmpxchg_tests(); run_concurrent_alt_weak_cmpxchg_tests(); + UseZabha = zabha; } } -TEST_VM(RiscV, weak_cmpxchg_int8_concurrent_lr_sc) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int8_concurrent_lr_sc) { bool zacas = UseZacas; UseZacas = false; run_concurrent_weak_cmpxchg_tests(); @@ -747,8 +813,25 @@ TEST_VM(RiscV, weak_cmpxchg_int8_concurrent_lr_sc) { UseZacas = zacas; } -TEST_VM(RiscV, weak_cmpxchg_int8_concurrent_maybe_zacas) { +TEST_VM(RiscV, weak_cmpxchg_narrow_int8_concurrent_maybe_zacas) { if (UseZacas) { + bool zabha = UseZabha; + UseZabha = false; + run_concurrent_weak_cmpxchg_tests(); + run_concurrent_alt_weak_cmpxchg_tests(); + UseZabha = zabha; + } +} + +TEST_VM(RiscV, weak_cmpxchg_int16_concurrent_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { + run_concurrent_weak_cmpxchg_tests(); + run_concurrent_alt_weak_cmpxchg_tests(); + } +} + +TEST_VM(RiscV, weak_cmpxchg_int8_concurrent_maybe_zacas_zabha) { + if (UseZacas && UseZabha) { run_concurrent_weak_cmpxchg_tests(); run_concurrent_alt_weak_cmpxchg_tests(); } From 09ec4de74d495560ffb9ec529df7ec818c1d617c Mon Sep 17 00:00:00 2001 From: Archie Cobbs Date: Wed, 4 Jun 2025 12:56:18 +0000 Subject: [PATCH 037/143] 8358066: Non-ascii package names gives compilation error "import requires canonical name" Reviewed-by: jlahoda, naoto --- .../com/sun/tools/javac/util/Convert.java | 8 +-- .../javac/nametable/TestUtfNumChars.java | 70 +++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 test/langtools/tools/javac/nametable/TestUtfNumChars.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java index 16127224dc8..7d5f878b676 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -224,10 +224,8 @@ public class Convert { public static int utfNumChars(byte[] buf, int off, int len) { int numChars = 0; while (len-- > 0) { - int byte1 = buf[off++]; - if (byte1 < 0) - len -= ((byte1 & 0xe0) == 0xc0) ? 1 : 2; - numChars++; + if ((buf[off++] & 0xc0) != 0x80) + numChars++; } return numChars; } diff --git a/test/langtools/tools/javac/nametable/TestUtfNumChars.java b/test/langtools/tools/javac/nametable/TestUtfNumChars.java new file mode 100644 index 00000000000..de9530ec4c4 --- /dev/null +++ b/test/langtools/tools/javac/nametable/TestUtfNumChars.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8358066 + * @summary Test for bug in Convert.utfNumChars() + * @modules jdk.compiler/com.sun.tools.javac.util + * @run main TestUtfNumChars + */ + +import com.sun.tools.javac.util.Convert; + +import java.util.function.IntPredicate; +import java.util.stream.IntStream; + +public class TestUtfNumChars { + + public static void main(String[] args) { + + // This is the string "ab«cd≤ef🟢gh" + String s = "ab\u00ABcd\u2264ef\ud83d\udd34gh"; + + // This is its modified UTF-8 encoding + byte[] utf8 = Convert.string2utf(s); // UTF-8: 61 62 c2 ab 63 64 e2 89 a4 65 66 ed a0 bd ed b4 b4 67 68 + // Bytes: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 + // Chars: 00 01 02 .. 03 04 05 .. .. 06 07 08 .. .. 09 .. .. 10 11 + + // These are the offsets in "utf8" marking the boundaries of encoded Java charcters + int[] offsets = new int[] { + 0, 1, 2, 4, 5, 6, 9, 10, 11, 14, 17, 18 + }; + IntPredicate boundary = off -> off == utf8.length || IntStream.of(offsets).anyMatch(off2 -> off2 == off); + + // Check Convert.utfNumChars() on every subsequence + for (int i = 0; i < offsets.length; i++) { + int i_off = offsets[i]; + if (!boundary.test(i_off)) + continue; + for (int j = i; j < offsets.length; j++) { + int j_off = offsets[j]; + if (!boundary.test(j_off)) + continue; + int nchars = Convert.utfNumChars(utf8, i_off, j_off - i_off); + if (nchars != j - i) + throw new AssertionError(String.format("nchars %d != %d for [%d, %d)", nchars, j - i, i_off, j_off)); + } + } + } +} From 248341d372ba9c1031729a65eb10d8def52de641 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Wed, 4 Jun 2025 13:16:24 +0000 Subject: [PATCH 038/143] 8344942: Template-Based Testing Framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tobias Hartmann Co-authored-by: Tobias Holenstein Co-authored-by: Theo Weidmann Co-authored-by: Roberto Castañeda Lozano Co-authored-by: Christian Hagedorn Co-authored-by: Manuel Hässig Reviewed-by: chagedorn, mhaessig, rcastanedalo --- .../lib/template_framework/AddNameToken.java | 26 + .../compiler/lib/template_framework/Code.java | 51 + .../lib/template_framework/CodeFrame.java | 156 ++ .../lib/template_framework/DataName.java | 227 ++ .../compiler/lib/template_framework/Hook.java | 100 + .../template_framework/HookAnchorToken.java | 28 + .../template_framework/HookInsertToken.java | 26 + .../compiler/lib/template_framework/Name.java | 50 + .../lib/template_framework/NameSet.java | 151 ++ .../lib/template_framework/NothingToken.java | 26 + .../compiler/lib/template_framework/README.md | 12 + .../lib/template_framework/Renderer.java | 437 ++++ .../template_framework/RendererException.java | 35 + .../lib/template_framework/StringToken.java | 26 + .../template_framework/StructuralName.java | 200 ++ .../lib/template_framework/Template.java | 844 ++++++ .../template_framework/TemplateBinding.java | 70 + .../lib/template_framework/TemplateBody.java | 34 + .../lib/template_framework/TemplateFrame.java | 99 + .../lib/template_framework/TemplateToken.java | 169 ++ .../lib/template_framework/Token.java | 78 + .../lib/template_framework/library/Hooks.java | 46 + .../examples/TestAdvanced.java | 162 ++ .../examples/TestSimple.java | 78 + .../examples/TestTutorial.java | 1227 +++++++++ .../template_framework/tests/TestFormat.java | 111 + .../tests/TestTemplate.java | 2253 +++++++++++++++++ 27 files changed, 6722 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Code.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/DataName.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Hook.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Name.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/README.md create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/RendererException.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/StringToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Template.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/TemplateBinding.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/Token.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/Hooks.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java new file mode 100644 index 00000000000..4f1f7e569bf --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java @@ -0,0 +1,26 @@ +/* + * 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 compiler.lib.template_framework; + +record AddNameToken(Name name) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Code.java b/test/hotspot/jtreg/compiler/lib/template_framework/Code.java new file mode 100644 index 00000000000..5806460ac88 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Code.java @@ -0,0 +1,51 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; + +/** + * This class collects code, i.e. {@link String}s or {@link List}s of {@link String}s. + * All the {@link String}s are later collected in a {@link StringBuilder}. If we used a {@link StringBuilder} + * directly to collect the {@link String}s, we could not as easily insert code at an "earlier" position, i.e. + * reaching out to a {@link Hook#anchor}. + */ +sealed interface Code permits Code.Token, Code.CodeList { + + record Token(String s) implements Code { + @Override + public void renderTo(StringBuilder builder) { + builder.append(s); + } + } + + record CodeList(List list) implements Code { + @Override + public void renderTo(StringBuilder builder) { + list.forEach(code -> code.renderTo(builder)); + } + } + + void renderTo(StringBuilder builder); +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java new file mode 100644 index 00000000000..5c4ff55614f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java @@ -0,0 +1,156 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.HashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'} + * as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/ + * {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that + * location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner + * {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame. + * + *

+ * On the other hand, each {@link TemplateFrame} represents the frame (or scope) of exactly one use of a + * Template. + * + *

+ * For simple Template nesting, the {@link CodeFrame}s and {@link TemplateFrame}s overlap exactly. + * However, when using {@link Hook#insert}, we simply nest {@link TemplateFrame}s, going further "in", + * but we jump to an outer {@link CodeFrame}, ensuring that we insert {@link Code} at the outer frame, + * and operating on the names of the outer frame. Once the {@link Hook#insert}ion is complete, we jump + * back to the caller {@link TemplateFrame} and {@link CodeFrame}. + */ +class CodeFrame { + public final CodeFrame parent; + private final List codeList = new ArrayList<>(); + private final Map hookCodeLists = new HashMap<>(); + + /** + * The {@link NameSet} is used for variable and fields etc. + */ + private final NameSet names; + + private CodeFrame(CodeFrame parent, boolean isTransparentForNames) { + this.parent = parent; + if (parent == null) { + // NameSet without any parent. + this.names = new NameSet(null); + } else if (isTransparentForNames) { + // We use the same NameSet as the parent - makes it transparent. + this.names = parent.names; + } else { + // New NameSet, to make sure we have a nested scope for the names. + this.names = new NameSet(parent.names); + } + } + + /** + * Creates a base frame, which has no {@link #parent}. + */ + public static CodeFrame makeBase() { + return new CodeFrame(null, false); + } + + /** + * Creates a normal frame, which has a {@link #parent} and which defines an inner + * {@link NameSet}, for the names that are generated inside this frame. Once this + * frame is exited, the name from inside this frame are not available anymore. + */ + public static CodeFrame make(CodeFrame parent) { + return new CodeFrame(parent, false); + } + + /** + * Creates a special frame, which has a {@link #parent} but uses the {@link NameSet} + * from the parent frame, allowing {@link Template#addDataName}/ + * {@link Template#addStructuralName} to persist in the outer frame when the current frame + * is exited. This is necessary for {@link Hook#insert}, where we would possibly want to + * make field or variable definitions during the insertion that are not just local to the + * insertion but affect the {@link CodeFrame} that we {@link Hook#anchor} earlier and are + * now {@link Hook#insert}ing into. + */ + public static CodeFrame makeTransparentForNames(CodeFrame parent) { + return new CodeFrame(parent, true); + } + + void addString(String s) { + codeList.add(new Code.Token(s)); + } + + void addCode(Code code) { + codeList.add(code); + } + + void addHook(Hook hook) { + if (hasHook(hook)) { + // This should never happen, as we add a dedicated CodeFrame for each hook. + throw new RuntimeException("Internal error: Duplicate Hook in CodeFrame: " + hook.name()); + } + hookCodeLists.put(hook, new Code.CodeList(new ArrayList<>())); + } + + private boolean hasHook(Hook hook) { + return hookCodeLists.containsKey(hook); + } + + CodeFrame codeFrameForHook(Hook hook) { + CodeFrame current = this; + while (current != null) { + if (current.hasHook(hook)) { + return current; + } + current = current.parent; + } + return null; + } + + void addName(Name name) { + names.add(name); + } + + Name sampleName(NameSet.Predicate predicate) { + return names.sample(predicate); + } + + int countNames(NameSet.Predicate predicate) { + return names.count(predicate); + } + + boolean hasAnyNames(NameSet.Predicate predicate) { + return names.hasAny(predicate); + } + + List listNames(NameSet.Predicate predicate) { + return names.toList(predicate); + } + + Code getCode() { + return new Code.CodeList(codeList); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java new file mode 100644 index 00000000000..f45a4db8a1e --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java @@ -0,0 +1,227 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; + +/** + * {@link DataName}s represent things like fields and local variables, and can be added to the local + * scope with {@link Template#addDataName}, and accessed with {@link Template#dataNames}, to + * count, list or even sample random {@link DataName}s. Every {@link DataName} has a {@link DataName.Type}, + * so that sampling can be restricted to these types. + * + *

+ * For method and class names and alike, there are the analogous {@link StructuralName}s. + * + * @param name The {@link String} name used in code. + * @param type The type of the {@link DataName}. + * @param mutable Defines if the {@link DataName} is considered mutable or immutable. + * @param weight The weight of the {@link DataName}, it corresponds to the probability of choosing this + * {@link DataName} when sampling later on. + */ +public record DataName(String name, DataName.Type type, boolean mutable, int weight) implements Name { + + /** + * {@link Mutability} defines the possible states of {@link DataName}s, or the + * desired state when filtering. + */ + public enum Mutability { + /** + * Used for mutable fields and variables, i.e. writing is allowed. + */ + MUTABLE, + /** + * Used for immutable fields and variables, i.e. writing is not allowed, + * for example because the field or variable is final. + */ + IMMUTABLE, + /** + * When filtering, we sometimes want to indicate that we accept + * mutable and immutable fields and variables, for example when + * we are only reading, the mutability state does not matter. + */ + MUTABLE_OR_IMMUTABLE + } + + /** + * Creates a new {@link DataName}. + */ + public DataName { + } + + /** + * The interface for the type of a {@link DataName}. + */ + public interface Type extends Name.Type { + /** + * The name of the type, that can be used in code. + * + * @return The {@link String} representation of the type, that can be used in code. + */ + String name(); + + /** + * Defines the subtype relationship with other types, which is used to filter {@link DataName}s + * in {@link FilteredSet#exactOf}, {@link FilteredSet#subtypeOf}, and {@link FilteredSet#supertypeOf}. + * + * @param other The other type, where we check if it is the supertype of {@code 'this'}. + * @return If {@code 'this'} is a subtype of {@code 'other'}. + */ + boolean isSubtypeOf(DataName.Type other); + } + + /** + * The {@link FilteredSet} represents a filtered set of {@link DataName}s in the current scope. + * It can be obtained with {@link Template#dataNames}. It can be used to count the + * available {@link DataName}s, or sample a random {@link DataName} according to the + * weights of the {@link DataName}s in the filtered set. + * Note: The {@link FilteredSet} is only a filtered view on the set of {@link DataName}s, + * and may return different results in different contexts. + */ + public static final class FilteredSet { + private final Mutability mutability; + private final DataName.Type subtype; + private final DataName.Type supertype; + + FilteredSet(Mutability mutability, DataName.Type subtype, DataName.Type supertype) { + this.mutability = mutability; + this.subtype = subtype; + this.supertype = supertype; + } + + FilteredSet(Mutability mutability) { + this(mutability, null, null); + } + + NameSet.Predicate predicate() { + if (subtype == null && supertype == null) { + throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + } + return (Name name) -> { + if (!(name instanceof DataName dataName)) { return false; } + if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; } + if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; } + if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; } + return true; + }; + } + + /** + * Create a {@link FilteredSet}, where all {@link DataName}s must be subtypes of {@code type}. + * + * @param type The type of which all {@link DataName}s must be subtypes of. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #subtypeOf} or {@link #exactOf}. + */ + public FilteredSet subtypeOf(DataName.Type type) { + if (subtype != null) { + throw new UnsupportedOperationException("Cannot constrain to subtype " + type + ", is already constrained: " + subtype); + } + return new FilteredSet(mutability, type, supertype); + } + + /** + * Create a {@link FilteredSet}, where all {@link DataName}s must be supertypes of {@code type}. + * + * @param type The type of which all {@link DataName}s must be supertype of. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #supertypeOf} or {@link #exactOf}. + */ + public FilteredSet supertypeOf(DataName.Type type) { + if (supertype != null) { + throw new UnsupportedOperationException("Cannot constrain to supertype " + type + ", is already constrained: " + supertype); + } + return new FilteredSet(mutability, subtype, type); + } + + /** + * Create a {@link FilteredSet}, where all {@link DataName}s must be of exact {@code type}, + * hence it must be both subtype and supertype thereof. + * + * @param type The type of which all {@link DataName}s must be. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public FilteredSet exactOf(DataName.Type type) { + return subtypeOf(type).supertypeOf(type); + } + + /** + * Samples a random {@link DataName} from the filtered set, according to the weights + * of the contained {@link DataName}s. + * + * @return The sampled {@link DataName}. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + * @throws RendererException If the set was empty. + */ + public DataName sample() { + DataName n = (DataName)Renderer.getCurrent().sampleName(predicate()); + if (n == null) { + String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")"; + throw new RendererException("No variable: " + mutability + msg1 + msg2 + "."); + } + return n; + } + + /** + * Counts the number of {@link DataName}s in the filtered set. + * + * @return The number of {@link DataName}s in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public int count() { + return Renderer.getCurrent().countNames(predicate()); + } + + /** + * Checks if there are any {@link DataName}s in the filtered set. + * + * @return Returns {@code true} iff there is at least one {@link DataName} in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public boolean hasAny() { + return Renderer.getCurrent().hasAnyNames(predicate()); + } + + /** + * Collects all {@link DataName}s in the filtered set. + * + * @return A {@link List} of all {@link DataName}s in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public List toList() { + List list = Renderer.getCurrent().listNames(predicate()); + return list.stream().map(n -> (DataName)n).toList(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java new file mode 100644 index 00000000000..48f7852d509 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java @@ -0,0 +1,100 @@ +/* + * 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 compiler.lib.template_framework; + +/** + * {@link Hook}s can be {@link #anchor}ed for a certain scope in a Template, and all nested + * Templates in this scope, and then from within this scope, any Template can + * {@link #insert} code to where the {@link Hook} was {@link #anchor}ed. This can be useful to reach + * "back" or to some outer scope, e.g. while generating code for a method, one can reach out + * to the class scope to insert fields. + * + *

+ * Example: + * {@snippet lang=java : + * var myHook = new Hook("MyHook"); + * + * var template1 = Template.make("name", (String name) -> body( + * """ + * public static int #name = 42; + * """ + * )); + * + * var template2 = Template.make(() -> body( + * """ + * public class Test { + * """, + * // Anchor the hook here. + * myHook.anchor( + * """ + * public static void main(String[] args) { + * System.out.println("$field: " + $field) + * """, + * // Reach out to where the hook was anchored, and insert the code of template1. + * myHook.insert(template1.asToken($("field"))), + * """ + * } + * """ + * ), + * """ + * } + * """ + * )); + * } + * + * @param name The name of the Hook, for debugging purposes only. + */ +public record Hook(String name) { + /** + * Anchor this {@link Hook} for the scope of the provided {@code 'tokens'}. + * From anywhere inside this scope, even in nested Templates, code can be + * {@link #insert}ed back to the location where this {@link Hook} was {@link #anchor}ed. + * + * @param tokens A list of tokens, which have the same restrictions as {@link Template#body}. + * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. + */ + public Token anchor(Object... tokens) { + return new HookAnchorToken(this, Token.parse(tokens)); + } + + /** + * Inserts a {@link TemplateToken} to the innermost location where this {@link Hook} was {@link #anchor}ed. + * This could be in the same Template, or one nested further out. + * + * @param templateToken The Template with applied arguments to be inserted at the {@link Hook}. + * @return The {@link Token} which when used inside a {@link Template#body} performs the code insertion into the {@link Hook}. + */ + public Token insert(TemplateToken templateToken) { + return new HookInsertToken(this, templateToken); + } + + /** + * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * + * @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + */ + public boolean isAnchored() { + return Renderer.getCurrent().isAnchored(this); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java new file mode 100644 index 00000000000..b025c5ff041 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java @@ -0,0 +1,28 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; + +record HookAnchorToken(Hook hook, List tokens) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java new file mode 100644 index 00000000000..de8b60bbf24 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java @@ -0,0 +1,26 @@ +/* + * 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 compiler.lib.template_framework; + +record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Name.java b/test/hotspot/jtreg/compiler/lib/template_framework/Name.java new file mode 100644 index 00000000000..b969ecaa13a --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Name.java @@ -0,0 +1,50 @@ +/* + * 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 compiler.lib.template_framework; + +sealed interface Name permits DataName, StructuralName { + /** + * The name of the name, that can be used in code. + * + * @return The {@link String} name of the name, that can be used in code. + */ + String name(); + + /** + * The type of the name, allowing for filtering by type. + * + * @return The type of the name. + */ + Type type(); + + /** + * The weight of the name, corresponds to the probability of + * choosing this name when sampling. + * + * @return The weight of the name. + */ + int weight(); + + interface Type {} +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java new file mode 100644 index 00000000000..ef79c33d48a --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java @@ -0,0 +1,151 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Optional; + +import jdk.test.lib.Utils; + +/** + * The {@link NameSet} defines a set of {@link Name}s (e.g. fields or variable names). They extend the + * set of the {@code 'parent'} set. + */ +class NameSet { + private static final Random RANDOM = Utils.getRandomInstance(); + + private final NameSet parent; + private final List children = new ArrayList<>(); + private final List names = new ArrayList<>(); + + interface Predicate { + boolean check(Name type); + } + + NameSet(NameSet parent) { + this.parent = parent; + if (parent != null) { parent.registerChild(this); } + } + + void registerChild(NameSet child) { + children.add(child); + } + + private long weight(Predicate predicate) { + long w = names.stream().filter(predicate::check).mapToInt(Name::weight).sum(); + if (parent != null) { w += parent.weight(predicate); } + return w; + } + + public int count(Predicate predicate) { + int c = (int)names.stream().filter(predicate::check).count(); + if (parent != null) { c += parent.count(predicate); } + return c; + } + + public boolean hasAny(Predicate predicate) { + return names.stream().anyMatch(predicate::check) || + (parent != null && parent.hasAny(predicate)); + } + + public List toList(Predicate predicate) { + List list = (parent != null) ? parent.toList(predicate) + : new ArrayList<>(); + list.addAll(names.stream().filter(predicate::check).toList()); + return list; + } + + /** + * Randomly sample a name from this set or a parent set, restricted to the predicate. + */ + public Name sample(Predicate predicate) { + long w = weight(predicate); + if (w <= 0) { + // Negative weight should never happen, as all names have positive weight. + if (w < 0) { + throw new RuntimeException("Negative weight not allowed: " + w); + } + // If the weight is zero, there is no matching Name available. + // Return null, and let the caller handle the situation, e.g. + // throw an exception. + return null; + } + + long r = RANDOM.nextLong(w); + return sample(predicate, r); + } + + private Name sample(Predicate predicate, long r) { + for (var name : names) { + if (predicate.check(name)) { + r -= name.weight(); + if (r < 0) { return name; } + } + } + return parent.sample(predicate, r); + } + + private Name findLocal(String name) { + Optional opt = names.stream().filter(n -> n.name().equals(name)).findAny(); + return opt.orElse(null); + } + + private Name findParents(String name) { + if (parent == null) { return null; } + Name n = parent.findLocal(name); + if (n != null) { return n; } + return parent.findParents(name); + } + + private Name findChildren(String name) { + for (NameSet child : children) { + Name n1 = child.findLocal(name); + if (n1 != null) { return n1; } + Name n2 = child.findChildren(name); + if (n2 != null) { return n2; } + } + return null; + } + + private Name find(String name) { + Name n1 = findLocal(name); + if (n1 != null) { return n1; } + Name n2 = findParents(name); + if (n2 != null) { return n2; } + return findChildren(name); + } + + /** + * Add a variable of a specified type to the set. + */ + public void add(Name name) { + Name other = find(name.name()); + if (other != null) { + throw new RendererException("Duplicate name: " + name + ", previously: " + other); + } + names.add(name); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java new file mode 100644 index 00000000000..540eaf1e14c --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java @@ -0,0 +1,26 @@ +/* + * 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 compiler.lib.template_framework; + +record NothingToken() implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/README.md b/test/hotspot/jtreg/compiler/lib/template_framework/README.md new file mode 100644 index 00000000000..bc09d34b928 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/README.md @@ -0,0 +1,12 @@ +# Template Framework +The Template Framework allows the generation of code with Templates. The goal is that these Templates are easy to write, and allow regression tests to cover a larger scope, and to make template based fuzzing easy to extend. + +We want to make it easy to generate variants of tests. Often, we would like to have a set of tests, corresponding to a set of types, a set of operators, a set of constants, etc. Writing all the tests by hand is cumbersome or even impossible. When generating such tests with scripts, it would be preferable if the code generation happens automatically, and the generator script was checked into the code base. Code generation can go beyond simple regression tests, and one might want to generate random code from a list of possible templates, to fuzz individual Java features and compiler optimizations. + +The Template Framework provides a facility to generate code with Templates. Templates are essentially a list of tokens that are concatenated (i.e. rendered) to a String. The Templates can have "holes", which are filled (replaced) by different values at each Template instantiation. For example, these "holes" can be filled with different types, operators or constants. Templates can also be nested, allowing a modular use of Templates. + +Detailed documentation can be found in [Template.java](./Template.java). + +The Template Framework only generates code in the form of a String. This code can then be compiled and executed, for example with the help of the [Compile Framework](../compile_framework/README.md). + +The basic functionalities of the Template Framework are described in the [Template Interface](./Template.java), together with some examples. More examples can be found in [TestSimple.java](../../../testlibrary_tests/template_framework/examples/TestSimple.java), [TestAdvanced.java](../../../testlibrary_tests/template_framework/examples/TestAdvanced.java) and [TestTutorial.java](../../../testlibrary_tests/template_framework/examples/TestTutorial.java). diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java new file mode 100644 index 00000000000..14adfc81d3f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java @@ -0,0 +1,437 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The {@link Renderer} class renders a tokenized {@link Template} in the form of a {@link TemplateToken}. + * It also keeps track of the states during a nested Template rendering. There can only be a single + * {@link Renderer} active at any point, since there are static methods that reference + * {@link Renderer#getCurrent}. + * + *

+ * The {@link Renderer} instance keeps track of the current frames. + * + * @see TemplateFrame + * @see CodeFrame + */ +final class Renderer { + private static final String NAME_CHARACTERS = "[a-zA-Z_][a-zA-Z0-9_]*"; + private static final Pattern NAME_PATTERN = Pattern.compile( + // We are parsing patterns: + // #name + // #{name} + // $name + // ${name} + // But the "#" or "$" have already been removed, and the String + // starts at the character after that. + // The pattern must be at the beginning of the String part. + "^" + + // We either have "name" or "{name}" + "(?:" + // non-capturing group for the OR + // capturing group for "name" + "(" + NAME_CHARACTERS + ")" + + "|" + // OR + // We want to trim off the brackets, so have + // another non-capturing group. + "(?:\\{" + + // capturing group for "name" inside "{name}" + "(" + NAME_CHARACTERS + ")" + + "\\})" + + ")"); + private static final Pattern NAME_CHARACTERS_PATTERN = Pattern.compile("^" + NAME_CHARACTERS + "$"); + + static boolean isValidHashtagOrDollarName(String name) { + return NAME_CHARACTERS_PATTERN.matcher(name).find(); + } + + /** + * There can be at most one Renderer instance at any time. + * + *

+ * When using nested templates, the user of the Template Framework may be tempted to first render + * the nested template to a {@link String}, and then use this {@link String} as a token in an outer + * {@link Template#body}. This would be a bad pattern: the outer and nested {@link Template} would + * be rendered separately, and could not interact. For example, the nested {@link Template} would + * not have access to the scopes of the outer {@link Template}. The inner {@link Template} could + * not access {@link Name}s and {@link Hook}s from the outer {@link Template}. The user might assume + * that the inner {@link Template} has access to the outer {@link Template}, but they would actually + * be separated. This could lead to unexpected behavior or even bugs. + * + *

+ * Instead, the user should create a {@link TemplateToken} from the inner {@link Template}, and + * use that {@link TemplateToken} in the {@link Template#body} of the outer {@link Template}. + * This way, the inner and outer {@link Template}s get rendered together, and the inner {@link Template} + * has access to the {@link Name}s and {@link Hook}s of the outer {@link Template}. + * + *

+ * The {@link Renderer} instance exists during the whole rendering process. Should the user ever + * attempt to render a nested {@link Template} to a {@link String}, we would detect that there is + * already a {@link Renderer} instance for the outer {@link Template}, and throw a {@link RendererException}. + */ + private static Renderer renderer = null; + + private int nextTemplateFrameId; + private final TemplateFrame baseTemplateFrame; + private TemplateFrame currentTemplateFrame; + private final CodeFrame baseCodeFrame; + private CodeFrame currentCodeFrame; + + // We do not want any other instances, so we keep it private. + private Renderer(float fuel) { + nextTemplateFrameId = 0; + baseTemplateFrame = TemplateFrame.makeBase(nextTemplateFrameId++, fuel); + currentTemplateFrame = baseTemplateFrame; + baseCodeFrame = CodeFrame.makeBase(); + currentCodeFrame = baseCodeFrame; + } + + static Renderer getCurrent() { + if (renderer == null) { + throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering."); + } + return renderer; + } + + static String render(TemplateToken templateToken) { + return render(templateToken, Template.DEFAULT_FUEL); + } + + static String render(TemplateToken templateToken, float fuel) { + // Check nobody else is using the Renderer. + if (renderer != null) { + throw new RendererException("Nested render not allowed. Please only use 'asToken' inside Templates, and call 'render' only once at the end."); + } + try { + renderer = new Renderer(fuel); + renderer.renderTemplateToken(templateToken); + renderer.checkFrameConsistencyAfterRendering(); + return renderer.collectCode(); + } finally { + // Release the Renderer. + renderer = null; + } + } + + private void checkFrameConsistencyAfterRendering() { + // Ensure CodeFrame consistency. + if (baseCodeFrame != currentCodeFrame) { + throw new RuntimeException("Internal error: Renderer did not end up at base CodeFrame."); + } + // Ensure TemplateFrame consistency. + if (baseTemplateFrame != currentTemplateFrame) { + throw new RuntimeException("Internal error: Renderer did not end up at base TemplateFrame."); + } + } + + private String collectCode() { + StringBuilder builder = new StringBuilder(); + baseCodeFrame.getCode().renderTo(builder); + return builder.toString(); + } + + String $(String name) { + return currentTemplateFrame.$(name); + } + + void addHashtagReplacement(String key, Object value) { + currentTemplateFrame.addHashtagReplacement(key, format(value)); + } + + private String getHashtagReplacement(String key) { + return currentTemplateFrame.getHashtagReplacement(key); + } + + float fuel() { + return currentTemplateFrame.fuel; + } + + void setFuelCost(float fuelCost) { + currentTemplateFrame.setFuelCost(fuelCost); + } + + Name sampleName(NameSet.Predicate predicate) { + return currentCodeFrame.sampleName(predicate); + } + + int countNames(NameSet.Predicate predicate) { + return currentCodeFrame.countNames(predicate); + } + + boolean hasAnyNames(NameSet.Predicate predicate) { + return currentCodeFrame.hasAnyNames(predicate); + } + + List listNames(NameSet.Predicate predicate) { + return currentCodeFrame.listNames(predicate); + } + + /** + * Formats values to {@link String} with the goal of using them in Java code. + * By default, we use the overrides of {@link Object#toString}. + * But for some boxed primitives we need to create a special formatting. + */ + static String format(Object value) { + return switch (value) { + case String s -> s; + case Integer i -> i.toString(); + // We need to append the "L" so that the values are not interpreted as ints, + // and then javac might complain that the values are too large for an int. + case Long l -> l.toString() + "L"; + // Some Float and Double values like Infinity and NaN need a special representation. + case Float f -> formatFloat(f); + case Double d -> formatDouble(d); + default -> value.toString(); + }; + } + + private static String formatFloat(Float f) { + if (Float.isFinite(f)) { + return f.toString() + "f"; + } else if (f.isNaN()) { + return "Float.intBitsToFloat(" + Float.floatToRawIntBits(f) + " /* NaN */)"; + } else if (f.isInfinite()) { + if (f > 0) { + return "Float.POSITIVE_INFINITY"; + } else { + return "Float.NEGATIVE_INFINITY"; + } + } else { + throw new RuntimeException("Not handled: " + f); + } + } + + private static String formatDouble(Double d) { + if (Double.isFinite(d)) { + return d.toString(); + } else if (d.isNaN()) { + return "Double.longBitsToDouble(" + Double.doubleToRawLongBits(d) + "L /* NaN */)"; + } else if (d.isInfinite()) { + if (d > 0) { + return "Double.POSITIVE_INFINITY"; + } else { + return "Double.NEGATIVE_INFINITY"; + } + } else { + throw new RuntimeException("Not handled: " + d); + } + } + + private void renderTemplateToken(TemplateToken templateToken) { + TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++); + currentTemplateFrame = templateFrame; + + templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value))); + TemplateBody body = templateToken.instantiate(); + renderTokenList(body.tokens()); + + if (currentTemplateFrame != templateFrame) { + throw new RuntimeException("Internal error: TemplateFrame mismatch!"); + } + currentTemplateFrame = currentTemplateFrame.parent; + } + + private void renderToken(Token token) { + switch (token) { + case StringToken(String s) -> { + renderStringWithDollarAndHashtagReplacements(s); + } + case NothingToken() -> { + // Nothing. + } + case HookAnchorToken(Hook hook, List tokens) -> { + CodeFrame outerCodeFrame = currentCodeFrame; + + // We need a CodeFrame to which the hook can insert code. That way, name + // definitions at the hook cannot escape the hookCodeFrame. + CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame); + hookCodeFrame.addHook(hook); + + // We need a CodeFrame where the tokens can be rendered. That way, name + // definitions from the tokens cannot escape the innerCodeFrame to the + // hookCodeFrame. + CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame); + currentCodeFrame = innerCodeFrame; + + renderTokenList(tokens); + + // Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the + // innerCodeFrame code from the tokens. + currentCodeFrame = outerCodeFrame; + currentCodeFrame.addCode(hookCodeFrame.getCode()); + currentCodeFrame.addCode(innerCodeFrame.getCode()); + } + case HookInsertToken(Hook hook, TemplateToken templateToken) -> { + // Switch to hook CodeFrame. + CodeFrame callerCodeFrame = currentCodeFrame; + CodeFrame hookCodeFrame = codeFrameForHook(hook); + + // Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated + // by the TemplateToken can be collected, and hook insertions from it can still + // be made to the hookCodeFrame before the code from the TemplateToken is added to + // the hookCodeFrame. + // But the CodeFrame must be transparent, so that its name definitions go out to + // the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken. + currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame); + + renderTemplateToken(templateToken); + + hookCodeFrame.addCode(currentCodeFrame.getCode()); + + // Switch back from hook CodeFrame to caller CodeFrame. + currentCodeFrame = callerCodeFrame; + } + case TemplateToken templateToken -> { + // Use a nested CodeFrame. + CodeFrame callerCodeFrame = currentCodeFrame; + currentCodeFrame = CodeFrame.make(currentCodeFrame); + + renderTemplateToken(templateToken); + + callerCodeFrame.addCode(currentCodeFrame.getCode()); + currentCodeFrame = callerCodeFrame; + } + case AddNameToken(Name name) -> { + currentCodeFrame.addName(name); + } + } + } + + private void renderTokenList(List tokens) { + CodeFrame codeFrame = currentCodeFrame; + for (Token t : tokens) { + renderToken(t); + } + if (codeFrame != currentCodeFrame) { + throw new RuntimeException("Internal error: CodeFrame mismatch."); + } + } + + /** + * We split a {@link String} by "#" and "$", and then look at each part. + * Example: + * + * s: "abcdefghijklmnop #name abcdefgh${var_name} 12345#{name2}_con $field_name something" + * parts: --------0-------- ------1------ --------2------- ------3----- ----------4--------- + * start: ^ ^ ^ ^ ^ + * next: ^ ^ ^ ^ ^ + * none hashtag dollar hashtag dollar done + */ + private void renderStringWithDollarAndHashtagReplacements(final String s) { + int count = 0; // First part needs special handling + int start = 0; + boolean startIsAfterDollar = false; + do { + // Find the next "$" or "#", after start. + int dollar = s.indexOf("$", start); + int hashtag = s.indexOf("#", start); + // If the character was not found, we want to have the rest of the + // String s, so instead of "-1" take the end/length of the String. + dollar = (dollar == -1) ? s.length() : dollar; + hashtag = (hashtag == -1) ? s.length() : hashtag; + // Take the first one. + int next = Math.min(dollar, hashtag); + String part = s.substring(start, next); + + if (count == 0) { + // First part has no "#" or "$" before it. + currentCodeFrame.addString(part); + } else { + // All others must do the replacement. + renderStringWithDollarAndHashtagReplacementsPart(s, part, startIsAfterDollar); + } + + if (next == s.length()) { + // No new "#" or "$" was found, we just processed the rest of the String, + // terminate now. + return; + } + start = next + 1; // skip over the "#" or "$" + startIsAfterDollar = next == dollar; // remember which character we just split with + count++; + } while (true); + } + + /** + * We are parsing a part now. Before the part, there was either a "#" or "$": + * isDollar = false: + * "#part" + * "#name abcdefgh" + * ---- + * "#{name2}_con " + * ------- + * + * isDollar = true: + * "$part" + * "${var_name} 12345" + * ---------- + * "$field_name something" + * ---------- + * + * We now want to find the name pattern at the beginning of the part, and replace + * it according to the hashtag or dollar replacement strategy. + */ + private void renderStringWithDollarAndHashtagReplacementsPart(final String s, final String part, final boolean isDollar) { + Matcher matcher = NAME_PATTERN.matcher(part); + // If the string has a "#" or "$" that is not followed by a correct name + // pattern, then the matcher will not match. These can be cases like: + // "##name" -> the first hashtag leads to an empty part, and an empty name. + // "#1name" -> the name pattern does not allow a digit as the first character. + // "anything#" -> a hashtag at the end of the string leads to an empty name. + if (!matcher.find()) { + String replacement = isDollar ? "$" : "#"; + throw new RendererException("Is not a valid '" + replacement + "' replacement pattern: '" + + replacement + part + "' in '" + s + "'."); + } + // We know that there is a correct pattern, and now we replace it. + currentCodeFrame.addString(matcher.replaceFirst( + (MatchResult result) -> { + // There are two groups: (1) for "name" and (2) for "{name}" + String name = result.group(1) != null ? result.group(1) : result.group(2); + if (isDollar) { + return $(name); + } else { + // replaceFirst needs some special escaping of backslashes and ollar signs. + return getHashtagReplacement(name).replace("\\", "\\\\").replace("$", "\\$"); + } + } + )); + } + + boolean isAnchored(Hook hook) { + return currentCodeFrame.codeFrameForHook(hook) != null; + } + + private CodeFrame codeFrameForHook(Hook hook) { + CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook); + if (codeFrame == null) { + throw new RendererException("Hook '" + hook.name() + "' was referenced but not found!"); + } + return codeFrame; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/RendererException.java b/test/hotspot/jtreg/compiler/lib/template_framework/RendererException.java new file mode 100644 index 00000000000..2ab542b6fe8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/RendererException.java @@ -0,0 +1,35 @@ +/* + * 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 compiler.lib.template_framework; + +/** + * This exception is thrown when something goes wrong during Template + * rendering, or in the use of any of its static methods. + * It most likely indicates a wrong use of the Templates. + */ +public class RendererException extends RuntimeException { + RendererException(String message) { + super(message); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/StringToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/StringToken.java new file mode 100644 index 00000000000..4926748e51a --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/StringToken.java @@ -0,0 +1,26 @@ +/* + * 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 compiler.lib.template_framework; + +record StringToken(String value) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java new file mode 100644 index 00000000000..866ac6dbfb8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java @@ -0,0 +1,200 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; + +/** + * {@link StructuralName}s represent things like method and class names, and can be added to the local + * scope with {@link Template#addStructuralName}, and accessed with {@link Template#structuralNames}, from where + * count, list or even sample random {@link StructuralName}s. Every {@link StructuralName} has a {@link StructuralName.Type}, + * so that sampling can be restricted to these types. + * + *

+ * For field and variable names and alike, there are the analogous {@link DataName}s. + * + * @param name The {@link String} name used in code. + * @param type The type of the {@link StructuralName}. + * @param weight The weight of the {@link StructuralName}, it corresponds to the probability of choosing this + * {@link StructuralName} when sampling later on. + */ +public record StructuralName(String name, StructuralName.Type type, int weight) implements Name { + + /** + * Creates a new {@link StructuralName}. + */ + public StructuralName { + } + + /** + * The interface for the type of a {@link StructuralName}. + */ + public interface Type extends Name.Type { + /** + * The name of the type, that can be used in code. + * + * @return The {@link String} representation of the type, that can be used in code. + */ + String name(); + + /** + * Defines the subtype relationship with other types, which is used to filter {@link StructuralName}s + * in {@link FilteredSet#exactOf}, {@link FilteredSet#subtypeOf}, and {@link FilteredSet#supertypeOf}. + * + * @param other The other type, where we check if it is the supertype of {@code 'this'}. + * @return If {@code 'this'} is a subtype of {@code 'other'}. + */ + boolean isSubtypeOf(StructuralName.Type other); + } + + /** + * The {@link FilteredSet} represents a filtered set of {@link StructuralName}s in the current scope. + * It can be obtained with {@link Template#structuralNames}. It can be used to count the + * available {@link StructuralName}s, or sample a random {@link StructuralName} according to the + * weights of the {@link StructuralName}s in the filtered set. + * Note: The {@link FilteredSet} is only a filtered view on the set of {@link StructuralName}s, + * and may return different results in different contexts. + */ + public static final class FilteredSet { + private final StructuralName.Type subtype; + private final StructuralName.Type supertype; + + FilteredSet(StructuralName.Type subtype, StructuralName.Type supertype) { + this.subtype = subtype; + this.supertype = supertype; + } + + FilteredSet() { + this(null, null); + } + + NameSet.Predicate predicate() { + if (subtype == null && supertype == null) { + throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + } + return (Name name) -> { + if (!(name instanceof StructuralName structuralName)) { return false; } + if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; } + return true; + }; + } + + /** + * Create a {@link FilteredSet}, where all {@link StructuralName}s must be subtypes of {@code type}. + * + * @param type The type of which all {@link StructuralName}s must be subtypes of. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #subtypeOf} or {@link #exactOf}. + */ + public FilteredSet subtypeOf(StructuralName.Type type) { + if (subtype != null) { + throw new UnsupportedOperationException("Cannot constrain to subtype " + type + ", is already constrained: " + subtype); + } + return new FilteredSet(type, supertype); + } + + /** + * Create a {@link FilteredSet}, where all {@link StructuralName}s must be supertypes of {@code type}. + * + * @param type The type of which all {@link StructuralName}s must be supertype of. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #supertypeOf} or {@link #exactOf}. + */ + public FilteredSet supertypeOf(StructuralName.Type type) { + if (supertype != null) { + throw new UnsupportedOperationException("Cannot constrain to supertype " + type + ", is already constrained: " + supertype); + } + return new FilteredSet(subtype, type); + } + + /** + * Create a {@link FilteredSet}, where all {@link StructuralName}s must be of exact {@code type}, + * hence it must be both subtype and supertype thereof. + * + * @param type The type of which all {@link StructuralName}s must be. + * @return The updated filtered set. + * @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public FilteredSet exactOf(StructuralName.Type type) { + return subtypeOf(type).supertypeOf(type); + } + + /** + * Samples a random {@link StructuralName} from the filtered set, according to the weights + * of the contained {@link StructuralName}s. + * + * @return The sampled {@link StructuralName}. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + * @throws RendererException If the set was empty. + */ + public StructuralName sample() { + StructuralName n = (StructuralName)Renderer.getCurrent().sampleName(predicate()); + if (n == null) { + String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")"; + throw new RendererException("No variable:" + msg1 + msg2 + "."); + } + return n; + } + + /** + * Counts the number of {@link StructuralName}s in the filtered set. + * + * @return The number of {@link StructuralName}s in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public int count() { + return Renderer.getCurrent().countNames(predicate()); + } + + /** + * Checks if there are any {@link StructuralName}s in the filtered set. + * + * @return Returns {@code true} iff there is at least one {@link StructuralName} in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public boolean hasAny() { + return Renderer.getCurrent().hasAnyNames(predicate()); + } + + /** + * Collects all {@link StructuralName}s in the filtered set. + * + * @return A {@link List} of all {@link StructuralName}s in the filtered set. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public List toList() { + List list = Renderer.getCurrent().listNames(predicate()); + return list.stream().map(n -> (StructuralName)n).toList(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java new file mode 100644 index 00000000000..f01c5ccffd3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java @@ -0,0 +1,844 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import java.util.List; + +import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.ir_framework.TestFramework; + +/** + * The Template Framework allows the generation of code with Templates. The goal is that these Templates are + * easy to write, and allow regression tests to cover a larger scope, and to make template based fuzzing easy + * to extend. + * + *

+ * Motivation: We want to make it easy to generate variants of tests. Often, we would like to + * have a set of tests, corresponding to a set of types, a set of operators, a set of constants, etc. Writing all + * the tests by hand is cumbersome or even impossible. When generating such tests with scripts, it would be + * preferable if the code generation happens automatically, and the generator script was checked into the code + * base. Code generation can go beyond simple regression tests, and one might want to generate random code from + * a list of possible templates, to fuzz individual Java features and compiler optimizations. + * + *

+ * The Template Framework provides a facility to generate code with Templates. A Template is essentially a list + * of tokens that are concatenated (i.e. rendered) to a {@link String}. The Templates can have "holes", which are + * filled (replaced) by different values at each Template instantiation. For example, these "holes" can + * be filled with different types, operators or constants. Templates can also be nested, allowing a modular + * use of Templates. + * + *

+ * Once we rendered the source code to a {@link String}, we can compile it with the {@link CompileFramework}. + * + *

+ * Example: + * The following snippets are from the example test {@code TestAdvanced.java}. + * First, we define a template that generates a {@code @Test} method for a given type, operator and + * constant generator. We define two constants {@code con1} and {@code con2}, and then use a multiline + * string with hashtags {@code #} (i.e. "holes") that are then replaced by the template arguments and the + * {@link #let} definitions. + * + *

+ * {@snippet lang=java : + * var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + * let("con1", generator.next()), + * let("con2", generator.next()), + * """ + * // #typeName #operator #con1 #con2 + * public static #typeName $GOLD = $test(); + * + * @Test + * public static #typeName $test() { + * return (#typeName)(#con1 #operator #con2); + * } + * + * @Check(test = "$test") + * public static void $check(#typeName result) { + * Verify.checkEQ(result, $GOLD); + * } + * """ + * )); + * } + * + *

+ * To get an executable test, we define a {@link Template} that produces a class body with a main method. The Template + * takes a list of types, and calls the {@code testTemplate} defined above for each type and operator. We use + * the {@link TestFramework} to call our {@code @Test} methods. + * + *

+ * {@snippet lang=java : + * var classTemplate = Template.make("types", (List types) -> body( + * let("classpath", comp.getEscapedClassPathOfCompiledClasses()), + * """ + * package p.xyz; + * + * import compiler.lib.ir_framework.*; + * import compiler.lib.verify.*; + * + * public class InnerTest { + * public static void main() { + * // Set the classpath, so that the TestFramework test VM knows where + * // the CompileFramework put the class files of the compiled source code. + * TestFramework framework = new TestFramework(InnerTest.class); + * framework.addFlags("-classpath", "#classpath"); + * framework.start(); + * } + * + * """, + * // Call the testTemplate for each type and operator, generating a + * // list of lists of TemplateToken: + * types.stream().map((Type type) -> + * type.operators().stream().map((String operator) -> + * testTemplate.asToken(type.name(), operator, type.generator())).toList() + * ).toList(), + * """ + * } + * """ + * )); + * } + * + *

+ * Finally, we generate the list of types, and pass it to the class template: + * + *

+ * {@snippet lang=java : + * List types = List.of( + * new Type("byte", GEN_BYTE::next, List.of("+", "-", "*", "&", "|", "^")), + * new Type("char", GEN_CHAR::next, List.of("+", "-", "*", "&", "|", "^")), + * new Type("short", GEN_SHORT::next, List.of("+", "-", "*", "&", "|", "^")), + * new Type("int", GEN_INT::next, List.of("+", "-", "*", "&", "|", "^")), + * new Type("long", GEN_LONG::next, List.of("+", "-", "*", "&", "|", "^")), + * new Type("float", GEN_FLOAT::next, List.of("+", "-", "*", "/")), + * new Type("double", GEN_DOUBLE::next, List.of("+", "-", "*", "/")) + * ); + * + * // Use the template with one argument, and render it to a String. + * return classTemplate.render(types); + * } + * + *

+ * Details: + *

+ * A {@link Template} can have zero or more arguments. A template can be created with {@code make} methods like + * {@link Template#make(String, Function)}. For each number of arguments there is an implementation + * (e.g. {@link Template.TwoArgs} for two arguments). This allows the use of generics for the + * {@link Template} argument types which enables type checking of the {@link Template} arguments. + * It is currently only allowed to use up to three arguments. + * + *

+ * A {@link Template} can be rendered to a {@link String} (e.g. {@link Template.ZeroArgs#render()}). + * Alternatively, we can generate a {@link Token} (more specifically, a {@link TemplateToken}) with {@code asToken()} + * (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#body}. + * + *

+ * Ideally, we would have used string templates to inject these Template + * arguments into the strings. But since string templates are not (yet) available, the Templates provide + * hashtag replacements in the {@link String}s: the Template argument names are captured, and + * the argument values automatically replace any {@code "#name"} in the {@link String}s. See the different overloads + * of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}. + * + *

+ * When using nested Templates, there can be collisions with identifiers (e.g. variable names and method names). + * For this, Templates provide dollar replacements, which automatically rename any + * {@code "$name"} in the {@link String} with a {@code "name_ID"}, where the {@code "ID"} is unique for every use of + * a Template. The dollar replacement can also be captured with {@link #$}, and passed to nested + * Templates, which allows sharing of these identifier names between Templates. + * + *

+ * The dollar and hashtag names must have at least one character. The first character must be a letter + * or underscore (i.e. {@code a-zA-Z_}), the other characters can also be digits (i.e. {@code a-zA-Z0-9_}). + * One can use them with or without curly braces, e.g. {@code #name}, {@code #{name}}, {@code $name}, or + * {@code #{name}}. + * + *

+ * A {@link TemplateToken} cannot just be used in {@link Template#body}, but it can also be + * {@link Hook#insert}ed to where a {@link Hook} was {@link Hook#anchor}ed earlier (in some outer scope of the code). + * For example, while generating code in a method, one can reach out to the scope of the class, and insert a + * new field, or define a utility method. + * + *

+ * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, + * a Template can reference itself. + * + *

+ * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the + * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting + * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each + * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a + * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default + * fuel cost with {@link #setFuelCost}) when defining the {@link #body(Object...)}. Recursive templates are + * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). + * + *

+ * Code generation can involve keeping track of fields and variables, as well as the scopes in which they + * are available, and if they are mutable or immutable. We model fields and variables with {@link DataName}s, + * which we can add to the current scope with {@link #addDataName}. We can access the {@link DataName}s with + * {@link #dataNames}. We can filter for {@link DataName}s of specific {@link DataName.Type}s, and then + * we can call {@link DataName.FilteredSet#count}, {@link DataName.FilteredSet#sample}, + * {@link DataName.FilteredSet#toList}, etc. There are many use-cases for this mechanism, especially + * facilitating communication between the code of outer and inner {@link Template}s. Especially for fuzzing, + * it may be useful to be able to add fields and variables, and sample them randomly, to create a random data + * flow graph. + * + *

+ * Similarly, we may want to model method and class names, and possibly other structural names. We model + * these names with {@link StructuralName}, which works analogously to {@link DataName}, except that they + * are not concerned about mutability. + * + *

+ * When working with {@link DataName}s and {@link StructuralName}s, it is important to be aware of the + * relevant scopes, as well as the execution order of the {@link Template} lambdas and the evaluation + * of the {@link Template#body} tokens. When a {@link Template} is rendered, its lambda is invoked. In the + * lambda, we generate the tokens, and create the {@link Template#body}. Once the lambda returns, the + * tokens are evaluated one by one. While evaluating the tokens, the {@link Renderer} might encounter a nested + * {@link TemplateToken}, which in turn triggers the evaluation of that nested {@link Template}, i.e. + * the evaluation of its lambda and later the evaluation of its tokens. It is important to keep in mind + * that the lambda is always executed first, and the tokens are evaluated afterwards. A method like + * {@code dataNames(MUTABLE).exactOf(type).count()} is a method that is executed during the evaluation + * of the lambda. But a method like {@link #addDataName} returns a token, and does not immediately add + * the {@link DataName}. This ensures that the {@link DataName} is only inserted when the tokens are + * evaluated, so that it is inserted at the exact scope where we would expect it. + * + *

+ * Let us look at the following example to better understand the execution order. + * + *

+ * {@snippet lang=java : + * var testTemplate = Template.make(() -> body( + * // The lambda has just been invoked. + * // We count the DataNames and assign the count to the hashtag replacement "c1". + * let("c1", dataNames(MUTABLE).exactOf(someType).count()), + * // We want to define a DataName "v1", and create a token for it. + * addDataName($("v1"), someType, MUTABLE), + * // We count the DataNames again, but the count does NOT change compared to "c1". + * // This is because the token for "v1" is only evaluated later. + * let("c2", dataNames(MUTABLE).exactOf(someType).count()), + * // Create a nested scope. + * METHOD_HOOK.anchor( + * // We want to define a DataName "v2", which is only valid inside this + * // nested scope. + * addDataName($("v2"), someType, MUTABLE), + * // The count is still not different to "c1". + * let("c3", dataNames(MUTABLE).exactOf(someType).count()), + * // We nest a Template. This creates a TemplateToken, which is later evaluated. + * // By the time the TemplateToken is evaluated, the tokens from above will + * // be already evaluated. Hence, "v1" and "v2" are added by then, and if the + * // "otherTemplate" were to count the DataNames, the count would be increased + * // by 2 compared to "c1". + * otherTemplate.asToken() + * ), + * // After closing the scope, "v2" is no longer available. + * // The count is still the same as "c1", since "v1" is still only a token. + * let("c4", dataNames(MUTABLE).exactOf(someType).count()), + * // We nest another Template. Again, this creates a TemplateToken, which is only + * // evaluated later. By that time, the token for "v1" is evaluated, and so the + * // nested Template would observe an increment in the count. + * anotherTemplate.asToken() + * // By this point, all methods are called, and the tokens generated. + * // The lambda returns the "body", which is all of the tokens that we just + * // generated. After returning from the lambda, the tokens will be evaluated + * // one by one. + * )); + * } + + *

+ * More examples for these functionalities can be found in {@code TestTutorial.java}, {@code TestSimple.java}, + * and {@code TestAdvanced.java}, which all produce compilable Java code. Additional examples can be found in + * the tests, such as {@code TestTemplate.java} and {@code TestFormat.java}, which do not necessarily generate + * valid Java code, but generate deterministic Strings which are easier to verify, and may also serve as a + * reference when learning about these functionalities. + */ +public sealed interface Template permits Template.ZeroArgs, + Template.OneArg, + Template.TwoArgs, + Template.ThreeArgs { + + /** + * A {@link Template} with no arguments. + * + * @param function The {@link Supplier} that creates the {@link TemplateBody}. + */ + record ZeroArgs(Supplier function) implements Template { + TemplateBody instantiate() { + return function.get(); + } + + /** + * Creates a {@link TemplateToken} which can be used as a {@link Token} inside + * a {@link Template} for nested code generation. + * + * @return The {@link TemplateToken} to use the {@link Template} inside another + * {@link Template}. + */ + public TemplateToken asToken() { + return new TemplateToken.ZeroArgs(this); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render() { + return new TemplateToken.ZeroArgs(this).render(); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param fuel The amount of fuel provided for recursive Template instantiations. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(float fuel) { + return new TemplateToken.ZeroArgs(this).render(fuel); + } + } + + /** + * A {@link Template} with one argument. + * + * @param arg1Name The name of the (first) argument, used for hashtag replacements in the {@link Template}. + * @param The type of the (first) argument. + * @param function The {@link Function} that creates the {@link TemplateBody} given the template argument. + */ + record OneArg(String arg1Name, Function function) implements Template { + TemplateBody instantiate(T1 arg1) { + return function.apply(arg1); + } + + /** + * Creates a {@link TemplateToken} which can be used as a {@link Token} inside + * a {@link Template} for nested code generation. + * + * @param arg1 The value for the (first) argument. + * @return The {@link TemplateToken} to use the {@link Template} inside another + * {@link Template}. + */ + public TemplateToken asToken(T1 arg1) { + return new TemplateToken.OneArg<>(this, arg1); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(T1 arg1) { + return new TemplateToken.OneArg<>(this, arg1).render(); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @param fuel The amount of fuel provided for recursive Template instantiations. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(float fuel, T1 arg1) { + return new TemplateToken.OneArg<>(this, arg1).render(fuel); + } + } + + /** + * A {@link Template} with two arguments. + * + * @param arg1Name The name of the first argument, used for hashtag replacements in the {@link Template}. + * @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}. + * @param The type of the first argument. + * @param The type of the second argument. + * @param function The {@link BiFunction} that creates the {@link TemplateBody} given the template arguments. + */ + record TwoArgs(String arg1Name, String arg2Name, BiFunction function) implements Template { + TemplateBody instantiate(T1 arg1, T2 arg2) { + return function.apply(arg1, arg2); + } + + /** + * Creates a {@link TemplateToken} which can be used as a {@link Token} inside + * a {@link Template} for nested code generation. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @return The {@link TemplateToken} to use the {@link Template} inside another + * {@link Template}. + */ + public TemplateToken asToken(T1 arg1, T2 arg2) { + return new TemplateToken.TwoArgs<>(this, arg1, arg2); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(T1 arg1, T2 arg2) { + return new TemplateToken.TwoArgs<>(this, arg1, arg2).render(); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @param fuel The amount of fuel provided for recursive Template instantiations. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(float fuel, T1 arg1, T2 arg2) { + return new TemplateToken.TwoArgs<>(this, arg1, arg2).render(fuel); + } + } + + /** + * Interface for function with three arguments. + * + * @param Type of the first argument. + * @param Type of the second argument. + * @param Type of the third argument. + * @param Type of the return value. + */ + @FunctionalInterface + interface TriFunction { + + /** + * Function definition for the three argument functions. + * + * @param t The first argument. + * @param u The second argument. + * @param v The third argument. + * @return Return value of the three argument function. + */ + R apply(T t, U u, V v); + } + + /** + * A {@link Template} with three arguments. + * + * @param arg1Name The name of the first argument, used for hashtag replacements in the {@link Template}. + * @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}. + * @param arg3Name The name of the third argument, used for hashtag replacements in the {@link Template}. + * @param The type of the first argument. + * @param The type of the second argument. + * @param The type of the third argument. + * @param function The function with three arguments that creates the {@link TemplateBody} given the template arguments. + */ + record ThreeArgs(String arg1Name, String arg2Name, String arg3Name, TriFunction function) implements Template { + TemplateBody instantiate(T1 arg1, T2 arg2, T3 arg3) { + return function.apply(arg1, arg2, arg3); + } + + /** + * Creates a {@link TemplateToken} which can be used as a {@link Token} inside + * a {@link Template} for nested code generation. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @param arg3 The value for the third argument. + * @return The {@link TemplateToken} to use the {@link Template} inside another + * {@link Template}. + */ + public TemplateToken asToken(T1 arg1, T2 arg2, T3 arg3) { + return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @param arg3 The value for the third argument. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(T1 arg1, T2 arg2, T3 arg3) { + return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3).render(); + } + + /** + * Renders the {@link Template} to a {@link String}. + * + * @param arg1 The value for the first argument. + * @param arg2 The value for the second argument. + * @param arg3 The value for the third argument. + * @param fuel The amount of fuel provided for recursive Template instantiations. + * @return The {@link String}, resulting from rendering the {@link Template}. + */ + public String render(float fuel, T1 arg1, T2 arg2, T3 arg3) { + return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3).render(fuel); + } + } + + /** + * Creates a {@link Template} with no arguments. + * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * + *

+ * Example: + * {@snippet lang=java : + * var template = Template.make(() -> body( + * """ + * Multi-line string or other tokens. + * """ + * )); + * } + * + * @param body The {@link TemplateBody} created by {@link Template#body}. + * @return A {@link Template} with zero arguments. + */ + static Template.ZeroArgs make(Supplier body) { + return new Template.ZeroArgs(body); + } + + /** + * Creates a {@link Template} with one argument. + * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * Good practice but not enforced but not enforced: {@code arg1Name} should match the lambda argument name. + * + *

+ * Here is an example with template argument {@code 'a'}, captured once as string name + * for use in hashtag replacements, and captured once as lambda argument with the corresponding type + * of the generic argument. + * {@snippet lang=java : + * var template = Template.make("a", (Integer a) -> body( + * """ + * Multi-line string or other tokens. + * We can use the hashtag replacement #a to directly insert the String value of a. + * """, + * "We can also use the captured parameter of a: " + a + * )); + * } + * + * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param Type of the (first) argument. + * @param arg1Name The name of the (first) argument for hashtag replacement. + * @return A {@link Template} with one argument. + */ + static Template.OneArg make(String arg1Name, Function body) { + return new Template.OneArg<>(arg1Name, body); + } + + /** + * Creates a {@link Template} with two arguments. + * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * Good practice but not enforced: {@code arg1Name} and {@code arg2Name} should match the lambda argument names. + * + *

+ * Here is an example with template arguments {@code 'a'} and {@code 'b'}, captured once as string names + * for use in hashtag replacements, and captured once as lambda arguments with the corresponding types + * of the generic arguments. + * {@snippet lang=java : + * var template = Template.make("a", "b", (Integer a, String b) -> body( + * """ + * Multi-line string or other tokens. + * We can use the hashtag replacement #a and #b to directly insert the String value of a and b. + * """, + * "We can also use the captured parameter of a and b: " + a + " and " + b + * )); + * } + * + * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param Type of the first argument. + * @param arg1Name The name of the first argument for hashtag replacement. + * @param Type of the second argument. + * @param arg2Name The name of the second argument for hashtag replacement. + * @return A {@link Template} with two arguments. + */ + static Template.TwoArgs make(String arg1Name, String arg2Name, BiFunction body) { + return new Template.TwoArgs<>(arg1Name, arg2Name, body); + } + + /** + * Creates a {@link Template} with three arguments. + * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * Good practice but not enforced: {@code arg1Name}, {@code arg2Name}, and {@code arg3Name} should match the lambda argument names. + * + * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param Type of the first argument. + * @param arg1Name The name of the first argument for hashtag replacement. + * @param Type of the second argument. + * @param arg2Name The name of the second argument for hashtag replacement. + * @param Type of the third argument. + * @param arg3Name The name of the third argument for hashtag replacement. + * @return A {@link Template} with three arguments. + */ + static Template.ThreeArgs make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction body) { + return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, body); + } + + /** + * Creates a {@link TemplateBody} from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), any {@link Token}, + * or {@link List}s of any of these. + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> body( + * """ + * Multi-line string + * """, + * "normal string ", Integer.valueOf(3), 3, Float.valueOf(1.5f), 1.5f, + * List.of("abc", "def"), + * nestedTemplate.asToken(42) + * )); + * } + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link TemplateBody} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static TemplateBody body(Object... tokens) { + return new TemplateBody(Token.parse(tokens)); + } + + /** + * Retrieves the dollar replacement of the {@code 'name'} for the + * current Template that is being instantiated. It returns the same + * dollar replacement as the string use {@code "$name"}. + * + *

+ * Here is an example where a Template creates a local variable {@code 'var'}, + * with an implicit dollar replacement, and then captures that dollar replacement + * using {@link #$} for the use inside a nested template. + * {@snippet lang=java : + * var template = Template.make(() -> body( + * """ + * int $var = 42; + * """, + * otherTemplate.asToken($("var")) + * )); + * } + * + * @param name The {@link String} name of the name. + * @return The dollar replacement for the {@code 'name'}. + */ + static String $(String name) { + return Renderer.getCurrent().$(name); + } + + /** + * Define a hashtag replacement for {@code "#key"}, with a specific value. + * + *

+ * {@snippet lang=java : + * var template = Template.make("a", (Integer a) -> body( + * let("b", a * 5), + * """ + * System.out.println("Use a and b with hashtag replacement: #a and #b"); + * """ + * )); + * } + * + * @param key Name for the hashtag replacement. + * @param value The value that the hashtag is replaced with. + * @return A token that does nothing, so that the {@link #let} can easily be put in a list of tokens + * inside a {@link Template#body}. + * @throws RendererException if there is a duplicate hashtag {@code key}. + */ + static Token let(String key, Object value) { + Renderer.getCurrent().addHashtagReplacement(key, value); + return new NothingToken(); + } + + /** + * Define a hashtag replacement for {@code "#key"}, with a specific value, which is also captured + * by the provided {@code function} with type {@code }. + * + *

+ * {@snippet lang=java : + * var template = Template.make("a", (Integer a) -> let("b", a * 2, (Integer b) -> body( + * """ + * System.out.println("Use a and b with hashtag replacement: #a and #b"); + * """, + * "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n" + * ))); + * } + * + * @param key Name for the hashtag replacement. + * @param value The value that the hashtag is replaced with. + * @param The type of the value. + * @param function The function that is applied with the provided {@code value}. + * @return A {@link TemplateBody}. + * @throws RendererException if there is a duplicate hashtag {@code key}. + */ + static TemplateBody let(String key, T value, Function function) { + Renderer.getCurrent().addHashtagReplacement(key, value); + return function.apply(value); + } + + /** + * Default amount of fuel for Template rendering. It guides the nesting depth of Templates. Can be changed when + * rendering a template with {@code render(fuel)} (e.g. {@link ZeroArgs#render(float)}). + */ + float DEFAULT_FUEL = 100.0f; + + /** + * The default amount of fuel spent per Template. It is subtracted from the current {@link #fuel} at every + * nesting level, and once the {@link #fuel} reaches zero, the nesting is supposed to terminate. Can be changed + * with {@link #setFuelCost(float)} inside {@link #body(Object...)}. + */ + float DEFAULT_FUEL_COST = 10.0f; + + /** + * The current remaining fuel for nested Templates. Every level of Template nesting + * subtracts a certain amount of fuel, and when it reaches zero, Templates are supposed to + * stop nesting, if possible. This is not a hard rule, but a guide, and a mechanism to ensure + * termination in recursive Template instantiations. + * + *

+ * Example of a recursive Template, which checks the remaining {@link #fuel} at every level, + * and terminates if it reaches zero. It also demonstrates the use of {@link TemplateBinding} for + * the recursive use of Templates. We {@link Template.OneArg#render} with {@code 30} total fuel, + * and spend {@code 5} fuel at each recursion level. + * + *

+ * {@snippet lang=java : + * var binding = new TemplateBinding>(); + * var template = Template.make("depth", (Integer depth) -> body( + * setFuelCost(5.0f), + * let("fuel", fuel()), + * """ + * System.out.println("Currently at depth #depth with fuel #fuel"); + * """, + * (fuel() > 0) ? binding.get().asToken(depth + 1) : + * "// terminate\n" + * )); + * binding.bind(template); + * String code = template.render(30.0f, 0); + * } + * + * @return The amount of fuel left for nested Template use. + */ + static float fuel() { + return Renderer.getCurrent().fuel(); + } + + /** + * Changes the amount of fuel used for the current Template, where the default is + * {@link Template#DEFAULT_FUEL_COST}. + * + * @param fuelCost The amount of fuel used for the current Template. + * @return A token for convenient use in {@link Template#body}. + */ + static Token setFuelCost(float fuelCost) { + Renderer.getCurrent().setFuelCost(fuelCost); + return new NothingToken(); + } + + /** + * Add a {@link DataName} in the current scope, that is the innermost of either + * {@link Template#body} or {@link Hook#anchor}. + * + * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. + * @param type The type of the {@link DataName}. + * @param mutability Indicates if the {@link DataName} is to be mutable or immutable, + * i.e. if we intend to use the {@link DataName} only for reading + * or if we also allow it to be mutated. + * @param weight The weight of the {@link DataName}, which correlates to the probability + * of this {@link DataName} being chosen when we sample. + * Must be a value from 1 to 1000. + * @return The token that performs the defining action. + */ + static Token addDataName(String name, DataName.Type type, DataName.Mutability mutability, int weight) { + if (mutability != DataName.Mutability.MUTABLE && + mutability != DataName.Mutability.IMMUTABLE) { + throw new IllegalArgumentException("Unexpected mutability: " + mutability); + } + boolean mutable = mutability == DataName.Mutability.MUTABLE; + if (weight <= 0 || 1000 < weight) { + throw new IllegalArgumentException("Unexpected weight: " + weight); + } + return new AddNameToken(new DataName(name, type, mutable, weight)); + } + + /** + * Add a {@link DataName} in the current scope, that is the innermost of either + * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * + * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. + * @param type The type of the {@link DataName}. + * @param mutability Indicates if the {@link DataName} is to be mutable or immutable, + * i.e. if we intend to use the {@link DataName} only for reading + * or if we also allow it to be mutated. + * @return The token that performs the defining action. + */ + static Token addDataName(String name, DataName.Type type, DataName.Mutability mutability) { + return addDataName(name, type, mutability, 1); + } + + /** + * Access the set of {@link DataName}s, for sampling, counting, etc. + * + * @param mutability Indicates if we only sample from mutable, immutable or either {@link DataName}s. + * @return A view on the {@link DataName}s, on which we can sample, count, etc. + */ + static DataName.FilteredSet dataNames(DataName.Mutability mutability) { + return new DataName.FilteredSet(mutability); + } + + /** + * Add a {@link StructuralName} in the current scope, that is the innermost of either + * {@link Template#body} or {@link Hook#anchor}. + * + * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. + * @param type The type of the {@link StructuralName}. + * @param weight The weight of the {@link StructuralName}, which correlates to the probability + * of this {@link StructuralName} being chosen when we sample. + * Must be a value from 1 to 1000. + * @return The token that performs the defining action. + */ + static Token addStructuralName(String name, StructuralName.Type type, int weight) { + if (weight <= 0 || 1000 < weight) { + throw new IllegalArgumentException("Unexpected weight: " + weight); + } + return new AddNameToken(new StructuralName(name, type, weight)); + } + + /** + * Add a {@link StructuralName} in the current scope, that is the innermost of either + * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * + * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. + * @param type The type of the {@link StructuralName}. + * @return The token that performs the defining action. + */ + static Token addStructuralName(String name, StructuralName.Type type) { + return addStructuralName(name, type, 1); + } + + /** + * Access the set of {@link StructuralName}s, for sampling, counting, etc. + * + * @return A view on the {@link StructuralName}s, on which we can sample, count, etc. + */ + static StructuralName.FilteredSet structuralNames() { + return new StructuralName.FilteredSet(); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBinding.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBinding.java new file mode 100644 index 00000000000..2073b788e71 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBinding.java @@ -0,0 +1,70 @@ +/* + * 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 compiler.lib.template_framework; + +/** + * To facilitate recursive uses of Templates, for example where a template uses + * itself and needs to be referenced before it is fully defined, + * one can use the indirection of a {@link TemplateBinding}. The {@link TemplateBinding} + * is allocated first without any Template bound to it yet. At this stage, + * it can be used with {@link #get} inside a Template. Later, we can {@link #bind} + * a Template to the binding, such that {@link #get} returns that bound + * Template. + * + * @param Type of the template. + */ +public class TemplateBinding { + private T template = null; + + /** + * Creates a new {@link TemplateBinding} that has no Template bound to it yet. + */ + public TemplateBinding() {} + + /** + * Retrieve the Template that was previously bound to the binding. + * + * @return The Template that was previously bound with {@link #bind}. + * @throws RendererException if no Template was bound yet. + */ + public T get() { + if (template == null) { + throw new RendererException("Cannot 'get' before 'bind'."); + } + return template; + } + + /** + * Binds a Template for future reference using {@link #get}. + * + * @param template The Template to be bound. + * @throws RendererException if a Template was already bound. + */ + public void bind(T template) { + if (this.template != null) { + throw new RendererException("Duplicate 'bind' not allowed."); + } + this.template = template; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java new file mode 100644 index 00000000000..440766b3f79 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java @@ -0,0 +1,34 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.List; + +/** + * A Template generates a {@link TemplateBody}, which is a list of {@link Token}s, + * which are then later rendered to {@link String}s. + * + * @param tokens The list of {@link Token}s that are later rendered to {@link String}s. + */ +public record TemplateBody(List tokens) {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java new file mode 100644 index 00000000000..cf8c4afb321 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java @@ -0,0 +1,99 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.HashMap; +import java.util.Map; + +/** + * The {@link TemplateFrame} is the frame for a {@link Template}, i.e. the corresponding + * {@link TemplateToken}. It ensures that each template use has its own unique {@link #id} + * used to deconflict names using {@link Template#$}. It also has a set of hashtag + * replacements, which combine the key-value pairs from the template argument and the + * {@link Template#let} definitions. The {@link #parent} relationship provides a trace + * for the use chain of templates. The {@link #fuel} is reduced over this chain, to give + * a heuristic on how much time is spent on the code from the template corresponding to + * the frame, and to give a termination criterion to avoid nesting templates too deeply. + * + *

+ * See also {@link CodeFrame} for more explanations about the frames. + */ +class TemplateFrame { + final TemplateFrame parent; + private final int id; + private final Map hashtagReplacements = new HashMap<>(); + final float fuel; + private float fuelCost; + + public static TemplateFrame makeBase(int id, float fuel) { + return new TemplateFrame(null, id, fuel, 0.0f); + } + + public static TemplateFrame make(TemplateFrame parent, int id) { + return new TemplateFrame(parent, id, parent.fuel - parent.fuelCost, Template.DEFAULT_FUEL_COST); + } + + private TemplateFrame(TemplateFrame parent, int id, float fuel, float fuelCost) { + this.parent = parent; + this.id = id; + this.fuel = fuel; + this.fuelCost = fuelCost; + } + + public String $(String name) { + if (name == null) { + throw new RendererException("A '$' name should not be null."); + } + if (!Renderer.isValidHashtagOrDollarName(name)) { + throw new RendererException("Is not a valid '$' name: '" + name + "'."); + } + return name + "_" + id; + } + + void addHashtagReplacement(String key, String value) { + if (key == null) { + throw new RendererException("A hashtag replacement should not be null."); + } + if (!Renderer.isValidHashtagOrDollarName(key)) { + throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); + } + if (hashtagReplacements.putIfAbsent(key, value) != null) { + throw new RendererException("Duplicate hashtag replacement for #" + key); + } + } + + String getHashtagReplacement(String key) { + if (!Renderer.isValidHashtagOrDollarName(key)) { + throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); + } + if (hashtagReplacements.containsKey(key)) { + return hashtagReplacements.get(key); + } + throw new RendererException("Missing hashtag replacement for #" + key); + } + + void setFuelCost(float fuelCost) { + this.fuelCost = fuelCost; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java new file mode 100644 index 00000000000..47262f152d4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java @@ -0,0 +1,169 @@ +/* + * 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 compiler.lib.template_framework; + +/** + * Represents a tokenized {@link Template} (after calling {@code asToken()}) ready for + * instantiation either as a {@link Token} inside another {@link Template} or as + * a {@link String} with {@link #render}. + */ +public sealed abstract class TemplateToken implements Token + permits TemplateToken.ZeroArgs, + TemplateToken.OneArg, + TemplateToken.TwoArgs, + TemplateToken.ThreeArgs +{ + private TemplateToken() {} + + /** + * Represents a tokenized zero-argument {@link Template} ready for instantiation + * either as a {@link Token} inside another {@link Template} or as a {@link String} + * with {@link #render}. + */ + static final class ZeroArgs extends TemplateToken implements Token { + private final Template.ZeroArgs zeroArgs; + + ZeroArgs(Template.ZeroArgs zeroArgs) { + this.zeroArgs = zeroArgs; + } + + @Override + public TemplateBody instantiate() { + return zeroArgs.instantiate(); + } + + @Override + public void visitArguments(ArgumentVisitor visitor) {} + } + + /** + * Represents a tokenized one-argument {@link Template}, already filled with arguments, ready for + * instantiation either as a {@link Token} inside another {@link Template} or as a {@link String} + * with {@link #render}. + * + * @param The type of the (first) argument. + */ + static final class OneArg extends TemplateToken implements Token { + private final Template.OneArg oneArgs; + private final T1 arg1; + + OneArg(Template.OneArg oneArgs, T1 arg1) { + this.oneArgs = oneArgs; + this.arg1 = arg1; + } + + @Override + public TemplateBody instantiate() { + return oneArgs.instantiate(arg1); + } + + @Override + public void visitArguments(ArgumentVisitor visitor) { + visitor.visit(oneArgs.arg1Name(), arg1); + } + } + + /** + * Represents a tokenized two-argument {@link Template}, already filled with arguments, ready for + * instantiation either as a {@link Token} inside another {@link Template} or as a {@link String} + * with {@link #render}. + * + * @param The type of the first argument. + * @param The type of the second argument. + */ + static final class TwoArgs extends TemplateToken implements Token { + private final Template.TwoArgs twoArgs; + private final T1 arg1; + private final T2 arg2; + + TwoArgs(Template.TwoArgs twoArgs, T1 arg1, T2 arg2) { + this.twoArgs = twoArgs; + this.arg1 = arg1; + this.arg2 = arg2; + } + + @Override + public TemplateBody instantiate() { + return twoArgs.instantiate(arg1, arg2); + } + + @Override + public void visitArguments(ArgumentVisitor visitor) { + visitor.visit(twoArgs.arg1Name(), arg1); + visitor.visit(twoArgs.arg2Name(), arg2); + } + } + + /** + * Represents a tokenized three-argument {@link TemplateToken}, already filled with arguments, ready for + * instantiation either as a {@link Token} inside another {@link Template} or as a {@link String} + * with {@link #render}. + * + * @param The type of the first argument. + * @param The type of the second argument. + * @param The type of the second argument. + */ + static final class ThreeArgs extends TemplateToken implements Token { + private final Template.ThreeArgs threeArgs; + private final T1 arg1; + private final T2 arg2; + private final T3 arg3; + + ThreeArgs(Template.ThreeArgs threeArgs, T1 arg1, T2 arg2, T3 arg3) { + this.threeArgs = threeArgs; + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + } + + @Override + public TemplateBody instantiate() { + return threeArgs.instantiate(arg1, arg2, arg3); + } + + @Override + public void visitArguments(ArgumentVisitor visitor) { + visitor.visit(threeArgs.arg1Name(), arg1); + visitor.visit(threeArgs.arg2Name(), arg2); + visitor.visit(threeArgs.arg3Name(), arg3); + } + } + + abstract TemplateBody instantiate(); + + @FunctionalInterface + interface ArgumentVisitor { + void visit(String name, Object value); + } + + abstract void visitArguments(ArgumentVisitor visitor); + + final String render() { + return Renderer.render(this); + } + + final String render(float fuel) { + return Renderer.render(this, fuel); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java new file mode 100644 index 00000000000..dc750c7f79f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java @@ -0,0 +1,78 @@ +/* + * 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 compiler.lib.template_framework; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed + * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens + * {@link List}s. + */ +sealed interface Token permits StringToken, + TemplateToken, + TemplateToken.ZeroArgs, + TemplateToken.OneArg, + TemplateToken.TwoArgs, + TemplateToken.ThreeArgs, + HookAnchorToken, + HookInsertToken, + AddNameToken, + NothingToken +{ + static List parse(Object[] objects) { + if (objects == null) { + throw new IllegalArgumentException("Unexpected tokens: null"); + } + List outputList = new ArrayList<>(); + parseToken(Arrays.asList(objects), outputList); + return outputList; + } + + private static void parseList(List inputList, List outputList) { + for (Object o : inputList) { + parseToken(o, outputList); + } + } + + private static void parseToken(Object o, List outputList) { + if (o == null) { + throw new IllegalArgumentException("Unexpected token: null"); + } + switch (o) { + case Token t -> outputList.add(t); + case String s -> outputList.add(new StringToken(Renderer.format(s))); + case Integer s -> outputList.add(new StringToken(Renderer.format(s))); + case Long s -> outputList.add(new StringToken(Renderer.format(s))); + case Double s -> outputList.add(new StringToken(Renderer.format(s))); + case Float s -> outputList.add(new StringToken(Renderer.format(s))); + case Boolean s -> outputList.add(new StringToken(Renderer.format(s))); + case List l -> parseList(l, outputList); + default -> throw new IllegalArgumentException("Unexpected token: " + o); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Hooks.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Hooks.java new file mode 100644 index 00000000000..410b790e3b5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Hooks.java @@ -0,0 +1,46 @@ +/* + * 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 compiler.lib.template_framework.library; + +import compiler.lib.template_framework.Hook; + +/** + * Provides a hook for class and method scopes, to be used in Templates. + */ +public final class Hooks { + private Hooks() {} // Avoid instantiation and need for documentation. + + /** + * Template {@link Hook} used by the Template Library for class scopes, to insert + * fields and methods. + */ + public static final Hook CLASS_HOOK = new Hook("Class"); + + /** + * Template {@link Hook} used by the Template Library for method scopes, to insert + * local variables, and computations for local variables at the beginning of a + * method. + */ + public static final Hook METHOD_HOOK = new Hook("Method"); +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java new file mode 100644 index 00000000000..c5a4528f63d --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java @@ -0,0 +1,162 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8344942 + * @summary Test simple use of Templates with the Compile Framework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/ir_framework/TestFramework.java + * @compile ../../../compiler/lib/verify/Verify.java + * @run main template_framework.examples.TestAdvanced + */ + +package template_framework.examples; + +import java.util.List; +import jdk.test.lib.Utils; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.generators.RestrictableGenerator; + +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.Template; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +/** + * This is a basic example for Templates, using them to cover a list of test variants. + *

+ * The "@compile" command for JTREG is required so that the frameworks used in the Template code + * are compiled and available for the Test-VM. + *

+ * Additionally, we must set the classpath for the Test-VM, so that it has access to all compiled + * classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}). + */ +public class TestAdvanced { + public static final RestrictableGenerator GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE); + public static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + public static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + public static final RestrictableGenerator GEN_INT = Generators.G.ints(); + public static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + public static final Generator GEN_FLOAT = Generators.G.floats(); + public static final Generator GEN_DOUBLE = Generators.G.doubles(); + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp)); + + // Compile the source file. + comp.compile(); + + // Object ret = p.xyz.InnerTest.main(); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {}); + } + + interface MyGenerator { + Object next(); + } + + record Type(String name, MyGenerator generator, List operators) {} + + // Generate a source Java file as String + public static String generate(CompileFramework comp) { + + // The test template: + // - For a chosen type, operator, and generator. + // - The variable name "GOLD" and the test name "test" would get conflicts + // if we instantiate the template multiple times. Thus, we use the $ prefix + // so that the Template Framework can replace the names and make them unique + // for each Template instantiation. + // - The GOLD value is computed at the beginning, hopefully by the interpreter. + // - The test method is eventually compiled, and the values are verified by the + // check method. + var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + let("con1", generator.next()), + let("con2", generator.next()), + """ + // #typeName #operator #con1 #con2 + public static #typeName $GOLD = $test(); + + @Test + public static #typeName $test() { + return (#typeName)(#con1 #operator #con2); + } + + @Check(test = "$test") + public static void $check(#typeName result) { + Verify.checkEQ(result, $GOLD); + } + """ + )); + + // Template for the Class. + var classTemplate = Template.make("types", (List types) -> body( + let("classpath", comp.getEscapedClassPathOfCompiledClasses()), + """ + package p.xyz; + + import compiler.lib.ir_framework.*; + import compiler.lib.verify.*; + + public class InnerTest { + public static void main() { + TestFramework framework = new TestFramework(InnerTest.class); + // Set the classpath, so that the TestFramework test VM knows where + // the CompileFramework put the class files of the compiled source code. + framework.addFlags("-classpath", "#classpath"); + framework.start(); + } + + """, + // Call the testTemplate for each type and operator, generating a + // list of lists of TemplateToken: + types.stream().map((Type type) -> + type.operators().stream().map((String operator) -> + testTemplate.asToken(type.name(), operator, type.generator())).toList() + ).toList(), + """ + } + """ + )); + + // For each type, we choose a list of operators that do not throw exceptions. + List types = List.of( + new Type("byte", GEN_BYTE::next, List.of("+", "-", "*", "&", "|", "^")), + new Type("char", GEN_CHAR::next, List.of("+", "-", "*", "&", "|", "^")), + new Type("short", GEN_SHORT::next, List.of("+", "-", "*", "&", "|", "^")), + new Type("int", GEN_INT::next, List.of("+", "-", "*", "&", "|", "^")), + new Type("long", GEN_LONG::next, List.of("+", "-", "*", "&", "|", "^")), + new Type("float", GEN_FLOAT::next, List.of("+", "-", "*", "/")), + new Type("double", GEN_DOUBLE::next, List.of("+", "-", "*", "/")) + ); + + // Use the template with one argument and render it to a String. + return classTemplate.render(types); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java new file mode 100644 index 00000000000..e06671ca951 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8344942 + * @summary Test simple use of Templates with the Compile Framework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main template_framework.examples.TestSimple + */ + +package template_framework.examples; + +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.Template; +import static compiler.lib.template_framework.Template.body; + +public class TestSimple { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate()); + + // Compile the source file. + comp.compile(); + + // Object ret = p.xyz.InnerTest.test(); + Object ret = comp.invoke("p.xyz.InnerTest", "test", new Object[] {}); + System.out.println("res: " + ret); + + // Check that the return value is the sum of the two arguments. + if ((42 + 7) != (int)ret) { + throw new RuntimeException("Unexpected result"); + } + } + + // Generate a source Java file as String + public static String generate() { + // Create a Template with two arguments. + var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> body( + """ + package p.xyz; + public class InnerTest { + public static int test() { + return #arg1 + #arg2; + } + } + """ + )); + + // Use the template with two arguments, and render it to a String. + return template.render(42, "7"); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java new file mode 100644 index 00000000000..faa05b29d82 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java @@ -0,0 +1,1227 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8344942 + * @summary Demonstrate the use of Templates with the Compile Framework. + * It displays the use of most features in the Template Framework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main template_framework.examples.TestTutorial + */ + +package template_framework.examples; + +import java.util.Collections; +import java.util.List; + +import compiler.lib.compile_framework.*; + +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.Hook; +import compiler.lib.template_framework.TemplateBinding; +import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.StructuralName; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; +import static compiler.lib.template_framework.Template.$; +import static compiler.lib.template_framework.Template.fuel; +import static compiler.lib.template_framework.Template.addDataName; +import static compiler.lib.template_framework.Template.dataNames; +import static compiler.lib.template_framework.Template.addStructuralName; +import static compiler.lib.template_framework.Template.structuralNames; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE; +import static compiler.lib.template_framework.DataName.Mutability.IMMUTABLE; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE_OR_IMMUTABLE; + +import compiler.lib.template_framework.library.Hooks; + +public class TestTutorial { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add Java source files. + comp.addJavaSourceCode("p.xyz.InnerTest1", generateWithListOfTokens()); + comp.addJavaSourceCode("p.xyz.InnerTest2", generateWithTemplateArguments()); + comp.addJavaSourceCode("p.xyz.InnerTest3", generateWithHashtagAndDollarReplacements()); + comp.addJavaSourceCode("p.xyz.InnerTest3b", generateWithHashtagAndDollarReplacements2()); + comp.addJavaSourceCode("p.xyz.InnerTest4", generateWithCustomHooks()); + comp.addJavaSourceCode("p.xyz.InnerTest5", generateWithLibraryHooks()); + comp.addJavaSourceCode("p.xyz.InnerTest6", generateWithRecursionAndBindingsAndFuel()); + comp.addJavaSourceCode("p.xyz.InnerTest7", generateWithDataNamesSimple()); + comp.addJavaSourceCode("p.xyz.InnerTest8", generateWithDataNamesForFieldsAndVariables()); + comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithDataNamesAndScopes1()); + comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithDataNamesAndScopes2()); + comp.addJavaSourceCode("p.xyz.InnerTest10", generateWithDataNamesForFuzzing()); + comp.addJavaSourceCode("p.xyz.InnerTest11", generateWithStructuralNamesForMethods()); + + // Compile the source files. + // Hint: if you want to see the generated source code, you can enable + // printing of the source code that the CompileFramework receives, + // with -DCompileFrameworkVerbose=true + // The code may not be nicely formatted, especially regarding + // indentation. You might consider dumping the generated code + // into an IDE or other auto-formatting tool. + comp.compile(); + + comp.invoke("p.xyz.InnerTest1", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest2", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest3", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest3b", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest4", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest5", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest6", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest7", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest8", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest9a", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest9b", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest10", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest11", "main", new Object[] {}); + } + + // This example shows the use of various Tokens. + public static String generateWithListOfTokens() { + // A Template is essentially a function / lambda that produces a + // token body, which is a list of Tokens that are concatenated. + var templateClass = Template.make(() -> body( + // The "body" method is filled by a sequence of "Tokens". + // These can be Strings and multi-line Strings, but also + // boxed primitives. + """ + package p.xyz; + + public class InnerTest1 { + public static void main() { + System.out.println("Hello World!"); + """, + "int a = ", 1, ";\n", + "float b = ", 1.5f, ";\n", + // Special Float values are "smartly" formatted! + "float nan = ", Float.POSITIVE_INFINITY, ";\n", + "boolean c = ", true, ";\n", + // Lists of Tokens are also allowed: + List.of("int ", "d = 5", ";\n"), + // We can also stream / map over an existing list, or one created on + // the fly: + List.of(3, 5, 7, 11).stream().map(i -> "System.out.println(" + i + ");\n").toList(), + """ + System.out.println(a + " " + b + " " + nan + " " + c + " " + d); + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // This example shows the use of Templates, with and without arguments. + public static String generateWithTemplateArguments() { + // A Template with no arguments. + var templateHello = Template.make(() -> body( + """ + System.out.println("Hello"); + """ + )); + + // A Template with a single Integer argument. + var templateCompare = Template.make("arg", (Integer arg) -> body( + "System.out.println(", arg, ");\n", // capture arg via lambda argument + "System.out.println(#arg);\n", // capture arg via hashtag replacement + "System.out.println(#{arg});\n", // capture arg via hashtag replacement with brackets + // It would have been optimal to use Java String Templates to format + // argument values into Strings. However, since these are not (yet) + // available, the Template Framework provides two alternative ways of + // formatting Strings: + // 1) By appending to the comma-separated list of Tokens passed to body(). + // Appending as a Token works whenever one has a reference to the Object + // in Java code. But often, this is rather cumbersome and looks awkward, + // given all the additional quotes and commands required. Hence, it + // is encouraged to only use this method when necessary. + // 2) By hashtag replacements inside a single string. One can either + // use "#arg" directly, or use brackets "#{arg}". When possible, one + // should prefer avoiding the brackets, as they create additional + // noise. However, there are cases where they are useful, for + // example "#TYPE_CON" would be parsed as a hashtag replacement + // for the hashtag name "TYPE_CON", whereas "#{TYPE}_CON" is + // parsed as hashtag name "TYPE", followed by literal string "_CON". + // See also: generateWithHashtagAndDollarReplacements2 + // There are two ways to define the value of a hashtag replacement: + // a) Capturing Template arguments as Strings. + // b) Using a "let" definition (see examples further down). + // Which one should be preferred is a code style question. Generally, we + // prefer the use of hashtag replacements because that allows easy use of + // multiline strings (i.e. text blocks). + "if (#arg != ", arg, ") { throw new RuntimeException(\"mismatch\"); }\n" + )); + + // A Template that creates the body of the Class and main method, and then + // uses the two Templates above inside it. + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest2 { + public static void main() { + """, + templateHello.asToken(), + templateCompare.asToken(7), + templateCompare.asToken(42), + """ + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // Example with hashtag replacements (arguments and let), and $-name renamings. + // Note: hashtag replacements are a workaround for the missing string templates. + // If we had string templates, we could just capture the typed lambda + // arguments, and use them directly in the String via string templating. + public static String generateWithHashtagAndDollarReplacements() { + var template1 = Template.make("x", (Integer x) -> body( + // We have the "#x" hashtag replacement from the argument capture above. + // Additionally, we can define "#con" as a hashtag replacement from let: + let("con", 3 * x), + // In the code below, we use "var" as a local variable. But if we were + // to instantiate this template twice, the names could conflict. Hence, + // we automatically rename the names that have a $ prepended with + // var_1, var_2, etc. + """ + int $var = #con; + System.out.println("T1: #x, #con, " + $var); + """ + )); + + var template2 = Template.make("x", (Integer x) -> + // Sometimes it can be helpful to not just create a hashtag replacement + // with let, but also to capture the variable to use it as lambda parameter. + let("y", 11 * x, y -> + body( + """ + System.out.println("T2: #x, #y"); + """, + template1.asToken(y) + ) + ) + ); + + // This template generates an int variable and assigns it a value. + // Together with template4, we see that each template has a unique renaming + // for a $-name replacement. + var template3 = Template.make("name", "value", (String name, Integer value) -> body( + """ + int #name = #value; // Note: $var is not #name + """ + )); + + var template4 = Template.make(() -> body( + """ + // We will define the variable $var: + """, + // We can capture the $-name programmatically, and pass it to other templates: + template3.asToken($("var"), 42), + """ + if ($var != 42) { throw new RuntimeException("Wrong value!"); } + """ + )); + + var templateClass = Template.make(() -> body( + // The Template Framework API only guarantees that every Template use + // has a unique ID. When using the Templates, all we need is that + // variables from different Template uses do not conflict. But it can + // be helpful to understand how the IDs are produced. The implementation + // simply gives the first Template use the ID=1, and increments from there. + // + // In this example, the templateClass is the first Template use, and + // has ID=1. We never use a dollar replacement here, so the code will + // not show any "_1". + """ + package p.xyz; + + public class InnerTest3 { + public static void main() { + """, + // Second Template use: ID=2 -> var_2 + template1.asToken(1), + // Third Template use: ID=3 -> var_3 + template1.asToken(7), + // Fourth Template use with template2, no use of dollar, so + // no "_4" shows up in the generated code. Internally, it + // calls template1, which is the fifth Template use, with + // ID = 5 -> var_5 + template2.asToken(2), + // Sixth and Seventh Template use -> var_7 + template2.asToken(5), + // Eighth Template use with template4 -> var_8. + // Ninth Template use with internal call to template3, + // The local "$var" turns to "var_9", but the Template + // argument captured value = "var_8" from the outer + // template use of $("var"). + template4.asToken(), + """ + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // In some cases, you may want to transform string arguments. You may + // be working with types "int" and "long", and want to create names like + // "INT_CON" and "LONG_CON". + public static String generateWithHashtagAndDollarReplacements2() { + // Let us define some final static variables of a specific type. + var template1 = Template.make("type", (String type) -> body( + // The type (e.g. "int") is lower case, let us create the upper case "INT_CON" from it. + let("TYPE", type.toUpperCase()), + """ + static final #type #{TYPE}_CON = 42; + """ + )); + + // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest3b { + """, + template1.asToken("int"), + template1.asToken("long"), + """ + public static void main() { + if (INT_CON != 42 || LONG_CON != 42) { + throw new RuntimeException("Wrong result!"); + } + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // In this example, we look at the use of Hooks. They allow us to reach back, to outer + // scopes. For example, we can reach out from inside a method body to a hook anchored at + // the top of the class, and insert a field. + public static String generateWithCustomHooks() { + // We can define a custom hook. + // Note: generally we prefer using the pre-defined CLASS_HOOK and METHOD_HOOK from the library, + // whenever possible. See also the example after this one. + var myHook = new Hook("MyHook"); + + var template1 = Template.make("name", "value", (String name, Integer value) -> body( + """ + public static int #name = #value; + """ + )); + + var template2 = Template.make("x", (Integer x) -> body( + """ + // Let us go back to where we anchored the hook with anchor() and define a field named $field there. + // Note that in the Java code we have not defined anchor() on the hook, yet. But since it's a lambda + // expression, it is not evaluated, yet! Eventually, anchor() will be evaluated before insert() in + // this example. + """, + myHook.insert(template1.asToken($("field"), x)), + """ + System.out.println("$field: " + $field); + if ($field != #x) { throw new RuntimeException("Wrong value!"); } + """ + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest4 { + """, + // We anchor a Hook outside the main method, but inside the Class. + // Anchoring a Hook creates a scope, spanning the braces of the + // "anchor" call. Any Hook.insert that happens inside this scope + // goes to the top of that scope. + myHook.anchor( + // Any Hook.insert goes here. + // + // <-------- field_X = 5 ------------------+ + // <-------- field_Y = 7 -------------+ | + // | | + """ + public static void main() { + """, // ^ ^ + template2.asToken(5), // -------- | ---+ + template2.asToken(7), // ---------+ + """ + } + """ + ), // The Hook scope ends here. + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // We saw the use of custom hooks above, but now we look at the use of CLASS_HOOK and METHOD_HOOK. + // By convention, we use the CLASS_HOOK for class scopes, and METHOD_HOOK for method scopes. + // Whenever we open a class scope, we should anchor a CLASS_HOOK for that scope, and whenever we + // open a method, we should anchor a METHOD_HOOK. Conversely, this allows us to check if we are + // inside a class or method scope by querying "isAnchored". This convention helps us when building + // a large library of Templates. But if you are writing your own self-contained set of Templates, + // you do not have to follow this convention. + // + // Hooks are "re-entrant", that is we can anchor the same hook inside a scope that we already + // anchored it previously. The "Hook.insert" always goes to the innermost anchoring of that + // hook. There are cases where "re-entrant" Hooks are helpful such as nested classes, where + // there is a class scope inside another class scope. Similarly, we can nest lambda bodies + // inside method bodies, so also METHOD_HOOK can be used in such a "re-entrant" way. + public static String generateWithLibraryHooks() { + var templateStaticField = Template.make("name", "value", (String name, Integer value) -> body( + """ + static { System.out.println("Defining static field #name"); } + public static int #name = #value; + """ + )); + + var templateLocalVariable = Template.make("name", "value", (String name, Integer value) -> body( + """ + System.out.println("Defining local variable #name"); + int #name = #value; + """ + )); + + var templateMethodBody = Template.make(() -> body( + """ + // Let's define a local variable $var and a static field $field. + """, + Hooks.CLASS_HOOK.insert(templateStaticField.asToken($("field"), 5)), + Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken($("var"), 11)), + """ + System.out.println("$field: " + $field); + System.out.println("$var: " + $var); + if ($field * $var != 55) { throw new RuntimeException("Wrong value!"); } + """ + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest5 { + """, + // Class Hook for fields. + Hooks.CLASS_HOOK.anchor( + """ + public static void main() { + """, + // Method Hook for local variables, and earlier computations. + Hooks.METHOD_HOOK.anchor( + """ + // This is the beginning of the "main" method body. + System.out.println("Welcome to main!"); + """, + templateMethodBody.asToken(), + """ + System.out.println("Going to call other..."); + other(); + """ + ), + """ + } + + private static void other() { + """, + // Have a separate method hook for other, so that it can insert + // its own local variables. + Hooks.METHOD_HOOK.anchor( + """ + System.out.println("Welcome to other!"); + """, + templateMethodBody.asToken(), + """ + System.out.println("Done with other."); + """ + ), + """ + } + """ + ), + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // This example shows the use of bindings to allow cyclic references of Templates, + // allowing recursive template generation. We also show the use of fuel to limit + // recursion. + public static String generateWithRecursionAndBindingsAndFuel() { + // Binding allows the use of template1 inside of template1, via the binding indirection. + var binding1 = new TemplateBinding>(); + var template1 = Template.make("depth", (Integer depth) -> body( + let("fuel", fuel()), + """ + System.out.println("At depth #depth with fuel #fuel."); + """, + // We cannot yet use template1 directly, as it is being defined. + // So we use binding1 instead. + // For every recursion depth, some fuel is automatically subtracted + // so that the fuel slowly depletes with the depth. + // We keep the recursion going until the fuel is depleted. + // + // Note: if we forget to check the fuel(), the renderer causes a + // StackOverflowException, because the recursion never ends. + (fuel() > 0) ? binding1.get().asToken(depth + 1) + : "System.out.println(\"Fuel depleted.\");\n", + """ + System.out.println("Exit depth #depth."); + """ + )); + binding1.bind(template1); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest6 { + public static void main() { + System.out.println("Welcome to main!"); + """, + template1.asToken(0), + """ + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // Below, we introduce the concept of "DataNames". Code generation often involves defining + // fields and variables, which are then available inside a defined scope. "DataNames" can + // be registered at a certain scope with addDataName. This "DataName" is then available + // in this scope, and in any nested scope, including nested Templates. This allows us to + // add some fields and variables in one Template, and later on, in another Template, we + // can access these fields and variables again with "dataNames()". + // + // Here are a few use-cases: + // - You are writing some inner Template, and would like to access a random field or + // variable from an outer Template. Luckily, the outer Templates have added their + // fields and variables, and you can now access them with "dataNames()". You can + // count them, get a list of them, or sample a random one. + // - You are writing some outer Template, and would like to generate a variable that + // an inner Template could read from or even write to. You can "addDataName" the + // variable, and the inner Template can then find that variable in "dataNames()". + // If the inner Template wants to find a random field or variable, it may sample + // from "dataNodes()", and with some probability, it would sample your variable. + // + // A "DataName" captures the name of the field or variable in a String. It also + // stores the type of the field or variable, as well as its "mutability", i.e. + // an indication if the field or variable is only for reading, or if writing to + // it is also allowed. If a field or variable is final, we must make sure that the + // "DataName" is immutable, otherwise we risk that some Template attempts to generate + // code that writes to the final field or variable, and then we get a compilation + // error from "javac" later on. + // + // To get started, we show an example where all DataNames have the same type, and where + // all Names are mutable. For simplicity, our type represents the primitive int type. + private record MySimpleInt() implements DataName.Type { + // The type is only subtype of itself. This is relevant when sampling or weighing + // DataNames, because we do not just sample from the given type, but also its subtypes. + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MySimpleInt(); + } + + // The name of the type can later be accessed, and used in code. We are working + // with ints, so that is what we return. + @Override + public String name() { return "int"; } + } + private static final MySimpleInt mySimpleInt = new MySimpleInt(); + + // In this example, we generate 3 fields, and add their names to the + // current scope. In a nested Template, we can then sample one of these + // DataNames, which gives us one of the fields. We increment that randomly + // chosen field. At the end, we print all three fields. + public static String generateWithDataNamesSimple() { + var templateMain = Template.make(() -> body( + // Sample a random DataName, i.e. field, and assign its name to + // the hashtag replacement "#f". + // We are picking a mutable DataName, because we are not just + // reading but also writing to the field. + let("f", dataNames(MUTABLE).exactOf(mySimpleInt).sample().name()), + """ + // Let us now sample a random field #f, and increment it. + #f += 42; + """ + )); + + var templateClass = Template.make(() -> body( + // Let us define the names for the three fields. + // We can then sample from these names in a nested Template. + // We make all DataNames mutable, and with the same weight of 1, + // so that they have equal probability of being sampled. + // Note: the default weight is 1, so we can also omit the weight. + addDataName($("f1"), mySimpleInt, MUTABLE, 1), + addDataName($("f2"), mySimpleInt, MUTABLE, 1), + addDataName($("f3"), mySimpleInt, MUTABLE), // omit weight, default is 1. + """ + package p.xyz; + + public class InnerTest7 { + // Let us define some fields. + public static int $f1 = 0; + public static int $f2 = 0; + public static int $f3 = 0; + + public static void main() { + // Let us now call the nested template that samples + // a random field and increments it. + """, + templateMain.asToken(), + """ + // Now, we can print all three fields, and see which + // one was incremented. + System.out.println("f1: " + $f1); + System.out.println("f2: " + $f2); + System.out.println("f3: " + $f3); + // We have two zeros, and one 42. + if ($f1 + $f2 + $f3 != 42) { throw new RuntimeException("wrong result!"); } + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // In the example above, we could have easily kept track of the three fields ourselves, + // and would not have had to rely on the Template Framework's DataNames for this. However, + // with more complicated examples, this gets more and more difficult, if not impossible. + // + // In the example below, we make the scenario a little more realistic. We work with an + // int and a long type. In the main method, we add some fields and local variables, and + // register their DataNames. When sampling from the main method, we should be able to see + // both fields and variables that we just registered. But from another method, we should + // only see the fields, but the local variables from main should not be sampled. + // + // Let us now define the wrapper for primitive types such as int and long. + private record MyPrimitive(String name) implements DataName.Type { + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MyPrimitive(String n) && n.equals(name()); + } + + // Note: the name method is automatically overridden by the record + // field accessor. + // But we would like to also directly use the type in the templates, + // hence we let "toString" return "int" or "long". + @Override + public String toString() { return name(); } + } + private static final MyPrimitive myInt = new MyPrimitive("int"); + private static final MyPrimitive myLong = new MyPrimitive("long"); + + public static String generateWithDataNamesForFieldsAndVariables() { + // Define a static field. + var templateStaticField = Template.make("type", (DataName.Type type) -> body( + addDataName($("field"), type, MUTABLE), + // Note: since we have overridden MyPrimitive::toString, we can use + // the type directly as "#type" in the template, which then + // gets hashtag replaced with "int" or "long". + """ + public static #type $field = 0; + """ + )); + + // Define a local variable. + var templateLocalVariable = Template.make("type", (DataName.Type type) -> body( + addDataName($("var"), type, MUTABLE), + """ + #type $var = 0; + """ + )); + + // Sample a random field or variable, from those that are available at + // the current scope. + var templateSample = Template.make("type", (DataName.Type type) -> body( + let("name", dataNames(MUTABLE).exactOf(type).sample().name()), + // Note: we could also sample from MUTABLE_OR_IMMUTABLE, we will + // cover the concept of mutability in an example further down. + """ + System.out.println("Sampling type #type: #name = " + #name); + """ + )); + + // Check how many fields and variables are available at the current scope. + var templateStatus = Template.make(() -> body( + let("ints", dataNames(MUTABLE).exactOf(myInt).count()), + let("longs", dataNames(MUTABLE).exactOf(myLong).count()), + // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will + // cover the concept of mutability in an example further down. + """ + System.out.println("Status: #ints ints, #longs longs."); + """ + )); + + // Definition of the main method body. + var templateMain = Template.make(() -> body( + """ + System.out.println("Starting inside main..."); + """, + // Check the initial status, there should be nothing available. + templateStatus.asToken(), + // Define some local variables. We place them at the beginning of + // the method, by using the METHOD_HOOK. + Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken(myInt)), + Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken(myLong)), + // Define some static fields. We place them at the top of the class, + // by using the CLASS_HOOK. + Hooks.CLASS_HOOK.insert(templateStaticField.asToken(myInt)), + Hooks.CLASS_HOOK.insert(templateStaticField.asToken(myLong)), + // If we check the status now, we should see two int and two + // long names, corresponding to our two fields and variables. + templateStatus.asToken(), + // Now, we sample 5 int and 5 long names. We should get a mix + // of fields and variables. We have access to the fields because + // we inserted them to the class scope. We have access to the + // variables because we inserted them to the current method + // body. + Collections.nCopies(5, templateSample.asToken(myInt)), + Collections.nCopies(5, templateSample.asToken(myLong)), + // The status should not have changed since we last checked. + templateStatus.asToken(), + """ + System.out.println("Finishing inside main."); + """ + )); + + // Definition of another method's body. It is in the same class + // as the main method, so it has access to the same static fields. + var templateOther = Template.make(() -> body( + """ + System.out.println("Starting inside other..."); + """, + // We should see the fields defined in the main body, + // one int and one long field. + templateStatus.asToken(), + // Sampling 5 random int and 5 random long DataNames. We should + // only see the fields, and not the local variables from main. + Collections.nCopies(5, templateSample.asToken(myInt)), + Collections.nCopies(5, templateSample.asToken(myLong)), + // The status should not have changed since we last checked. + templateStatus.asToken(), + """ + System.out.println("Finishing inside other."); + """ + )); + + // Finally, we put it all together in a class. + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest8 { + """, + // Class Hook for fields. + Hooks.CLASS_HOOK.anchor( + """ + public static void main() { + """, + // Method Hook for local variables. + Hooks.METHOD_HOOK.anchor( + """ + // This is the beginning of the "main" method body. + System.out.println("Welcome to main!"); + """, + templateMain.asToken(), + """ + System.out.println("Going to call other..."); + other(); + """ + ), + """ + } + + private static void other() { + """, + // Have a separate method hook for other, where it could insert + // its own local variables (but happens not to). + Hooks.METHOD_HOOK.anchor( + """ + System.out.println("Welcome to other!"); + """, + templateOther.asToken(), + """ + System.out.println("Done with other."); + """ + ), + """ + } + """ + ), + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // Let us have a closer look at how DataNames interact with scopes created by + // Templates and Hooks. Additionally, we see how the execution order of the + // lambdas and token evaluation affects the availability of DataNames. + // + // We inject the results directly into verification inside the code, so it + // is relatively simple to see what the expected results are. + // + // For simplicity, we define a simple "list" function. It collects all + // field and variable names, and immediately returns the comma separated + // list of the names. We can use that to visualize the available names + // at any point. + public static String listNames() { + return "{" + String.join(", ", dataNames(MUTABLE).exactOf(myInt).toList() + .stream().map(DataName::name).toList()) + "}"; + } + + // Even simpler: count the available variables and return the count immediately. + public static int countNames() { + return dataNames(MUTABLE).exactOf(myInt).count(); + } + + // Having defined these helper methods, let us start with the first example. + // You should start reading this example bottom-up, starting at + // templateClass, then going to templateMain and last to templateInner. + public static String generateWithDataNamesAndScopes1() { + + var templateInner = Template.make(() -> body( + // We just got called from the templateMain. All tokens from there + // are already evaluated, so "v1" is now available: + let("l1", listNames()), + """ + if (!"{v1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{v1}' but was '#l1'"); } + """ + )); + + var templateMain = Template.make(() -> body( + // So far, no names were defined. We expect "c1" to be zero. + let("c1", countNames()), + """ + if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } + """, + // We now add a local variable "v1" to the scope of this templateMain. + // This only generates a token, and does not immediately add the name. + // The name is only added once we evaluate the tokens, and arrive at + // this particular token. + addDataName("v1", myInt, MUTABLE), + // We count again with "c2". The variable "v1" is at this point still + // in token form, hence it is not yet made available while executing + // the template lambda of templateMain. + let("c2", countNames()), + """ + if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } + """, + // But now we call an inner Template. This is added as a TemplateToken. + // This means it is not evaluated immediately, but only once we evaluate + // the tokens. By that time, all tokens from above are already evaluated + // and we see that "v1" is available. + templateInner.asToken() + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest9a { + """, + Hooks.CLASS_HOOK.anchor( + """ + public static void main() { + """, + Hooks.METHOD_HOOK.anchor( + templateMain.asToken() + ), + """ + } + """ + ), + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + // Now that we understand this simple example, we go to a more complicated one + // where we use Hook.insert. Just as above, you should read this example + // bottom-up, starting at templateClass. + public static String generateWithDataNamesAndScopes2() { + + var templateFields = Template.make(() -> body( + // We were just called from templateMain. But the code is not + // generated into the main scope, rather into the class scope + // out in templateClass. + // Let us now add a field "f1". + addDataName("f1", myInt, MUTABLE), + // And let's also generate the code for it. + """ + public static int f1 = 42; + """, + // But why is this DataName now available inside the scope of + // templateInner? Does that not mean that "f1" escapes this + // templateFields here? Yes it does! + // For normal template nesting, the names do not escape the + // scope of the nested template. But this here is no normal + // template nesting, rather it is an insertion into a Hook, + // and we treat those differently. We make the scope of the + // inserted templateFields transparent, so that any added + // DataNames are added to the scope of the Hook we just + // inserted into, i.e. the CLASS_HOOK. This is very important, + // if we did not make that scope transparent, we could not + // add any DataNames to the class scope anymore, and we could + // not add any fields that would be available in the class + // scope. + Hooks.METHOD_HOOK.anchor( + // We now create a separate scope. This one is not the + // template scope from above, and it is not transparent. + // Hence, "f2" will not be available outside of this + // scope. + addDataName("f2", myInt, MUTABLE), + // And let's also generate the code for it. + """ + public static int f2 = 666; + """ + // Similarly, if we called any nested Template here, + // and added DataNames inside, this would happen inside + // nested scopes that are not transparent. If one wanted + // to add names to the CLASS_HOOK from there, one would + // have to do another Hook.insert, and make sure that + // the names are added from the outermost scope of that + // inserted Template, because only that outermost scope + // is transparent to the CLASS_HOOK. + ) + )); + + var templateInner = Template.make(() -> body( + // We just got called from the templateMain. All tokens from there + // are already evaluated, so there should be some fields available. + // We can see field "f1". + let("l1", listNames()), + """ + if (!"{f1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{f1}' but was '#l1'"); } + """ + // Now go and have a look at templateFields, to understand how that + // field was added, and why not any others. + )); + + var templateMain = Template.make(() -> body( + // So far, no names were defined. We expect "c1" to be zero. + let("c1", countNames()), + """ + if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } + """, + // We would now like to add some fields to the class scope, out in the + // templateClass. This creates a token, which is only evaluated after + // the completion of the templateMain lambda. Before you go and look + // at templateFields, just assume that it does add some fields, and + // continue reading in templateMain. + Hooks.CLASS_HOOK.insert(templateFields.asToken()), + // We count again with "c2". The fields we wanted to add above are not + // yet available, because the token is not yet evaluated. Hence, we + // still only count zero names. + let("c2", countNames()), + """ + if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } + """, + // Now we call an inner Template. This also creates a token, and so it + // is not evaluated immediately. And by the time this token is evaluated + // the tokens from above are already evaluated, and so the fields should + // be available. Go have a look at templateInner now. + templateInner.asToken() + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest9b { + """, + Hooks.CLASS_HOOK.anchor( + """ + public static void main() { + """, + Hooks.METHOD_HOOK.anchor( + templateMain.asToken() + ), + """ + } + """ + ), + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + + + // There are two more concepts to understand more deeply with DataNames. + // + // One is the use of mutable and immutable DataNames. + // In some cases, we only want to sample DataNames that are mutable, because + // we want to store to a field or variable. We have to make sure that we + // do not generate code that tries to store to a final field or variable. + // In other cases, we only want to load, and we do not care if the + // fields or variables are final or non-final. + // + // Another concept is subtyping of DataName Types. With primitive types, this + // is irrelevant, but with instances of Objects, this becomes relevant. + // We may want to load an object of any field or variable of a certain + // class, or any subclass. When a value is of a given class, we can only + // store it to fields and variables of that class or any superclass. + // + // Let us look at an example that demonstrates these two concepts. + // + // First, we define a DataName Type that represents different classes, that + // may or may not be in a subtype relation. Subtypes start with the name + // of the super type. + private record MyClass(String name) implements DataName.Type { + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MyClass(String n) && name().startsWith(n); + } + + @Override + public String toString() { return name(); } + } + private static final MyClass myClassA = new MyClass("MyClassA"); + private static final MyClass myClassA1 = new MyClass("MyClassA1"); + private static final MyClass myClassA2 = new MyClass("MyClassA2"); + private static final MyClass myClassA11 = new MyClass("MyClassA11"); + private static final MyClass myClassB = new MyClass("MyClassB"); + private static final List myClassList = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); + + public static String generateWithDataNamesForFuzzing() { + var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> body( + addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), + let("isFinal", mutable ? "" : "final"), + """ + public static #isFinal #type $field = new #type(); + """ + )); + + var templateLoad = Template.make("type", (DataName.Type type) -> body( + // We only load from the field, so we do not need a mutable one, + // we can load from final and non-final fields. + // We want to find any field from which we can read the value and store + // it in our variable v of our given type. Hence, we can take a field + // of the given type or any subtype thereof. + let("field", dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sample().name()), + """ + #type $v = #field; + System.out.println("#field: " + $v); + """ + )); + + var templateStore = Template.make("type", (DataName.Type type) -> body( + // We are storing to a field, so it better be non-final, i.e. mutable. + // We want to store a new instance of our given type to a field. This + // field must be of the given type or any supertype. + let("field", dataNames(MUTABLE).supertypeOf(type).sample().name()), + """ + #field = new #type(); + """ + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest10 { + // First, we define our classes. + public static class MyClassA {} + public static class MyClassA1 extends MyClassA {} + public static class MyClassA2 extends MyClassA {} + public static class MyClassA11 extends MyClassA1 {} + public static class MyClassB {} + + // Now, we define a list of static fields. Some of them are final, others not. + """, + // We must create a CLASS_HOOK and insert the fields to it. Otherwise, + // addDataName is restricted to the scope of the templateStaticField. But + // with the insertion to CLASS_HOOK, the addDataName goes through the scope + // of the templateStaticField out to the scope of the CLASS_HOOK. + Hooks.CLASS_HOOK.anchor( + myClassList.stream().map(c -> + (Object)Hooks.CLASS_HOOK.insert(templateStaticField.asToken(c, true)) + ).toList(), + myClassList.stream().map(c -> + (Object)Hooks.CLASS_HOOK.insert(templateStaticField.asToken(c, false)) + ).toList(), + """ + + public static void main() { + // All fields are still in their initial state. + """, + myClassList.stream().map(templateLoad::asToken).toList(), + """ + // Now let us mutate some fields. + """, + myClassList.stream().map(templateStore::asToken).toList(), + """ + // And now some fields are different than before. + """, + myClassList.stream().map(templateLoad::asToken).toList(), + """ + } + """ + ), + """ + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + + } + + // "DataNames" are useful for modeling fields and variables. They hold data, + // and we can read and write to them, they may be mutable or immutable. + // We now introduce another set of "Names", the "StructuralNames". They are + // useful for modeling method names and class names, and possibly more. Anything + // that has a fixed name in the Java code, for which mutability is inapplicable. + // Some use-cases for "StructuralNames": + // - Method names. The Type could represent the signature of the static method + // or the class of the non-static method. + // - Class names. Type could represent the signature of the constructor, so + // that we could instantiate random instances. + // - try/catch blocks. If a specific Exception is caught in the scope, we could + // register that Exception, and in the inner scope we can + // check if there is any "StructuralName" for an Exception + // and its subtypes - if so, we know the exception would be + // caught. + // + // Let us look at an example with Method names. But for simplicity, we assume they + // all have the same signature: they take two int arguments and return an int. + // + // Should you ever work on a test where there are methods with different signatures, + // then you would have to very carefully study and design the subtype relation between + // methods. You may want to read up about covariance and contravariance. This + // example ignores all of that, because we only have "(II)I" methods. + private record MyMethodType() implements StructuralName.Type { + @Override + public boolean isSubtypeOf(StructuralName.Type other) { + return other instanceof MyMethodType(); + } + + @Override + public String name() { return ""; } + } + private static final MyMethodType myMethodType = new MyMethodType(); + + public static String generateWithStructuralNamesForMethods() { + // Define a method, which takes two ints, returns the result of op. + var templateMethod = Template.make("op", (String op) -> body( + // Register the method name, so we can later sample. + addStructuralName($("methodName"), myMethodType), + """ + public static int $methodName(int a, int b) { + return a #op b; + } + """ + )); + + var templateSample = Template.make(() -> body( + // Sample a random method, and retrieve its name. + let("methodName", structuralNames().exactOf(myMethodType).sample().name()), + """ + System.out.println("Calling #methodName with inputs 7 and 11"); + System.out.println(" result: " + #methodName(7, 11)); + """ + )); + + var templateClass = Template.make(() -> body( + """ + package p.xyz; + + public class InnerTest11 { + // Let us define some methods that we can sample from later. + """, + // We must anchor a CLASS_HOOK here, and insert the method definitions to that hook. + Hooks.CLASS_HOOK.anchor( + // If we directly nest the templateMethod, then the addStructuralName goes to the nested + // scope, and is not available at the class scope, i.e. it is not visible + // for sampleStructuralName outside of the templateMethod. + // DO NOT DO THIS, the nested addStructuralName will not be visible: + "// We cannot sample from the following methods:\n", + templateMethod.asToken("+"), + templateMethod.asToken("-"), + // However, if we insert to the CLASS_HOOK, then the Renderer makes the + // scope of the inserted templateMethod transparent, and the addStructuralName + // goes out to the scope of the CLASS_HOOK (but no further than that). + // RATHER, DO THIS to ensure the addStructuralName is visible: + Hooks.CLASS_HOOK.insert(templateMethod.asToken("*")), + Hooks.CLASS_HOOK.insert(templateMethod.asToken("|")), + Hooks.CLASS_HOOK.insert(templateMethod.asToken("&")), + """ + + public static void main() { + // Now, we call some random methods, but only those that were inserted + // to the CLASS_HOOK. + """, + Collections.nCopies(10, templateSample.asToken()), + """ + } + } + """ + ) + )); + + // Render templateClass to String. + return templateClass.render(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java new file mode 100644 index 00000000000..fe267a3ff63 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java @@ -0,0 +1,111 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8344942 + * @summary Test formatting of Integer, Long, Float and Double. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main template_framework.tests.TestFormat + */ + +package template_framework.tests; + +import java.util.List; +import java.util.ArrayList; + +import compiler.lib.compile_framework.*; +import compiler.lib.generators.*; +import compiler.lib.verify.*; +import compiler.lib.template_framework.Template; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +public class TestFormat { + record FormatInfo(int id, String type, Object value) {} + + public static void main(String[] args) { + List list = new ArrayList<>(); + + for (int i = 0; i < 1000; i++) { + int v = Generators.G.ints().next(); + list.add(new FormatInfo(i, "int", v)); + } + + for (int i = 1000; i < 2000; i++) { + long v = Generators.G.longs().next(); + list.add(new FormatInfo(i, "long", v)); + } + + for (int i = 2000; i < 3000; i++) { + float v = Generators.G.floats().next(); + list.add(new FormatInfo(i, "float", v)); + } + + for (int i = 3000; i < 4000; i++) { + double v = Generators.G.doubles().next(); + list.add(new FormatInfo(i, "double", v)); + } + + CompileFramework comp = new CompileFramework(); + comp.addJavaSourceCode("p.xyz.InnerTest", generate(list)); + comp.compile(); + + // Run each of the "get" methods, and check the result. + for (FormatInfo info : list) { + Object ret1 = comp.invoke("p.xyz.InnerTest", "get_let_" + info.id, new Object[] {}); + Object ret2 = comp.invoke("p.xyz.InnerTest", "get_token_" + info.id, new Object[] {}); + System.out.println("id: " + info.id + " -> " + info.value + " == " + ret1 + " == " + ret2); + Verify.checkEQ(ret1, info.value); + Verify.checkEQ(ret2, info.value); + } + } + + private static String generate(List list) { + // Generate 2 "get" methods, one that formats via "let" (hashtag), the other via direct token. + var template1 = Template.make("info", (FormatInfo info) -> body( + let("id", info.id()), + let("type", info.type()), + let("value", info.value()), + """ + public static #type get_let_#id() { return #value; } + """, + "public static #type get_token_#id() { return ", info.value(), "; }\n" + )); + + // For each FormatInfo in list, generate the "get" methods inside InnerTest class. + var template2 = Template.make(() -> body( + """ + package p.xyz; + public class InnerTest { + """, + list.stream().map(info -> template1.asToken(info)).toList(), + """ + } + """ + )); + + return template2.render(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java new file mode 100644 index 00000000000..35d020b6080 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java @@ -0,0 +1,2253 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8344942 + * @summary Test some basic Template instantiations. We do not necessarily generate correct + * java code, we just test that the code generation deterministically creates the + * expected String. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main template_framework.tests.TestTemplate + */ + +package template_framework.tests; + +import java.util.Arrays; +import java.util.List; +import java.util.HashSet; + +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.StructuralName; +import compiler.lib.template_framework.Hook; +import compiler.lib.template_framework.TemplateBinding; +import compiler.lib.template_framework.RendererException; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.$; +import static compiler.lib.template_framework.Template.let; +import static compiler.lib.template_framework.Template.fuel; +import static compiler.lib.template_framework.Template.setFuelCost; +import static compiler.lib.template_framework.Template.addDataName; +import static compiler.lib.template_framework.Template.dataNames; +import static compiler.lib.template_framework.Template.addStructuralName; +import static compiler.lib.template_framework.Template.structuralNames; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE; +import static compiler.lib.template_framework.DataName.Mutability.IMMUTABLE; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE_OR_IMMUTABLE; + +/** + * The tests in this file are mostly there to ensure that the Template Rendering + * works correctly, and not that we produce compilable Java code. Rather, we + * produce deterministic output, and compare it to the expected String. + * Still, this file may be helpful for learning more about how Templates + * work and can be used. + * + * We assume that you have already studied {@code TestTutorial.java}. + */ +public class TestTemplate { + // Interface for failing tests. + interface FailingTest { + void run(); + } + + // Define a simple type to model primitive types. + private record MyPrimitive(String name) implements DataName.Type { + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MyPrimitive(String n) && n.equals(name()); + } + + @Override + public String toString() { return name(); } + } + private static final MyPrimitive myInt = new MyPrimitive("int"); + private static final MyPrimitive myLong = new MyPrimitive("long"); + + // Simulate classes. Subtypes start with the name of the super type. + private record MyClass(String name) implements DataName.Type { + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MyClass(String n) && name().startsWith(n); + } + + @Override + public String toString() { return name(); } + } + private static final MyClass myClassA = new MyClass("myClassA"); + private static final MyClass myClassA1 = new MyClass("myClassA1"); + private static final MyClass myClassA2 = new MyClass("myClassA2"); + private static final MyClass myClassA11 = new MyClass("myClassA11"); + private static final MyClass myClassB = new MyClass("myClassB"); + + // Simulate some structural types. + private record MyStructuralType(String name) implements StructuralName.Type { + @Override + public boolean isSubtypeOf(StructuralName.Type other) { + return other instanceof MyStructuralType(String n) && name().startsWith(n); + } + + @Override + public String toString() { return name(); } + } + private static final MyStructuralType myStructuralTypeA = new MyStructuralType("StructuralA"); + private static final MyStructuralType myStructuralTypeA1 = new MyStructuralType("StructuralA1"); + private static final MyStructuralType myStructuralTypeA2 = new MyStructuralType("StructuralA2"); + private static final MyStructuralType myStructuralTypeA11 = new MyStructuralType("StructuralA11"); + private static final MyStructuralType myStructuralTypeB = new MyStructuralType("StructuralB"); + + public static void main(String[] args) { + // The following tests all pass, i.e. have no errors during rendering. + testSingleLine(); + testMultiLine(); + testBodyTokens(); + testWithOneArgument(); + testWithTwoArguments(); + testWithThreeArguments(); + testNested(); + testHookSimple(); + testHookIsAnchored(); + testHookNested(); + testHookWithNestedTemplates(); + testHookRecursion(); + testDollar(); + testLet(); + testDollarAndHashtagBrackets(); + testSelector(); + testRecursion(); + testFuel(); + testFuelCustom(); + testDataNames1(); + testDataNames2(); + testDataNames3(); + testDataNames4(); + testDataNames5(); + testStructuralNames1(); + testStructuralNames2(); + testListArgument(); + + // The following tests should all fail, with an expected exception and message. + expectRendererException(() -> testFailingNestedRendering(), "Nested render not allowed."); + expectRendererException(() -> $("name"), "A Template method such as"); + expectRendererException(() -> let("x","y"), "A Template method such as"); + expectRendererException(() -> fuel(), "A Template method such as"); + expectRendererException(() -> setFuelCost(1.0f), "A Template method such as"); + expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), "A Template method such as"); + expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).sample(), "A Template method such as"); + expectRendererException(() -> (new Hook("abc")).isAnchored(), "A Template method such as"); + expectRendererException(() -> testFailingDollarName1(), "Is not a valid '$' name: ''."); + expectRendererException(() -> testFailingDollarName2(), "Is not a valid '$' name: '#abc'."); + expectRendererException(() -> testFailingDollarName3(), "Is not a valid '$' name: 'abc#'."); + expectRendererException(() -> testFailingDollarName4(), "A '$' name should not be null."); + expectRendererException(() -> testFailingDollarName5(), "Is not a valid '$' replacement pattern: '$' in '$'."); + expectRendererException(() -> testFailingDollarName6(), "Is not a valid '$' replacement pattern: '$' in 'asdf$'."); + expectRendererException(() -> testFailingDollarName7(), "Is not a valid '$' replacement pattern: '$1' in 'asdf$1'."); + expectRendererException(() -> testFailingDollarName8(), "Is not a valid '$' replacement pattern: '$' in 'abc$$abc'."); + expectRendererException(() -> testFailingLetName1(), "A hashtag replacement should not be null."); + expectRendererException(() -> testFailingHashtagName1(), "Is not a valid hashtag replacement name: ''."); + expectRendererException(() -> testFailingHashtagName2(), "Is not a valid hashtag replacement name: 'abc#abc'."); + expectRendererException(() -> testFailingHashtagName3(), "Is not a valid hashtag replacement name: ''."); + expectRendererException(() -> testFailingHashtagName4(), "Is not a valid hashtag replacement name: 'xyz#xyz'."); + expectRendererException(() -> testFailingHashtagName5(), "Is not a valid '#' replacement pattern: '#' in '#'."); + expectRendererException(() -> testFailingHashtagName6(), "Is not a valid '#' replacement pattern: '#' in 'asdf#'."); + expectRendererException(() -> testFailingHashtagName7(), "Is not a valid '#' replacement pattern: '#1' in 'asdf#1'."); + expectRendererException(() -> testFailingHashtagName8(), "Is not a valid '#' replacement pattern: '#' in 'abc##abc'."); + expectRendererException(() -> testFailingDollarHashtagName1(), "Is not a valid '#' replacement pattern: '#' in '#$'."); + expectRendererException(() -> testFailingDollarHashtagName2(), "Is not a valid '$' replacement pattern: '$' in '$#'."); + expectRendererException(() -> testFailingDollarHashtagName3(), "Is not a valid '#' replacement pattern: '#' in '#$name'."); + expectRendererException(() -> testFailingDollarHashtagName4(), "Is not a valid '$' replacement pattern: '$' in '$#name'."); + expectRendererException(() -> testFailingHook(), "Hook 'Hook1' was referenced but not found!"); + expectRendererException(() -> testFailingSample1(), "No variable: MUTABLE, subtypeOf(int), supertypeOf(int)."); + expectRendererException(() -> testFailingHashtag1(), "Duplicate hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag2(), "Duplicate hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag3(), "Duplicate hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag4(), "Missing hashtag replacement for #a"); + expectRendererException(() -> testFailingBinding1(), "Duplicate 'bind' not allowed."); + expectRendererException(() -> testFailingBinding2(), "Cannot 'get' before 'bind'."); + expectIllegalArgumentException(() -> body(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> body("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> body(new Hook("Hook1")), "Unexpected token:"); + Hook hook1 = new Hook("Hook1"); + expectIllegalArgumentException(() -> hook1.anchor(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> hook1.anchor("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> hook1.anchor(hook1), "Unexpected token:"); + expectIllegalArgumentException(() -> testFailingAddDataName1(), "Unexpected mutability: MUTABLE_OR_IMMUTABLE"); + expectIllegalArgumentException(() -> testFailingAddDataName2(), "Unexpected weight: "); + expectIllegalArgumentException(() -> testFailingAddDataName3(), "Unexpected weight: "); + expectIllegalArgumentException(() -> testFailingAddDataName4(), "Unexpected weight: "); + expectIllegalArgumentException(() -> testFailingAddStructuralName1(), "Unexpected weight: "); + expectIllegalArgumentException(() -> testFailingAddStructuralName2(), "Unexpected weight: "); + expectIllegalArgumentException(() -> testFailingAddStructuralName3(), "Unexpected weight: "); + expectUnsupportedOperationException(() -> testFailingSample2(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectRendererException(() -> testFailingAddNameDuplication1(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication2(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication3(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication4(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication5(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication6(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication7(), "Duplicate name:"); + expectRendererException(() -> testFailingAddNameDuplication8(), "Duplicate name:"); + } + + public static void testSingleLine() { + var template = Template.make(() -> body("Hello World!")); + String code = template.render(); + checkEQ(code, "Hello World!"); + } + + public static void testMultiLine() { + var template = Template.make(() -> body( + """ + Code on more + than a single line + """ + )); + String code = template.render(); + String expected = + """ + Code on more + than a single line + """; + checkEQ(code, expected); + } + + public static void testBodyTokens() { + // We can fill the body with Objects of different types, and they get concatenated. + // Lists get flattened into the body. + var template = Template.make(() -> body( + "start ", + Integer.valueOf(1), 1, + Long.valueOf(2), 2L, + Double.valueOf(3.4), 3.4, + Float.valueOf(5.6f), 5.6f, + List.of(" ", 1, " and ", 2), + " end" + )); + String code = template.render(); + checkEQ(code, "start 112L2L3.43.45.6f5.6f 1 and 2 end"); + } + + public static void testWithOneArgument() { + // Capture String argument via String name. + var template1 = Template.make("a", (String a) -> body("start #a end")); + checkEQ(template1.render("x"), "start x end"); + checkEQ(template1.render("a"), "start a end"); + checkEQ(template1.render("" ), "start end"); + + // Capture String argument via typed lambda argument. + var template2 = Template.make("a", (String a) -> body("start ", a, " end")); + checkEQ(template2.render("x"), "start x end"); + checkEQ(template2.render("a"), "start a end"); + checkEQ(template2.render("" ), "start end"); + + // Capture Integer argument via String name. + var template3 = Template.make("a", (Integer a) -> body("start #a end")); + checkEQ(template3.render(0 ), "start 0 end"); + checkEQ(template3.render(22 ), "start 22 end"); + checkEQ(template3.render(444), "start 444 end"); + + // Capture Integer argument via templated lambda argument. + var template4 = Template.make("a", (Integer a) -> body("start ", a, " end")); + checkEQ(template4.render(0 ), "start 0 end"); + checkEQ(template4.render(22 ), "start 22 end"); + checkEQ(template4.render(444), "start 444 end"); + + // Test Strings with backslashes: + var template5 = Template.make("a", (String a) -> body("start #a " + a + " end")); + checkEQ(template5.render("/"), "start / / end"); + checkEQ(template5.render("\\"), "start \\ \\ end"); + checkEQ(template5.render("\\\\"), "start \\\\ \\\\ end"); + } + + public static void testWithTwoArguments() { + // Capture 2 String arguments via String names. + var template1 = Template.make("a1", "a2", (String a1, String a2) -> body("start #a1 #a2 end")); + checkEQ(template1.render("x", "y"), "start x y end"); + checkEQ(template1.render("a", "b"), "start a b end"); + checkEQ(template1.render("", "" ), "start end"); + + // Capture 2 String arguments via typed lambda arguments. + var template2 = Template.make("a1", "a2", (String a1, String a2) -> body("start ", a1, " ", a2, " end")); + checkEQ(template2.render("x", "y"), "start x y end"); + checkEQ(template2.render("a", "b"), "start a b end"); + checkEQ(template2.render("", "" ), "start end"); + + // Capture 2 Integer arguments via String names. + var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start #a1 #a2 end")); + checkEQ(template3.render(0, 1 ), "start 0 1 end"); + checkEQ(template3.render(22, 33 ), "start 22 33 end"); + checkEQ(template3.render(444, 555), "start 444 555 end"); + + // Capture 2 Integer arguments via templated lambda arguments. + var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start ", a1, " ", a2, " end")); + checkEQ(template4.render(0, 1 ), "start 0 1 end"); + checkEQ(template4.render(22, 33 ), "start 22 33 end"); + checkEQ(template4.render(444, 555), "start 444 555 end"); + } + + public static void testWithThreeArguments() { + // Capture 3 String arguments via String names. + var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start #a1 #a2 #a3 end")); + checkEQ(template1.render("x", "y", "z"), "start x y z end"); + checkEQ(template1.render("a", "b", "c"), "start a b c end"); + checkEQ(template1.render("", "", "" ), "start end"); + + // Capture 3 String arguments via typed lambda arguments. + var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + checkEQ(template1.render("x", "y", "z"), "start x y z end"); + checkEQ(template1.render("a", "b", "c"), "start a b c end"); + checkEQ(template1.render("", "", "" ), "start end"); + + // Capture 3 Integer arguments via String names. + var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start #a1 #a2 #a3 end")); + checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); + checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); + checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); + + // Capture 2 Integer arguments via templated lambda arguments. + var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); + checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); + checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); + } + + public static void testNested() { + var template1 = Template.make(() -> body("proton")); + + var template2 = Template.make("a1", "a2", (String a1, String a2) -> body( + "electron #a1\n", + "neutron #a2\n" + )); + + var template3 = Template.make("a1", "a2", (String a1, String a2) -> body( + "Universe ", template1.asToken(), " {\n", + template2.asToken("up", "down"), + template2.asToken(a1, a2), + "}\n" + )); + + var template4 = Template.make(() -> body( + template3.asToken("low", "high"), + "{\n", + template3.asToken("42", "24"), + "}" + )); + + String code = template4.render(); + String expected = + """ + Universe proton { + electron up + neutron down + electron low + neutron high + } + { + Universe proton { + electron up + neutron down + electron 42 + neutron 24 + } + }"""; + checkEQ(code, expected); + } + + public static void testHookSimple() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make(() -> body("Hello\n")); + + var template2 = Template.make(() -> body( + "{\n", + hook1.anchor( + "World\n", + // Note: "Hello" from the template below will be inserted + // above "World" above. + hook1.insert(template1.asToken()) + ), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + Hello + World + }"""; + checkEQ(code, expected); + } + + public static void testHookIsAnchored() { + var hook1 = new Hook("Hook1"); + + var template0 = Template.make(() -> body("isAnchored: ", hook1.isAnchored(), "\n")); + + var template1 = Template.make(() -> body("Hello\n", template0.asToken())); + + var template2 = Template.make(() -> body( + "{\n", + template0.asToken(), + hook1.anchor( + "World\n", + template0.asToken(), + hook1.insert(template1.asToken()) + ), + template0.asToken(), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + isAnchored: false + Hello + isAnchored: true + World + isAnchored: true + isAnchored: false + }"""; + checkEQ(code, expected); + } + + public static void testHookNested() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("a", (String a) -> body("x #a x\n")); + + // Test nested use of hooks in the same template. + var template2 = Template.make(() -> body( + "{\n", + hook1.anchor(), // empty + "zero\n", + hook1.anchor( + template1.asToken("one"), + template1.asToken("two"), + hook1.insert(template1.asToken("intoHook1a")), + hook1.insert(template1.asToken("intoHook1b")), + template1.asToken("three"), + hook1.anchor( + template1.asToken("four"), + hook1.insert(template1.asToken("intoHook1c")), + template1.asToken("five") + ), + template1.asToken("six"), + hook1.anchor(), // empty + template1.asToken("seven"), + hook1.insert(template1.asToken("intoHook1d")), + template1.asToken("eight"), + hook1.anchor( + template1.asToken("nine"), + hook1.insert(template1.asToken("intoHook1e")), + template1.asToken("ten") + ), + template1.asToken("eleven") + ), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + zero + x intoHook1a x + x intoHook1b x + x intoHook1d x + x one x + x two x + x three x + x intoHook1c x + x four x + x five x + x six x + x seven x + x eight x + x intoHook1e x + x nine x + x ten x + x eleven x + }"""; + checkEQ(code, expected); + } + + public static void testHookWithNestedTemplates() { + var hook1 = new Hook("Hook1"); + var hook2 = new Hook("Hook2"); + + var template1 = Template.make("a", (String a) -> body("x #a x\n")); + + var template2 = Template.make("b", (String b) -> body( + "{\n", + template1.asToken(b + "A"), + hook1.insert(template1.asToken(b + "B")), + hook2.insert(template1.asToken(b + "C")), + template1.asToken(b + "D"), + hook1.anchor( + template1.asToken(b + "E"), + hook1.insert(template1.asToken(b + "F")), + hook2.insert(template1.asToken(b + "G")), + template1.asToken(b + "H"), + hook2.anchor( + template1.asToken(b + "I"), + hook1.insert(template1.asToken(b + "J")), + hook2.insert(template1.asToken(b + "K")), + template1.asToken(b + "L") + ), + template1.asToken(b + "M"), + hook1.insert(template1.asToken(b + "N")), + hook2.insert(template1.asToken(b + "O")), + template1.asToken(b + "O") + ), + template1.asToken(b + "P"), + hook1.insert(template1.asToken(b + "Q")), + hook2.insert(template1.asToken(b + "R")), + template1.asToken(b + "S"), + "}\n" + )); + + // Test use of hooks across templates. + var template3 = Template.make(() -> body( + "{\n", + "base-A\n", + hook1.anchor( + "base-B\n", + hook2.anchor( + "base-C\n", + template2.asToken("sub-"), + "base-D\n" + ), + "base-E\n" + ), + "base-F\n", + "}\n" + )); + + String code = template3.render(); + String expected = + """ + { + base-A + x sub-B x + x sub-Q x + base-B + x sub-C x + x sub-G x + x sub-O x + x sub-R x + base-C + { + x sub-A x + x sub-D x + x sub-F x + x sub-J x + x sub-N x + x sub-E x + x sub-H x + x sub-K x + x sub-I x + x sub-L x + x sub-M x + x sub-O x + x sub-P x + x sub-S x + } + base-D + base-E + base-F + } + """; + checkEQ(code, expected); + } + + public static void testHookRecursion() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("a", (String a) -> body("x #a x\n")); + + var template2 = Template.make("b", (String b) -> body( + "<\n", + template1.asToken(b + "A"), + hook1.insert(template1.asToken(b + "B")), // sub-B is rendered before template2. + template1.asToken(b + "C"), + "inner-hook-start\n", + hook1.anchor( + "inner-hook-end\n", + template1.asToken(b + "E"), + hook1.insert(template1.asToken(b + "E")), + template1.asToken(b + "F") + ), + ">\n" + )); + + // Test use of hooks across templates. + var template3 = Template.make(() -> body( + "{\n", + "hook-start\n", + hook1.anchor( + "hook-end\n", + hook1.insert(template2.asToken("sub-")), + "base-C\n" + ), + "base-D\n", + "}\n" + )); + + String code = template3.render(); + String expected = + """ + { + hook-start + x sub-B x + < + x sub-A x + x sub-C x + inner-hook-start + x sub-E x + inner-hook-end + x sub-E x + x sub-F x + > + hook-end + base-C + base-D + } + """; + checkEQ(code, expected); + } + + public static void testDollar() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("a", (String a) -> body("x $name #a x\n")); + + var template2 = Template.make("a", (String a) -> body( + "{\n", + "y $name #a y\n", + template1.asToken($("name")), + "}\n" + )); + + var template3 = Template.make(() -> body( + "{\n", + "$name\n", + "$name", "\n", + "z $name z\n", + "z$name z\n", + template1.asToken("name"), // does not capture -> literal "$name" + template1.asToken("$name"), // does not capture -> literal "$name" + template1.asToken($("name")), // capture replacement name "name_1" + hook1.anchor( + "$name\n" + ), + "break\n", + hook1.anchor( + "one\n", + hook1.insert(template1.asToken($("name"))), + "two\n", + template1.asToken($("name")), + "three\n", + hook1.insert(template2.asToken($("name"))), + "four\n" + ), + "}\n" + )); + + String code = template3.render(); + String expected = + """ + { + name_1 + name_1 + z name_1 z + zname_1 z + x name_2 name x + x name_3 $name x + x name_4 name_1 x + name_1 + break + x name_5 name_1 x + { + y name_7 name_1 y + x name_8 name_7 x + } + one + two + x name_6 name_1 x + three + four + } + """; + checkEQ(code, expected); + } + + public static void testLet() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("a", (String a) -> body( + "{\n", + "y #a y\n", + let("b", "<" + a + ">"), + "y #b y\n", + "}\n" + )); + + var template2 = Template.make("a", (Integer a) -> let("b", a * 10, b -> + body( + let("c", b * 3), + "abc = #a #b #c\n" + ) + )); + + var template3 = Template.make(() -> body( + "{\n", + let("x", "abc"), + template1.asToken("alpha"), + "break\n", + "x1 = #x\n", + hook1.anchor( + "x2 = #x\n", // leaks inside + template1.asToken("beta"), + let("y", "one"), + "y1 = #y\n" + ), + "break\n", + "y2 = #y\n", // leaks outside + "break\n", + template2.asToken(5), + "}\n" + )); + + String code = template3.render(); + String expected = + """ + { + { + y alpha y + y y + } + break + x1 = abc + x2 = abc + { + y beta y + y y + } + y1 = one + break + y2 = one + break + abc = 5 50 150 + } + """; + checkEQ(code, expected); + } + + public static void testDollarAndHashtagBrackets() { + var template1 = Template.make(() -> body( + let("xyz", "abc"), + let("xyz_", "def"), + let("xyz_klm", "ghi"), + let("klm", "jkl"), + """ + no bracket: #xyz #xyz_klm #xyz_#klm + no bracket: $var $var_two $var_$two + with bracket: #{xyz} #{xyz_klm} #{xyz}_#{klm} + with bracket: ${var} ${var_two} ${var}_${two} + """ + )); + + String code = template1.render(); + String expected = + """ + no bracket: abc ghi defjkl + no bracket: var_1 var_two_1 var__1two_1 + with bracket: abc ghi abc_jkl + with bracket: var_1 var_two_1 var_1_two_1 + """; + checkEQ(code, expected); + } + + public static void testSelector() { + var template1 = Template.make("a", (String a) -> body( + "<\n", + "x #a x\n", + ">\n" + )); + + var template2 = Template.make("a", (String a) -> body( + "<\n", + "y #a y\n", + ">\n" + )); + + var template3 = Template.make("a", (Integer a) -> body( + "[\n", + "z #a z\n", + // Select which template should be used: + a > 0 ? template1.asToken("A_" + a) + : template2.asToken("B_" + a), + "]\n" + )); + + var template4 = Template.make(() -> body( + "{\n", + template3.asToken(-1), + "break\n", + template3.asToken(0), + "break\n", + template3.asToken(1), + "break\n", + template3.asToken(2), + "}\n" + )); + + String code = template4.render(); + String expected = + """ + { + [ + z -1 z + < + y B_-1 y + > + ] + break + [ + z 0 z + < + y B_0 y + > + ] + break + [ + z 1 z + < + x A_1 x + > + ] + break + [ + z 2 z + < + x A_2 x + > + ] + } + """; + checkEQ(code, expected); + } + + public static void testRecursion() { + // Binding allows use of template1 inside template1, via the Binding indirection. + var binding1 = new TemplateBinding>(); + + var template1 = Template.make("i", (Integer i) -> body( + "[ #i\n", + // We cannot yet use the template1 directly, as it is being defined. + // So we use binding1 instead. + i < 0 ? "done\n" : binding1.get().asToken(i - 1), + "] #i\n" + )); + binding1.bind(template1); + + var template2 = Template.make(() -> body( + "{\n", + // Now, we can use template1 normally, as it is already defined. + template1.asToken(3), + "}\n" + )); + + String code = template2.render(); + String expected = + """ + { + [ 3 + [ 2 + [ 1 + [ 0 + [ -1 + done + ] -1 + ] 0 + ] 1 + ] 2 + ] 3 + } + """; + checkEQ(code, expected); + } + + public static void testFuel() { + var template1 = Template.make(() -> body( + let("f", fuel()), + + "<#f>\n" + )); + + // Binding allows use of template2 inside template2, via the Binding indirection. + var binding2 = new TemplateBinding>(); + var template2 = Template.make("i", (Integer i) -> body( + let("f", fuel()), + + "[ #i #f\n", + template1.asToken(), + fuel() <= 60.f ? "done" : binding2.get().asToken(i - 1), + "] #i #f\n" + )); + binding2.bind(template2); + + var template3 = Template.make(() -> body( + "{\n", + template2.asToken(3), + "}\n" + )); + + String code = template3.render(); + String expected = + """ + { + [ 3 90.0f + <80.0f> + [ 2 80.0f + <70.0f> + [ 1 70.0f + <60.0f> + [ 0 60.0f + <50.0f> + done] 0 60.0f + ] 1 70.0f + ] 2 80.0f + ] 3 90.0f + } + """; + checkEQ(code, expected); + } + + public static void testFuelCustom() { + var template1 = Template.make(() -> body( + setFuelCost(2.0f), + let("f", fuel()), + + "<#f>\n" + )); + + // Binding allows use of template2 inside template2, via the Binding indirection. + var binding2 = new TemplateBinding>(); + var template2 = Template.make("i", (Integer i) -> body( + setFuelCost(3.0f), + let("f", fuel()), + + "[ #i #f\n", + template1.asToken(), + fuel() <= 5.f ? "done\n" : binding2.get().asToken(i - 1), + "] #i #f\n" + )); + binding2.bind(template2); + + var template3 = Template.make(() -> body( + setFuelCost(5.0f), + let("f", fuel()), + + "{ #f\n", + template2.asToken(3), + "} #f\n" + )); + + String code = template3.render(20.0f); + String expected = + """ + { 20.0f + [ 3 15.0f + <12.0f> + [ 2 12.0f + <9.0f> + [ 1 9.0f + <6.0f> + [ 0 6.0f + <3.0f> + [ -1 3.0f + <0.0f> + done + ] -1 3.0f + ] 0 6.0f + ] 1 9.0f + ] 2 12.0f + ] 3 15.0f + } 20.0f + """; + checkEQ(code, expected); + } + + public static void testDataNames1() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make(() -> body( + "[", + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(), + ", ", + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), + ", names: {", + String.join(", ", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList().stream().map(DataName::name).toList()), + "}]\n" + )); + + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( + addDataName(name, type, MUTABLE), + "define #type #name\n", + template1.asToken() + )); + + var template3 = Template.make(() -> body( + "<\n", + hook1.insert(template2.asToken($("name"), myInt)), + "$name = 5\n", + ">\n" + )); + + var template4 = Template.make(() -> body( + "{\n", + template1.asToken(), + hook1.anchor( + template1.asToken(), + "something\n", + template3.asToken(), + "more\n", + template1.asToken(), + "more\n", + template2.asToken($("name"), myInt), + "more\n", + template1.asToken() + ), + template1.asToken(), + "}\n" + )); + + String code = template4.render(); + String expected = + """ + { + [false, 0, names: {}] + define int name_4 + [true, 1, names: {name_4}] + [false, 0, names: {}] + something + < + name_4 = 5 + > + more + [true, 1, names: {name_4}] + more + define int name_1 + [true, 2, names: {name_4, name_1}] + more + [true, 1, names: {name_4}] + [false, 0, names: {}] + } + """; + checkEQ(code, expected); + } + + public static void testDataNames2() { + var hook1 = new Hook("Hook1"); + + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + " #mutability: [", + dataNames(mutability).exactOf(myInt).hasAny(), + ", ", + dataNames(mutability).exactOf(myInt).count(), + ", names: {", + String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + "}]\n" + )); + + var template1 = Template.make("type", (DataName.Type type) -> body( + "[#type:\n", + template0.asToken(type, MUTABLE), + template0.asToken(type, IMMUTABLE), + template0.asToken(type, MUTABLE_OR_IMMUTABLE), + "]\n" + )); + + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( + addDataName(name, type, MUTABLE), + "define mutable #type #name\n", + template1.asToken(type) + )); + + var template3 = Template.make("name", "type", (String name, DataName.Type type) -> body( + addDataName(name, type, IMMUTABLE), + "define immutable #type #name\n", + template1.asToken(type) + )); + + var template4 = Template.make("type", (DataName.Type type) -> body( + "{ $store\n", + hook1.insert(template2.asToken($("name"), type)), + "$name = 5\n", + "} $store\n" + )); + + var template5 = Template.make("type", (DataName.Type type) -> body( + "{ $load\n", + hook1.insert(template3.asToken($("name"), type)), + "blackhole($name)\n", + "} $load\n" + )); + + var template6 = Template.make("type", (DataName.Type type) -> body( + let("v", dataNames(MUTABLE).exactOf(type).sample().name()), + "{ $sample\n", + "#v = 7\n", + "} $sample\n" + )); + + var template7 = Template.make("type", (DataName.Type type) -> body( + let("v", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sample().name()), + "{ $sample\n", + "blackhole(#v)\n", + "} $sample\n" + )); + + var template8 = Template.make(() -> body( + "class $X {\n", + template1.asToken(myInt), + hook1.anchor( + "begin $body\n", + template1.asToken(myInt), + "start with immutable\n", + template5.asToken(myInt), + "then load from it\n", + template7.asToken(myInt), + template1.asToken(myInt), + "now make something mutable\n", + template4.asToken(myInt), + "then store to it\n", + template6.asToken(myInt), + template1.asToken(myInt) + ), + template1.asToken(myInt), + "}\n" + )); + + String code = template8.render(); + String expected = + """ + class X_1 { + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + define immutable int name_10 + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [true, 1, names: {name_10}] + MUTABLE_OR_IMMUTABLE: [true, 1, names: {name_10}] + ] + define mutable int name_21 + [int: + MUTABLE: [true, 1, names: {name_21}] + IMMUTABLE: [true, 1, names: {name_10}] + MUTABLE_OR_IMMUTABLE: [true, 2, names: {name_10, name_21}] + ] + begin body_1 + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + start with immutable + { load_10 + blackhole(name_10) + } load_10 + then load from it + { sample_16 + blackhole(name_10) + } sample_16 + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [true, 1, names: {name_10}] + MUTABLE_OR_IMMUTABLE: [true, 1, names: {name_10}] + ] + now make something mutable + { store_21 + name_21 = 5 + } store_21 + then store to it + { sample_27 + name_21 = 7 + } sample_27 + [int: + MUTABLE: [true, 1, names: {name_21}] + IMMUTABLE: [true, 1, names: {name_10}] + MUTABLE_OR_IMMUTABLE: [true, 2, names: {name_10, name_21}] + ] + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + } + """; + checkEQ(code, expected); + } + + public static void testDataNames3() { + var hook1 = new Hook("Hook1"); + + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + " #mutability: [", + dataNames(mutability).exactOf(myInt).hasAny(), + ", ", + dataNames(mutability).exactOf(myInt).count(), + ", names: {", + String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + "}]\n" + )); + + var template1 = Template.make("type", (DataName.Type type) -> body( + "[#type:\n", + template0.asToken(type, MUTABLE), + template0.asToken(type, IMMUTABLE), + template0.asToken(type, MUTABLE_OR_IMMUTABLE), + "]\n" + )); + + var template2 = Template.make(() -> body( + "class $Y {\n", + template1.asToken(myInt), + hook1.anchor( + "begin $body\n", + template1.asToken(myInt), + "define mutable $v1\n", + addDataName($("v1"), myInt, MUTABLE), + template1.asToken(myInt), + "define immutable $v2\n", + addDataName($("v2"), myInt, IMMUTABLE), + template1.asToken(myInt) + ), + template1.asToken(myInt), + "}\n" + )); + + String code = template2.render(); + String expected = + """ + class Y_1 { + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + begin body_1 + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + define mutable v1_1 + [int: + MUTABLE: [true, 1, names: {v1_1}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [true, 1, names: {v1_1}] + ] + define immutable v2_1 + [int: + MUTABLE: [true, 1, names: {v1_1}] + IMMUTABLE: [true, 1, names: {v2_1}] + MUTABLE_OR_IMMUTABLE: [true, 2, names: {v1_1, v2_1}] + ] + [int: + MUTABLE: [false, 0, names: {}] + IMMUTABLE: [false, 0, names: {}] + MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] + ] + } + """; + checkEQ(code, expected); + } + + public static void testDataNames4() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("type", (DataName.Type type) -> body( + "[#type:\n", + " exact: ", + dataNames(MUTABLE).exactOf(type).hasAny(), + ", ", + dataNames(MUTABLE).exactOf(type).count(), + ", {", + String.join(", ", dataNames(MUTABLE).exactOf(type).toList().stream().map(DataName::name).toList()), + "}\n", + " subtype: ", + dataNames(MUTABLE).subtypeOf(type).hasAny(), + ", ", + dataNames(MUTABLE).subtypeOf(type).count(), + ", {", + String.join(", ", dataNames(MUTABLE).subtypeOf(type).toList().stream().map(DataName::name).toList()), + "}\n", + " supertype: ", + dataNames(MUTABLE).supertypeOf(type).hasAny(), + ", ", + dataNames(MUTABLE).supertypeOf(type).count(), + ", {", + String.join(", ", dataNames(MUTABLE).supertypeOf(type).toList().stream().map(DataName::name).toList()), + "}\n", + "]\n" + )); + + List types = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); + var template2 = Template.make(() -> body( + "DataNames:\n", + types.stream().map(t -> template1.asToken(t)).toList() + )); + + var template3 = Template.make("type", (DataName.Type type) -> body( + let("name", dataNames(MUTABLE).subtypeOf(type).sample()), + "Sample #type: #name\n" + )); + + var template4 = Template.make(() -> body( + "class $W {\n", + template2.asToken(), + hook1.anchor( + "Create name for myClassA11, should be visible for the super classes\n", + addDataName($("v1"), myClassA11, MUTABLE), + template3.asToken(myClassA11), + template3.asToken(myClassA1), + template3.asToken(myClassA), + "Create name for myClassA, should never be visible for the sub classes\n", + addDataName($("v2"), myClassA, MUTABLE), + template3.asToken(myClassA11), + template3.asToken(myClassA1), + template2.asToken() + ), + template2.asToken(), + "}\n" + )); + + String code = template4.render(); + String expected = + """ + class W_1 { + DataNames: + [myClassA: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA1: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA11: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + Create name for myClassA11, should be visible for the super classes + Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Create name for myClassA, should never be visible for the sub classes + Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + DataNames: + [myClassA: + exact: true, 1, {v2_1} + subtype: true, 2, {v1_1, v2_1} + supertype: true, 1, {v2_1} + ] + [myClassA1: + exact: false, 0, {} + subtype: true, 1, {v1_1} + supertype: true, 1, {v2_1} + ] + [myClassA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: true, 1, {v2_1} + ] + [myClassA11: + exact: true, 1, {v1_1} + subtype: true, 1, {v1_1} + supertype: true, 2, {v1_1, v2_1} + ] + [myClassB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + DataNames: + [myClassA: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA1: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassA11: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [myClassB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + } + """; + checkEQ(code, expected); + } + + // Test duplicate names in safe cases. + public static void testDataNames5() { + var hook1 = new Hook("Hook1"); + var hook2 = new Hook("Hook2"); + + // It is safe in separate Hook scopes. + var template1 = Template.make(() -> body( + hook1.anchor( + addDataName("name1", myInt, MUTABLE) + ), + hook1.anchor( + addDataName("name1", myInt, MUTABLE) + ) + )); + + // It is safe in separate Template scopes. + var template2 = Template.make(() -> body( + addDataName("name2", myInt, MUTABLE) + )); + var template3 = Template.make(() -> body( + template2.asToken(), + template2.asToken() + )); + + var template4 = Template.make(() -> body( + // The following is not safe, it would collide + // with (1), because it would be inserted to the + // hook1.anchor in template5, and hence be available + // inside the scope where (1) is available. + // See: testFailingAddNameDuplication8 + // addDataName("name", myInt, MUTABLE), + hook2.anchor( + // (2) This one is added second. Since it is + // inside the hook2.anchor, it does not go + // out to the hook1.anchor, and is not + // available inside the scope of (1). + addDataName("name3", myInt, MUTABLE) + ) + )); + var template5 = Template.make(() -> body( + hook1.anchor( + // (1) this is the first one we add. + addDataName("name3", myInt, MUTABLE) + ) + )); + + // Put it all together into a single test. + var template6 = Template.make(() -> body( + template1.asToken(), + template3.asToken(), + template5.asToken() + )); + + String code = template1.render(); + String expected = ""; + checkEQ(code, expected); + } + + public static void testStructuralNames1() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("type", (StructuralName.Type type) -> body( + "[#type:\n", + " exact: ", + structuralNames().exactOf(type).hasAny(), + ", ", + structuralNames().exactOf(type).count(), + ", {", + String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + "}\n", + " subtype: ", + structuralNames().subtypeOf(type).hasAny(), + ", ", + structuralNames().subtypeOf(type).count(), + ", {", + String.join(", ", structuralNames().subtypeOf(type).toList().stream().map(StructuralName::name).toList()), + "}\n", + " supertype: ", + structuralNames().supertypeOf(type).hasAny(), + ", ", + structuralNames().supertypeOf(type).count(), + ", {", + String.join(", ", structuralNames().supertypeOf(type).toList().stream().map(StructuralName::name).toList()), + "}\n", + "]\n" + )); + + List types = List.of(myStructuralTypeA, + myStructuralTypeA1, + myStructuralTypeA2, + myStructuralTypeA11, + myStructuralTypeB); + var template2 = Template.make(() -> body( + "StructuralNames:\n", + types.stream().map(t -> template1.asToken(t)).toList() + )); + + var template3 = Template.make("type", (StructuralName.Type type) -> body( + let("name", structuralNames().subtypeOf(type).sample()), + "Sample #type: #name\n" + )); + + var template4 = Template.make(() -> body( + "class $Q {\n", + template2.asToken(), + hook1.anchor( + "Create name for myStructuralTypeA11, should be visible for the supertypes\n", + addStructuralName($("v1"), myStructuralTypeA11), + template3.asToken(myStructuralTypeA11), + template3.asToken(myStructuralTypeA1), + template3.asToken(myStructuralTypeA), + "Create name for myStructuralTypeA, should never be visible for the subtypes\n", + addStructuralName($("v2"), myStructuralTypeA), + template3.asToken(myStructuralTypeA11), + template3.asToken(myStructuralTypeA1), + template2.asToken() + ), + template2.asToken(), + "}\n" + )); + + String code = template4.render(); + String expected = + """ + class Q_1 { + StructuralNames: + [StructuralA: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA1: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA11: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + Create name for myStructuralTypeA11, should be visible for the supertypes + Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Create name for myStructuralTypeA, should never be visible for the subtypes + Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] + StructuralNames: + [StructuralA: + exact: true, 1, {v2_1} + subtype: true, 2, {v1_1, v2_1} + supertype: true, 1, {v2_1} + ] + [StructuralA1: + exact: false, 0, {} + subtype: true, 1, {v1_1} + supertype: true, 1, {v2_1} + ] + [StructuralA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: true, 1, {v2_1} + ] + [StructuralA11: + exact: true, 1, {v1_1} + subtype: true, 1, {v1_1} + supertype: true, 2, {v1_1, v2_1} + ] + [StructuralB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + StructuralNames: + [StructuralA: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA1: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA2: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralA11: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + [StructuralB: + exact: false, 0, {} + subtype: false, 0, {} + supertype: false, 0, {} + ] + } + """; + checkEQ(code, expected); + } + + public static void testStructuralNames2() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make("type", (StructuralName.Type type) -> body( + "[#type: ", + structuralNames().exactOf(type).hasAny(), + ", ", + structuralNames().exactOf(type).count(), + ", names: {", + String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + "}]\n" + )); + + var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> body( + addStructuralName(name, type), + "define #type #name\n" + )); + + var template3 = Template.make("type", (StructuralName.Type type) -> body( + "{ $access\n", + hook1.insert(template2.asToken($("name"), type)), + "$name = 5\n", + "} $access\n" + )); + + var template4 = Template.make("type", (StructuralName.Type type) -> body( + let("v", structuralNames().exactOf(type).sample().name()), + "{ $sample\n", + "blackhole(#v)\n", + "} $sample\n" + )); + + var template8 = Template.make(() -> body( + "class $X {\n", + template1.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeB), + hook1.anchor( + "begin $body\n", + template1.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeB), + "start with A\n", + template3.asToken(myStructuralTypeA), + "then access it\n", + template4.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeB), + "now make a B\n", + template3.asToken(myStructuralTypeB), + "then access to it\n", + template4.asToken(myStructuralTypeB), + template1.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeB) + ), + template1.asToken(myStructuralTypeA), + template1.asToken(myStructuralTypeB), + "}\n" + )); + + String code = template8.render(); + String expected = + """ + class X_1 { + [StructuralA: false, 0, names: {}] + [StructuralB: false, 0, names: {}] + define StructuralA name_6 + define StructuralB name_11 + begin body_1 + [StructuralA: false, 0, names: {}] + [StructuralB: false, 0, names: {}] + start with A + { access_6 + name_6 = 5 + } access_6 + then access it + { sample_8 + blackhole(name_6) + } sample_8 + [StructuralA: true, 1, names: {name_6}] + [StructuralB: false, 0, names: {}] + now make a B + { access_11 + name_11 = 5 + } access_11 + then access to it + { sample_13 + blackhole(name_11) + } sample_13 + [StructuralA: true, 1, names: {name_6}] + [StructuralB: true, 1, names: {name_11}] + [StructuralA: false, 0, names: {}] + [StructuralB: false, 0, names: {}] + } + """; + checkEQ(code, expected); + } + + record MyItem(DataName.Type type, String op) {} + + public static void testListArgument() { + var template1 = Template.make("item", (MyItem item) -> body( + let("type", item.type()), + let("op", item.op()), + "#type apply #op\n" + )); + + var template2 = Template.make("list", (List list) -> body( + "class $Z {\n", + // Use template1 for every item in the list. + list.stream().map(item -> template1.asToken(item)).toList(), + "}\n" + )); + + List list = List.of(new MyItem(myInt, "+"), + new MyItem(myInt, "-"), + new MyItem(myInt, "*"), + new MyItem(myInt, "/"), + new MyItem(myLong, "+"), + new MyItem(myLong, "-"), + new MyItem(myLong, "*"), + new MyItem(myLong, "/")); + + String code = template2.render(list); + String expected = + """ + class Z_1 { + int apply + + int apply - + int apply * + int apply / + long apply + + long apply - + long apply * + long apply / + } + """; + checkEQ(code, expected); + } + + public static void testFailingNestedRendering() { + var template1 = Template.make(() -> body( + "alpha\n" + )); + + var template2 = Template.make(() -> body( + "beta\n", + // Nested "render" call not allowed! + template1.render(), + "gamma\n" + )); + + String code = template2.render(); + } + + public static void testFailingDollarName1() { + var template1 = Template.make(() -> body( + let("x", $("")) // empty string not allowed + )); + String code = template1.render(); + } + + public static void testFailingDollarName2() { + var template1 = Template.make(() -> body( + let("x", $("#abc")) // "#" character not allowed + )); + String code = template1.render(); + } + + public static void testFailingDollarName3() { + var template1 = Template.make(() -> body( + let("x", $("abc#")) // "#" character not allowed + )); + String code = template1.render(); + } + + public static void testFailingDollarName4() { + var template1 = Template.make(() -> body( + let("x", $(null)) // Null input to dollar + )); + String code = template1.render(); + } + + public static void testFailingDollarName5() { + var template1 = Template.make(() -> body( + "$" // empty dollar name + )); + String code = template1.render(); + } + + public static void testFailingDollarName6() { + var template1 = Template.make(() -> body( + "asdf$" // empty dollar name + )); + String code = template1.render(); + } + + public static void testFailingDollarName7() { + var template1 = Template.make(() -> body( + "asdf$1" // Bad pattern after dollar + )); + String code = template1.render(); + } + + public static void testFailingDollarName8() { + var template1 = Template.make(() -> body( + "abc$$abc" // empty dollar name + )); + String code = template1.render(); + } + + public static void testFailingLetName1() { + var template1 = Template.make(() -> body( + let(null, $("abc")) // Null input for hashtag name + )); + String code = template1.render(); + } + + public static void testFailingHashtagName1() { + // Empty Template argument + var template1 = Template.make("", (String x) -> body( + )); + String code = template1.render("abc"); + } + + public static void testFailingHashtagName2() { + // "#" character not allowed in template argument + var template1 = Template.make("abc#abc", (String x) -> body( + )); + String code = template1.render("abc"); + } + + public static void testFailingHashtagName3() { + var template1 = Template.make(() -> body( + // Empty let hashtag name not allowed + let("", "abc") + )); + String code = template1.render(); + } + + public static void testFailingHashtagName4() { + var template1 = Template.make(() -> body( + // "#" character not allowed in let hashtag name + let("xyz#xyz", "abc") + )); + String code = template1.render(); + } + + public static void testFailingHashtagName5() { + var template1 = Template.make(() -> body( + "#" // empty hashtag name + )); + String code = template1.render(); + } + + public static void testFailingHashtagName6() { + var template1 = Template.make(() -> body( + "asdf#" // empty hashtag name + )); + String code = template1.render(); + } + + public static void testFailingHashtagName7() { + var template1 = Template.make(() -> body( + "asdf#1" // Bad pattern after hashtag + )); + String code = template1.render(); + } + + public static void testFailingHashtagName8() { + var template1 = Template.make(() -> body( + "abc##abc" // empty hashtag name + )); + String code = template1.render(); + } + + public static void testFailingDollarHashtagName1() { + var template1 = Template.make(() -> body( + "#$" // empty hashtag name + )); + String code = template1.render(); + } + + public static void testFailingDollarHashtagName2() { + var template1 = Template.make(() -> body( + "$#" // empty dollar name + )); + String code = template1.render(); + } + + public static void testFailingDollarHashtagName3() { + var template1 = Template.make(() -> body( + "#$name" // empty hashtag name + )); + String code = template1.render(); + } + + public static void testFailingDollarHashtagName4() { + var template1 = Template.make(() -> body( + "$#name" // empty dollar name + )); + String code = template1.render(); + } + + public static void testFailingHook() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make(() -> body( + "alpha\n" + )); + + var template2 = Template.make(() -> body( + "beta\n", + // Use hook without hook1.anchor + hook1.insert(template1.asToken()), + "gamma\n" + )); + + String code = template2.render(); + } + + public static void testFailingSample1() { + var template1 = Template.make(() -> body( + // No variable added yet. + let("v", dataNames(MUTABLE).exactOf(myInt).sample().name()), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2() { + var template1 = Template.make(() -> body( + // no type restriction + let("v", dataNames(MUTABLE).sample().name()), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingHashtag1() { + // Duplicate hashtag definition from arguments. + var template1 = Template.make("a", "a", (String _, String _) -> body( + "nothing\n" + )); + + String code = template1.render("x", "y"); + } + + public static void testFailingHashtag2() { + var template1 = Template.make("a", (String _) -> body( + // Duplicate hashtag name + let("a", "x"), + "nothing\n" + )); + + String code = template1.render("y"); + } + + public static void testFailingHashtag3() { + var template1 = Template.make(() -> body( + let("a", "x"), + // Duplicate hashtag name + let("a", "y"), + "nothing\n" + )); + + String code = template1.render(); + } + + public static void testFailingHashtag4() { + var template1 = Template.make(() -> body( + // Missing hashtag name definition + "#a\n" + )); + + String code = template1.render(); + } + + public static void testFailingBinding1() { + var binding = new TemplateBinding(); + var template1 = Template.make(() -> body( + "nothing\n" + )); + binding.bind(template1); + // Duplicate bind + binding.bind(template1); + } + + public static void testFailingBinding2() { + var binding = new TemplateBinding(); + var template1 = Template.make(() -> body( + "nothing\n", + // binding was never bound. + binding.get() + )); + // Should have bound the binding here. + String code = template1.render(); + } + + public static void testFailingAddDataName1() { + var template1 = Template.make(() -> body( + // Must pick either MUTABLE or IMMUTABLE. + addDataName("name", myInt, MUTABLE_OR_IMMUTABLE) + )); + String code = template1.render(); + } + + public static void testFailingAddDataName2() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addDataName("name", myInt, MUTABLE, 0) + )); + String code = template1.render(); + } + + public static void testFailingAddDataName3() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addDataName("name", myInt, MUTABLE, -1) + )); + String code = template1.render(); + } + + public static void testFailingAddDataName4() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addDataName("name", myInt, MUTABLE, 1001) + )); + String code = template1.render(); + } + + public static void testFailingAddStructuralName1() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addStructuralName("name", myStructuralTypeA, 0) + )); + String code = template1.render(); + } + + public static void testFailingAddStructuralName2() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addStructuralName("name", myStructuralTypeA, -1) + )); + String code = template1.render(); + } + + public static void testFailingAddStructuralName3() { + var template1 = Template.make(() -> body( + // weight out of bounds [0..1000] + addStructuralName("name", myStructuralTypeA, 1001) + )); + String code = template1.render(); + } + + // Duplicate name in the same scope, name identical -> expect RendererException. + public static void testFailingAddNameDuplication1() { + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE), + addDataName("name", myInt, MUTABLE) + )); + String code = template1.render(); + } + + // Duplicate name in the same scope, names have different mutability -> expect RendererException. + public static void testFailingAddNameDuplication2() { + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE), + addDataName("name", myInt, IMMUTABLE) + )); + String code = template1.render(); + } + + // Duplicate name in the same scope, names have different type -> expect RendererException. + public static void testFailingAddNameDuplication3() { + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE), + addDataName("name", myLong, MUTABLE) + )); + String code = template1.render(); + } + + // Duplicate name in the same scope, name identical -> expect RendererException. + public static void testFailingAddNameDuplication4() { + var template1 = Template.make(() -> body( + addStructuralName("name", myStructuralTypeA), + addStructuralName("name", myStructuralTypeA) + )); + String code = template1.render(); + } + + // Duplicate name in the same scope, names have different type -> expect RendererException. + public static void testFailingAddNameDuplication5() { + var template1 = Template.make(() -> body( + addStructuralName("name", myStructuralTypeA), + addStructuralName("name", myStructuralTypeB) + )); + String code = template1.render(); + } + + // Duplicate name in inner Template, name identical -> expect RendererException. + public static void testFailingAddNameDuplication6() { + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE) + )); + var template2 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE), + template1.asToken() + )); + String code = template2.render(); + } + + // Duplicate name in Hook scope, name identical -> expect RendererException. + public static void testFailingAddNameDuplication7() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE), + hook1.anchor( + addDataName("name", myInt, MUTABLE) + ) + )); + String code = template1.render(); + } + + // Duplicate name in Hook.insert, name identical -> expect RendererException. + public static void testFailingAddNameDuplication8() { + var hook1 = new Hook("Hook1"); + + var template1 = Template.make(() -> body( + addDataName("name", myInt, MUTABLE) + )); + + var template2 = Template.make(() -> body( + hook1.anchor( + addDataName("name", myInt, MUTABLE), + hook1.insert(template1.asToken()) + ) + )); + String code = template2.render(); + } + + public static void expectRendererException(FailingTest test, String errorPrefix) { + try { + test.run(); + System.out.println("Should have thrown RendererException with prefix: " + errorPrefix); + throw new RuntimeException("Should have thrown!"); + } catch(RendererException e) { + if (!e.getMessage().startsWith(errorPrefix)) { + System.out.println("Should have thrown with prefix: " + errorPrefix); + System.out.println("got: " + e.getMessage()); + throw new RuntimeException("Prefix mismatch", e); + } + } + } + + public static void expectIllegalArgumentException(FailingTest test, String errorPrefix) { + try { + test.run(); + System.out.println("Should have thrown IllegalArgumentException with prefix: " + errorPrefix); + throw new RuntimeException("Should have thrown!"); + } catch(IllegalArgumentException e) { + if (!e.getMessage().startsWith(errorPrefix)) { + System.out.println("Should have thrown with prefix: " + errorPrefix); + System.out.println("got: " + e.getMessage()); + throw new RuntimeException("Prefix mismatch", e); + } + } + } + + public static void expectUnsupportedOperationException(FailingTest test, String errorPrefix) { + try { + test.run(); + System.out.println("Should have thrown UnsupportedOperationException with prefix: " + errorPrefix); + throw new RuntimeException("Should have thrown!"); + } catch(UnsupportedOperationException e) { + if (!e.getMessage().startsWith(errorPrefix)) { + System.out.println("Should have thrown with prefix: " + errorPrefix); + System.out.println("got: " + e.getMessage()); + throw new RuntimeException("Prefix mismatch", e); + } + } + } + + public static void checkEQ(String code, String expected) { + if (!code.equals(expected)) { + System.out.println("\"" + code + "\""); + System.out.println("\"" + expected + "\""); + throw new RuntimeException("Template rendering mismatch!"); + } + } +} From a653ff48933bfd72c7c3d004ccc5bd0d9c1162cb Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 4 Jun 2025 13:39:31 +0000 Subject: [PATCH 039/143] 8358536: jdk/jfr/api/consumer/TestRecordingFileWrite.java times out Reviewed-by: mgronlun --- .../classes/jdk/jfr/internal/tool/PrettyWriter.java | 9 ++++++--- test/jdk/ProblemList.txt | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java index 274a21d9b49..10f50095247 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java @@ -117,11 +117,11 @@ public final class PrettyWriter extends EventPrintWriter { // At the same time, a too large window, means it will take more time // before the first event is printed and the tool will feel unresponsive. private static final int EVENT_WINDOW_SIZE = 1_000_000; - private final PriorityQueue timeline = new PriorityQueue<>(EVENT_WINDOW_SIZE + 4); - private final Map typeInformation = new HashMap<>(); - private final Map> contexts = new HashMap<>(); private final boolean showExact; private RecordedEvent currentEvent; + private PriorityQueue timeline; + private Map typeInformation; + private Map> contexts; public PrettyWriter(PrintWriter destination, boolean showExact) { super(destination); @@ -133,6 +133,9 @@ public final class PrettyWriter extends EventPrintWriter { } void print(Path source) throws IOException { + timeline = new PriorityQueue<>(EVENT_WINDOW_SIZE + 4); + typeInformation = new HashMap<>(); + contexts = new HashMap<>(); printBegin(); int counter = 0; try (RecordingFile file = new RecordingFile(source)) { diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index f44dd5c7a7d..28bb2a1f033 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -774,7 +774,6 @@ jdk/jfr/event/compiler/TestCodeSweeper.java 8338127 generic- jdk/jfr/event/oldobject/TestShenandoah.java 8342951 generic-all jdk/jfr/event/runtime/TestResidentSetSizeEvent.java 8309846 aix-ppc64 jdk/jfr/jvm/TestWaste.java 8282427 generic-all -jdk/jfr/api/consumer/TestRecordingFileWrite.java 8358536 generic-all ############################################################################ From 0352477ff5977b0010e62000adbde88026a49a7e Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Wed, 4 Jun 2025 13:50:36 +0000 Subject: [PATCH 040/143] 8357660: [JVMCI] Add support for retrieving all BootstrapMethodInvocations directly from ConstantPool Reviewed-by: dnsimon, yzheng --- src/hotspot/share/jvmci/jvmciCompilerToVM.cpp | 9 ++++ .../jdk/vm/ci/hotspot/CompilerToVM.java | 14 ++++- .../vm/ci/hotspot/HotSpotConstantPool.java | 51 +++++++++++++++++-- .../classes/jdk/vm/ci/meta/ConstantPool.java | 35 ++++++++++++- .../ci/hotspot/test/TestDynamicConstant.java | 47 +++++++++++++++-- 5 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index 4f6d4aaa099..d45036ced94 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -813,6 +813,14 @@ C2V_VMENTRY_NULL(jobject, lookupConstantInPool, (JNIEnv* env, jobject, ARGUMENT_ return JVMCIENV->get_jobject(JVMCIENV->get_object_constant(obj)); C2V_END +C2V_VMENTRY_0(jint, getNumIndyEntries, (JNIEnv* env, jobject, ARGUMENT_PAIR(cp))) + constantPoolHandle cp(THREAD, UNPACK_PAIR(ConstantPool, cp)); + if (cp->cache()->resolved_indy_entries() == nullptr) { + return 0; + } + return cp->resolved_indy_entries_length(); +C2V_END + C2V_VMENTRY_NULL(jobjectArray, resolveBootstrapMethod, (JNIEnv* env, jobject, ARGUMENT_PAIR(cp), jint index)) constantPoolHandle cp(THREAD, UNPACK_PAIR(ConstantPool, cp)); constantTag tag = cp->tag_at(index); @@ -3320,6 +3328,7 @@ JNINativeMethod CompilerToVM::methods[] = { {CC "lookupAppendixInPool", CC "(" HS_CONSTANT_POOL2 "II)" OBJECTCONSTANT, FN_PTR(lookupAppendixInPool)}, {CC "lookupMethodInPool", CC "(" HS_CONSTANT_POOL2 "IB" HS_METHOD2 ")" HS_METHOD, FN_PTR(lookupMethodInPool)}, {CC "lookupConstantInPool", CC "(" HS_CONSTANT_POOL2 "IZ)" JAVACONSTANT, FN_PTR(lookupConstantInPool)}, + {CC "getNumIndyEntries", CC "(" HS_CONSTANT_POOL2 ")I", FN_PTR(getNumIndyEntries)}, {CC "resolveBootstrapMethod", CC "(" HS_CONSTANT_POOL2 "I)[" OBJECT, FN_PTR(resolveBootstrapMethod)}, {CC "bootstrapArgumentIndexAt", CC "(" HS_CONSTANT_POOL2 "II)I", FN_PTR(bootstrapArgumentIndexAt)}, {CC "getUncachedStringInPool", CC "(" HS_CONSTANT_POOL2 "I)" JAVACONSTANT, FN_PTR(getUncachedStringInPool)}, diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index 045b1f0fc12..0cb56354498 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -468,9 +468,19 @@ final class CompilerToVM { */ int decodeMethodIndexToCPIndex(HotSpotConstantPool constantPool, int rawIndex) { return decodeMethodIndexToCPIndex(constantPool, constantPool.getConstantPoolPointer(), rawIndex); - } + } - private native int decodeMethodIndexToCPIndex(HotSpotConstantPool constantPool, long constantPoolPointer, int rawIndex); + private native int decodeMethodIndexToCPIndex(HotSpotConstantPool constantPool, long constantPoolPointer, int rawIndex); + + /** + * Returns the number of {@code ResolvedIndyEntry}s present within this constant + * pool. + */ + int getNumIndyEntries(HotSpotConstantPool constantPool) { + return getNumIndyEntries(constantPool, constantPool.getConstantPoolPointer()); + } + + private native int getNumIndyEntries(HotSpotConstantPool constantPool, long constantPoolPointer); /** * Resolves the details for invoking the bootstrap method associated with the diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java index 84fb21d1948..20e3915527d 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotConstantPool.java @@ -25,6 +25,7 @@ package jdk.vm.ci.hotspot; import java.util.AbstractList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; import jdk.vm.ci.common.JVMCIError; import static jdk.vm.ci.hotspot.CompilerToVM.compilerToVM; @@ -530,19 +531,21 @@ public final class HotSpotConstantPool implements ConstantPool, MetaspaceHandleO } } - static class BootstrapMethodInvocationImpl implements BootstrapMethodInvocation { + class BootstrapMethodInvocationImpl implements BootstrapMethodInvocation { private final boolean indy; private final ResolvedJavaMethod method; private final String name; private final JavaConstant type; private final List staticArguments; + private final int cpiOrIndyIndex; - BootstrapMethodInvocationImpl(boolean indy, ResolvedJavaMethod method, String name, JavaConstant type, List staticArguments) { + BootstrapMethodInvocationImpl(boolean indy, ResolvedJavaMethod method, String name, JavaConstant type, List staticArguments, int cpiOrIndyIndex) { this.indy = indy; this.method = method; this.name = name; this.type = type; this.staticArguments = staticArguments; + this.cpiOrIndyIndex = cpiOrIndyIndex; } @Override @@ -570,6 +573,24 @@ public final class HotSpotConstantPool implements ConstantPool, MetaspaceHandleO return staticArguments; } + @Override + public void resolve() { + if (isInvokeDynamic()) { + loadReferencedType(cpiOrIndyIndex, Bytecodes.INVOKEDYNAMIC); + } else { + lookupConstant(cpiOrIndyIndex, true); + } + } + + @Override + public JavaConstant lookup() { + if (isInvokeDynamic()) { + return lookupAppendix(cpiOrIndyIndex, Bytecodes.INVOKEDYNAMIC); + } else { + return (JavaConstant) lookupConstant(cpiOrIndyIndex, false); + } + } + @Override public String toString() { String static_args = staticArguments.stream().map(BootstrapMethodInvocationImpl::argumentAsString).collect(Collectors.joining(", ", "[", "]")); @@ -612,12 +633,36 @@ public final class HotSpotConstantPool implements ConstantPool, MetaspaceHandleO int bss_index = bsciArgs[1]; staticArgumentsList = new CachedBSMArgs(this, bss_index, argCount); } - return new BootstrapMethodInvocationImpl(tag.name.equals("InvokeDynamic"), method, name, type, staticArgumentsList); + boolean isIndy = tag.name.equals("InvokeDynamic"); + return new BootstrapMethodInvocationImpl(isIndy, method, name, type, staticArgumentsList, isIndy ? index : cpi); default: return null; } } + private boolean isDynamicEntry(int cpi) { + JvmConstant tagAt = getTagAt(cpi); + return tagAt != null && tagAt.name.equals("Dynamic"); + } + + @Override + public List lookupBootstrapMethodInvocations(boolean invokeDynamic){ + if (invokeDynamic) { + int numIndys = compilerToVM().getNumIndyEntries(this); + if (numIndys == 0) { + return List.of(); + } + return IntStream.range(0, numIndys) + .mapToObj(i -> lookupBootstrapMethodInvocation(i, Bytecodes.INVOKEDYNAMIC)) + .toList(); + } else { + return IntStream.range(1, length()) + .filter(this::isDynamicEntry) + .mapToObj(cpi -> lookupBootstrapMethodInvocation(cpi, -1)) + .toList(); + } + } + /** * Gets the {@link JavaConstant} for the {@code ConstantValue} attribute of a field. */ diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java index 2273b256f03..0b7fd70762d 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ConstantPool.java @@ -186,11 +186,28 @@ public interface ConstantPool { * } * * - * The other types of entries are already resolved an can be used directly. + * The other types of entries are already resolved and can be used directly. * * @jvms 5.4.3.6 */ List getStaticArguments(); + + /** + * Resolves the element corresponding to this bootstrap. If + * {@code isInvokeDynamic()}, then the corresponding invoke dynamic is resolved. + * If {@code !isInvokeDynamic()}, then the dynamic constant pool entry will be + * resolved. + * + * @jvms 5.4.3.6 + */ + void resolve(); + + /** + * If {@code isInvokeDynamic()}, then this method looks up the corresponding + * invoke dynamic's appendix. If {@code !isInvokeDynamic()}, then this will + * return the constant pool entry's value. + */ + JavaConstant lookup(); } /** @@ -204,13 +221,27 @@ public interface ConstantPool { * @param opcode must be {@code Bytecodes.INVOKEDYNAMIC}, or -1 if * {@code index} was not decoded from a bytecode stream * @return the bootstrap method invocation details or {@code null} if the entry specified by {@code index} - * is not a {@code CONSTANT_Dynamic_info} or @{code CONSTANT_InvokeDynamic_info} + * is not a {@code CONSTANT_Dynamic_info} or {@code CONSTANT_InvokeDynamic_info} * @jvms 4.7.23 The {@code BootstrapMethods} Attribute */ default BootstrapMethodInvocation lookupBootstrapMethodInvocation(int index, int opcode) { throw new UnsupportedOperationException(); } + /** + * Returns either the BootstrapMethodInvocation instances for all invokedynamic + * bytecodes which reference this constant pool, or all + * {@code CONSTANT_Dynamic_info} BootstrapMethodInvocations within this constant + * pool. The returned List is unmodifiable; calls to any mutator method will + * always cause {@code UnsupportedOperationException} to be thrown. + * + * @param invokeDynamic when true, return all invokedynamic + * BootstrapMethodInvocations; otherwise, return all + * {@code CONSTANT_Dynamic_info} + * BootstrapMethodInvocations. + */ + List lookupBootstrapMethodInvocations(boolean invokeDynamic); + /** * Looks up a reference to a type. If {@code opcode} is non-negative, then resolution checks * specific to the bytecode it denotes are performed if the type is already resolved. Should any diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java index b22cb01d81a..b6e4f9cf2ef 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestDynamicConstant.java @@ -49,7 +49,7 @@ import java.lang.invoke.StringConcatFactory; import java.lang.reflect.Method; import java.nio.file.Files; import java.util.List; -import java.util.Set; +import java.util.Map; import org.testng.Assert; import org.testng.annotations.Test; @@ -376,9 +376,10 @@ public class TestDynamicConstant implements Opcodes { ResolvedJavaMethod concat = metaAccess.lookupJavaMethod(m); ConstantPool cp = concat.getConstantPool(); - Set expectedBSMs = Set.of( - "jdk.vm.ci.hotspot.test.TestDynamicConstant.shouldNotBeCalledBSM", - "java.lang.invoke.StringConcatFactory.makeConcatWithConstants" + // Contains a map of (bootstrap method names, resolvable) values. + Map expectedIndyBSMs = Map.of( + "jdk.vm.ci.hotspot.test.TestDynamicConstant.shouldNotBeCalledBSM", false, + "java.lang.invoke.StringConcatFactory.makeConcatWithConstants", true ); for (int cpi = 1; cpi < cp.length(); cpi++) { @@ -389,9 +390,10 @@ public class TestDynamicConstant implements Opcodes { String bsm = bsmi.getMethod().format("%H.%n"); if (tag.equals("InvokeDynamic")) { Assert.assertTrue(bsmi.isInvokeDynamic()); - Assert.assertTrue(expectedBSMs.contains(bsm), expectedBSMs.toString()); + Assert.assertTrue(expectedIndyBSMs.containsKey(bsm), expectedIndyBSMs.toString()); } else { Assert.assertFalse(bsmi.isInvokeDynamic()); + Assert.assertNull(bsmi.lookup()); checkBsmName(condyType, bsm); List staticArguments = bsmi.getStaticArguments(); for (int i = 0; i < staticArguments.size(); ++i) { @@ -423,6 +425,41 @@ public class TestDynamicConstant implements Opcodes { } testLoadReferencedType(concat, cp); + + testLookupBootstrapMethodInvocations(condyType, cp, expectedIndyBSMs); + } + + private static void testLookupBootstrapMethodInvocations(CondyType condyType, ConstantPool cp, Map expectedIndyBSMs) { + List indyBSMs = cp.lookupBootstrapMethodInvocations(true); + Assert.assertEquals(indyBSMs.size(), 2); + for (var bsmi : indyBSMs) { + String bsm = bsmi.getMethod().format("%H.%n"); + Assert.assertTrue(expectedIndyBSMs.containsKey(bsm), expectedIndyBSMs.toString()); + Assert.assertTrue(bsmi.isInvokeDynamic()); + if (expectedIndyBSMs.get(bsm)) { + bsmi.resolve(); + Assert.assertNotNull(bsmi.lookup()); + } else { + try { + bsmi.resolve(); + } catch (BootstrapMethodError bme) { + // expected error + } + Assert.assertNull(bsmi.lookup()); + } + } + + List condyBSMs = cp.lookupBootstrapMethodInvocations(false); + int expectedNumCondys = switch(condyType) { + case CALL_DIRECT_BSM, CALL_INDIRECT_BSM -> 1; + case CALL_DIRECT_WITH_ARGS_BSM, CALL_INDIRECT_WITH_ARGS_BSM -> 2; + }; + Assert.assertEquals(condyBSMs.size(), expectedNumCondys); + for (var bsmi : condyBSMs) { + Assert.assertTrue(!bsmi.isInvokeDynamic()); + bsmi.resolve(); + Assert.assertNotNull(bsmi.lookup()); + } } private static void checkBsmName(CondyType condyType, String bsm) { From ae1892fb0fb6b7646f9ca60067d6945ccea7f888 Mon Sep 17 00:00:00 2001 From: Igor Veresov Date: Wed, 4 Jun 2025 14:07:49 +0000 Subject: [PATCH 041/143] 8358003: KlassTrainingData initializer reads garbage holder Reviewed-by: coleenp, shade, vlivanov --- src/hotspot/share/oops/trainingData.cpp | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/hotspot/share/oops/trainingData.cpp b/src/hotspot/share/oops/trainingData.cpp index 543462b1182..2ae1ac18699 100644 --- a/src/hotspot/share/oops/trainingData.cpp +++ b/src/hotspot/share/oops/trainingData.cpp @@ -432,24 +432,11 @@ void KlassTrainingData::print_on(outputStream* st, bool name_only) const { } KlassTrainingData::KlassTrainingData(InstanceKlass* klass) : TrainingData(klass) { - if (holder() == klass) { - return; // no change to make - } - - jobject hmj = _holder_mirror; - if (hmj != nullptr) { // clear out previous handle, if any - _holder_mirror = nullptr; - assert(JNIHandles::is_global_handle(hmj), ""); - JNIHandles::destroy_global(hmj); - } - - if (klass != nullptr) { - Handle hm(JavaThread::current(), klass->java_mirror()); - hmj = JNIHandles::make_global(hm); - Atomic::release_store(&_holder_mirror, hmj); - } - - Atomic::release_store(&_holder, const_cast(klass)); + assert(klass != nullptr, ""); + Handle hm(JavaThread::current(), klass->java_mirror()); + jobject hmj = JNIHandles::make_global(hm); + _holder_mirror = hmj; + _holder = klass; assert(holder() == klass, ""); } From a2723d91dfba2850e3070083fa94dc3fecc46a00 Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Wed, 4 Jun 2025 14:16:20 +0000 Subject: [PATCH 042/143] 8345347: Test runtime/cds/TestDefaultArchiveLoading.java should accept VM flags or be marked as flagless Reviewed-by: lmesnik, stefank, ccheung --- .../jtreg/runtime/cds/TestDefaultArchiveLoading.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java index ddc3603b16d..d90618b51f2 100644 --- a/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java +++ b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,6 +26,7 @@ * @test id=nocoops_nocoh * @summary Test Loading of default archives in all configurations * @requires vm.cds + * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib * @modules java.base/jdk.internal.misc @@ -37,6 +38,7 @@ * @test id=nocoops_coh * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-coh) * @requires vm.cds + * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib * @modules java.base/jdk.internal.misc @@ -48,6 +50,7 @@ * @test id=coops_nocoh * @summary Test Loading of default archives in all configurations * @requires vm.cds + * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib * @modules java.base/jdk.internal.misc @@ -59,6 +62,7 @@ * @test id=coops_coh * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-coh) * @requires vm.cds + * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib * @modules java.base/jdk.internal.misc @@ -131,7 +135,7 @@ public class TestDefaultArchiveLoading { default: throw new RuntimeException("Invalid argument " + args[0]); } - ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( "-XX:" + coh + "UseCompactObjectHeaders", "-XX:" + coops + "UseCompressedOops", "-Xlog:cds", From 4e314cb9e025672b2f7b68cc021fa516ee219ad8 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 4 Jun 2025 14:21:34 +0000 Subject: [PATCH 043/143] 8356000: C1/C2-only modes use 2 compiler threads on low CPU count machines Reviewed-by: kvn, dfenacci, galder --- .../share/compiler/compilationPolicy.cpp | 11 +- .../arguments/TestCompilerCounts.java | 177 ++++++++++++++++++ 2 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/arguments/TestCompilerCounts.java diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index d95debeda44..39c90281c79 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -550,6 +550,7 @@ void CompilationPolicy::initialize() { int count = CICompilerCount; bool c1_only = CompilerConfig::is_c1_only(); bool c2_only = CompilerConfig::is_c2_or_jvmci_compiler_only(); + int min_count = (c1_only || c2_only) ? 1 : 2; #ifdef _LP64 // Turn on ergonomic compiler count selection @@ -560,7 +561,7 @@ void CompilationPolicy::initialize() { // Simple log n seems to grow too slowly for tiered, try something faster: log n * log log n int log_cpu = log2i(os::active_processor_count()); int loglog_cpu = log2i(MAX2(log_cpu, 1)); - count = MAX2(log_cpu * loglog_cpu * 3 / 2, 2); + count = MAX2(log_cpu * loglog_cpu * 3 / 2, min_count); // Make sure there is enough space in the code cache to hold all the compiler buffers size_t c1_size = 0; #ifdef COMPILER1 @@ -574,7 +575,7 @@ void CompilationPolicy::initialize() { int max_count = (ReservedCodeCacheSize - (CodeCacheMinimumUseSpace DEBUG_ONLY(* 3))) / (int)buffer_size; if (count > max_count) { // Lower the compiler count such that all buffers fit into the code cache - count = MAX2(max_count, c1_only ? 1 : 2); + count = MAX2(max_count, min_count); } FLAG_SET_ERGO(CICompilerCount, count); } @@ -593,9 +594,10 @@ void CompilationPolicy::initialize() { #endif if (c1_only) { - // No C2 compiler thread required + // No C2 compiler threads are needed set_c1_count(count); } else if (c2_only) { + // No C1 compiler threads are needed set_c2_count(count); } else { #if INCLUDE_JVMCI @@ -613,6 +615,9 @@ void CompilationPolicy::initialize() { } assert(count == c1_count() + c2_count(), "inconsistent compiler thread count"); set_increase_threshold_at_ratio(); + } else { + // Interpreter mode creates no compilers + FLAG_SET_ERGO(CICompilerCount, 0); } set_start_time(nanos_to_millis(os::javaTimeNanos())); } diff --git a/test/hotspot/jtreg/compiler/arguments/TestCompilerCounts.java b/test/hotspot/jtreg/compiler/arguments/TestCompilerCounts.java new file mode 100644 index 00000000000..420dc2b3414 --- /dev/null +++ b/test/hotspot/jtreg/compiler/arguments/TestCompilerCounts.java @@ -0,0 +1,177 @@ +/* + * Copyright Amazon.com Inc. 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. + */ + +/* + * @test + * @library /test/lib / + * @bug 8356000 + * @requires vm.flagless + * @requires vm.bits == "64" + * @run driver compiler.arguments.TestCompilerCounts + */ + +package compiler.arguments; + +import java.io.IOException; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.function.Function; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.cli.CommandLineOptionTest; + +public class TestCompilerCounts { + + // Try not to go over max CPU count on the machine, since we do not + // know if the rest of runtime would accept it. + // For local testing, feel free to override this to a larger value + // if you want to see how heuristics works on even larger machines. + static final int MAX_CPUS = Runtime.getRuntime().availableProcessors(); + + // Test at most 16 CPUs linearly, to limit test execution time. + // After this limit, go for exponential steps. + static final int MAX_LINEAR_CPUS = Math.min(16, MAX_CPUS); + + public static void main(String[] args) throws Throwable { + // CICompilerCount=0 is incorrect in default modes. + fail("-XX:CICompilerCount=0"); + + // Interpreter-only mode accepts all values, but sets 0 threads + pass(0, "-XX:CICompilerCount=1", "-XX:TieredStopAtLevel=0"); + + // C1/C2 only modes accept CICompilerCount=1 + pass(1, "-XX:CICompilerCount=1", "-XX:TieredStopAtLevel=1"); + pass(1, "-XX:CICompilerCount=1", "-XX:TieredStopAtLevel=2"); + pass(1, "-XX:CICompilerCount=1", "-XX:TieredStopAtLevel=3"); + pass(1, "-XX:CICompilerCount=1", "-XX:-TieredCompilation"); + + // C1+C2 modes need at least 2 threads + fail("-XX:CICompilerCount=1"); + fail("-XX:CICompilerCount=1", "-XX:TieredStopAtLevel=4"); + + // Overriding the CICompilerCount overrides compiler counts hard. + for (int count = 2; count <= MAX_CPUS; count += (count >= MAX_LINEAR_CPUS ? count : 1)) { + String opt = "-XX:CICompilerCount=" + count; + + // Interpreter-only mode always sets 0 threads + pass(0, opt, "-XX:TieredStopAtLevel=0"); + + // All compiled modes accept reasonable CICompilerCount + pass(count, opt); + pass(count, opt, "-XX:TieredStopAtLevel=1"); + pass(count, opt, "-XX:TieredStopAtLevel=2"); + pass(count, opt, "-XX:TieredStopAtLevel=3"); + pass(count, opt, "-XX:TieredStopAtLevel=4"); + pass(count, opt, "-XX:-TieredCompilation"); + } + + // Per CPU heuristics is disabled, we are going to set up defaults. + + for (int cpus = 2; cpus <= MAX_CPUS; cpus += (cpus >= MAX_LINEAR_CPUS ? cpus : 1)) { + String opt = "-XX:ActiveProcessorCount=" + cpus; + String opt2 = "-XX:-CICompilerCountPerCPU"; + + // Interpreter-only mode always set 0 threads + pass(0, opt, opt2, "-XX:TieredStopAtLevel=0"); + + // All compiled modes default to 2 threads, statically compiled in + pass(2, opt, opt2); + pass(2, opt, opt2, "-XX:TieredStopAtLevel=1"); + pass(2, opt, opt2, "-XX:TieredStopAtLevel=2"); + pass(2, opt, opt2, "-XX:TieredStopAtLevel=3"); + pass(2, opt, opt2, "-XX:TieredStopAtLevel=4"); + pass(2, opt, opt2, "-XX:-TieredCompilation"); + } + + // Otherwise, we set CICompilerCount heuristically. + + // Check hitting the lower values exactly first. + for (int cpus = 1; cpus <= 3; cpus++) { + String opt = "-XX:ActiveProcessorCount=" + cpus; + + // Interpreter-only mode always set 0 threads + pass(0, opt, "-XX:TieredStopAtLevel=0"); + + // Non-tiered modes set 1 thread + pass(1, opt, "-XX:TieredStopAtLevel=1"); + pass(1, opt, "-XX:TieredStopAtLevel=2"); + pass(1, opt, "-XX:TieredStopAtLevel=3"); + pass(1, opt, "-XX:-TieredCompilation"); + + // Tiered modes set 2 threads + pass(2, opt); + pass(2, opt, "-XX:TieredStopAtLevel=4"); + } + + // Check what heuristics sets past the trivial number of CPUs. + for (int cpus = 4; cpus <= MAX_CPUS; cpus += (cpus >= MAX_LINEAR_CPUS ? cpus : 1)) { + String opt = "-XX:ActiveProcessorCount=" + cpus; + + // Interpreter-only mode always set 0 threads + pass(0, opt, "-XX:TieredStopAtLevel=0"); + + // Non-tiered modes + int nonTieredCount = heuristicCount(cpus, false); + pass(nonTieredCount, opt, "-XX:TieredStopAtLevel=1"); + pass(nonTieredCount, opt, "-XX:TieredStopAtLevel=2"); + pass(nonTieredCount, opt, "-XX:TieredStopAtLevel=3"); + pass(nonTieredCount, opt, "-XX:-TieredCompilation"); + + // Tiered modes + int tieredCount = heuristicCount(cpus, true); + pass(tieredCount, opt); + pass(tieredCount, opt, "-XX:TieredStopAtLevel=4"); + + // Also check that heuristics did not set up more threads than CPUs available + Asserts.assertTrue(nonTieredCount <= cpus, + "Non-tiered count is larger than number of CPUs: " + nonTieredCount + " > " + cpus); + Asserts.assertTrue(tieredCount <= cpus, + "Tiered count is larger than number of CPUs: " + tieredCount + " > " + cpus); + } + } + + // Direct translation from CompilationPolicy::initialize: + public static int heuristicCount(int cpus, boolean tiered) { + int log_cpu = log2(cpus); + int loglog_cpu = log2(Math.max(log_cpu, 1)); + int min_count = tiered ? 2 : 1; + return Math.max(log_cpu * loglog_cpu * 3 / 2, min_count); + } + + public static int log2(int v) { + return (int)(Math.log(v) / Math.log(2)); + } + + public static void pass(int count, String... args) throws Throwable { + CommandLineOptionTest.verifyOptionValueForSameVM("CICompilerCount", "" + count, "", args); + } + + public static void fail(String... args) throws Throwable { + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(args); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldNotHaveExitValue(0); + } + +} From c90921644643bc731cab4c014a5144a74e670df1 Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Wed, 4 Jun 2025 14:56:20 +0000 Subject: [PATCH 044/143] 8357443: ZGC: Optimize old page iteration in remap remembered phase Reviewed-by: aboldtch, eosterlund --- src/hotspot/share/gc/z/zGeneration.cpp | 19 ++-- src/hotspot/share/gc/z/zGeneration.hpp | 6 ++ src/hotspot/share/gc/z/zRemembered.cpp | 120 +++++++++++++------------ src/hotspot/share/gc/z/zRemembered.hpp | 22 +++++ 4 files changed, 101 insertions(+), 66 deletions(-) diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 78860bbe4cb..772c5a0a98b 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -948,6 +948,14 @@ void ZGenerationYoung::register_with_remset(ZPage* page) { _remembered.register_found_old(page); } +ZRemembered* ZGenerationYoung::remembered() { + return &_remembered; +} + +void ZGenerationYoung::remap_current_remset(ZRemsetTableIterator* iter) { + _remembered.remap_current(iter); +} + ZGenerationTracer* ZGenerationYoung::jfr_tracer() { return &_jfr_tracer; } @@ -1435,7 +1443,7 @@ typedef ClaimingCLDToOopClosure ZRemapCLDClosure; class ZRemapYoungRootsTask : public ZTask { private: - ZGenerationPagesParallelIterator _old_pages_parallel_iterator; + ZRemsetTableIterator _remset_table_iterator; ZRootsIteratorAllColored _roots_colored; ZRootsIteratorAllUncolored _roots_uncolored; @@ -1449,7 +1457,7 @@ private: public: ZRemapYoungRootsTask(ZPageTable* page_table, ZPageAllocator* page_allocator) : ZTask("ZRemapYoungRootsTask"), - _old_pages_parallel_iterator(page_table, ZGenerationId::old, page_allocator), + _remset_table_iterator(ZGeneration::young()->remembered(), false /* previous */), _roots_colored(ZGenerationIdOptional::old), _roots_uncolored(ZGenerationIdOptional::old), _cl_colored(), @@ -1472,11 +1480,8 @@ public: { ZStatTimerWorker timer(ZSubPhaseConcurrentRemapRememberedOld); - _old_pages_parallel_iterator.do_pages([&](ZPage* page) { - // Visit all object fields that potentially pointing into young generation - page->oops_do_current_remembered(ZBarrier::load_barrier_on_oop_field); - return true; - }); + // Visit all object fields that potentially pointing into young generation + ZGeneration::young()->remap_current_remset(&_remset_table_iterator); } } }; diff --git a/src/hotspot/share/gc/z/zGeneration.hpp b/src/hotspot/share/gc/z/zGeneration.hpp index 922efe5ef9e..13adc06b123 100644 --- a/src/hotspot/share/gc/z/zGeneration.hpp +++ b/src/hotspot/share/gc/z/zGeneration.hpp @@ -191,6 +191,7 @@ class ZGenerationYoung : public ZGeneration { friend class VM_ZMarkStartYoung; friend class VM_ZMarkStartYoungAndOld; friend class VM_ZRelocateStartYoung; + friend class ZRemapYoungRootsTask; friend class ZYoungTypeSetter; private: @@ -219,6 +220,8 @@ private: void pause_relocate_start(); void concurrent_relocate(); + ZRemembered* remembered(); + public: ZGenerationYoung(ZPageTable* page_table, const ZForwardingTable* old_forwarding_table, @@ -252,6 +255,9 @@ public: // Register old pages with remembered set void register_with_remset(ZPage* page); + // Remap the oops of the current remembered set + void remap_current_remset(ZRemsetTableIterator* iter); + // Serviceability ZGenerationTracer* jfr_tracer(); diff --git a/src/hotspot/share/gc/z/zRemembered.cpp b/src/hotspot/share/gc/z/zRemembered.cpp index b94d676242c..815b914dd8d 100644 --- a/src/hotspot/share/gc/z/zRemembered.cpp +++ b/src/hotspot/share/gc/z/zRemembered.cpp @@ -392,69 +392,71 @@ struct ZRemsetTableEntry { ZForwarding* _forwarding; }; -class ZRemsetTableIterator { -private: - ZRemembered* const _remembered; - ZPageTable* const _page_table; - const ZForwardingTable* const _old_forwarding_table; - volatile BitMap::idx_t _claimed; - -public: - ZRemsetTableIterator(ZRemembered* remembered) - : _remembered(remembered), - _page_table(remembered->_page_table), - _old_forwarding_table(remembered->_old_forwarding_table), - _claimed(0) {} +ZRemsetTableIterator::ZRemsetTableIterator(ZRemembered* remembered, bool previous) + : _remembered(remembered), + _bm(previous + ? _remembered->_found_old.previous_bitmap() + : _remembered->_found_old.current_bitmap()), + _page_table(remembered->_page_table), + _old_forwarding_table(remembered->_old_forwarding_table), + _claimed(0) {} // This iterator uses the "found old" optimization. - bool next(ZRemsetTableEntry* entry_addr) { - BitMap* const bm = _remembered->_found_old.previous_bitmap(); +bool ZRemsetTableIterator::next(ZRemsetTableEntry* entry_addr) { + BitMap::idx_t prev = Atomic::load(&_claimed); - BitMap::idx_t prev = Atomic::load(&_claimed); - - for (;;) { - if (prev == bm->size()) { - return false; - } - - const BitMap::idx_t page_index = bm->find_first_set_bit(_claimed); - if (page_index == bm->size()) { - Atomic::cmpxchg(&_claimed, prev, page_index, memory_order_relaxed); - return false; - } - - const BitMap::idx_t res = Atomic::cmpxchg(&_claimed, prev, page_index + 1, memory_order_relaxed); - if (res != prev) { - // Someone else claimed - prev = res; - continue; - } - - // Found bit - look around for page or forwarding to scan - - ZForwarding* forwarding = nullptr; - if (ZGeneration::old()->is_phase_relocate()) { - forwarding = _old_forwarding_table->at(page_index); - } - - ZPage* page = _page_table->at(page_index); - if (page != nullptr && !page->is_old()) { - page = nullptr; - } - - if (page == nullptr && forwarding == nullptr) { - // Nothing to scan - continue; - } - - // Found old page or old forwarding - entry_addr->_forwarding = forwarding; - entry_addr->_page = page; - - return true; + for (;;) { + if (prev == _bm->size()) { + return false; } + + const BitMap::idx_t page_index = _bm->find_first_set_bit(_claimed); + if (page_index == _bm->size()) { + Atomic::cmpxchg(&_claimed, prev, page_index, memory_order_relaxed); + return false; + } + + const BitMap::idx_t res = Atomic::cmpxchg(&_claimed, prev, page_index + 1, memory_order_relaxed); + if (res != prev) { + // Someone else claimed + prev = res; + continue; + } + + // Found bit - look around for page or forwarding to scan + + ZForwarding* forwarding = nullptr; + if (ZGeneration::old()->is_phase_relocate()) { + forwarding = _old_forwarding_table->at(page_index); + } + + ZPage* page = _page_table->at(page_index); + if (page != nullptr && !page->is_old()) { + page = nullptr; + } + + if (page == nullptr && forwarding == nullptr) { + // Nothing to scan + continue; + } + + // Found old page or old forwarding + entry_addr->_forwarding = forwarding; + entry_addr->_page = page; + + return true; } -}; +} + +void ZRemembered::remap_current(ZRemsetTableIterator* iter) { + for (ZRemsetTableEntry entry; iter->next(&entry);) { + assert(entry._forwarding == nullptr, "Shouldn't be looking for forwardings"); + assert(entry._page != nullptr, "Must have found a page"); + assert(entry._page->is_old(), "Should only have found old pages"); + + entry._page->oops_do_current_remembered(ZBarrier::load_barrier_on_oop_field); + } +} // This task scans the remembered set and follows pointers when possible. // Interleaving remembered set scanning with marking makes the marking times @@ -470,7 +472,7 @@ public: : ZRestartableTask("ZRememberedScanMarkFollowTask"), _remembered(remembered), _mark(mark), - _remset_table_iterator(remembered) { + _remset_table_iterator(remembered, true /* previous */) { _mark->prepare_work(); _remembered->_page_allocator->enable_safe_destroy(); } diff --git a/src/hotspot/share/gc/z/zRemembered.hpp b/src/hotspot/share/gc/z/zRemembered.hpp index 1400472f2bc..6cc76f5dc26 100644 --- a/src/hotspot/share/gc/z/zRemembered.hpp +++ b/src/hotspot/share/gc/z/zRemembered.hpp @@ -35,7 +35,9 @@ class ZMark; class ZPage; class ZPageAllocator; class ZPageTable; +class ZRemsetTableIterator; struct ZRememberedSetContaining; +struct ZRemsetTableEntry; class ZRemembered { friend class ZRememberedScanMarkFollowTask; @@ -99,6 +101,26 @@ public: // Register pages with the remembered set void register_found_old(ZPage* page); + + // Remap the current remembered set + void remap_current(ZRemsetTableIterator* iter); +}; + +// This iterator uses the "found old" optimization to skip having to iterate +// over the entire page table. Make sure to check where and how the FoundOld +// data is cycled before using this iterator. +class ZRemsetTableIterator { +private: + ZRemembered* const _remembered; + BitMap* const _bm; + ZPageTable* const _page_table; + const ZForwardingTable* const _old_forwarding_table; + volatile BitMap::idx_t _claimed; + +public: + ZRemsetTableIterator(ZRemembered* remembered, bool previous); + + bool next(ZRemsetTableEntry* entry_addr); }; #endif // SHARE_GC_Z_ZREMEMBERED_HPP From ef47635d5a27b003937d865ad9067dbd151db888 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Wed, 4 Jun 2025 16:14:31 +0000 Subject: [PATCH 045/143] 8358015: Fix SequencedMap sequenced view method specifications Reviewed-by: jpai, bchristi --- .../share/classes/java/util/AbstractMap.java | 8 +++ .../share/classes/java/util/SequencedMap.java | 65 +++++++++++++++++-- .../util/SequencedCollection/BasicMap.java | 10 +-- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/util/AbstractMap.java b/src/java.base/share/classes/java/util/AbstractMap.java index 7c0b4d9dd1b..afc5b339354 100644 --- a/src/java.base/share/classes/java/util/AbstractMap.java +++ b/src/java.base/share/classes/java/util/AbstractMap.java @@ -877,6 +877,14 @@ public abstract class AbstractMap implements Map { */ /* non-public */ abstract static class ViewCollection implements Collection { UnsupportedOperationException uoe() { return new UnsupportedOperationException(); } + // convert null entry return values into NSEE + static > T nsee(T entry) { + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry; + } + } abstract Collection view(); public boolean add(E t) { throw uoe(); } diff --git a/src/java.base/share/classes/java/util/SequencedMap.java b/src/java.base/share/classes/java/util/SequencedMap.java index 6bff204b65d..cf27b82896b 100644 --- a/src/java.base/share/classes/java/util/SequencedMap.java +++ b/src/java.base/share/classes/java/util/SequencedMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -263,8 +263,15 @@ public interface SequencedMap extends Map { * * @implSpec * The implementation in this interface returns a {@code SequencedSet} instance - * that behaves as follows. Its {@link SequencedSet#add add} and {@link - * SequencedSet#addAll addAll} methods throw {@link UnsupportedOperationException}. + * that behaves as follows. Its {@link SequencedSet#add add}, {@link + * SequencedSet#addAll addAll}, {@link SequencedSet#addFirst addFirst}, and {@link + * SequencedSet#addLast addLast} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedSet#getFirst getFirst} and {@link SequencedSet#getLast getLast} + * methods are implemented in terms of the {@link #firstEntry firstEntry} and {@link + * #lastEntry lastEntry} methods of this interface, respectively. Its {@link + * SequencedSet#removeFirst removeFirst} and {@link SequencedSet#removeLast removeLast} + * methods are implemented in terms of the {@link #pollFirstEntry pollFirstEntry} and + * {@link #pollLastEntry pollLastEntry} methods of this interface, respectively. * Its {@link SequencedSet#reversed reversed} method returns the {@link * #sequencedKeySet sequencedKeySet} view of the {@link #reversed reversed} view of * this map. Each of its other methods calls the corresponding method of the {@link @@ -286,6 +293,16 @@ public interface SequencedMap extends Map { public int hashCode() { return view().hashCode(); } + public void addFirst(K k) { throw new UnsupportedOperationException(); } + public void addLast(K k) { throw new UnsupportedOperationException(); } + public K getFirst() { return nsee(SequencedMap.this.firstEntry()).getKey(); } + public K getLast() { return nsee(SequencedMap.this.lastEntry()).getKey(); } + public K removeFirst() { + return nsee(SequencedMap.this.pollFirstEntry()).getKey(); + } + public K removeLast() { + return nsee(SequencedMap.this.pollLastEntry()).getKey(); + } } return new SeqKeySet(); } @@ -295,8 +312,15 @@ public interface SequencedMap extends Map { * * @implSpec * The implementation in this interface returns a {@code SequencedCollection} instance - * that behaves as follows. Its {@link SequencedCollection#add add} and {@link - * SequencedCollection#addAll addAll} methods throw {@link UnsupportedOperationException}. + * that behaves as follows. Its {@link SequencedCollection#add add}, {@link + * SequencedCollection#addAll addAll}, {@link SequencedCollection#addFirst addFirst}, and {@link + * SequencedCollection#addLast addLast} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedCollection#getFirst getFirst} and {@link SequencedCollection#getLast getLast} + * methods are implemented in terms of the {@link #firstEntry firstEntry} and {@link + * #lastEntry lastEntry} methods of this interface, respectively. Its {@link + * SequencedCollection#removeFirst removeFirst} and {@link SequencedCollection#removeLast removeLast} + * methods are implemented in terms of the {@link #pollFirstEntry pollFirstEntry} and + * {@link #pollLastEntry pollLastEntry} methods of this interface, respectively. * Its {@link SequencedCollection#reversed reversed} method returns the {@link * #sequencedValues sequencedValues} view of the {@link #reversed reversed} view of * this map. Its {@link Object#equals equals} and {@link Object#hashCode hashCode} methods @@ -313,6 +337,16 @@ public interface SequencedMap extends Map { public SequencedCollection reversed() { return SequencedMap.this.reversed().sequencedValues(); } + public void addFirst(V v) { throw new UnsupportedOperationException(); } + public void addLast(V v) { throw new UnsupportedOperationException(); } + public V getFirst() { return nsee(SequencedMap.this.firstEntry()).getValue(); } + public V getLast() { return nsee(SequencedMap.this.lastEntry()).getValue(); } + public V removeFirst() { + return nsee(SequencedMap.this.pollFirstEntry()).getValue(); + } + public V removeLast() { + return nsee(SequencedMap.this.pollLastEntry()).getValue(); + } } return new SeqValues(); } @@ -322,8 +356,15 @@ public interface SequencedMap extends Map { * * @implSpec * The implementation in this interface returns a {@code SequencedSet} instance - * that behaves as follows. Its {@link SequencedSet#add add} and {@link - * SequencedSet#addAll addAll} methods throw {@link UnsupportedOperationException}. + * that behaves as follows. Its {@link SequencedSet#add add}, {@link + * SequencedSet#addAll addAll}, {@link SequencedSet#addFirst addFirst}, and {@link + * SequencedSet#addLast addLast} methods throw {@link UnsupportedOperationException}. + * Its {@link SequencedSet#getFirst getFirst} and {@link SequencedSet#getLast getLast} + * methods are implemented in terms of the {@link #firstEntry firstEntry} and {@link + * #lastEntry lastEntry} methods of this interface, respectively. Its {@link + * SequencedSet#removeFirst removeFirst} and {@link SequencedSet#removeLast removeLast} + * methods are implemented in terms of the {@link #pollFirstEntry pollFirstEntry} and + * {@link #pollLastEntry pollLastEntry} methods of this interface, respectively. * Its {@link SequencedSet#reversed reversed} method returns the {@link * #sequencedEntrySet sequencedEntrySet} view of the {@link #reversed reversed} view of * this map. Each of its other methods calls the corresponding method of the {@link @@ -346,6 +387,16 @@ public interface SequencedMap extends Map { public int hashCode() { return view().hashCode(); } + public void addFirst(Map.Entry e) { throw new UnsupportedOperationException(); } + public void addLast(Map.Entry e) { throw new UnsupportedOperationException(); } + public Map.Entry getFirst() { return nsee(SequencedMap.this.firstEntry()); } + public Map.Entry getLast() { return nsee(SequencedMap.this.lastEntry()); } + public Map.Entry removeFirst() { + return nsee(SequencedMap.this.pollFirstEntry()); + } + public Map.Entry removeLast() { + return nsee(SequencedMap.this.pollLastEntry()); + } } return new SeqEntrySet(); } diff --git a/test/jdk/java/util/SequencedCollection/BasicMap.java b/test/jdk/java/util/SequencedCollection/BasicMap.java index 028ae4e42c2..40bc2aa99dd 100644 --- a/test/jdk/java/util/SequencedCollection/BasicMap.java +++ b/test/jdk/java/util/SequencedCollection/BasicMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -563,12 +563,12 @@ public class BasicMap { SequencedMap objMap = (SequencedMap)(SequencedMap)map; assertThrows(CCE, () -> { objMap.put(new Object(), 99); }); assertThrows(CCE, () -> { objMap.put("x", new Object()); }); - assertThrows(CCE, () -> { objMap.sequencedEntrySet().getFirst().setValue(new Object()); }); - assertThrows(CCE, () -> { objMap.sequencedEntrySet().reversed().getFirst().setValue(new Object()); }); + assertThrows(CCE, () -> { objMap.sequencedEntrySet().iterator().next().setValue(new Object()); }); + assertThrows(CCE, () -> { objMap.sequencedEntrySet().reversed().iterator().next().setValue(new Object()); }); assertThrows(CCE, () -> { objMap.reversed().put(new Object(), 99); }); assertThrows(CCE, () -> { objMap.reversed().put("x", new Object()); }); - assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().getFirst().setValue(new Object()); }); - assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().reversed().getFirst().setValue(new Object()); }); + assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().iterator().next().setValue(new Object()); }); + assertThrows(CCE, () -> { objMap.reversed().sequencedEntrySet().reversed().iterator().next().setValue(new Object()); }); } public void checkEntry(Map.Entry entry, String key, Integer value) { From 8a79ac88639c35a6938f82a391c4b5d77e6eda32 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 4 Jun 2025 16:40:22 +0000 Subject: [PATCH 046/143] 8358449: Locale.getISOCountries does not specify the returned set is unmodifiable Reviewed-by: naoto --- src/java.base/share/classes/java/util/Locale.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 9c745c1731b..993495ff5ab 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -1310,7 +1310,7 @@ public final class Locale implements Cloneable, Serializable { } /** - * {@return a {@code Set} of ISO3166 country codes for the specified type} + * {@return an unmodifiable {@code Set} of ISO3166 country codes for the specified type} * * @param type {@link Locale.IsoCountryCode} specified ISO code type. * @see java.util.Locale.IsoCountryCode From fd0ab043677d103628afde628e3e75e23fb518b2 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Wed, 4 Jun 2025 16:52:38 +0000 Subject: [PATCH 047/143] 8358330: AsmRemarks and DbgStrings clear() method may not get called before their destructor Reviewed-by: kvn --- src/hotspot/share/asm/codeBuffer.cpp | 14 ++++++++++++-- src/hotspot/share/asm/codeBuffer.hpp | 4 ++++ src/hotspot/share/code/aotCodeCache.cpp | 18 +++++++----------- src/hotspot/share/code/codeBlob.cpp | 11 ----------- src/hotspot/share/code/codeBlob.hpp | 7 +------ 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index ca25cf56be0..3d2d97d1e45 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -1099,7 +1099,8 @@ CHeapString::~CHeapString() { // offset is a byte offset into an instruction stream (CodeBuffer, CodeBlob or // other memory buffer) and remark is a string (comment). // -AsmRemarks::AsmRemarks() : _remarks(new AsmRemarkCollection()) { +AsmRemarks::AsmRemarks() { + init(); assert(_remarks != nullptr, "Allocation failure!"); } @@ -1107,6 +1108,10 @@ AsmRemarks::~AsmRemarks() { assert(_remarks == nullptr, "Must 'clear()' before deleting!"); } +void AsmRemarks::init() { + _remarks = new AsmRemarkCollection(); +} + const char* AsmRemarks::insert(uint offset, const char* remstr) { precond(remstr != nullptr); return _remarks->insert(offset, remstr); @@ -1151,7 +1156,8 @@ uint AsmRemarks::print(uint offset, outputStream* strm) const { // Acting as interface to reference counted collection of (debug) strings used // in the code generated, and thus requiring a fixed address. // -DbgStrings::DbgStrings() : _strings(new DbgStringCollection()) { +DbgStrings::DbgStrings() { + init(); assert(_strings != nullptr, "Allocation failure!"); } @@ -1159,6 +1165,10 @@ DbgStrings::~DbgStrings() { assert(_strings == nullptr, "Must 'clear()' before deleting!"); } +void DbgStrings::init() { + _strings = new DbgStringCollection(); +} + const char* DbgStrings::insert(const char* dbgstr) { const char* str = _strings->lookup(dbgstr); return str != nullptr ? str : _strings->insert(dbgstr); diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp index e6dac484649..96e9a77a923 100644 --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -426,6 +426,8 @@ class AsmRemarks { AsmRemarks(); ~AsmRemarks(); + void init(); + const char* insert(uint offset, const char* remstr); bool is_empty() const; @@ -452,6 +454,8 @@ class DbgStrings { DbgStrings(); ~DbgStrings(); + void init(); + const char* insert(const char* dbgstr); bool is_empty() const; diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 674d71d1bfc..6d8faa10a38 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -915,26 +915,22 @@ CodeBlob* AOTCodeReader::compile_code_blob(const char* name, int entry_offset_co oop_maps = read_oop_map_set(); } -#ifndef PRODUCT - AsmRemarks asm_remarks; - read_asm_remarks(asm_remarks); - DbgStrings dbg_strings; - read_dbg_strings(dbg_strings); -#endif // PRODUCT - CodeBlob* code_blob = CodeBlob::create(archived_blob, stored_name, reloc_data, oop_maps -#ifndef PRODUCT - , asm_remarks - , dbg_strings -#endif ); if (code_blob == nullptr) { // no space left in CodeCache return nullptr; } +#ifndef PRODUCT + code_blob->asm_remarks().init(); + read_asm_remarks(code_blob->asm_remarks()); + code_blob->dbg_strings().init(); + read_dbg_strings(code_blob->dbg_strings()); +#endif // PRODUCT + fix_relocations(code_blob); // Read entries offsets diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index 185b6c06598..5bb37c198d0 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -281,10 +281,6 @@ CodeBlob* CodeBlob::create(CodeBlob* archived_blob, const char* name, address archived_reloc_data, ImmutableOopMapSet* archived_oop_maps -#ifndef PRODUCT - , AsmRemarks& archived_asm_remarks - , DbgStrings& archived_dbg_strings -#endif // PRODUCT ) { ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock @@ -303,13 +299,6 @@ CodeBlob* CodeBlob::create(CodeBlob* archived_blob, archived_oop_maps); assert(blob != nullptr, "sanity check"); -#ifndef PRODUCT - blob->use_remarks(archived_asm_remarks); - archived_asm_remarks.clear(); - blob->use_strings(archived_dbg_strings); - archived_dbg_strings.clear(); -#endif // PRODUCT - // Flush the code block ICache::invalidate_range(blob->code_begin(), blob->code_size()); CodeCache::commit(blob); // Count adapters diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index f813752e01e..f1920a829fc 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -318,12 +318,7 @@ public: static CodeBlob* create(CodeBlob* archived_blob, const char* name, address archived_reloc_data, - ImmutableOopMapSet* archived_oop_maps -#ifndef PRODUCT - , AsmRemarks& archived_asm_remarks - , DbgStrings& archived_dbg_strings -#endif // PRODUCT - ); + ImmutableOopMapSet* archived_oop_maps); }; //---------------------------------------------------------------------------------------------------- From 8939acc8ab0e45b82252f0f2de37e9bd5c3e1493 Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Wed, 4 Jun 2025 17:53:17 +0000 Subject: [PATCH 048/143] 8358057: Update validation of ICC_Profile header data Reviewed-by: honkar --- .../classes/java/awt/color/ICC_Profile.java | 42 +++----- .../java/awt/image/ColorConvertOp.java | 13 +-- .../RenderingIntentStressTest.java | 97 +++++++++++++++++++ .../ValidateICCHeaderData.java | 21 +++- 4 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 test/jdk/java/awt/color/ICC_Profile/RenderingIntentStressTest.java diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java index a3e60217d15..c641e469972 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java @@ -811,12 +811,12 @@ public sealed class ICC_Profile implements Serializable } try { - if (getColorSpaceType(p) == ColorSpace.TYPE_GRAY + if (getColorSpaceType(data) == ColorSpace.TYPE_GRAY && getData(p, icSigMediaWhitePointTag) != null && getData(p, icSigGrayTRCTag) != null) { return new ICC_ProfileGray(p); } - if (getColorSpaceType(p) == ColorSpace.TYPE_RGB + if (getColorSpaceType(data) == ColorSpace.TYPE_RGB && getData(p, icSigMediaWhitePointTag) != null && getData(p, icSigRedColorantTag) != null && getData(p, icSigGreenColorantTag) != null @@ -1028,13 +1028,8 @@ public sealed class ICC_Profile implements Serializable if (info != null) { return info.colorSpaceType; } - return getColorSpaceType(cmmProfile()); - } - - private static int getColorSpaceType(Profile p) { - byte[] theHeader = getData(p, icSigHead); - int theColorSpaceSig = intFromBigEndian(theHeader, icHdrColorSpace); - return iccCStoJCS(theColorSpaceSig); + byte[] theHeader = getData(cmmProfile(), icSigHead); + return getColorSpaceType(theHeader); } private static int getColorSpaceType(byte[] theHeader) { @@ -1057,8 +1052,7 @@ public sealed class ICC_Profile implements Serializable */ public int getPCSType() { byte[] theHeader = getData(icSigHead); - int thePCSSig = intFromBigEndian(theHeader, icHdrPcs); - return iccCStoJCS(thePCSSig); + return getPCSType(theHeader); } private static int getPCSType(byte[] theHeader) { @@ -1189,23 +1183,19 @@ public sealed class ICC_Profile implements Serializable checkRenderingIntent(data); } - private static boolean checkRenderingIntent(byte[] header) { + private static void checkRenderingIntent(byte[] header) { int index = ICC_Profile.icHdrRenderingIntent; - - /* According to ICC spec, only the least-significant 16 bits shall be - * used to encode the rendering intent. The most significant 16 bits - * shall be set to zero. Thus, we are ignoring two most significant - * bytes here. Please refer ICC Spec Document for more details. + /* + * ICC spec: only the least-significant 16 bits encode the rendering + * intent. The most significant 16 bits must be zero and can be ignored. + * https://www.color.org/specification/ICC.1-2022-05.pdf, section 7.2.15 */ - int renderingIntent = ((header[index+2] & 0xff) << 8) | - (header[index+3] & 0xff); - - switch (renderingIntent) { - case icPerceptual, icMediaRelativeColorimetric, - icSaturation, icAbsoluteColorimetric -> { - return true; - } - default -> throw new IllegalArgumentException("Unknown Rendering Intent"); + // Extract 16-bit unsigned rendering intent (0–65535) + int intent = (header[index + 2] & 0xff) << 8 | header[index + 3] & 0xff; + // Only check upper bound since intent can't be negative + if (intent > icICCAbsoluteColorimetric) { + throw new IllegalArgumentException( + "Unknown Rendering Intent: %d".formatted(intent)); } } diff --git a/src/java.desktop/share/classes/java/awt/image/ColorConvertOp.java b/src/java.desktop/share/classes/java/awt/image/ColorConvertOp.java index 3202d2d690d..c1c60397198 100644 --- a/src/java.desktop/share/classes/java/awt/image/ColorConvertOp.java +++ b/src/java.desktop/share/classes/java/awt/image/ColorConvertOp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -684,13 +684,10 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp { private int getRenderingIntent (ICC_Profile profile) { byte[] header = profile.getData(ICC_Profile.icSigHead); int index = ICC_Profile.icHdrRenderingIntent; - - /* According to ICC spec, only the least-significant 16 bits shall be - * used to encode the rendering intent. The most significant 16 bits - * shall be set to zero. Thus, we are ignoring two most significant - * bytes here. - * - * See https://www.color.org/ICC1v42_2006-05.pdf, section 7.2.15. + /* + * ICC spec: only the least-significant 16 bits encode the rendering + * intent. The most significant 16 bits must be zero and can be ignored. + * https://www.color.org/specification/ICC.1-2022-05.pdf, section 7.2.15 */ return ((header[index+2] & 0xff) << 8) | (header[index+3] & 0xff); diff --git a/test/jdk/java/awt/color/ICC_Profile/RenderingIntentStressTest.java b/test/jdk/java/awt/color/ICC_Profile/RenderingIntentStressTest.java new file mode 100644 index 00000000000..05ec6d0d112 --- /dev/null +++ b/test/jdk/java/awt/color/ICC_Profile/RenderingIntentStressTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com Inc. 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. + */ + +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; + +import static java.awt.color.ICC_Profile.icAbsoluteColorimetric; +import static java.awt.color.ICC_Profile.icICCAbsoluteColorimetric; +import static java.awt.color.ICC_Profile.icMediaRelativeColorimetric; +import static java.awt.color.ICC_Profile.icPerceptual; +import static java.awt.color.ICC_Profile.icRelativeColorimetric; +import static java.awt.color.ICC_Profile.icSaturation; + +/** + * @test + * @bug 8358057 + * @summary Stress test for ICC_Profile rendering intent parsing and validation + */ +public final class RenderingIntentStressTest { + + public static void main(String[] args) { + ICC_Profile builtin = ICC_Profile.getInstance(ColorSpace.CS_sRGB); + ICC_Profile profile = ICC_Profile.getInstance(builtin.getData()); + // some random combinations that should be ignored + int[] upperBytes = {0x0000, 0xFFFF, 0xA5A5, 0x8000, 0x0001, 0x8080, + 0x0101, 0xAA55, 0x550A, 0xFF00}; + for (int up : upperBytes) { + for (int low = 0; low <= 0xFFFF; low++) { + test(profile, up, low); + } + } + } + + private static int getRenderingIntent(byte[] header) { + // replicate the logic we have in jdk + int index = ICC_Profile.icHdrRenderingIntent; + return (header[index + 2] & 0xff) << 8 | header[index + 3] & 0xff; + } + + private static void test(ICC_Profile profile, int up, int low) { + byte[] header = profile.getData(ICC_Profile.icSigHead); + // These bytes should be ignored + header[ICC_Profile.icHdrRenderingIntent + 0] = (byte) (up >> 8 & 0xFF); + header[ICC_Profile.icHdrRenderingIntent + 1] = (byte) (up & 0xFF); + // This is the actual intent + header[ICC_Profile.icHdrRenderingIntent + 2] = (byte) (low >> 8 & 0xFF); + header[ICC_Profile.icHdrRenderingIntent + 3] = (byte) (low & 0xFF); + + boolean isValid = isValidIntent(low); + try { + profile.setData(ICC_Profile.icSigHead, header); + if (!isValid) { + throw new RuntimeException("IAE is expected"); + } + } catch (IllegalArgumentException e) { + if (isValid) { + throw e; + } + return; + } + // verify that the intent is correctly stored in the profile by the CMM + byte[] data = profile.getData(ICC_Profile.icSigHead); + int actualIntent = getRenderingIntent(data); + if (actualIntent != low) { + System.out.println("Expected: " + low); + System.out.println("Actual: " + actualIntent); + throw new RuntimeException("Unexpected intent"); + } + } + + private static boolean isValidIntent(int intent) { + return intent == icPerceptual || intent == icRelativeColorimetric + || intent == icMediaRelativeColorimetric + || intent == icSaturation || intent == icAbsoluteColorimetric + || intent == icICCAbsoluteColorimetric; + } +} diff --git a/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java b/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java index 28831a422b0..9867b727a09 100644 --- a/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java +++ b/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8337703 + * @bug 8347377 8358057 * @summary To verify if ICC_Profile's setData() and getInstance() methods * validate header data and throw IAE for invalid values. * @run main ValidateICCHeaderData @@ -144,9 +144,7 @@ public class ValidateICCHeaderData { System.out.println("CASE 10: Passed \n"); System.out.println("CASE 11: Testing INVALID Rendering Intent ..."); - //valid rendering intent values are 0-3 - int invalidRenderIntent = 5; - testInvalidHeaderData(invalidRenderIntent, RENDER_INTENT_START_INDEX, 4); + testInvalidIntent(); System.out.println("CASE 11: Passed \n"); System.out.println("CASE 12: Testing INVALID Header Size ..."); @@ -187,6 +185,21 @@ public class ValidateICCHeaderData { } } + private static void testInvalidIntent() { + //valid rendering intent values are 0-3 + int invalidRenderIntent = 5; + try { + setTag(invalidRenderIntent, RENDER_INTENT_START_INDEX, 4); + throw new RuntimeException("Test Failed ! Expected IAE NOT thrown"); + } catch (IllegalArgumentException iae) { + String message = iae.getMessage(); + System.out.println("Expected IAE thrown: " + message); + if (!message.contains(": " + invalidRenderIntent)) { + throw new RuntimeException("Test Failed ! Unexpected text"); + } + } + } + private static void setTag(int value, int startIndex, int fieldLength) { byte[] byteArray; if (startIndex == RENDER_INTENT_START_INDEX) { From 5ed246d17d9f40489ed715b7df104ec6a832841e Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Wed, 4 Jun 2025 18:07:07 +0000 Subject: [PATCH 049/143] 8357592: Update output parsing in test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java Reviewed-by: rhalade --- .../tools/jarsigner/compatibility/Compatibility.java | 6 +++--- test/jdk/sun/security/tools/jarsigner/warnings/Test.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java b/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java index 3be1e74d972..25bb67c230b 100644 --- a/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java +++ b/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java @@ -516,7 +516,7 @@ public class Compatibility { String line; while ((line = reader.readLine()) != null) { String item = line.trim(); - if (!item.isEmpty()) { + if (!item.isEmpty() && !item.startsWith("#")) { list.add(item); } } @@ -718,8 +718,8 @@ public class Compatibility { String match = "^ (" + " Signature algorithm: " + signItem.certInfo. expectedSigalg(signItem) + ", " + signItem.certInfo. - expectedKeySize() + "-bit " + signItem.certInfo. - expectedKeyAlgorithm() + " key" + expectedKeySize() + "-bit (" + signItem.certInfo. + expectedKeyAlgorithm() + " key|key)" + ")|(" + " Digest algorithm: " + signItem.expectedDigestAlg() + (isWeakAlg(signItem.expectedDigestAlg()) ? " \\(weak\\)" : "") diff --git a/test/jdk/sun/security/tools/jarsigner/warnings/Test.java b/test/jdk/sun/security/tools/jarsigner/warnings/Test.java index 0683c03c10c..224fa754eec 100644 --- a/test/jdk/sun/security/tools/jarsigner/warnings/Test.java +++ b/test/jdk/sun/security/tools/jarsigner/warnings/Test.java @@ -149,7 +149,7 @@ public abstract class Test { + "This algorithm will be disabled in a future update."; static final String WEAK_KEY_WARNING - = "It will be disabled in a future update."; + = "will be disabled in a future update."; static final String JAR_SIGNED = "jar signed."; From 8f821175cc4484d651151818cc518ef608ebcc83 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 4 Jun 2025 18:46:31 +0000 Subject: [PATCH 050/143] 8358170: Repurpose testCompat in test/jdk/java/util/TimeZone/Bug8167143.java Reviewed-by: naoto --- test/jdk/java/util/TimeZone/Bug8167143.java | 56 +++++++-------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/test/jdk/java/util/TimeZone/Bug8167143.java b/test/jdk/java/util/TimeZone/Bug8167143.java index fb94c94214f..c1b9b5a813a 100644 --- a/test/jdk/java/util/TimeZone/Bug8167143.java +++ b/test/jdk/java/util/TimeZone/Bug8167143.java @@ -23,14 +23,14 @@ /* * @test - * @bug 8167143 8174269 + * @bug 8167143 8174269 8358170 * @summary Test * Timezone parsing works for all locales for default providers preference * CLDR implicit locales are correctly reflected, * th_TH bundle is not wrongly cached in DateFormatSymbols, * correct candidate locale list is retrieved for * zh_Hant and zh_Hans and - * Implicit COMPAT Locales nn-NO, nb-NO are reflected in available locales + * Implicit FALLBACK Locales nn-NO, nb-NO are reflected in available locales * @modules java.base/sun.util.locale.provider * java.base/sun.util.spi * jdk.localedata @@ -38,6 +38,7 @@ * @run main Bug8167143 testCldr * @run main Bug8167143 testCache * @run main Bug8167143 testCandidateLocales + * @run main Bug8167143 testFallback */ import java.text.ParseException; import java.text.SimpleDateFormat; @@ -64,7 +65,7 @@ public class Bug8167143 { Locale.forLanguageTag("zh-Hant-TW"), Locale.forLanguageTag("zh-Hant-MO")); - private static final List COMPAT_IMPLICIT_LOCS = List.of(Locale.forLanguageTag("nn-NO"), + private static final List FALLBACK_IMPLICIT_LOCS = List.of(Locale.forLanguageTag("nn-NO"), Locale.forLanguageTag("nb-NO")); /** * List of candidate locales for zh_Hant @@ -97,8 +98,8 @@ public class Bug8167143 { case "testCandidateLocales": testCandidateLocales(); break; - case "testCompat": - testImplicitCompatLocales(); + case "testFallback": + testImplicitFallbackLocales(); break; default: throw new RuntimeException("no test was specified."); @@ -230,45 +231,26 @@ public class Bug8167143 { } /** - * checks that locales nn-NO and nb-NO should be present in list of supported locales for - * all Providers for COMPAT. + * Checks that locales (nn-NO and nb-NO) implicitly supported in FALLBACK + * provider should be present in output of getAvailableLocales() for + * BreakIteratorProvider and CollatorProvider. */ - private static void testImplicitCompatLocales() { - LocaleProviderAdapter jre = LocaleProviderAdapter.forJRE(); - checkPresenceCompat("BreakIteratorProvider", - jre.getBreakIteratorProvider().getAvailableLocales()); - checkPresenceCompat("CollatorProvider", - jre.getCollatorProvider().getAvailableLocales()); - checkPresenceCompat("DateFormatProvider", - jre.getDateFormatProvider().getAvailableLocales()); - checkPresenceCompat("DateFormatSymbolsProvider", - jre.getDateFormatSymbolsProvider().getAvailableLocales()); - checkPresenceCompat("DecimalFormatSymbolsProvider", - jre.getDecimalFormatSymbolsProvider().getAvailableLocales()); - checkPresenceCompat("NumberFormatProvider", - jre.getNumberFormatProvider().getAvailableLocales()); - checkPresenceCompat("CurrencyNameProvider", - jre.getCurrencyNameProvider().getAvailableLocales()); - checkPresenceCompat("LocaleNameProvider", - jre.getLocaleNameProvider().getAvailableLocales()); - checkPresenceCompat("TimeZoneNameProvider", - jre.getTimeZoneNameProvider().getAvailableLocales()); - checkPresenceCompat("CalendarDataProvider", - jre.getCalendarDataProvider().getAvailableLocales()); - checkPresenceCompat("CalendarNameProvider", - jre.getCalendarNameProvider().getAvailableLocales()); - checkPresenceCompat("CalendarProvider", - jre.getCalendarProvider().getAvailableLocales()); + private static void testImplicitFallbackLocales() { + LocaleProviderAdapter fallback = LocaleProviderAdapter.forType(Type.FALLBACK); + checkPresenceFallback("BreakIteratorProvider", + fallback.getBreakIteratorProvider().getAvailableLocales()); + checkPresenceFallback("CollatorProvider", + fallback.getCollatorProvider().getAvailableLocales()); } - private static void checkPresenceCompat(String testName, Locale[] got) { + private static void checkPresenceFallback(String testName, Locale[] got) { List gotLocalesList = Arrays.asList(got); List gotList = new ArrayList<>(gotLocalesList); - if (!gotList.removeAll(COMPAT_IMPLICIT_LOCS)) { + if (!gotList.removeAll(FALLBACK_IMPLICIT_LOCS)) { // check which Implicit locale are not present in retrievedLocales List. - List implicitLocales = new ArrayList<>(COMPAT_IMPLICIT_LOCS); + List implicitLocales = new ArrayList<>(FALLBACK_IMPLICIT_LOCS); implicitLocales.removeAll(gotList); - throw new RuntimeException("Locales those not correctly reflected are " + throw new RuntimeException("Locale(s) not correctly reflected are " + implicitLocales + " for test " + testName); } } From 901144ee0d3e984aa8b8f047498440450942f3e5 Mon Sep 17 00:00:00 2001 From: Ian Graves Date: Wed, 4 Jun 2025 19:46:30 +0000 Subject: [PATCH 051/143] 8358217: jdk/incubator/vector/PreferredSpeciesTest.java#id0 failures - expected [128] but found [256] Co-authored-by: Paul Sandoz Co-authored-by: Jaikiran Pai Reviewed-by: syan, psandoz --- test/jdk/ProblemList.txt | 1 - .../jdk/incubator/vector/PreferredSpeciesTest.java | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 28bb2a1f033..c1303377ddb 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -764,7 +764,6 @@ sun/tools/jstat/jstatLineCounts4.sh 8248691,8268211 jdk/incubator/vector/ShortMaxVectorTests.java 8306592 generic-i586 jdk/incubator/vector/LoadJsvmlTest.java 8305390 windows-x64 -jdk/incubator/vector/PreferredSpeciesTest.java#id0 8358217 generic-all ############################################################################ diff --git a/test/jdk/jdk/incubator/vector/PreferredSpeciesTest.java b/test/jdk/jdk/incubator/vector/PreferredSpeciesTest.java index eb8b0358538..32d44d28ee1 100644 --- a/test/jdk/jdk/incubator/vector/PreferredSpeciesTest.java +++ b/test/jdk/jdk/incubator/vector/PreferredSpeciesTest.java @@ -133,25 +133,18 @@ public class PreferredSpeciesTest { void testLargestShapeFor(Class c) { final int S_64_BITS = 64; int elemSize = 0; - VectorSpecies maxVectorSpecies; if (c == byte.class) { elemSize = Byte.SIZE; - maxVectorSpecies = ByteVector.SPECIES_MAX; } else if (c == short.class) { elemSize = Short.SIZE; - maxVectorSpecies = ShortVector.SPECIES_MAX; } else if (c == int.class) { elemSize = Integer.SIZE; - maxVectorSpecies = IntVector.SPECIES_MAX; } else if (c == long.class) { elemSize = Long.SIZE; - maxVectorSpecies = LongVector.SPECIES_MAX; } else if (c == float.class) { elemSize = Float.SIZE; - maxVectorSpecies = FloatVector.SPECIES_MAX; } else if (c == double.class) { elemSize = Double.SIZE; - maxVectorSpecies = DoubleVector.SPECIES_MAX; } else { throw new IllegalArgumentException("Bad vector element type: " + c.getName()); } @@ -161,11 +154,6 @@ public class PreferredSpeciesTest { int maxLaneCount = VectorSupport.getMaxLaneCount(c); int max = Math.max(maxLaneCount * elemSize, S_64_BITS); - //Assert we're using the same element when comparing shapes - Assert.assertEquals(c, maxVectorSpecies.elementType()); - Assert.assertEquals(vs.vectorBitSize(), max); - Assert.assertEquals(vs.vectorBitSize(), maxVectorSpecies.vectorBitSize()); - } } From 77c110c309739c2e10c9b321914309affe749e6d Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Wed, 4 Jun 2025 20:03:48 +0000 Subject: [PATCH 052/143] 8357000: Write overview documentation for start of release changes Reviewed-by: erikj, iris, ihse, dholmes --- doc/starting-next-release.html | 127 +++++++++++++++++++++++++++++++++ doc/starting-next-release.md | 68 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 doc/starting-next-release.html create mode 100644 doc/starting-next-release.md diff --git a/doc/starting-next-release.html b/doc/starting-next-release.html new file mode 100644 index 00000000000..421229f9fbc --- /dev/null +++ b/doc/starting-next-release.html @@ -0,0 +1,127 @@ + + + + + + + Explanation of start of release changes + + + + + +

+

Explanation of start of release changes

+
+ +

Overview

+

The start of release changes, the changes that turn JDK N +into JDK (N+1), are primarily small updates to various files +along with new files to store symbol information to allow +javac --release N ... to run on JDK (N+1).

+

The updates include changes to files holding meta-data about the +release, files under the src directory for API and tooling +updates, and incidental updates under the test +directory.

+

Details and file updates

+

As a matter of policy, there are a number of semantically distinct +concepts which get incremented separately at the start of a new +release:

+
    +
  • Feature value of Runtime.version()
  • +
  • Highest source version modeled by +javax.lang.model.SourceVersion
  • +
  • Highest class file format major version recognized by the +platform
  • +
  • Highest +-source/-target/--release +argument recognized by javac and related tools
  • +
+

The expected file updates are listed below. Additional files may need +to be updated for a particular release.

+

Meta-data files

+
    +
  • jcheck/conf: update meta-data used by +jcheck and the Skara tooling
  • +
  • make/conf/version-numbers.conf: update to meta-data +used in the build
  • +
+

src files

+
    +
  • src/hotspot/share/classfile/classFileParser.cpp: add a +#define for the new version
  • +
  • src/java.base/share/classes/java/lang/classfile/ClassFile.java: +add a constant for the new class file format version
  • +
  • src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java: +add an enum constant for the new class file format +version
  • +
  • src/java.compiler/share/classes/javax/lang/model/SourceVersion.java: +add an enum constant for the new source version
  • +
  • src/java.compiler/share/classes/javax/lang/model/util/* +visitors: Update @SupportedSourceVersion annotations to +latest value. Note this update is done in lieu of introducing another +set of visitors for each Java SE release.
  • +
  • src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java: +add an enum constant for the new source version internal to +javac
  • +
  • src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java: +add an enum constant for the new class file format version +internal to javac
  • +
  • src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java: +add an enum constant for the new target version internal to +javac
  • +
  • src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java +update printing processor to support the new source version
  • +
  • The symbol information for --release is stored as new +text files in the src/jdk.compiler/share/data/symbols +directory, one file per module. The README file in that directory +contains directions on how to create the files.
  • +
+

test files

+
    +
  • test/langtools/tools/javac/api/TestGetSourceVersions.java: +add new SourceVersion constant to test matrix.
  • +
  • test/langtools/tools/javac/classfiles/ClassVersionChecker.java: +add new enum constant for the new class file version
  • +
  • test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java +update annotation processor extended by javac tests to +cover the new source version
  • +
  • test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out +and +test/langtools/tools/javac/preview/classReaderTest/Client.preview.out: +update expected messages for preview errors and warnings
  • +
+ + diff --git a/doc/starting-next-release.md b/doc/starting-next-release.md new file mode 100644 index 00000000000..10bc364a3e4 --- /dev/null +++ b/doc/starting-next-release.md @@ -0,0 +1,68 @@ +% Explanation of start of release changes + +## Overview + +The start of release changes, the changes that turn JDK _N_ into JDK +(_N_+1), are primarily small updates to various files along with new files to +store symbol information to allow `javac --release N ...` to run on +JDK (_N_+1). + +The updates include changes to files holding meta-data about the +release, files under the `src` directory for API and tooling updates, +and incidental updates under the `test` directory. + +## Details and file updates + +As a matter of policy, there are a number of semantically distinct +concepts which get incremented separately at the start of a new +release: + +* Feature value of `Runtime.version()` +* Highest source version modeled by `javax.lang.model.SourceVersion` +* Highest class file format major version recognized by the platform +* Highest `-source`/`-target`/`--release` argument recognized by + `javac` and related tools + +The expected file updates are listed below. Additional files may need +to be updated for a particular release. + +### Meta-data files + +* `jcheck/conf`: update meta-data used by `jcheck` and the Skara tooling +* `make/conf/version-numbers.conf`: update to meta-data used in the build + +### `src` files + +* `src/hotspot/share/classfile/classFileParser.cpp`: add a `#define` + for the new version +* `src/java.base/share/classes/java/lang/classfile/ClassFile.java`: + add a constant for the new class file format version +* `src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java`: + add an `enum` constant for the new class file format version +* `src/java.compiler/share/classes/javax/lang/model/SourceVersion.java`: + add an `enum` constant for the new source version +* `src/java.compiler/share/classes/javax/lang/model/util/*` visitors: Update + `@SupportedSourceVersion` annotations to latest value. Note this update + is done in lieu of introducing another set of visitors for each Java + SE release. +* `src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java`: + add an `enum` constant for the new source version internal to `javac` +* `src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java`: + add an `enum` constant for the new class file format version internal to `javac` +* `src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java`: + add an `enum` constant for the new target version internal to `javac` +* `src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java` + update printing processor to support the new source version +* The symbol information for `--release` is stored as new text files in the + `src/jdk.compiler/share/data/symbols` directory, one file per + module. The README file in that directory contains directions on how + to create the files. + +### `test` files + +* `test/langtools/tools/javac/api/TestGetSourceVersions.java`: add new `SourceVersion` constant to test matrix. +* `test/langtools/tools/javac/classfiles/ClassVersionChecker.java`: add new enum constant for the new class file version +* `test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java` + update annotation processor extended by `javac` tests to cover the new source version +* `test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out` and `test/langtools/tools/javac/preview/classReaderTest/Client.preview.out`: update expected messages for preview errors and warnings + From 3cf3e4bbec26a84d77cb7a3125a60ba1e1e4ee97 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 4 Jun 2025 21:32:29 +0000 Subject: [PATCH 053/143] 8358339: Handle MethodCounters::_method backlinks after JDK-8355003 Reviewed-by: coleenp, kvn, iveresov --- src/hotspot/share/asm/codeBuffer.cpp | 7 +++++++ src/hotspot/share/code/nmethod.cpp | 2 ++ src/hotspot/share/oops/metadata.hpp | 1 + 3 files changed, 10 insertions(+) diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index 3d2d97d1e45..81bfc932147 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -29,6 +29,7 @@ #include "compiler/disassembler.hpp" #include "logging/log.hpp" #include "oops/klass.inline.hpp" +#include "oops/methodCounters.hpp" #include "oops/methodData.hpp" #include "oops/oop.inline.hpp" #include "runtime/icache.hpp" @@ -537,6 +538,9 @@ void CodeBuffer::finalize_oop_references(const methodHandle& mh) { if (m->is_methodData()) { m = ((MethodData*)m)->method(); } + if (m->is_methodCounters()) { + m = ((MethodCounters*)m)->method(); + } if (m->is_method()) { m = ((Method*)m)->method_holder(); } @@ -561,6 +565,9 @@ void CodeBuffer::finalize_oop_references(const methodHandle& mh) { if (m->is_methodData()) { m = ((MethodData*)m)->method(); } + if (m->is_methodCounters()) { + m = ((MethodCounters*)m)->method(); + } if (m->is_method()) { m = ((Method*)m)->method_holder(); } diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index d82e31fd855..510160a6667 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -788,6 +788,8 @@ class CheckClass : public MetadataClosure { klass = ((Method*)md)->method_holder(); } else if (md->is_methodData()) { klass = ((MethodData*)md)->method()->method_holder(); + } else if (md->is_methodCounters()) { + klass = ((MethodCounters*)md)->method()->method_holder(); } else { md->print(); ShouldNotReachHere(); diff --git a/src/hotspot/share/oops/metadata.hpp b/src/hotspot/share/oops/metadata.hpp index ff18cef60a7..5a890a27fa1 100644 --- a/src/hotspot/share/oops/metadata.hpp +++ b/src/hotspot/share/oops/metadata.hpp @@ -44,6 +44,7 @@ class Metadata : public MetaspaceObj { virtual bool is_method() const { return false; } virtual bool is_methodData() const { return false; } virtual bool is_constantPool() const { return false; } + virtual bool is_methodCounters() const { return false; } virtual int size() const = 0; virtual MetaspaceObj::Type type() const = 0; virtual const char* internal_name() const = 0; From 5b27e9c2df8b386b38b0553d941469cd8aa65c28 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Wed, 4 Jun 2025 22:08:58 +0000 Subject: [PATCH 054/143] 8342818: Implement JEP 509: JFR CPU-Time Profiling Reviewed-by: mgronlun, mdoerr, pchilanomate, apangin, shade --- src/hotspot/os/posix/signals_posix.cpp | 8 + src/hotspot/os/posix/signals_posix.hpp | 2 + src/hotspot/share/jfr/jfr.inline.hpp | 3 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 6 + src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 2 + .../jfr/jni/jfrJniMethodRegistration.cpp | 1 + src/hotspot/share/jfr/metadata/metadata.xml | 16 + .../sampling/jfrCPUTimeThreadSampler.cpp | 769 ++++++++++++++++++ .../sampling/jfrCPUTimeThreadSampler.hpp | 152 ++++ .../periodic/sampling/jfrSampleRequest.cpp | 33 +- .../periodic/sampling/jfrSampleRequest.hpp | 5 + .../periodic/sampling/jfrThreadSampling.cpp | 92 ++- .../periodic/sampling/jfrThreadSampling.hpp | 2 + .../share/jfr/recorder/jfrRecorder.cpp | 15 + .../share/jfr/recorder/jfrRecorder.hpp | 1 + .../recorder/service/jfrEventThrottler.cpp | 12 +- .../share/jfr/support/jfrThreadLocal.cpp | 98 ++- .../share/jfr/support/jfrThreadLocal.hpp | 53 ++ src/hotspot/share/runtime/thread.hpp | 1 + src/hotspot/share/runtime/vmOperation.hpp | 2 + src/hotspot/share/utilities/ticks.hpp | 1 + .../jdk/jfr/internal/EventControl.java | 4 + .../share/classes/jdk/jfr/internal/JVM.java | 10 + .../jdk/jfr/internal/PlatformEventType.java | 19 + .../classes/jdk/jfr/internal/query/view.ini | 25 +- .../internal/settings/CPUThrottleSetting.java | 89 ++ .../classes/jdk/jfr/internal/util/Rate.java | 4 + .../jdk/jfr/internal/util/TimespanRate.java | 68 ++ src/jdk.jfr/share/conf/jfr/default.jfc | 10 + src/jdk.jfr/share/conf/jfr/profile.jfc | 10 + .../metadata/TestLookForUntestedEvents.java | 9 +- .../profiling/BaseTestFullStackTrace.java | 176 ++++ .../TestCPUTimeAndExecutionSample.java | 65 ++ .../TestCPUTimeSampleFullStackTrace.java | 41 + .../TestCPUTimeSampleMultipleRecordings.java | 70 ++ .../profiling/TestCPUTimeSampleNative.java | 66 ++ .../TestCPUTimeSampleThrottling.java | 111 +++ .../TestCPUTimeSamplingLongPeriod.java | 58 ++ .../event/profiling/TestFullStackTrace.java | 131 +-- .../classes/test/RecursiveMethods.java | 76 ++ test/lib/jdk/test/lib/jfr/EventNames.java | 2 + 41 files changed, 2178 insertions(+), 140 deletions(-) create mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp create mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java create mode 100644 test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java create mode 100644 test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp index e900d5695ae..0157d354f40 100644 --- a/src/hotspot/os/posix/signals_posix.cpp +++ b/src/hotspot/os/posix/signals_posix.cpp @@ -1505,6 +1505,14 @@ bool PosixSignals::is_sig_ignored(int sig) { } } +void* PosixSignals::get_signal_handler_for_signal(int sig) { + struct sigaction oact; + if (sigaction(sig, (struct sigaction*)nullptr, &oact) == -1) { + return nullptr; + } + return get_signal_handler(&oact); +} + static void signal_sets_init() { sigemptyset(&preinstalled_sigs); diff --git a/src/hotspot/os/posix/signals_posix.hpp b/src/hotspot/os/posix/signals_posix.hpp index 9deade4db18..c1cb70df153 100644 --- a/src/hotspot/os/posix/signals_posix.hpp +++ b/src/hotspot/os/posix/signals_posix.hpp @@ -52,6 +52,8 @@ public: static bool is_sig_ignored(int sig); + static void* get_signal_handler_for_signal(int sig); + static void hotspot_sigmask(Thread* thread); static void print_signal_handler(outputStream* st, int sig, char* buf, size_t buflen); diff --git a/src/hotspot/share/jfr/jfr.inline.hpp b/src/hotspot/share/jfr/jfr.inline.hpp index bdb47f600e6..5b6fc62d0ea 100644 --- a/src/hotspot/share/jfr/jfr.inline.hpp +++ b/src/hotspot/share/jfr/jfr.inline.hpp @@ -32,7 +32,8 @@ inline bool Jfr::has_sample_request(JavaThread* jt) { assert(jt != nullptr, "invariant"); - return jt->jfr_thread_local()->has_sample_request(); + JfrThreadLocal* tl = jt->jfr_thread_local(); + return tl->has_sample_request() || tl->has_cpu_time_jfr_requests(); } inline void Jfr::check_and_process_sample_request(JavaThread* jt) { diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 6f1c1936574..bc2412a90c1 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -24,6 +24,7 @@ #include "jfr/jfr.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrEventSetting.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -169,6 +170,11 @@ NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_ty return JNI_TRUE; NO_TRANSITION_END +JVM_ENTRY_NO_ENV(void, jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt)) + JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, rate > 0); + JfrCPUTimeThreadSampling::set_rate(rate, auto_adapt == JNI_TRUE); +JVM_END + NO_TRANSITION(void, jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong event_type_id, jlong value)) JfrEventSetting::set_miscellaneous(event_type_id, value); const JfrEventId typed_event_id = (JfrEventId)event_type_id; diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 9c78c6239d4..dbea7f0180d 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -129,6 +129,8 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jclass jvm); jboolean JNICALL jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms); +void JNICALL jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt); + void JNICALL jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong id, jlong value); void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jclass jvm, jlong cutoff_ticks, jboolean, jboolean); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 33a564dee2f..82ef93d95b2 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -83,6 +83,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count, (char*)"setMiscellaneous", (char*)"(JJ)V", (void*)jfr_set_miscellaneous, (char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_throttle, + (char*)"setCPUThrottle", (char*)"(DZ)V", (void*)jfr_set_cpu_throttle, (char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples, (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk, (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread, diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 9c04ec3dca1..03daca946f6 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -962,6 +962,22 @@ + + + + + + + + + + + + + diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp new file mode 100644 index 00000000000..ae8877ee3f2 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp @@ -0,0 +1,769 @@ +/* + * Copyright (c) 2025 SAP SE. 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. + * + */ + +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" +#include "logging/log.hpp" + + +#if defined(LINUX) +#include "jfr/periodic/sampling/jfrThreadSampling.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/utilities/jfrThreadIterator.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "jfrfiles/jfrEventClasses.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/atomic.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/osThread.hpp" +#include "runtime/safepointMechanism.inline.hpp" +#include "runtime/threadSMR.hpp" +#include "runtime/vmOperation.hpp" +#include "runtime/vmThread.hpp" +#include "utilities/ticks.hpp" + +#include "signals_posix.hpp" + +static const int64_t AUTOADAPT_INTERVAL_MS = 100; + +static bool is_excluded(JavaThread* jt) { + return jt->is_hidden_from_external_view() || + jt->jfr_thread_local()->is_excluded() || + jt->is_JfrRecorder_thread(); +} + +static JavaThread* get_java_thread_if_valid() { + Thread* raw_thread = Thread::current_or_null_safe(); + if (raw_thread == nullptr) { + // probably while shutting down + return nullptr; + } + assert(raw_thread->is_Java_thread(), "invariant"); + JavaThread* jt = JavaThread::cast(raw_thread); + if (is_excluded(jt) || jt->is_exiting()) { + return nullptr; + } + return jt; +} + +JfrCPUTimeTraceQueue::JfrCPUTimeTraceQueue(u4 capacity) : + _capacity(capacity), _head(0), _lost_samples(0) { + _data = JfrCHeapObj::new_array(capacity); +} + +JfrCPUTimeTraceQueue::~JfrCPUTimeTraceQueue() { + JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); +} + +bool JfrCPUTimeTraceQueue::enqueue(JfrCPUTimeSampleRequest& request) { + assert(JavaThread::current()->jfr_thread_local()->is_cpu_time_jfr_enqueue_locked(), "invariant"); + assert(&JavaThread::current()->jfr_thread_local()->cpu_time_jfr_queue() == this, "invariant"); + u4 elementIndex; + do { + elementIndex = Atomic::load_acquire(&_head); + if (elementIndex >= _capacity) { + return false; + } + } while (Atomic::cmpxchg(&_head, elementIndex, elementIndex + 1) != elementIndex); + _data[elementIndex] = request; + return true; +} + +JfrCPUTimeSampleRequest& JfrCPUTimeTraceQueue::at(u4 index) { + assert(index < _head, "invariant"); + return _data[index]; +} + +static volatile u4 _lost_samples_sum = 0; + +u4 JfrCPUTimeTraceQueue::size() const { + return Atomic::load_acquire(&_head); +} + +void JfrCPUTimeTraceQueue::set_size(u4 size) { + Atomic::release_store(&_head, size); +} + +u4 JfrCPUTimeTraceQueue::capacity() const { + return _capacity; +} + +void JfrCPUTimeTraceQueue::set_capacity(u4 capacity) { + _head = 0; + JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); + _data = JfrCHeapObj::new_array(capacity); + _capacity = capacity; +} + +bool JfrCPUTimeTraceQueue::is_empty() const { + return Atomic::load_acquire(&_head) == 0; +} + +u4 JfrCPUTimeTraceQueue::lost_samples() const { + return Atomic::load(&_lost_samples); +} + +void JfrCPUTimeTraceQueue::increment_lost_samples() { + Atomic::inc(&_lost_samples_sum); + Atomic::inc(&_lost_samples); +} + +u4 JfrCPUTimeTraceQueue::get_and_reset_lost_samples() { + return Atomic::xchg(&_lost_samples, (u4)0); +} + +void JfrCPUTimeTraceQueue::resize(u4 capacity) { + if (capacity != _capacity) { + set_capacity(capacity); + } +} + +void JfrCPUTimeTraceQueue::resize_for_period(u4 period_millis) { + u4 capacity = CPU_TIME_QUEUE_CAPACITY; + if (period_millis > 0 && period_millis < 10) { + capacity = (u4) ((double) capacity * 10 / period_millis); + } + resize(capacity); +} + +void JfrCPUTimeTraceQueue::clear() { + Atomic::release_store(&_head, (u4)0); +} + +static int64_t compute_sampling_period(double rate) { + if (rate == 0) { + return 0; + } + return os::active_processor_count() * 1000000000.0 / rate; +} + +class JfrCPUSamplerThread : public NonJavaThread { + friend class JfrCPUTimeThreadSampling; + private: + Semaphore _sample; + NonJavaThread* _sampler_thread; + double _rate; + bool _auto_adapt; + volatile int64_t _current_sampling_period_ns; + volatile bool _disenrolled; + // top bit is used to indicate that no signal handler should proceed + volatile u4 _active_signal_handlers; + volatile bool _is_async_processing_of_cpu_time_jfr_requests_triggered; + volatile bool _warned_about_timer_creation_failure; + volatile bool _signal_handler_installed; + + static const u4 STOP_SIGNAL_BIT = 0x80000000; + + JfrCPUSamplerThread(double rate, bool auto_adapt); + + void start_thread(); + + void enroll(); + void disenroll(); + void update_all_thread_timers(); + + void auto_adapt_period_if_needed(); + + void set_rate(double rate, bool auto_adapt); + int64_t get_sampling_period() const { return Atomic::load(&_current_sampling_period_ns); }; + + void sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now); + + // process the queues for all threads that are in native state (and requested to be processed) + void stackwalk_threads_in_native(); + bool create_timer_for_thread(JavaThread* thread, timer_t &timerid); + + void stop_signal_handlers(); + + // returns false if the stop signal bit was set, true otherwise + bool increment_signal_handler_count(); + + void decrement_signal_handler_count(); + + void initialize_active_signal_handler_counter(); + +protected: + virtual void post_run(); +public: + virtual const char* name() const { return "JFR CPU Sampler Thread"; } + virtual const char* type_name() const { return "JfrCPUTimeSampler"; } + void run(); + void on_javathread_create(JavaThread* thread); + void on_javathread_terminate(JavaThread* thread); + + void handle_timer_signal(siginfo_t* info, void* context); + bool init_timers(); + void stop_timer(); + + void trigger_async_processing_of_cpu_time_jfr_requests(); +}; + +JfrCPUSamplerThread::JfrCPUSamplerThread(double rate, bool auto_adapt) : + _sample(), + _sampler_thread(nullptr), + _rate(rate), + _auto_adapt(auto_adapt), + _current_sampling_period_ns(compute_sampling_period(rate)), + _disenrolled(true), + _active_signal_handlers(STOP_SIGNAL_BIT), + _is_async_processing_of_cpu_time_jfr_requests_triggered(false), + _warned_about_timer_creation_failure(false), + _signal_handler_installed(false) { + assert(rate >= 0, "invariant"); +} + +void JfrCPUSamplerThread::trigger_async_processing_of_cpu_time_jfr_requests() { + Atomic::release_store(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true); +} + +void JfrCPUSamplerThread::on_javathread_create(JavaThread* thread) { + if (thread->is_hidden_from_external_view() || thread->is_JfrRecorder_thread() || + !Atomic::load_acquire(&_signal_handler_installed)) { + return; + } + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + tl->cpu_time_jfr_queue().resize_for_period(_current_sampling_period_ns / 1000000); + timer_t timerid; + if (create_timer_for_thread(thread, timerid)) { + tl->set_cpu_timer(&timerid); + } else { + if (!Atomic::or_then_fetch(&_warned_about_timer_creation_failure, true)) { + log_warning(jfr)("Failed to create timer for a thread"); + } + tl->deallocate_cpu_time_jfr_queue(); + } +} + +void JfrCPUSamplerThread::on_javathread_terminate(JavaThread* thread) { + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + timer_t* timer = tl->cpu_timer(); + if (timer == nullptr) { + return; // no timer was created for this thread + } + tl->unset_cpu_timer(); + tl->deallocate_cpu_time_jfr_queue(); + s4 lost_samples = tl->cpu_time_jfr_queue().lost_samples(); + if (lost_samples > 0) { + JfrCPUTimeThreadSampling::send_lost_event(JfrTicks::now(), JfrThreadLocal::thread_id(thread), lost_samples); + } +} + +void JfrCPUSamplerThread::start_thread() { + if (os::create_thread(this, os::os_thread)) { + os::start_thread(this); + } else { + log_error(jfr)("Failed to create thread for thread sampling"); + } +} + +void JfrCPUSamplerThread::enroll() { + if (Atomic::cmpxchg(&_disenrolled, true, false)) { + Atomic::store(&_warned_about_timer_creation_failure, false); + initialize_active_signal_handler_counter(); + log_trace(jfr)("Enrolling CPU thread sampler"); + _sample.signal(); + if (!init_timers()) { + log_error(jfr)("Failed to initialize timers for CPU thread sampler"); + disenroll(); + return; + } + log_trace(jfr)("Enrolled CPU thread sampler"); + } +} + +void JfrCPUSamplerThread::disenroll() { + if (!Atomic::cmpxchg(&_disenrolled, false, true)) { + log_trace(jfr)("Disenrolling CPU thread sampler"); + if (Atomic::load_acquire(&_signal_handler_installed)) { + stop_timer(); + stop_signal_handlers(); + } + _sample.wait(); + log_trace(jfr)("Disenrolled CPU thread sampler"); + } +} + +void JfrCPUSamplerThread::run() { + assert(_sampler_thread == nullptr, "invariant"); + _sampler_thread = this; + int64_t last_auto_adapt_check = os::javaTimeNanos(); + while (true) { + if (!_sample.trywait()) { + // disenrolled + _sample.wait(); + } + _sample.signal(); + + if (os::javaTimeNanos() - last_auto_adapt_check > AUTOADAPT_INTERVAL_MS * 1000000) { + auto_adapt_period_if_needed(); + last_auto_adapt_check = os::javaTimeNanos(); + } + + if (Atomic::cmpxchg(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true, false)) { + stackwalk_threads_in_native(); + } + os::naked_sleep(100); + } +} + +void JfrCPUSamplerThread::stackwalk_threads_in_native() { + ResourceMark rm; + // Required to prevent JFR from sampling through an ongoing safepoint + MutexLocker tlock(Threads_lock); + ThreadsListHandle tlh; + Thread* current = Thread::current(); + for (size_t i = 0; i < tlh.list()->length(); i++) { + JavaThread* jt = tlh.list()->thread_at(i); + JfrThreadLocal* tl = jt->jfr_thread_local(); + if (tl->wants_async_processing_of_cpu_time_jfr_requests()) { + if (jt->thread_state() != _thread_in_native || !tl->try_acquire_cpu_time_jfr_dequeue_lock()) { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + continue; + } + if (jt->has_last_Java_frame()) { + JfrThreadSampling::process_cpu_time_request(jt, tl, current, false); + } else { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + } + tl->release_cpu_time_jfr_queue_lock(); + } + } +} + +static volatile size_t count = 0; + +void JfrCPUTimeThreadSampling::send_empty_event(const JfrTicks &start_time, traceid tid, Tickspan cpu_time_period) { + EventCPUTimeSample event(UNTIMED); + event.set_failed(true); + event.set_starttime(start_time); + event.set_eventThread(tid); + event.set_stackTrace(0); + event.set_samplingPeriod(cpu_time_period); + event.set_biased(false); + event.commit(); +} + + +static volatile size_t biased_count = 0; + +void JfrCPUTimeThreadSampling::send_event(const JfrTicks &start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased) { + EventCPUTimeSample event(UNTIMED); + event.set_failed(false); + event.set_starttime(start_time); + event.set_eventThread(tid); + event.set_stackTrace(sid); + event.set_samplingPeriod(cpu_time_period); + event.set_biased(biased); + event.commit(); + Atomic::inc(&count); + if (biased) { + Atomic::inc(&biased_count); + } + if (Atomic::load(&count) % 1000 == 0) { + log_debug(jfr)("CPU thread sampler sent %zu events, lost %d, biased %zu\n", Atomic::load(&count), Atomic::load(&_lost_samples_sum), Atomic::load(&biased_count)); + } +} + +void JfrCPUTimeThreadSampling::send_lost_event(const JfrTicks &time, traceid tid, s4 lost_samples) { + if (!EventCPUTimeSamplesLost::is_enabled()) { + return; + } + EventCPUTimeSamplesLost event(UNTIMED); + event.set_starttime(time); + event.set_lostSamples(lost_samples); + event.set_eventThread(tid); + event.commit(); +} + +void JfrCPUSamplerThread::post_run() { + this->NonJavaThread::post_run(); + delete this; +} + +static JfrCPUTimeThreadSampling* _instance = nullptr; + +JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { + return *_instance; +} + +JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { + assert(_instance == nullptr, "invariant"); + _instance = new JfrCPUTimeThreadSampling(); + return _instance; +} + +void JfrCPUTimeThreadSampling::destroy() { + if (_instance != nullptr) { + delete _instance; + _instance = nullptr; + } +} + +JfrCPUTimeThreadSampling::JfrCPUTimeThreadSampling() : _sampler(nullptr) {} + +JfrCPUTimeThreadSampling::~JfrCPUTimeThreadSampling() { + if (_sampler != nullptr) { + _sampler->disenroll(); + } +} + +void JfrCPUTimeThreadSampling::create_sampler(double rate, bool auto_adapt) { + assert(_sampler == nullptr, "invariant"); + _sampler = new JfrCPUSamplerThread(rate, auto_adapt); + _sampler->start_thread(); + _sampler->enroll(); +} + +void JfrCPUTimeThreadSampling::update_run_state(double rate, bool auto_adapt) { + if (rate != 0) { + if (_sampler == nullptr) { + create_sampler(rate, auto_adapt); + } else { + _sampler->set_rate(rate, auto_adapt); + _sampler->enroll(); + } + return; + } + if (_sampler != nullptr) { + _sampler->set_rate(rate /* 0 */, auto_adapt); + _sampler->disenroll(); + } +} + +void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { + assert(rate >= 0, "invariant"); + if (_instance == nullptr) { + return; + } + instance().set_rate_value(rate, auto_adapt); +} + +void JfrCPUTimeThreadSampling::set_rate_value(double rate, bool auto_adapt) { + if (_sampler != nullptr) { + _sampler->set_rate(rate, auto_adapt); + } + update_run_state(rate, auto_adapt); +} + +void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread *thread) { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->on_javathread_create(thread); + } +} + +void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread *thread) { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->on_javathread_terminate(thread); + } +} + +void JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests() { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->trigger_async_processing_of_cpu_time_jfr_requests(); + } +} + +void handle_timer_signal(int signo, siginfo_t* info, void* context) { + assert(_instance != nullptr, "invariant"); + _instance->handle_timer_signal(info, context); +} + + +void JfrCPUTimeThreadSampling::handle_timer_signal(siginfo_t* info, void* context) { + if (info->si_code != SI_TIMER) { + // not the signal we are interested in + return; + } + assert(_sampler != nullptr, "invariant"); + + if (!_sampler->increment_signal_handler_count()) { + return; + } + _sampler->handle_timer_signal(info, context); + _sampler->decrement_signal_handler_count(); +} + +void JfrCPUSamplerThread::sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now) { + JfrSampleRequestBuilder::build_cpu_time_sample_request(request, ucontext, jt, jt->jfr_thread_local(), now); +} + +static bool check_state(JavaThread* thread) { + switch (thread->thread_state()) { + case _thread_in_Java: + case _thread_in_native: + return true; + default: + return false; + } +} + +void JfrCPUSamplerThread::handle_timer_signal(siginfo_t* info, void* context) { + JfrTicks now = JfrTicks::now(); + JavaThread* jt = get_java_thread_if_valid(); + if (jt == nullptr) { + return; + } + JfrThreadLocal* tl = jt->jfr_thread_local(); + JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); + if (!check_state(jt)) { + queue.increment_lost_samples(); + return; + } + if (!tl->try_acquire_cpu_time_jfr_enqueue_lock()) { + queue.increment_lost_samples(); + return; + } + + JfrCPUTimeSampleRequest request; + // the sampling period might be too low for the current Linux configuration + // so samples might be skipped and we have to compute the actual period + int64_t period = get_sampling_period() * (info->si_overrun + 1); + request._cpu_time_period = Ticks(period / 1000000000.0 * JfrTime::frequency()) - Ticks(0); + sample_thread(request._request, context, jt, tl, now); + + if (queue.enqueue(request)) { + if (queue.size() == 1) { + tl->set_has_cpu_time_jfr_requests(true); + SafepointMechanism::arm_local_poll_release(jt); + } + } else { + queue.increment_lost_samples(); + } + + if (jt->thread_state() == _thread_in_native) { + if (!tl->wants_async_processing_of_cpu_time_jfr_requests()) { + tl->set_do_async_processing_of_cpu_time_jfr_requests(true); + JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests(); + } + } else { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + } + + tl->release_cpu_time_jfr_queue_lock(); +} + +static const int SIG = SIGPROF; + +static void set_timer_time(timer_t timerid, int64_t period_nanos) { + struct itimerspec its; + if (period_nanos == 0) { + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + } else { + its.it_interval.tv_sec = period_nanos / NANOSECS_PER_SEC; + its.it_interval.tv_nsec = period_nanos % NANOSECS_PER_SEC; + } + its.it_value = its.it_interval; + if (timer_settime(timerid, 0, &its, nullptr) == -1) { + warning("Failed to set timer for thread sampling: %s", os::strerror(os::get_last_error())); + } +} + +bool JfrCPUSamplerThread::create_timer_for_thread(JavaThread* thread, timer_t& timerid) { + struct sigevent sev; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = SIG; + sev.sigev_value.sival_ptr = nullptr; + ((int*)&sev.sigev_notify)[1] = thread->osthread()->thread_id(); + clockid_t clock; + int err = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clock); + if (err != 0) { + log_error(jfr)("Failed to get clock for thread sampling: %s", os::strerror(err)); + return false; + } + if (timer_create(clock, &sev, &timerid) < 0) { + return false; + } + int64_t period = get_sampling_period(); + if (period != 0) { + set_timer_time(timerid, period); + } + return true; +} + + +void JfrCPUSamplerThread::stop_signal_handlers() { + // set the stop signal bit + Atomic::or_then_fetch(&_active_signal_handlers, STOP_SIGNAL_BIT, memory_order_acq_rel); + while (Atomic::load_acquire(&_active_signal_handlers) > STOP_SIGNAL_BIT) { + // wait for all signal handlers to finish + os::naked_short_nanosleep(1000); + } +} + +// returns false if the stop signal bit was set, true otherwise +bool JfrCPUSamplerThread::increment_signal_handler_count() { + // increment the count of active signal handlers + u4 old_value = Atomic::fetch_then_add(&_active_signal_handlers, (u4)1, memory_order_acq_rel); + if ((old_value & STOP_SIGNAL_BIT) != 0) { + // if the stop signal bit was set, we are not allowed to increment + Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); + return false; + } + return true; +} + +void JfrCPUSamplerThread::decrement_signal_handler_count() { + Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); +} + +void JfrCPUSamplerThread::initialize_active_signal_handler_counter() { + Atomic::release_store(&_active_signal_handlers, (u4)0); +} + +class VM_JFRInitializeCPUTimeSampler : public VM_Operation { + private: + JfrCPUSamplerThread* const _sampler; + + public: + VM_JFRInitializeCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} + + VMOp_Type type() const { return VMOp_JFRInitializeCPUTimeSampler; } + void doit() { + JfrJavaThreadIterator iter; + while (iter.has_next()) { + _sampler->on_javathread_create(iter.next()); + } + }; +}; + +bool JfrCPUSamplerThread::init_timers() { + // install sig handler for sig + void* prev_handler = PosixSignals::get_signal_handler_for_signal(SIG); + if ((prev_handler != SIG_DFL && prev_handler != SIG_IGN && prev_handler != (void*)::handle_timer_signal) || + PosixSignals::install_generic_signal_handler(SIG, (void*)::handle_timer_signal) == (void*)-1) { + log_error(jfr)("Conflicting SIGPROF handler found: %p. CPUTimeSample events will not be recorded", prev_handler); + return false; + } + Atomic::release_store(&_signal_handler_installed, true); + VM_JFRInitializeCPUTimeSampler op(this); + VMThread::execute(&op); + return true; +} + +class VM_JFRTerminateCPUTimeSampler : public VM_Operation { + private: + JfrCPUSamplerThread* const _sampler; + + public: + VM_JFRTerminateCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} + + VMOp_Type type() const { return VMOp_JFRTerminateCPUTimeSampler; } + void doit() { + JfrJavaThreadIterator iter; + while (iter.has_next()) { + JavaThread *thread = iter.next(); + JfrThreadLocal* tl = thread->jfr_thread_local(); + timer_t* timer = tl->cpu_timer(); + if (timer == nullptr) { + continue; + } + timer_delete(*timer); + tl->deallocate_cpu_time_jfr_queue(); + tl->unset_cpu_timer(); + } + }; +}; + +void JfrCPUSamplerThread::stop_timer() { + VM_JFRTerminateCPUTimeSampler op(this); + VMThread::execute(&op); +} + +void JfrCPUSamplerThread::auto_adapt_period_if_needed() { + int64_t current_period = get_sampling_period(); + if (_auto_adapt || current_period == -1) { + int64_t period = compute_sampling_period(_rate); + if (period != current_period) { + Atomic::store(&_current_sampling_period_ns, period); + update_all_thread_timers(); + } + } +} + +void JfrCPUSamplerThread::set_rate(double rate, bool auto_adapt) { + _rate = rate; + _auto_adapt = auto_adapt; + if (_rate > 0 && Atomic::load_acquire(&_disenrolled) == false) { + auto_adapt_period_if_needed(); + } else { + Atomic::store(&_current_sampling_period_ns, compute_sampling_period(rate)); + } +} + +void JfrCPUSamplerThread::update_all_thread_timers() { + int64_t period_millis = get_sampling_period(); + ThreadsListHandle tlh; + for (size_t i = 0; i < tlh.length(); i++) { + JavaThread* thread = tlh.thread_at(i); + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + timer_t* timer = tl->cpu_timer(); + if (timer != nullptr) { + set_timer_time(*timer, period_millis); + } + } +} + +#else + +static void warn() { + static bool displayed_warning = false; + if (!displayed_warning) { + warning("CPU time method sampling not supported in JFR on your platform"); + displayed_warning = true; + } +} + +static JfrCPUTimeThreadSampling* _instance = nullptr; + +JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { + return *_instance; +} + +JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { + _instance = new JfrCPUTimeThreadSampling(); + return _instance; +} + +void JfrCPUTimeThreadSampling::destroy() { + delete _instance; + _instance = nullptr; +} + +void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { + if (rate != 0) { + warn(); + } +} + +void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread* thread) { +} + +void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread* thread) { +} + +#endif // defined(LINUX) && defined(INCLUDE_JFR) diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp new file mode 100644 index 00000000000..7c0545f4772 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025 SAP SE. 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. + * + */ + +#ifndef SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP +#define SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP + +#include "jfr/utilities/jfrAllocation.hpp" + +class JavaThread; + +#if defined(LINUX) + +#include "jfr/periodic/sampling/jfrSampleRequest.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +struct JfrCPUTimeSampleRequest { + JfrSampleRequest _request; + Tickspan _cpu_time_period; + + JfrCPUTimeSampleRequest() {} +}; + +// Fixed size async-signal-safe SPSC linear queue backed by an array. +// Designed to be only used under lock and read linearly +class JfrCPUTimeTraceQueue { + + // the default queue capacity, scaled if the sampling period is smaller than 10ms + // when the thread is started + static const u4 CPU_TIME_QUEUE_CAPACITY = 500; + + JfrCPUTimeSampleRequest* _data; + u4 _capacity; + // next unfilled index + volatile u4 _head; + + volatile u4 _lost_samples; + +public: + JfrCPUTimeTraceQueue(u4 capacity); + + ~JfrCPUTimeTraceQueue(); + + // signal safe, but can't be interleaved with dequeue + bool enqueue(JfrCPUTimeSampleRequest& trace); + + JfrCPUTimeSampleRequest& at(u4 index); + + u4 size() const; + + void set_size(u4 size); + + u4 capacity() const; + + // deletes all samples in the queue + void set_capacity(u4 capacity); + + bool is_empty() const; + + u4 lost_samples() const; + + void increment_lost_samples(); + + // returns the previous lost samples count + u4 get_and_reset_lost_samples(); + + void resize(u4 capacity); + + void resize_for_period(u4 period_millis); + + void clear(); + +}; + + +class JfrCPUSamplerThread; + +class JfrCPUTimeThreadSampling : public JfrCHeapObj { + friend class JfrRecorder; + private: + + JfrCPUSamplerThread* _sampler; + + void create_sampler(double rate, bool auto_adapt); + void set_rate_value(double rate, bool auto_adapt); + + JfrCPUTimeThreadSampling(); + ~JfrCPUTimeThreadSampling(); + + static JfrCPUTimeThreadSampling& instance(); + static JfrCPUTimeThreadSampling* create(); + static void destroy(); + + void update_run_state(double rate, bool auto_adapt); + + public: + static void set_rate(double rate, bool auto_adapt); + + static void on_javathread_create(JavaThread* thread); + static void on_javathread_terminate(JavaThread* thread); + void handle_timer_signal(siginfo_t* info, void* context); + + static void send_empty_event(const JfrTicks& start_time, traceid tid, Tickspan cpu_time_period); + static void send_event(const JfrTicks& start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased); + static void send_lost_event(const JfrTicks& time, traceid tid, s4 lost_samples); + + static void trigger_async_processing_of_cpu_time_jfr_requests(); +}; + +#else + +// a basic implementation on other platforms that +// emits warnings + +class JfrCPUTimeThreadSampling : public JfrCHeapObj { + friend class JfrRecorder; +private: + static JfrCPUTimeThreadSampling& instance(); + static JfrCPUTimeThreadSampling* create(); + static void destroy(); + + public: + static void set_rate(double rate, bool auto_adapt); + + static void on_javathread_create(JavaThread* thread); + static void on_javathread_terminate(JavaThread* thread); +}; + +#endif // defined(LINUX) + + +#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp index f8e63e2e344..7049df0198b 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp @@ -24,6 +24,7 @@ #include "asm/codeBuffer.hpp" #include "interpreter/interpreter.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" +#include "jfr/utilities/jfrTime.hpp" #include "runtime/continuationEntry.hpp" #include "runtime/frame.inline.hpp" #include "runtime/javaThread.inline.hpp" @@ -171,7 +172,7 @@ static bool build(JfrSampleRequest& request, intptr_t* fp, JavaThread* jt) { assert(request._sample_sp != nullptr, "invariant"); assert(request._sample_pc != nullptr, "invariant"); assert(jt != nullptr, "invariant"); - assert(jt->thread_state() == _thread_in_Java, "invariant"); + assert(jt->thread_state() == _thread_in_Java || jt->thread_state() == _thread_in_native, "invariant"); // 1. Interpreter frame? if (is_interpreter(request)) { @@ -303,3 +304,33 @@ JfrSampleResult JfrSampleRequestBuilder::build_java_sample_request(const void* u } return set_biased_java_sample(request, tl, jt); } + + +// A biased sample request is denoted by an empty bcp and an empty pc. +static inline void set_cpu_time_biased_sample(JfrSampleRequest& request, JavaThread* jt) { + if (request._sample_bcp != nullptr) { + request._sample_bcp = nullptr; + } + assert(request._sample_bcp == nullptr, "invariant"); + request._sample_pc = nullptr; +} + +void JfrSampleRequestBuilder::build_cpu_time_sample_request(JfrSampleRequest& request, + void* ucontext, + JavaThread* jt, + JfrThreadLocal* tl, + JfrTicks& now) { + assert(jt != nullptr, "invariant"); + request._sample_ticks = now; + + // Prioritize the ljf, if one exists. + request._sample_sp = jt->last_Java_sp(); + if (request._sample_sp == nullptr || !build_from_ljf(request, tl, jt)) { + intptr_t* fp; + request._sample_pc = os::fetch_frame_from_context(ucontext, reinterpret_cast(&request._sample_sp), &fp); + assert(sp_in_stack(request, jt), "invariant"); + if (!build(request, fp, jt)) { + set_cpu_time_biased_sample(request, jt); + } + } +} diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp index 6567e7f8bff..20e737e0cbf 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp @@ -81,6 +81,11 @@ class JfrSampleRequestBuilder : AllStatic { static JfrSampleResult build_java_sample_request(const void* ucontext, JfrThreadLocal* tl, JavaThread* jt); + static void build_cpu_time_sample_request(JfrSampleRequest &request, + void* ucontext, + JavaThread* jt, + JfrThreadLocal* tl, + JfrTicks& now); }; #endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEREQUEST_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index aa72c29cf50..ddc9d59b295 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -28,6 +28,7 @@ #include "code/nmethod.hpp" #include "interpreter/interpreter.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrSampleMonitor.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" #include "jfr/periodic/sampling/jfrThreadSampling.hpp" @@ -161,7 +162,7 @@ static inline bool is_valid(const PcDesc* pc_desc) { return pc_desc != nullptr && pc_desc->scope_decode_offset() != DebugInformationRecorder::serialized_null; } -static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt) { +static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt, bool& biased) { assert(jt != nullptr, "invariant"); if (!jt->has_last_Java_frame()) { @@ -178,6 +179,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, // A biased sample is requested or no code blob. top_frame = jt->last_frame(); in_continuation = is_in_continuation(top_frame, jt); + biased = true; return true; } @@ -227,6 +229,8 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, assert(!stream.current()->is_safepoint_blob_frame(), "invariant"); + biased = true; + // Search the first frame that is above the sampled sp. for (; !stream.is_done(); stream.next()) { frame* const current = stream.current(); @@ -250,6 +254,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc); if (is_valid(pc_desc)) { current->adjust_pc(pc_desc->real_pc(sampled_nm)); + biased = false; } } } @@ -270,8 +275,9 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick assert(current != nullptr, "invariant"); frame top_frame; + bool biased = false; bool in_continuation; - if (!compute_top_frame(request, top_frame, in_continuation, jt)) { + if (!compute_top_frame(request, top_frame, in_continuation, jt, biased)) { return; } @@ -293,6 +299,42 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick } } +#ifdef LINUX +static void record_cpu_time_thread(const JfrCPUTimeSampleRequest& request, const JfrTicks& now, const JfrThreadLocal* tl, JavaThread* jt, Thread* current) { + assert(jt != nullptr, "invariant"); + assert(tl != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + frame top_frame; + bool biased = false; + bool in_continuation = false; + bool could_compute_top_frame = compute_top_frame(request._request, top_frame, in_continuation, jt, biased); + const traceid tid = in_continuation ? tl->vthread_id_with_epoch_update(jt) : JfrThreadLocal::jvm_thread_id(jt); + + if (!could_compute_top_frame) { + JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); + return; + } + traceid sid; + { + ResourceMark rm(current); + JfrStackTrace stacktrace; + if (!stacktrace.record(jt, top_frame, in_continuation, request._request)) { + // Unable to record stacktrace. Fail. + JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); + return; + } + sid = JfrStackTraceRepository::add(stacktrace); + } + assert(sid != 0, "invariant"); + + + JfrCPUTimeThreadSampling::send_event(request._request._sample_ticks, sid, tid, request._cpu_time_period, biased); + if (current == jt) { + send_safepoint_latency_event(request._request, now, sid, jt); + } +} +#endif + static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { assert(tl != nullptr, "invariant"); assert(jt != nullptr, "invariant"); @@ -308,6 +350,49 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav assert(!tl->has_enqueued_requests(), "invariant"); } +static void drain_enqueued_cpu_time_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current, bool lock) { + assert(tl != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); +#ifdef LINUX + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + if (lock) { + tl->acquire_cpu_time_jfr_dequeue_lock(); + } + JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); + for (u4 i = 0; i < queue.size(); i++) { + record_cpu_time_thread(queue.at(i), now, tl, jt, current); + } + queue.clear(); + assert(queue.is_empty(), "invariant"); + tl->set_has_cpu_time_jfr_requests(false); + if (queue.lost_samples() > 0) { + JfrCPUTimeThreadSampling::send_lost_event( now, JfrThreadLocal::thread_id(jt), queue.get_and_reset_lost_samples()); + } + if (lock) { + tl->release_cpu_time_jfr_queue_lock(); + } +#endif +} + +// Entry point for a thread that has been sampled in native code and has a pending JFR CPU time request. +void JfrThreadSampling::process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock) { + assert(jt != nullptr, "invariant"); + + const JfrTicks now = JfrTicks::now(); + drain_enqueued_cpu_time_requests(now, tl, jt, current, lock); +} + +static void drain_all_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { + assert(tl != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + drain_enqueued_requests(now, tl, jt, current); + if (tl->has_cpu_time_jfr_requests()) { + drain_enqueued_cpu_time_requests(now, tl, jt, current, true); + } +} + // Only entered by the JfrSampler thread. bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) { assert(tl != nullptr, "invairant"); @@ -382,5 +467,6 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) { break; } } - drain_enqueued_requests(now, tl, jt, jt); + drain_all_enqueued_requests(now, tl, jt, jt); } + diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp index d4c4bc0d873..3c4d1a126b0 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp @@ -33,8 +33,10 @@ class Thread; class JfrThreadSampling : AllStatic { friend class JfrSamplerThread; + friend class JfrCPUSamplerThread; private: static bool process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread); + static void process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock); public: static void process_sample_request(JavaThread* jt); }; diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index 384305862ca..dd75cb2929f 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -29,6 +29,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/sampling/objectSampler.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" @@ -304,6 +305,9 @@ bool JfrRecorder::create_components() { if (!create_thread_sampler()) { return false; } + if (!create_cpu_time_thread_sampling()) { + return false; + } if (!create_event_throttler()) { return false; } @@ -318,6 +322,7 @@ static JfrStackTraceRepository* _stack_trace_repository; static JfrStringPool* _stringpool = nullptr; static JfrOSInterface* _os_interface = nullptr; static JfrThreadSampler* _thread_sampler = nullptr; +static JfrCPUTimeThreadSampling* _cpu_time_thread_sampling = nullptr; static JfrCheckpointManager* _checkpoint_manager = nullptr; bool JfrRecorder::create_java_event_writer() { @@ -390,6 +395,12 @@ bool JfrRecorder::create_thread_sampler() { return _thread_sampler != nullptr; } +bool JfrRecorder::create_cpu_time_thread_sampling() { + assert(_cpu_time_thread_sampling == nullptr, "invariant"); + _cpu_time_thread_sampling = JfrCPUTimeThreadSampling::create(); + return _cpu_time_thread_sampling != nullptr; +} + bool JfrRecorder::create_event_throttler() { return JfrEventThrottler::create(); } @@ -428,6 +439,10 @@ void JfrRecorder::destroy_components() { JfrThreadSampler::destroy(); _thread_sampler = nullptr; } + if (_cpu_time_thread_sampling != nullptr) { + JfrCPUTimeThreadSampling::destroy(); + _cpu_time_thread_sampling = nullptr; + } JfrEventThrottler::destroy(); } diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp index b917904c5fb..3099c8ad344 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp @@ -54,6 +54,7 @@ class JfrRecorder : public JfrCHeapObj { static bool create_storage(); static bool create_stringpool(); static bool create_thread_sampler(); + static bool create_cpu_time_thread_sampling(); static bool create_event_throttler(); static bool create_components(); static void destroy_components(); diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp index 0befaae7751..f660a01c04c 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp @@ -34,6 +34,7 @@ constexpr static const JfrSamplerParams _disabled_params = { false // reconfigure }; +static JfrEventThrottler* _disabled_cpu_time_sample_throttler = nullptr; static JfrEventThrottler* _object_allocation_throttler = nullptr; static JfrEventThrottler* _safepoint_latency_throttler = nullptr; @@ -48,6 +49,9 @@ JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) : _update(false) {} bool JfrEventThrottler::create() { + assert(_disabled_cpu_time_sample_throttler == nullptr, "invariant"); + _disabled_cpu_time_sample_throttler = new JfrEventThrottler(JfrCPUTimeSampleEvent); + _disabled_cpu_time_sample_throttler->_disabled = true; assert(_object_allocation_throttler == nullptr, "invariant"); _object_allocation_throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent); if (_object_allocation_throttler == nullptr || !_object_allocation_throttler->initialize()) { @@ -59,6 +63,8 @@ bool JfrEventThrottler::create() { } void JfrEventThrottler::destroy() { + delete _disabled_cpu_time_sample_throttler; + _disabled_cpu_time_sample_throttler = nullptr; delete _object_allocation_throttler; _object_allocation_throttler = nullptr; delete _safepoint_latency_throttler; @@ -69,15 +75,19 @@ void JfrEventThrottler::destroy() { // and another for the SamplingLatency event. // When introducing many more throttlers, consider adding a lookup map keyed by event id. JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) { + assert(_disabled_cpu_time_sample_throttler != nullptr, "Disabled CPU time throttler has not been properly initialized"); assert(_object_allocation_throttler != nullptr, "ObjectAllocation throttler has not been properly initialized"); assert(_safepoint_latency_throttler != nullptr, "SafepointLatency throttler has not been properly initialized"); - assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent, "Event type has an unconfigured throttler"); + assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent || event_id == JfrCPUTimeSampleEvent, "Event type has an unconfigured throttler"); if (event_id == JfrObjectAllocationSampleEvent) { return _object_allocation_throttler; } if (event_id == JfrSafepointLatencyEvent) { return _safepoint_latency_throttler; } + if (event_id == JfrCPUTimeSampleEvent) { + return _disabled_cpu_time_sample_throttler; + } return nullptr; } diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index 503aa85e02f..4b805a98a32 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -26,6 +26,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -78,7 +79,15 @@ JfrThreadLocal::JfrThreadLocal() : _enqueued_requests(false), _vthread(false), _notified(false), - _dead(false) { + _dead(false) +#ifdef LINUX + ,_cpu_timer(nullptr), + _cpu_time_jfr_locked(UNLOCKED), + _has_cpu_time_jfr_requests(false), + _cpu_time_jfr_queue(0), + _do_async_processing_of_cpu_time_jfr_requests(false) +#endif + { Thread* thread = Thread::current_or_null(); _parent_trace_id = thread != nullptr ? jvm_thread_id(thread) : (traceid)0; } @@ -129,7 +138,9 @@ void JfrThreadLocal::on_start(Thread* t) { if (JfrRecorder::is_recording()) { JfrCheckpointManager::write_checkpoint(t); if (t->is_Java_thread()) { - send_java_thread_start_event(JavaThread::cast(t)); + JavaThread *const jt = JavaThread::cast(t); + JfrCPUTimeThreadSampling::on_javathread_create(jt); + send_java_thread_start_event(jt); } } if (t->jfr_thread_local()->has_cached_stack_trace()) { @@ -221,6 +232,7 @@ void JfrThreadLocal::on_exit(Thread* t) { if (t->is_Java_thread()) { JavaThread* const jt = JavaThread::cast(t); send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); + JfrCPUTimeThreadSampling::on_javathread_terminate(jt); JfrThreadCPULoadEvent::send_event_for_thread(jt); } release(tl, Thread::current()); // because it could be that Thread::current() != t @@ -537,3 +549,85 @@ Arena* JfrThreadLocal::dcmd_arena(JavaThread* jt) { tl->_dcmd_arena = arena; return arena; } + + +#ifdef LINUX + +void JfrThreadLocal::set_cpu_timer(timer_t* timer) { + if (_cpu_timer == nullptr) { + _cpu_timer = JfrCHeapObj::new_array(1); + } + *_cpu_timer = *timer; +} + +void JfrThreadLocal::unset_cpu_timer() { + if (_cpu_timer != nullptr) { + timer_delete(*_cpu_timer); + JfrCHeapObj::free(_cpu_timer, sizeof(timer_t)); + _cpu_timer = nullptr; + } +} + +timer_t* JfrThreadLocal::cpu_timer() const { + return _cpu_timer; +} + +bool JfrThreadLocal::is_cpu_time_jfr_enqueue_locked() { + return Atomic::load_acquire(&_cpu_time_jfr_locked) == ENQUEUE; +} + +bool JfrThreadLocal::is_cpu_time_jfr_dequeue_locked() { + return Atomic::load_acquire(&_cpu_time_jfr_locked) == DEQUEUE; +} + +bool JfrThreadLocal::try_acquire_cpu_time_jfr_enqueue_lock() { + return Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, ENQUEUE) == UNLOCKED; +} + +bool JfrThreadLocal::try_acquire_cpu_time_jfr_dequeue_lock() { + CPUTimeLockState got; + while (true) { + CPUTimeLockState got = Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE); + if (got == UNLOCKED) { + return true; // successfully locked for dequeue + } + if (got == DEQUEUE) { + return false; // already locked for dequeue + } + // else wait for the lock to be released from a signal handler + } +} + +void JfrThreadLocal::acquire_cpu_time_jfr_dequeue_lock() { + while (Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE) != UNLOCKED); +} + +void JfrThreadLocal::release_cpu_time_jfr_queue_lock() { + Atomic::release_store(&_cpu_time_jfr_locked, UNLOCKED); +} + +void JfrThreadLocal::set_has_cpu_time_jfr_requests(bool has_requests) { + Atomic::release_store(&_has_cpu_time_jfr_requests, has_requests); +} + +bool JfrThreadLocal::has_cpu_time_jfr_requests() { + return Atomic::load_acquire(&_has_cpu_time_jfr_requests); +} + +JfrCPUTimeTraceQueue& JfrThreadLocal::cpu_time_jfr_queue() { + return _cpu_time_jfr_queue; +} + +void JfrThreadLocal::deallocate_cpu_time_jfr_queue() { + cpu_time_jfr_queue().resize(0); +} + +void JfrThreadLocal::set_do_async_processing_of_cpu_time_jfr_requests(bool wants) { + Atomic::release_store(&_do_async_processing_of_cpu_time_jfr_requests, wants); +} + +bool JfrThreadLocal::wants_async_processing_of_cpu_time_jfr_requests() { + return Atomic::load_acquire(&_do_async_processing_of_cpu_time_jfr_requests); +} + +#endif diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 8e545d9c429..715a2c44f93 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -33,6 +33,10 @@ #include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" +#ifdef LINUX +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" +#endif + class Arena; class JavaThread; class JfrBuffer; @@ -79,6 +83,22 @@ class JfrThreadLocal { bool _dead; bool _sampling_critical_section; +#ifdef LINUX + timer_t* _cpu_timer; + + enum CPUTimeLockState { + UNLOCKED, + // locked for enqueuing + ENQUEUE, + // locked for dequeuing + DEQUEUE + }; + volatile CPUTimeLockState _cpu_time_jfr_locked; + volatile bool _has_cpu_time_jfr_requests; + JfrCPUTimeTraceQueue _cpu_time_jfr_queue; + volatile bool _do_async_processing_of_cpu_time_jfr_requests; +#endif + JfrBuffer* install_native_buffer() const; JfrBuffer* install_java_buffer() const; void release(Thread* t); @@ -342,6 +362,39 @@ class JfrThreadLocal { void set_thread_blob(const JfrBlobHandle& handle); const JfrBlobHandle& thread_blob() const; + // CPU time sampling +#ifdef LINUX + void set_cpu_timer(timer_t* timer); + void unset_cpu_timer(); + timer_t* cpu_timer() const; + + // The CPU time JFR lock has three different states: + // - ENQUEUE: lock for enqueuing CPU time requests + // - DEQUEUE: lock for dequeuing CPU time requests + // - UNLOCKED: no lock held + // This ensures that we can safely enqueue and dequeue CPU time requests, + // without interleaving + + bool is_cpu_time_jfr_enqueue_locked(); + bool is_cpu_time_jfr_dequeue_locked(); + + bool try_acquire_cpu_time_jfr_enqueue_lock(); + bool try_acquire_cpu_time_jfr_dequeue_lock(); + void acquire_cpu_time_jfr_dequeue_lock(); + void release_cpu_time_jfr_queue_lock(); + + void set_has_cpu_time_jfr_requests(bool has_events); + bool has_cpu_time_jfr_requests(); + + JfrCPUTimeTraceQueue& cpu_time_jfr_queue(); + void deallocate_cpu_time_jfr_queue(); + + void set_do_async_processing_of_cpu_time_jfr_requests(bool wants); + bool wants_async_processing_of_cpu_time_jfr_requests(); +#else + bool has_cpu_time_jfr_requests() { return false; } +#endif + // Hooks static void on_start(Thread* t); static void on_exit(Thread* t); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index f3a06d5efd2..772ef7bbe82 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -78,6 +78,7 @@ class JavaThread; // - WorkerThread // - WatcherThread // - JfrThreadSampler +// - JfrCPUSamplerThread // - LogAsyncWriter // // All Thread subclasses must be either JavaThread or NonJavaThread. diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 50d85944485..ac5d37381f9 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -115,6 +115,8 @@ template(JFROldObject) \ template(JvmtiPostObjectFree) \ template(RendezvousGCThreads) \ + template(JFRInitializeCPUTimeSampler) \ + template(JFRTerminateCPUTimeSampler) \ template(ReinitializeMDO) class Thread; diff --git a/src/hotspot/share/utilities/ticks.hpp b/src/hotspot/share/utilities/ticks.hpp index 8d2bbc7ce1f..88dc9a787f9 100644 --- a/src/hotspot/share/utilities/ticks.hpp +++ b/src/hotspot/share/utilities/ticks.hpp @@ -236,6 +236,7 @@ class TimeInstant : public Rep { friend class TimePartitionsTest; friend class GCTimerTest; friend class CompilerEvent; + friend class JfrCPUSamplerThread; }; #if INCLUDE_JFR diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index f43e45b724e..2ea4725abc8 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -51,6 +51,7 @@ import jdk.jfr.internal.settings.EnabledSetting; import jdk.jfr.internal.settings.LevelSetting; import jdk.jfr.internal.settings.MethodSetting; import jdk.jfr.internal.settings.PeriodSetting; +import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; @@ -326,6 +327,9 @@ public final class EventControl { private static Control defineThrottle(PlatformEventType type) { String def = type.getAnnotationValue(Throttle.class, ThrottleSetting.DEFAULT_VALUE); type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList())); + if (type.getName().equals("jdk.CPUTimeSample")) { + return new Control(new CPUThrottleSetting(type), def); + } return new Control(new ThrottleSetting(type, def), def); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index e0eaef74b4c..b91f0c337b2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -270,6 +270,16 @@ public final class JVM { */ public static native void setMethodSamplingPeriod(long type, long periodMillis); + /** + * Set the maximum event emission rate for the CPU time sampler + * + * Setting rate to 0 turns off the CPU time sampler. + * + * @param rate the new rate in events per second + * @param autoAdapt true if the rate should be adapted automatically + */ + public static native void setCPUThrottle(double rate, boolean autoAdapt); + /** * Sets the file where data should be written. * diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 32b59bca4c0..769e7055305 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -30,8 +30,10 @@ import java.util.List; import java.util.Objects; import jdk.jfr.SettingDescriptor; +import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; +import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.tracing.Modification; @@ -45,6 +47,7 @@ public final class PlatformEventType extends Type { private final boolean isJVM; private final boolean isJDK; private final boolean isMethodSampling; + private final boolean isCPUTimeMethodSampling; private final List settings = new ArrayList<>(5); private final boolean dynamicSettings; private final int stackTraceOffset; @@ -56,6 +59,7 @@ public final class PlatformEventType extends Type { private boolean stackTraceEnabled = true; private long thresholdTicks = 0; private long period = 0; + private TimespanRate cpuRate; private boolean hasHook; private boolean beginChunk; @@ -75,6 +79,7 @@ public final class PlatformEventType extends Type { this.dynamicSettings = dynamicSettings; this.isJVM = Type.isDefinedByJVM(id); this.isMethodSampling = determineMethodSampling(); + this.isCPUTimeMethodSampling = isJVM && name.equals(Type.EVENT_NAME_PREFIX + "CPUTimeSample"); this.isJDK = isJDK; this.stackTraceOffset = determineStackTraceOffset(); } @@ -191,6 +196,13 @@ public final class PlatformEventType extends Type { } } + public void setCPUThrottle(TimespanRate rate) { + if (isCPUTimeMethodSampling) { + this.cpuRate = rate; + JVM.setCPUThrottle(rate.rate(), rate.autoAdapt()); + } + } + public void setHasPeriod(boolean hasPeriod) { this.hasPeriod = hasPeriod; } @@ -251,6 +263,9 @@ public final class PlatformEventType extends Type { if (isMethodSampling) { long p = enabled ? period : 0; JVM.setMethodSamplingPeriod(getId(), p); + } else if (isCPUTimeMethodSampling) { + TimespanRate r = enabled ? cpuRate : new TimespanRate(0, false); + JVM.setCPUThrottle(r.rate(), r.autoAdapt()); } else { JVM.setEnabled(getId(), enabled); } @@ -388,6 +403,10 @@ public final class PlatformEventType extends Type { return isMethodSampling; } + public boolean isCPUTimeMethodSampling() { + return isCPUTimeMethodSampling; + } + public void setStackFilterId(long id) { startFilterId = id; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index a6192db1ab0..a2ac74142f4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -39,7 +39,8 @@ label = "Active Settings" table = "COLUMN 'Event Type', 'Enabled', 'Threshold', 'Stack Trace','Period','Cutoff', 'Throttle' FORMAT none, missing:whitespace, missing:whitespace, missing:whitespace, - missing:whitespace, missing:whitespace, missing:whitespace + missing:whitespace, missing:whitespace, missing:whitespace, + missing:whitespace SELECT E.id, LAST_BATCH(E.value), LAST_BATCH(T.value), LAST_BATCH(S.value), LAST_BATCH(P.value), LAST_BATCH(C.value), LAST_BATCH(U.value) @@ -400,6 +401,28 @@ table = "COLUMN 'Method', 'Samples', 'Percent' SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) FROM ExecutionSample GROUP BY T LIMIT 25" +[application.cpu-time-hot-methods] +label = "Java Methods that Execute the Most from CPU Time Sampler" +table = "COLUMN 'Method', 'Samples', 'Percent' + FORMAT none, none, normalized + SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) + FROM CPUTimeSample GROUP BY T LIMIT 25" + +[application.cpu-time-statistics] +label = "CPU Time Sample Statistics" +form = "COLUMN 'Successful Samples', 'Failed Samples', 'Biased Samples', 'Total Samples', 'Lost Samples' + SELECT COUNT(S.startTime), COUNT(F.startTime), COUNT(B.startTime), Count(A.startTime), SUM(L.lostSamples) + FROM + CPUTimeSample AS S, + CPUTimeSample AS F, + CPUTimeSample AS A, + CPUTimeSample AS B, + CPUTimeSamplesLost AS L + WHERE + S.failed = 'false' AND + F.failed = 'true' AND + B.biased = 'true'" + [jvm.jdk-agents] label = "JDK Agents" table = "COLUMN 'Time', 'Initialization', 'Name', 'Options' diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java new file mode 100644 index 00000000000..c18aeef2132 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Datadog, Inc. All rights reserved. + * Copyright (c) 2025 SAP SE. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.settings; + +import static jdk.jfr.internal.util.TimespanUnit.SECONDS; +import static jdk.jfr.internal.util.TimespanUnit.MILLISECONDS; + +import java.util.Objects; +import java.util.Set; + +import jdk.jfr.Description; +import jdk.jfr.SettingControl; +import jdk.jfr.Label; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.Type; +import jdk.jfr.internal.util.TimespanRate; +import jdk.jfr.internal.util.Utils; + +@MetadataDefinition +@Label("CPUThrottleSetting") +@Description("Upper bounds the emission rate for CPU time samples") +@Name(Type.SETTINGS_PREFIX + "Rate") +public final class CPUThrottleSetting extends SettingControl { + public static final String DEFAULT_VALUE = "0/s"; + private final PlatformEventType eventType; + private String value = DEFAULT_VALUE; + + public CPUThrottleSetting(PlatformEventType eventType) { + this.eventType = Objects.requireNonNull(eventType); + } + + @Override + public String combine(Set values) { + TimespanRate max = null; + for (String value : values) { + TimespanRate rate = TimespanRate.of(value); + if (rate != null) { + if (max == null || rate.isHigher(max)) { + max = rate; + } + max = new TimespanRate(max.rate(), max.autoAdapt() || rate.autoAdapt()); + } + } + // "off" is not supported + return Objects.requireNonNullElse(max.toString(), DEFAULT_VALUE); + } + + @Override + public void setValue(String value) { + TimespanRate rate = TimespanRate.of(value); + if (rate != null) { + eventType.setCPUThrottle(rate); + this.value = value; + } + } + + @Override + public String getValue() { + return value; + } +} + diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java index f32436a5e0f..2632cd63848 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java @@ -55,4 +55,8 @@ public record Rate(long amount, TimespanUnit unit) { private double inNanos() { return (double) amount / unit.nanos; } + + public double perSecond() { + return inNanos() * 1_000_000_000.0; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java new file mode 100644 index 00000000000..5d671310e3c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 SAP SE. All rights reserved. + * Copyright (c) 2024, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.util; + +import jdk.jfr.internal.settings.CPUThrottleSetting; + +/** + * A rate or fixed period, see {@link jdk.jfr.internal.Rate} + */ +public record TimespanRate(double rate, boolean autoAdapt) { + + public static TimespanRate of(String text) { + if (text.equals("off")) { + text = CPUThrottleSetting.DEFAULT_VALUE; + } + boolean isPeriod = !text.contains("/"); + if (isPeriod) { + var period = ValueParser.parseTimespanWithInfinity(text, Long.MAX_VALUE); + if (period == Long.MAX_VALUE) { + return null; + } + if (period == 0) { + return new TimespanRate(0, false); + } + return new TimespanRate(Runtime.getRuntime().availableProcessors() / (period / 1_000_000_000.0), false); + } + Rate r = Rate.of(text); + if (r == null) { + return null; + } + return new TimespanRate(r.perSecond(), true); + } + + public boolean isHigher(TimespanRate that) { + return rate() > that.rate(); + } + + @Override + public String toString() { + if (autoAdapt) { + return String.format("%d/ns", (long)(rate * 1_000_000_000L)); + } + return String.format("%dns", (long)(Runtime.getRuntime().availableProcessors() / rate * 1_000_000_000L)); + } +} diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 293af26746f..541d1d3aa2f 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -226,6 +226,16 @@ off + + false + 500/s + true + + + + true + + true 10 ms diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 89a9022d11e..9cec2d9a70f 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -206,6 +206,16 @@ 20 ms + + false + 10ms + true + + + + true + + true diff --git a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java index 5b8aacfb1d2..d6e126a493f 100644 --- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java +++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java @@ -89,7 +89,10 @@ public class TestLookForUntestedEvents { // Experimental events private static final Set experimentalEvents = Set.of( - "Flush", "SyncOnValueBasedClass"); + "Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost"); + + // Subset of the experimental events that should have tests + private static final Set experimentalButTestedEvents = Set.of("CPUTimeSample"); public static void main(String[] args) throws Exception { for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) { @@ -110,7 +113,9 @@ public class TestLookForUntestedEvents { .collect(Collectors.toList()); Set eventsNotCoveredByTest = new HashSet<>(jfrEventTypes); - for (String event : jfrEventTypes) { + Set checkedEvents = new HashSet<>(jfrEventTypes); + checkedEvents.addAll(experimentalButTestedEvents); + for (String event : checkedEvents) { for (Path p : paths) { if (findStringInFile(p, event)) { eventsNotCoveredByTest.remove(event); diff --git a/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java new file mode 100644 index 00000000000..de211b8c454 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013, 2023, 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.jfr.event.profiling; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.RecurseThread; + +public class BaseTestFullStackTrace { + private final static int MAX_DEPTH = 64; // currently hardcoded in jvm + + private final String eventName; + private final String threadFieldName; + + public BaseTestFullStackTrace(String eventName, String threadFieldName) { + this.eventName = eventName; + this.threadFieldName = threadFieldName; + } + + public void run() throws Throwable { + RecurseThread[] threads = new RecurseThread[3]; + for (int i = 0; i < threads.length; ++i) { + int depth = MAX_DEPTH - 1 + i; + threads[i] = new RecurseThread(depth); + threads[i].setName("recursethread-" + depth); + threads[i].start(); + } + + for (RecurseThread thread : threads) { + while (!thread.isInRunLoop()) { + Thread.sleep(20); + } + } + + assertStackTraces(threads); + + for (RecurseThread thread : threads) { + thread.quit(); + thread.join(); + } + } + + private void assertStackTraces(RecurseThread[] threads) throws Throwable { + while (true) { + try (Recording recording = new Recording()) { + if (eventName.equals(EventNames.CPUTimeSample)) { + recording.enable(eventName).with("throttle", "50ms"); + } else { + recording.enable(eventName).withPeriod(Duration.ofMillis(50)); + } + recording.start(); + Thread.sleep(500); + recording.stop(); + if (hasValidStackTraces(recording, threads)) { + break; + } + } + }; + } + + private boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { + boolean[] isEventFound = new boolean[threads.length]; + + for (RecordedEvent event : Events.fromRecording(recording)) { + System.out.println("Event: " + event); + String threadName = Events.assertField(event, threadFieldName + ".javaName").getValue(); + long threadId = Events.assertField(event, threadFieldName + ".javaThreadId").getValue(); + + for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { + RecurseThread currThread = threads[threadIndex]; + if (threadId == currThread.getId()) { + System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); + Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); + if ("recurseEnd".equals(getTopMethodName(event))) { + isEventFound[threadIndex] = true; + checkEvent(event, currThread.totalDepth); + break; + } + } + } + } + + for (int i = 0; i < threads.length; ++i) { + String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; + System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); + } + for (int i = 0; i < threads.length; ++i) { + if(!isEventFound[i]) { + // no assertion, let's retry. + // Could be race condition, i.e safe point during Thread.sleep + System.out.println("Failed to validate all threads, will retry."); + return false; + } + } + return true; + } + + public String getTopMethodName(RecordedEvent event) { + List frames = event.getStackTrace().getFrames(); + Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); + return frames.getFirst().getMethod().getName(); + } + + private void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { + RecordedStackTrace stacktrace = null; + try { + stacktrace = event.getStackTrace(); + List frames = stacktrace.getFrames(); + Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); + List expectedMethods = getExpectedMethods(expectedDepth); + Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); + + for (int i = 0; i < frames.size(); ++i) { + String name = frames.get(i).getMethod().getName(); + String expectedName = expectedMethods.get(i); + System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); + Asserts.assertEquals(name, expectedName, "Wrong method name"); + } + + boolean isTruncated = stacktrace.isTruncated(); + boolean isTruncateExpected = expectedDepth > MAX_DEPTH; + Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); + + String firstMethod = frames.getLast().getMethod().getName(); + boolean isFullTrace = "run".equals(firstMethod); + String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); + Asserts.assertTrue(isTruncated != isFullTrace, msg); + } catch (Throwable t) { + System.out.println(String.format("stacktrace:%n%s", stacktrace)); + throw t; + } + } + + private List getExpectedMethods(int depth) { + List methods = new ArrayList<>(); + methods.add("recurseEnd"); + for (int i = 0; i < depth - 2; ++i) { + methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); + } + methods.add("run"); + if (depth > MAX_DEPTH) { + methods = methods.subList(0, MAX_DEPTH); + } + return methods; + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java new file mode 100644 index 00000000000..eb8d33832b5 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.RecurseThread; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main/timeout=30 jdk.jfr.event.profiling.TestCPUTimeAndExecutionSample + */ +public class TestCPUTimeAndExecutionSample { + + static String sampleEvent = EventNames.CPUTimeSample; + + // The period is set to 1100 ms to provoke the 1000 ms + // threshold in the JVM for os::naked_short_sleep(). + public static void main(String[] args) throws Exception { + run(EventNames.ExecutionSample); + run(EventNames.CPUTimeSample); + run(EventNames.ExecutionSample); + run(EventNames.CPUTimeSample); + } + + private static void run(String eventType) { + RecurseThread t = new RecurseThread(50); + t.setDaemon(true); + try (RecordingStream rs = new RecordingStream()) { + rs.enable(sampleEvent).with("throttle", "1000/s"); + rs.onEvent(sampleEvent, e -> { + t.quit(); + rs.close(); + }); + t.start(); + rs.start(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java new file mode 100644 index 00000000000..0d1108346ab --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java @@ -0,0 +1,41 @@ +/* + * 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.jfr.event.profiling; + +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @build jdk.jfr.event.profiling.BaseTestFullStackTrace + * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleFullStackTrace + */ +public class TestCPUTimeSampleFullStackTrace { + + public static void main(String[] args) throws Throwable { + new BaseTestFullStackTrace(EventNames.CPUTimeSample, "eventThread").run(); + } + +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java new file mode 100644 index 00000000000..133df36684c --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.internal.JVM; +import jdk.test.lib.jfr.EventNames; + +/* + * Tests that creating multiple recordings after another is possible. + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSampleMultipleRecordings + */ +public class TestCPUTimeSampleMultipleRecordings { + + static String nativeEvent = EventNames.CPUTimeSample; + + static volatile boolean alive = true; + + public static void main(String[] args) throws Exception { + Thread t = new Thread(TestCPUTimeSampleMultipleRecordings::nativeMethod); + t.setDaemon(true); + t.start(); + for (int i = 0; i < 2; i++) { + try (RecordingStream rs = new RecordingStream()) { + rs.enable(nativeEvent).with("throttle", "1ms"); + rs.onEvent(nativeEvent, e -> { + alive = false; + rs.close(); + }); + + rs.start(); + } + } + alive = false; + } + + public static void nativeMethod() { + while (alive) { + JVM.getPid(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java new file mode 100644 index 00000000000..1617bce4ba3 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017, 2023, 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.internal.JVM; +import jdk.test.lib.jfr.EventNames; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSampleNative + */ +public class TestCPUTimeSampleNative { + + static String nativeEvent = EventNames.CPUTimeSample; + + static volatile boolean alive = true; + + public static void main(String[] args) throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.enable(nativeEvent).with("throttle", "1ms"); + rs.onEvent(nativeEvent, e -> { + alive = false; + rs.close(); + }); + Thread t = new Thread(TestCPUTimeSampleNative::nativeMethod); + t.setDaemon(true); + t.start(); + rs.start(); + } + + } + + public static void nativeMethod() { + while (alive) { + JVM.getPid(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java new file mode 100644 index 00000000000..55b350ad096 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; +import java.lang.management.ManagementFactory; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Comparator; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleThrottling + */ +public class TestCPUTimeSampleThrottling { + + public static void main(String[] args) throws Exception { + testZeroPerSecond(); + testThrottleSettings(); + testThrottleSettingsPeriod(); + } + + private static void testZeroPerSecond() throws Exception { + Asserts.assertTrue(0L == countEvents(1000, "0/s").count()); + } + + private static void testThrottleSettings() throws Exception { + long count = countEvents(1000, + Runtime.getRuntime().availableProcessors() * 2 + "/s").count(); + Asserts.assertTrue(count > 0 && count < 3, + "Expected between 0 and 3 events, got " + count); + } + + private static void testThrottleSettingsPeriod() throws Exception { + float rate = countEvents(1000, "10ms").rate(); + Asserts.assertTrue(rate > 90 && rate < 110, "Expected around 100 events per second, got " + rate); + } + + private record EventCount(long count, float time) { + float rate() { + return count / time; + } + } + + private static EventCount countEvents(int timeMs, String rate) throws Exception { + try(Recording recording = new Recording()) { + recording.enable(EventNames.CPUTimeSample) + .with("throttle", rate); + + var bean = ManagementFactory.getThreadMXBean(); + + recording.start(); + + long startThreadCpuTime = bean.getCurrentThreadCpuTime(); + + wasteCPU(timeMs); + + long spendCPUTime = bean.getCurrentThreadCpuTime() - startThreadCpuTime; + + recording.stop(); + + long eventCount = Events.fromRecording(recording).stream() + .filter(e -> e.getThread().getJavaName() + .equals(Thread.currentThread().getName())) + .count(); + + System.out.println("Event count: " + eventCount + ", CPU time: " + spendCPUTime / 1_000_000_000f + "s"); + + return new EventCount(eventCount, spendCPUTime / 1_000_000_000f); + } + } + + private static void wasteCPU(int durationMs) { + long start = System.currentTimeMillis(); + double i = 0; + while (System.currentTimeMillis() - start < durationMs) { + for (int j = 0; j < 100000; j++) { + i = Math.sqrt(i * Math.pow(Math.sqrt(Math.random()), Math.random())); + } + } + } + +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java new file mode 100644 index 00000000000..69d32d48282 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2022, 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.RecurseThread; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSamplingLongPeriod + */ +public class TestCPUTimeSamplingLongPeriod { + + static String sampleEvent = EventNames.CPUTimeSample; + + // The period is set to 1100 ms to provoke the 1000 ms + // threshold in the JVM for os::naked_short_sleep(). + public static void main(String[] args) throws Exception { + RecurseThread t = new RecurseThread(50); + t.setDaemon(true); + try (RecordingStream rs = new RecordingStream()) { + rs.enable(sampleEvent).with("throttle", "1100ms"); + rs.onEvent(sampleEvent, e -> { + t.quit(); + rs.close(); + }); + t.start(); + rs.start(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java index b06f9eed03d..c337a8128ab 100644 --- a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java +++ b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java @@ -23,147 +23,20 @@ package jdk.jfr.event.profiling; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedFrame; -import jdk.jfr.consumer.RecordedStackTrace; -import jdk.test.lib.Asserts; import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.Events; -import jdk.test.lib.jfr.RecurseThread; /** * @test * @requires vm.hasJFR * @requires vm.opt.DeoptimizeALot != true * @library /test/lib + * @build jdk.jfr.event.profiling.BaseTestFullStackTrace * @run main/othervm jdk.jfr.event.profiling.TestFullStackTrace */ public class TestFullStackTrace { - private final static String EVENT_NAME = EventNames.ExecutionSample; - private final static int MAX_DEPTH = 64; // currently hardcoded in jvm public static void main(String[] args) throws Throwable { - RecurseThread[] threads = new RecurseThread[3]; - for (int i = 0; i < threads.length; ++i) { - int depth = MAX_DEPTH - 1 + i; - threads[i] = new RecurseThread(depth); - threads[i].setName("recursethread-" + depth); - threads[i].start(); - } - - for (RecurseThread thread : threads) { - while (!thread.isInRunLoop()) { - Thread.sleep(20); - } - } - - assertStackTraces(threads); - - for (RecurseThread thread : threads) { - thread.quit(); - thread.join(); - } + new BaseTestFullStackTrace(EventNames.ExecutionSample, "sampledThread").run(); } - private static void assertStackTraces( RecurseThread[] threads) throws Throwable { - Recording recording= null; - do { - recording = new Recording(); - recording.enable(EVENT_NAME).withPeriod(Duration.ofMillis(50)); - recording.start(); - Thread.sleep(500); - recording.stop(); - } while (!hasValidStackTraces(recording, threads)); - } - - private static boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { - boolean[] isEventFound = new boolean[threads.length]; - - for (RecordedEvent event : Events.fromRecording(recording)) { - //System.out.println("Event: " + event); - String threadName = Events.assertField(event, "sampledThread.javaName").getValue(); - long threadId = Events.assertField(event, "sampledThread.javaThreadId").getValue(); - - for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { - RecurseThread currThread = threads[threadIndex]; - if (threadId == currThread.getId()) { - System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); - Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); - if ("recurseEnd".equals(getTopMethodName(event))) { - isEventFound[threadIndex] = true; - checkEvent(event, currThread.totalDepth); - break; - } - } - } - } - - for (int i = 0; i < threads.length; ++i) { - String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; - System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); - } - for (int i = 0; i < threads.length; ++i) { - if(!isEventFound[i]) { - // no assertion, let's retry. - // Could be race condition, i.e safe point during Thread.sleep - System.out.println("Failed to validate all threads, will retry."); - return false; - } - } - return true; - } - - public static String getTopMethodName(RecordedEvent event) { - List frames = event.getStackTrace().getFrames(); - Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); - return frames.getFirst().getMethod().getName(); - } - - private static void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { - RecordedStackTrace stacktrace = null; - try { - stacktrace = event.getStackTrace(); - List frames = stacktrace.getFrames(); - Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); - List expectedMethods = getExpectedMethods(expectedDepth); - Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); - - for (int i = 0; i < frames.size(); ++i) { - String name = frames.get(i).getMethod().getName(); - String expectedName = expectedMethods.get(i); - System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); - Asserts.assertEquals(name, expectedName, "Wrong method name"); - } - - boolean isTruncated = stacktrace.isTruncated(); - boolean isTruncateExpected = expectedDepth > MAX_DEPTH; - Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); - - String firstMethod = frames.getLast().getMethod().getName(); - boolean isFullTrace = "run".equals(firstMethod); - String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); - Asserts.assertTrue(isTruncated != isFullTrace, msg); - } catch (Throwable t) { - System.out.println(String.format("stacktrace:%n%s", stacktrace)); - throw t; - } - } - - private static List getExpectedMethods(int depth) { - List methods = new ArrayList<>(); - methods.add("recurseEnd"); - for (int i = 0; i < depth - 2; ++i) { - methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); - } - methods.add("run"); - if (depth > MAX_DEPTH) { - methods = methods.subList(0, MAX_DEPTH); - } - return methods; - } } diff --git a/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java new file mode 100644 index 00000000000..8dc77fd38da --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 SAP SE. 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 test; + + import java.time.Duration; + + /* + * A class used to create a simple deep call stack for testing purposes + */ + public class RecursiveMethods { + + /** Method that uses recursion to produce a call stack of at least {@code depth} depth */ + public static int entry(int depth) { + return method2(--depth); + } + + private static int method2(int depth) { + return method3(--depth); + } + + private static int method3(int depth) { + return method4(--depth); + } + + private static int method4(int depth) { + return method5(--depth); + } + + private static int method5(int depth) { + return method6(--depth); + } + + private static int method6(int depth) { + return method7(--depth); + } + + private static int method7(int depth) { + return method8(--depth); + } + + private static int method8(int depth) { + return method9(--depth); + } + + private static int method9(int depth) { + return method10(--depth); + } + + private static int method10(int depth) { + if (depth > 0) { + return entry(--depth); + } + return depth; + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 904abe8e3e2..a00898358a8 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -77,6 +77,8 @@ public class EventNames { public static final String ThreadAllocationStatistics = PREFIX + "ThreadAllocationStatistics"; public static final String ExecutionSample = PREFIX + "ExecutionSample"; public static final String NativeMethodSample = PREFIX + "NativeMethodSample"; + public static final String CPUTimeSample = PREFIX + "CPUTimeSample"; + public static final String CPUTimeSamplesLost = PREFIX + "CPUTimeSamplesLost"; public static final String ThreadDump = PREFIX + "ThreadDump"; public static final String OldObjectSample = PREFIX + "OldObjectSample"; public static final String SymbolTableStatistics = PREFIX + "SymbolTableStatistics"; From b787ff6def08a050b690b60e4a0ceb3aec2b73c8 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Wed, 4 Jun 2025 23:19:33 +0000 Subject: [PATCH 055/143] 8358538: Update GHA Windows runner to 2025 Reviewed-by: shade --- .github/workflows/build-windows.yml | 6 +++--- .github/workflows/main.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index d02ef91ad86..9bb43a8b83c 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -63,7 +63,7 @@ env: jobs: build-windows: name: build - runs-on: windows-2019 + runs-on: windows-2025 defaults: run: shell: bash @@ -102,7 +102,7 @@ jobs: id: toolchain-check run: | set +e - '/c/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/vc/auxiliary/build/vcvars64.bat' -vcvars_ver=${{ inputs.msvc-toolset-version }} + '/c/Program Files/Microsoft Visual Studio/2022/Enterprise/vc/auxiliary/build/vcvars64.bat' -vcvars_ver=${{ inputs.msvc-toolset-version }} if [ $? -eq 0 ]; then echo "Toolchain is already installed" echo "toolchain-installed=true" >> $GITHUB_OUTPUT @@ -115,7 +115,7 @@ jobs: run: | # Run Visual Studio Installer '/c/Program Files (x86)/Microsoft Visual Studio/Installer/vs_installer.exe' \ - modify --quiet --installPath 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise' \ + modify --quiet --installPath 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise' \ --add Microsoft.VisualStudio.Component.VC.${{ inputs.msvc-toolset-version }}.${{ inputs.msvc-toolset-architecture }} if: steps.toolchain-check.outputs.toolchain-installed != 'true' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ca64bb82d0..0e64ad78625 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -310,7 +310,7 @@ jobs: uses: ./.github/workflows/build-windows.yml with: platform: windows-x64 - msvc-toolset-version: '14.29' + msvc-toolset-version: '14.43' msvc-toolset-architecture: 'x86.x64' configure-arguments: ${{ github.event.inputs.configure-arguments }} make-arguments: ${{ github.event.inputs.make-arguments }} @@ -322,7 +322,7 @@ jobs: uses: ./.github/workflows/build-windows.yml with: platform: windows-aarch64 - msvc-toolset-version: '14.29' + msvc-toolset-version: '14.43' msvc-toolset-architecture: 'arm64' make-target: 'hotspot' extra-conf-options: '--openjdk-target=aarch64-unknown-cygwin' @@ -393,5 +393,5 @@ jobs: with: platform: windows-x64 bootjdk-platform: windows-x64 - runs-on: windows-2019 + runs-on: windows-2025 debug-suffix: -debug From 9186cc7310c0cca2fca776031280f08d84e43b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Wed, 4 Jun 2025 23:55:18 +0000 Subject: [PATCH 056/143] 8358628: [BACKOUT] 8342818: Implement JEP 509: JFR CPU-Time Profiling Reviewed-by: pchilanomate, dholmes --- src/hotspot/os/posix/signals_posix.cpp | 8 - src/hotspot/os/posix/signals_posix.hpp | 2 - src/hotspot/share/jfr/jfr.inline.hpp | 3 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 6 - src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 2 - .../jfr/jni/jfrJniMethodRegistration.cpp | 1 - src/hotspot/share/jfr/metadata/metadata.xml | 16 - .../sampling/jfrCPUTimeThreadSampler.cpp | 769 ------------------ .../sampling/jfrCPUTimeThreadSampler.hpp | 152 ---- .../periodic/sampling/jfrSampleRequest.cpp | 33 +- .../periodic/sampling/jfrSampleRequest.hpp | 5 - .../periodic/sampling/jfrThreadSampling.cpp | 92 +-- .../periodic/sampling/jfrThreadSampling.hpp | 2 - .../share/jfr/recorder/jfrRecorder.cpp | 15 - .../share/jfr/recorder/jfrRecorder.hpp | 1 - .../recorder/service/jfrEventThrottler.cpp | 12 +- .../share/jfr/support/jfrThreadLocal.cpp | 98 +-- .../share/jfr/support/jfrThreadLocal.hpp | 53 -- src/hotspot/share/runtime/thread.hpp | 1 - src/hotspot/share/runtime/vmOperation.hpp | 2 - src/hotspot/share/utilities/ticks.hpp | 1 - .../jdk/jfr/internal/EventControl.java | 4 - .../share/classes/jdk/jfr/internal/JVM.java | 10 - .../jdk/jfr/internal/PlatformEventType.java | 19 - .../classes/jdk/jfr/internal/query/view.ini | 25 +- .../internal/settings/CPUThrottleSetting.java | 89 -- .../classes/jdk/jfr/internal/util/Rate.java | 4 - .../jdk/jfr/internal/util/TimespanRate.java | 68 -- src/jdk.jfr/share/conf/jfr/default.jfc | 10 - src/jdk.jfr/share/conf/jfr/profile.jfc | 10 - .../metadata/TestLookForUntestedEvents.java | 9 +- .../profiling/BaseTestFullStackTrace.java | 176 ---- .../TestCPUTimeAndExecutionSample.java | 65 -- .../TestCPUTimeSampleFullStackTrace.java | 41 - .../TestCPUTimeSampleMultipleRecordings.java | 70 -- .../profiling/TestCPUTimeSampleNative.java | 66 -- .../TestCPUTimeSampleThrottling.java | 111 --- .../TestCPUTimeSamplingLongPeriod.java | 58 -- .../event/profiling/TestFullStackTrace.java | 131 ++- .../classes/test/RecursiveMethods.java | 76 -- test/lib/jdk/test/lib/jfr/EventNames.java | 2 - 41 files changed, 140 insertions(+), 2178 deletions(-) delete mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp delete mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp delete mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java delete mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java delete mode 100644 test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp index 0157d354f40..e900d5695ae 100644 --- a/src/hotspot/os/posix/signals_posix.cpp +++ b/src/hotspot/os/posix/signals_posix.cpp @@ -1505,14 +1505,6 @@ bool PosixSignals::is_sig_ignored(int sig) { } } -void* PosixSignals::get_signal_handler_for_signal(int sig) { - struct sigaction oact; - if (sigaction(sig, (struct sigaction*)nullptr, &oact) == -1) { - return nullptr; - } - return get_signal_handler(&oact); -} - static void signal_sets_init() { sigemptyset(&preinstalled_sigs); diff --git a/src/hotspot/os/posix/signals_posix.hpp b/src/hotspot/os/posix/signals_posix.hpp index c1cb70df153..9deade4db18 100644 --- a/src/hotspot/os/posix/signals_posix.hpp +++ b/src/hotspot/os/posix/signals_posix.hpp @@ -52,8 +52,6 @@ public: static bool is_sig_ignored(int sig); - static void* get_signal_handler_for_signal(int sig); - static void hotspot_sigmask(Thread* thread); static void print_signal_handler(outputStream* st, int sig, char* buf, size_t buflen); diff --git a/src/hotspot/share/jfr/jfr.inline.hpp b/src/hotspot/share/jfr/jfr.inline.hpp index 5b6fc62d0ea..bdb47f600e6 100644 --- a/src/hotspot/share/jfr/jfr.inline.hpp +++ b/src/hotspot/share/jfr/jfr.inline.hpp @@ -32,8 +32,7 @@ inline bool Jfr::has_sample_request(JavaThread* jt) { assert(jt != nullptr, "invariant"); - JfrThreadLocal* tl = jt->jfr_thread_local(); - return tl->has_sample_request() || tl->has_cpu_time_jfr_requests(); + return jt->jfr_thread_local()->has_sample_request(); } inline void Jfr::check_and_process_sample_request(JavaThread* jt) { diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index bc2412a90c1..6f1c1936574 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -24,7 +24,6 @@ #include "jfr/jfr.hpp" #include "jfr/jfrEvents.hpp" -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrEventSetting.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -170,11 +169,6 @@ NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_ty return JNI_TRUE; NO_TRANSITION_END -JVM_ENTRY_NO_ENV(void, jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt)) - JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, rate > 0); - JfrCPUTimeThreadSampling::set_rate(rate, auto_adapt == JNI_TRUE); -JVM_END - NO_TRANSITION(void, jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong event_type_id, jlong value)) JfrEventSetting::set_miscellaneous(event_type_id, value); const JfrEventId typed_event_id = (JfrEventId)event_type_id; diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index dbea7f0180d..9c78c6239d4 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -129,8 +129,6 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jclass jvm); jboolean JNICALL jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms); -void JNICALL jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt); - void JNICALL jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong id, jlong value); void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jclass jvm, jlong cutoff_ticks, jboolean, jboolean); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 82ef93d95b2..33a564dee2f 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -83,7 +83,6 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count, (char*)"setMiscellaneous", (char*)"(JJ)V", (void*)jfr_set_miscellaneous, (char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_throttle, - (char*)"setCPUThrottle", (char*)"(DZ)V", (void*)jfr_set_cpu_throttle, (char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples, (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk, (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread, diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 03daca946f6..9c04ec3dca1 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -962,22 +962,6 @@ - - - - - - - - - - - - - diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp deleted file mode 100644 index ae8877ee3f2..00000000000 --- a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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. - * - */ - -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" -#include "logging/log.hpp" - - -#if defined(LINUX) -#include "jfr/periodic/sampling/jfrThreadSampling.hpp" -#include "jfr/support/jfrThreadLocal.hpp" -#include "jfr/utilities/jfrTime.hpp" -#include "jfr/utilities/jfrThreadIterator.hpp" -#include "jfr/utilities/jfrTypes.hpp" -#include "jfrfiles/jfrEventClasses.hpp" -#include "memory/resourceArea.hpp" -#include "runtime/atomic.hpp" -#include "runtime/javaThread.hpp" -#include "runtime/osThread.hpp" -#include "runtime/safepointMechanism.inline.hpp" -#include "runtime/threadSMR.hpp" -#include "runtime/vmOperation.hpp" -#include "runtime/vmThread.hpp" -#include "utilities/ticks.hpp" - -#include "signals_posix.hpp" - -static const int64_t AUTOADAPT_INTERVAL_MS = 100; - -static bool is_excluded(JavaThread* jt) { - return jt->is_hidden_from_external_view() || - jt->jfr_thread_local()->is_excluded() || - jt->is_JfrRecorder_thread(); -} - -static JavaThread* get_java_thread_if_valid() { - Thread* raw_thread = Thread::current_or_null_safe(); - if (raw_thread == nullptr) { - // probably while shutting down - return nullptr; - } - assert(raw_thread->is_Java_thread(), "invariant"); - JavaThread* jt = JavaThread::cast(raw_thread); - if (is_excluded(jt) || jt->is_exiting()) { - return nullptr; - } - return jt; -} - -JfrCPUTimeTraceQueue::JfrCPUTimeTraceQueue(u4 capacity) : - _capacity(capacity), _head(0), _lost_samples(0) { - _data = JfrCHeapObj::new_array(capacity); -} - -JfrCPUTimeTraceQueue::~JfrCPUTimeTraceQueue() { - JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); -} - -bool JfrCPUTimeTraceQueue::enqueue(JfrCPUTimeSampleRequest& request) { - assert(JavaThread::current()->jfr_thread_local()->is_cpu_time_jfr_enqueue_locked(), "invariant"); - assert(&JavaThread::current()->jfr_thread_local()->cpu_time_jfr_queue() == this, "invariant"); - u4 elementIndex; - do { - elementIndex = Atomic::load_acquire(&_head); - if (elementIndex >= _capacity) { - return false; - } - } while (Atomic::cmpxchg(&_head, elementIndex, elementIndex + 1) != elementIndex); - _data[elementIndex] = request; - return true; -} - -JfrCPUTimeSampleRequest& JfrCPUTimeTraceQueue::at(u4 index) { - assert(index < _head, "invariant"); - return _data[index]; -} - -static volatile u4 _lost_samples_sum = 0; - -u4 JfrCPUTimeTraceQueue::size() const { - return Atomic::load_acquire(&_head); -} - -void JfrCPUTimeTraceQueue::set_size(u4 size) { - Atomic::release_store(&_head, size); -} - -u4 JfrCPUTimeTraceQueue::capacity() const { - return _capacity; -} - -void JfrCPUTimeTraceQueue::set_capacity(u4 capacity) { - _head = 0; - JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); - _data = JfrCHeapObj::new_array(capacity); - _capacity = capacity; -} - -bool JfrCPUTimeTraceQueue::is_empty() const { - return Atomic::load_acquire(&_head) == 0; -} - -u4 JfrCPUTimeTraceQueue::lost_samples() const { - return Atomic::load(&_lost_samples); -} - -void JfrCPUTimeTraceQueue::increment_lost_samples() { - Atomic::inc(&_lost_samples_sum); - Atomic::inc(&_lost_samples); -} - -u4 JfrCPUTimeTraceQueue::get_and_reset_lost_samples() { - return Atomic::xchg(&_lost_samples, (u4)0); -} - -void JfrCPUTimeTraceQueue::resize(u4 capacity) { - if (capacity != _capacity) { - set_capacity(capacity); - } -} - -void JfrCPUTimeTraceQueue::resize_for_period(u4 period_millis) { - u4 capacity = CPU_TIME_QUEUE_CAPACITY; - if (period_millis > 0 && period_millis < 10) { - capacity = (u4) ((double) capacity * 10 / period_millis); - } - resize(capacity); -} - -void JfrCPUTimeTraceQueue::clear() { - Atomic::release_store(&_head, (u4)0); -} - -static int64_t compute_sampling_period(double rate) { - if (rate == 0) { - return 0; - } - return os::active_processor_count() * 1000000000.0 / rate; -} - -class JfrCPUSamplerThread : public NonJavaThread { - friend class JfrCPUTimeThreadSampling; - private: - Semaphore _sample; - NonJavaThread* _sampler_thread; - double _rate; - bool _auto_adapt; - volatile int64_t _current_sampling_period_ns; - volatile bool _disenrolled; - // top bit is used to indicate that no signal handler should proceed - volatile u4 _active_signal_handlers; - volatile bool _is_async_processing_of_cpu_time_jfr_requests_triggered; - volatile bool _warned_about_timer_creation_failure; - volatile bool _signal_handler_installed; - - static const u4 STOP_SIGNAL_BIT = 0x80000000; - - JfrCPUSamplerThread(double rate, bool auto_adapt); - - void start_thread(); - - void enroll(); - void disenroll(); - void update_all_thread_timers(); - - void auto_adapt_period_if_needed(); - - void set_rate(double rate, bool auto_adapt); - int64_t get_sampling_period() const { return Atomic::load(&_current_sampling_period_ns); }; - - void sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now); - - // process the queues for all threads that are in native state (and requested to be processed) - void stackwalk_threads_in_native(); - bool create_timer_for_thread(JavaThread* thread, timer_t &timerid); - - void stop_signal_handlers(); - - // returns false if the stop signal bit was set, true otherwise - bool increment_signal_handler_count(); - - void decrement_signal_handler_count(); - - void initialize_active_signal_handler_counter(); - -protected: - virtual void post_run(); -public: - virtual const char* name() const { return "JFR CPU Sampler Thread"; } - virtual const char* type_name() const { return "JfrCPUTimeSampler"; } - void run(); - void on_javathread_create(JavaThread* thread); - void on_javathread_terminate(JavaThread* thread); - - void handle_timer_signal(siginfo_t* info, void* context); - bool init_timers(); - void stop_timer(); - - void trigger_async_processing_of_cpu_time_jfr_requests(); -}; - -JfrCPUSamplerThread::JfrCPUSamplerThread(double rate, bool auto_adapt) : - _sample(), - _sampler_thread(nullptr), - _rate(rate), - _auto_adapt(auto_adapt), - _current_sampling_period_ns(compute_sampling_period(rate)), - _disenrolled(true), - _active_signal_handlers(STOP_SIGNAL_BIT), - _is_async_processing_of_cpu_time_jfr_requests_triggered(false), - _warned_about_timer_creation_failure(false), - _signal_handler_installed(false) { - assert(rate >= 0, "invariant"); -} - -void JfrCPUSamplerThread::trigger_async_processing_of_cpu_time_jfr_requests() { - Atomic::release_store(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true); -} - -void JfrCPUSamplerThread::on_javathread_create(JavaThread* thread) { - if (thread->is_hidden_from_external_view() || thread->is_JfrRecorder_thread() || - !Atomic::load_acquire(&_signal_handler_installed)) { - return; - } - JfrThreadLocal* tl = thread->jfr_thread_local(); - assert(tl != nullptr, "invariant"); - tl->cpu_time_jfr_queue().resize_for_period(_current_sampling_period_ns / 1000000); - timer_t timerid; - if (create_timer_for_thread(thread, timerid)) { - tl->set_cpu_timer(&timerid); - } else { - if (!Atomic::or_then_fetch(&_warned_about_timer_creation_failure, true)) { - log_warning(jfr)("Failed to create timer for a thread"); - } - tl->deallocate_cpu_time_jfr_queue(); - } -} - -void JfrCPUSamplerThread::on_javathread_terminate(JavaThread* thread) { - JfrThreadLocal* tl = thread->jfr_thread_local(); - assert(tl != nullptr, "invariant"); - timer_t* timer = tl->cpu_timer(); - if (timer == nullptr) { - return; // no timer was created for this thread - } - tl->unset_cpu_timer(); - tl->deallocate_cpu_time_jfr_queue(); - s4 lost_samples = tl->cpu_time_jfr_queue().lost_samples(); - if (lost_samples > 0) { - JfrCPUTimeThreadSampling::send_lost_event(JfrTicks::now(), JfrThreadLocal::thread_id(thread), lost_samples); - } -} - -void JfrCPUSamplerThread::start_thread() { - if (os::create_thread(this, os::os_thread)) { - os::start_thread(this); - } else { - log_error(jfr)("Failed to create thread for thread sampling"); - } -} - -void JfrCPUSamplerThread::enroll() { - if (Atomic::cmpxchg(&_disenrolled, true, false)) { - Atomic::store(&_warned_about_timer_creation_failure, false); - initialize_active_signal_handler_counter(); - log_trace(jfr)("Enrolling CPU thread sampler"); - _sample.signal(); - if (!init_timers()) { - log_error(jfr)("Failed to initialize timers for CPU thread sampler"); - disenroll(); - return; - } - log_trace(jfr)("Enrolled CPU thread sampler"); - } -} - -void JfrCPUSamplerThread::disenroll() { - if (!Atomic::cmpxchg(&_disenrolled, false, true)) { - log_trace(jfr)("Disenrolling CPU thread sampler"); - if (Atomic::load_acquire(&_signal_handler_installed)) { - stop_timer(); - stop_signal_handlers(); - } - _sample.wait(); - log_trace(jfr)("Disenrolled CPU thread sampler"); - } -} - -void JfrCPUSamplerThread::run() { - assert(_sampler_thread == nullptr, "invariant"); - _sampler_thread = this; - int64_t last_auto_adapt_check = os::javaTimeNanos(); - while (true) { - if (!_sample.trywait()) { - // disenrolled - _sample.wait(); - } - _sample.signal(); - - if (os::javaTimeNanos() - last_auto_adapt_check > AUTOADAPT_INTERVAL_MS * 1000000) { - auto_adapt_period_if_needed(); - last_auto_adapt_check = os::javaTimeNanos(); - } - - if (Atomic::cmpxchg(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true, false)) { - stackwalk_threads_in_native(); - } - os::naked_sleep(100); - } -} - -void JfrCPUSamplerThread::stackwalk_threads_in_native() { - ResourceMark rm; - // Required to prevent JFR from sampling through an ongoing safepoint - MutexLocker tlock(Threads_lock); - ThreadsListHandle tlh; - Thread* current = Thread::current(); - for (size_t i = 0; i < tlh.list()->length(); i++) { - JavaThread* jt = tlh.list()->thread_at(i); - JfrThreadLocal* tl = jt->jfr_thread_local(); - if (tl->wants_async_processing_of_cpu_time_jfr_requests()) { - if (jt->thread_state() != _thread_in_native || !tl->try_acquire_cpu_time_jfr_dequeue_lock()) { - tl->set_do_async_processing_of_cpu_time_jfr_requests(false); - continue; - } - if (jt->has_last_Java_frame()) { - JfrThreadSampling::process_cpu_time_request(jt, tl, current, false); - } else { - tl->set_do_async_processing_of_cpu_time_jfr_requests(false); - } - tl->release_cpu_time_jfr_queue_lock(); - } - } -} - -static volatile size_t count = 0; - -void JfrCPUTimeThreadSampling::send_empty_event(const JfrTicks &start_time, traceid tid, Tickspan cpu_time_period) { - EventCPUTimeSample event(UNTIMED); - event.set_failed(true); - event.set_starttime(start_time); - event.set_eventThread(tid); - event.set_stackTrace(0); - event.set_samplingPeriod(cpu_time_period); - event.set_biased(false); - event.commit(); -} - - -static volatile size_t biased_count = 0; - -void JfrCPUTimeThreadSampling::send_event(const JfrTicks &start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased) { - EventCPUTimeSample event(UNTIMED); - event.set_failed(false); - event.set_starttime(start_time); - event.set_eventThread(tid); - event.set_stackTrace(sid); - event.set_samplingPeriod(cpu_time_period); - event.set_biased(biased); - event.commit(); - Atomic::inc(&count); - if (biased) { - Atomic::inc(&biased_count); - } - if (Atomic::load(&count) % 1000 == 0) { - log_debug(jfr)("CPU thread sampler sent %zu events, lost %d, biased %zu\n", Atomic::load(&count), Atomic::load(&_lost_samples_sum), Atomic::load(&biased_count)); - } -} - -void JfrCPUTimeThreadSampling::send_lost_event(const JfrTicks &time, traceid tid, s4 lost_samples) { - if (!EventCPUTimeSamplesLost::is_enabled()) { - return; - } - EventCPUTimeSamplesLost event(UNTIMED); - event.set_starttime(time); - event.set_lostSamples(lost_samples); - event.set_eventThread(tid); - event.commit(); -} - -void JfrCPUSamplerThread::post_run() { - this->NonJavaThread::post_run(); - delete this; -} - -static JfrCPUTimeThreadSampling* _instance = nullptr; - -JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { - return *_instance; -} - -JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { - assert(_instance == nullptr, "invariant"); - _instance = new JfrCPUTimeThreadSampling(); - return _instance; -} - -void JfrCPUTimeThreadSampling::destroy() { - if (_instance != nullptr) { - delete _instance; - _instance = nullptr; - } -} - -JfrCPUTimeThreadSampling::JfrCPUTimeThreadSampling() : _sampler(nullptr) {} - -JfrCPUTimeThreadSampling::~JfrCPUTimeThreadSampling() { - if (_sampler != nullptr) { - _sampler->disenroll(); - } -} - -void JfrCPUTimeThreadSampling::create_sampler(double rate, bool auto_adapt) { - assert(_sampler == nullptr, "invariant"); - _sampler = new JfrCPUSamplerThread(rate, auto_adapt); - _sampler->start_thread(); - _sampler->enroll(); -} - -void JfrCPUTimeThreadSampling::update_run_state(double rate, bool auto_adapt) { - if (rate != 0) { - if (_sampler == nullptr) { - create_sampler(rate, auto_adapt); - } else { - _sampler->set_rate(rate, auto_adapt); - _sampler->enroll(); - } - return; - } - if (_sampler != nullptr) { - _sampler->set_rate(rate /* 0 */, auto_adapt); - _sampler->disenroll(); - } -} - -void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { - assert(rate >= 0, "invariant"); - if (_instance == nullptr) { - return; - } - instance().set_rate_value(rate, auto_adapt); -} - -void JfrCPUTimeThreadSampling::set_rate_value(double rate, bool auto_adapt) { - if (_sampler != nullptr) { - _sampler->set_rate(rate, auto_adapt); - } - update_run_state(rate, auto_adapt); -} - -void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread *thread) { - if (_instance != nullptr && _instance->_sampler != nullptr) { - _instance->_sampler->on_javathread_create(thread); - } -} - -void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread *thread) { - if (_instance != nullptr && _instance->_sampler != nullptr) { - _instance->_sampler->on_javathread_terminate(thread); - } -} - -void JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests() { - if (_instance != nullptr && _instance->_sampler != nullptr) { - _instance->_sampler->trigger_async_processing_of_cpu_time_jfr_requests(); - } -} - -void handle_timer_signal(int signo, siginfo_t* info, void* context) { - assert(_instance != nullptr, "invariant"); - _instance->handle_timer_signal(info, context); -} - - -void JfrCPUTimeThreadSampling::handle_timer_signal(siginfo_t* info, void* context) { - if (info->si_code != SI_TIMER) { - // not the signal we are interested in - return; - } - assert(_sampler != nullptr, "invariant"); - - if (!_sampler->increment_signal_handler_count()) { - return; - } - _sampler->handle_timer_signal(info, context); - _sampler->decrement_signal_handler_count(); -} - -void JfrCPUSamplerThread::sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now) { - JfrSampleRequestBuilder::build_cpu_time_sample_request(request, ucontext, jt, jt->jfr_thread_local(), now); -} - -static bool check_state(JavaThread* thread) { - switch (thread->thread_state()) { - case _thread_in_Java: - case _thread_in_native: - return true; - default: - return false; - } -} - -void JfrCPUSamplerThread::handle_timer_signal(siginfo_t* info, void* context) { - JfrTicks now = JfrTicks::now(); - JavaThread* jt = get_java_thread_if_valid(); - if (jt == nullptr) { - return; - } - JfrThreadLocal* tl = jt->jfr_thread_local(); - JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); - if (!check_state(jt)) { - queue.increment_lost_samples(); - return; - } - if (!tl->try_acquire_cpu_time_jfr_enqueue_lock()) { - queue.increment_lost_samples(); - return; - } - - JfrCPUTimeSampleRequest request; - // the sampling period might be too low for the current Linux configuration - // so samples might be skipped and we have to compute the actual period - int64_t period = get_sampling_period() * (info->si_overrun + 1); - request._cpu_time_period = Ticks(period / 1000000000.0 * JfrTime::frequency()) - Ticks(0); - sample_thread(request._request, context, jt, tl, now); - - if (queue.enqueue(request)) { - if (queue.size() == 1) { - tl->set_has_cpu_time_jfr_requests(true); - SafepointMechanism::arm_local_poll_release(jt); - } - } else { - queue.increment_lost_samples(); - } - - if (jt->thread_state() == _thread_in_native) { - if (!tl->wants_async_processing_of_cpu_time_jfr_requests()) { - tl->set_do_async_processing_of_cpu_time_jfr_requests(true); - JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests(); - } - } else { - tl->set_do_async_processing_of_cpu_time_jfr_requests(false); - } - - tl->release_cpu_time_jfr_queue_lock(); -} - -static const int SIG = SIGPROF; - -static void set_timer_time(timer_t timerid, int64_t period_nanos) { - struct itimerspec its; - if (period_nanos == 0) { - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - } else { - its.it_interval.tv_sec = period_nanos / NANOSECS_PER_SEC; - its.it_interval.tv_nsec = period_nanos % NANOSECS_PER_SEC; - } - its.it_value = its.it_interval; - if (timer_settime(timerid, 0, &its, nullptr) == -1) { - warning("Failed to set timer for thread sampling: %s", os::strerror(os::get_last_error())); - } -} - -bool JfrCPUSamplerThread::create_timer_for_thread(JavaThread* thread, timer_t& timerid) { - struct sigevent sev; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = SIG; - sev.sigev_value.sival_ptr = nullptr; - ((int*)&sev.sigev_notify)[1] = thread->osthread()->thread_id(); - clockid_t clock; - int err = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clock); - if (err != 0) { - log_error(jfr)("Failed to get clock for thread sampling: %s", os::strerror(err)); - return false; - } - if (timer_create(clock, &sev, &timerid) < 0) { - return false; - } - int64_t period = get_sampling_period(); - if (period != 0) { - set_timer_time(timerid, period); - } - return true; -} - - -void JfrCPUSamplerThread::stop_signal_handlers() { - // set the stop signal bit - Atomic::or_then_fetch(&_active_signal_handlers, STOP_SIGNAL_BIT, memory_order_acq_rel); - while (Atomic::load_acquire(&_active_signal_handlers) > STOP_SIGNAL_BIT) { - // wait for all signal handlers to finish - os::naked_short_nanosleep(1000); - } -} - -// returns false if the stop signal bit was set, true otherwise -bool JfrCPUSamplerThread::increment_signal_handler_count() { - // increment the count of active signal handlers - u4 old_value = Atomic::fetch_then_add(&_active_signal_handlers, (u4)1, memory_order_acq_rel); - if ((old_value & STOP_SIGNAL_BIT) != 0) { - // if the stop signal bit was set, we are not allowed to increment - Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); - return false; - } - return true; -} - -void JfrCPUSamplerThread::decrement_signal_handler_count() { - Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); -} - -void JfrCPUSamplerThread::initialize_active_signal_handler_counter() { - Atomic::release_store(&_active_signal_handlers, (u4)0); -} - -class VM_JFRInitializeCPUTimeSampler : public VM_Operation { - private: - JfrCPUSamplerThread* const _sampler; - - public: - VM_JFRInitializeCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} - - VMOp_Type type() const { return VMOp_JFRInitializeCPUTimeSampler; } - void doit() { - JfrJavaThreadIterator iter; - while (iter.has_next()) { - _sampler->on_javathread_create(iter.next()); - } - }; -}; - -bool JfrCPUSamplerThread::init_timers() { - // install sig handler for sig - void* prev_handler = PosixSignals::get_signal_handler_for_signal(SIG); - if ((prev_handler != SIG_DFL && prev_handler != SIG_IGN && prev_handler != (void*)::handle_timer_signal) || - PosixSignals::install_generic_signal_handler(SIG, (void*)::handle_timer_signal) == (void*)-1) { - log_error(jfr)("Conflicting SIGPROF handler found: %p. CPUTimeSample events will not be recorded", prev_handler); - return false; - } - Atomic::release_store(&_signal_handler_installed, true); - VM_JFRInitializeCPUTimeSampler op(this); - VMThread::execute(&op); - return true; -} - -class VM_JFRTerminateCPUTimeSampler : public VM_Operation { - private: - JfrCPUSamplerThread* const _sampler; - - public: - VM_JFRTerminateCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} - - VMOp_Type type() const { return VMOp_JFRTerminateCPUTimeSampler; } - void doit() { - JfrJavaThreadIterator iter; - while (iter.has_next()) { - JavaThread *thread = iter.next(); - JfrThreadLocal* tl = thread->jfr_thread_local(); - timer_t* timer = tl->cpu_timer(); - if (timer == nullptr) { - continue; - } - timer_delete(*timer); - tl->deallocate_cpu_time_jfr_queue(); - tl->unset_cpu_timer(); - } - }; -}; - -void JfrCPUSamplerThread::stop_timer() { - VM_JFRTerminateCPUTimeSampler op(this); - VMThread::execute(&op); -} - -void JfrCPUSamplerThread::auto_adapt_period_if_needed() { - int64_t current_period = get_sampling_period(); - if (_auto_adapt || current_period == -1) { - int64_t period = compute_sampling_period(_rate); - if (period != current_period) { - Atomic::store(&_current_sampling_period_ns, period); - update_all_thread_timers(); - } - } -} - -void JfrCPUSamplerThread::set_rate(double rate, bool auto_adapt) { - _rate = rate; - _auto_adapt = auto_adapt; - if (_rate > 0 && Atomic::load_acquire(&_disenrolled) == false) { - auto_adapt_period_if_needed(); - } else { - Atomic::store(&_current_sampling_period_ns, compute_sampling_period(rate)); - } -} - -void JfrCPUSamplerThread::update_all_thread_timers() { - int64_t period_millis = get_sampling_period(); - ThreadsListHandle tlh; - for (size_t i = 0; i < tlh.length(); i++) { - JavaThread* thread = tlh.thread_at(i); - JfrThreadLocal* tl = thread->jfr_thread_local(); - assert(tl != nullptr, "invariant"); - timer_t* timer = tl->cpu_timer(); - if (timer != nullptr) { - set_timer_time(*timer, period_millis); - } - } -} - -#else - -static void warn() { - static bool displayed_warning = false; - if (!displayed_warning) { - warning("CPU time method sampling not supported in JFR on your platform"); - displayed_warning = true; - } -} - -static JfrCPUTimeThreadSampling* _instance = nullptr; - -JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { - return *_instance; -} - -JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { - _instance = new JfrCPUTimeThreadSampling(); - return _instance; -} - -void JfrCPUTimeThreadSampling::destroy() { - delete _instance; - _instance = nullptr; -} - -void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { - if (rate != 0) { - warn(); - } -} - -void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread* thread) { -} - -void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread* thread) { -} - -#endif // defined(LINUX) && defined(INCLUDE_JFR) diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp deleted file mode 100644 index 7c0545f4772..00000000000 --- a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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. - * - */ - -#ifndef SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP -#define SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP - -#include "jfr/utilities/jfrAllocation.hpp" - -class JavaThread; - -#if defined(LINUX) - -#include "jfr/periodic/sampling/jfrSampleRequest.hpp" -#include "jfr/utilities/jfrTypes.hpp" - -struct JfrCPUTimeSampleRequest { - JfrSampleRequest _request; - Tickspan _cpu_time_period; - - JfrCPUTimeSampleRequest() {} -}; - -// Fixed size async-signal-safe SPSC linear queue backed by an array. -// Designed to be only used under lock and read linearly -class JfrCPUTimeTraceQueue { - - // the default queue capacity, scaled if the sampling period is smaller than 10ms - // when the thread is started - static const u4 CPU_TIME_QUEUE_CAPACITY = 500; - - JfrCPUTimeSampleRequest* _data; - u4 _capacity; - // next unfilled index - volatile u4 _head; - - volatile u4 _lost_samples; - -public: - JfrCPUTimeTraceQueue(u4 capacity); - - ~JfrCPUTimeTraceQueue(); - - // signal safe, but can't be interleaved with dequeue - bool enqueue(JfrCPUTimeSampleRequest& trace); - - JfrCPUTimeSampleRequest& at(u4 index); - - u4 size() const; - - void set_size(u4 size); - - u4 capacity() const; - - // deletes all samples in the queue - void set_capacity(u4 capacity); - - bool is_empty() const; - - u4 lost_samples() const; - - void increment_lost_samples(); - - // returns the previous lost samples count - u4 get_and_reset_lost_samples(); - - void resize(u4 capacity); - - void resize_for_period(u4 period_millis); - - void clear(); - -}; - - -class JfrCPUSamplerThread; - -class JfrCPUTimeThreadSampling : public JfrCHeapObj { - friend class JfrRecorder; - private: - - JfrCPUSamplerThread* _sampler; - - void create_sampler(double rate, bool auto_adapt); - void set_rate_value(double rate, bool auto_adapt); - - JfrCPUTimeThreadSampling(); - ~JfrCPUTimeThreadSampling(); - - static JfrCPUTimeThreadSampling& instance(); - static JfrCPUTimeThreadSampling* create(); - static void destroy(); - - void update_run_state(double rate, bool auto_adapt); - - public: - static void set_rate(double rate, bool auto_adapt); - - static void on_javathread_create(JavaThread* thread); - static void on_javathread_terminate(JavaThread* thread); - void handle_timer_signal(siginfo_t* info, void* context); - - static void send_empty_event(const JfrTicks& start_time, traceid tid, Tickspan cpu_time_period); - static void send_event(const JfrTicks& start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased); - static void send_lost_event(const JfrTicks& time, traceid tid, s4 lost_samples); - - static void trigger_async_processing_of_cpu_time_jfr_requests(); -}; - -#else - -// a basic implementation on other platforms that -// emits warnings - -class JfrCPUTimeThreadSampling : public JfrCHeapObj { - friend class JfrRecorder; -private: - static JfrCPUTimeThreadSampling& instance(); - static JfrCPUTimeThreadSampling* create(); - static void destroy(); - - public: - static void set_rate(double rate, bool auto_adapt); - - static void on_javathread_create(JavaThread* thread); - static void on_javathread_terminate(JavaThread* thread); -}; - -#endif // defined(LINUX) - - -#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp index 7049df0198b..f8e63e2e344 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp @@ -24,7 +24,6 @@ #include "asm/codeBuffer.hpp" #include "interpreter/interpreter.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" -#include "jfr/utilities/jfrTime.hpp" #include "runtime/continuationEntry.hpp" #include "runtime/frame.inline.hpp" #include "runtime/javaThread.inline.hpp" @@ -172,7 +171,7 @@ static bool build(JfrSampleRequest& request, intptr_t* fp, JavaThread* jt) { assert(request._sample_sp != nullptr, "invariant"); assert(request._sample_pc != nullptr, "invariant"); assert(jt != nullptr, "invariant"); - assert(jt->thread_state() == _thread_in_Java || jt->thread_state() == _thread_in_native, "invariant"); + assert(jt->thread_state() == _thread_in_Java, "invariant"); // 1. Interpreter frame? if (is_interpreter(request)) { @@ -304,33 +303,3 @@ JfrSampleResult JfrSampleRequestBuilder::build_java_sample_request(const void* u } return set_biased_java_sample(request, tl, jt); } - - -// A biased sample request is denoted by an empty bcp and an empty pc. -static inline void set_cpu_time_biased_sample(JfrSampleRequest& request, JavaThread* jt) { - if (request._sample_bcp != nullptr) { - request._sample_bcp = nullptr; - } - assert(request._sample_bcp == nullptr, "invariant"); - request._sample_pc = nullptr; -} - -void JfrSampleRequestBuilder::build_cpu_time_sample_request(JfrSampleRequest& request, - void* ucontext, - JavaThread* jt, - JfrThreadLocal* tl, - JfrTicks& now) { - assert(jt != nullptr, "invariant"); - request._sample_ticks = now; - - // Prioritize the ljf, if one exists. - request._sample_sp = jt->last_Java_sp(); - if (request._sample_sp == nullptr || !build_from_ljf(request, tl, jt)) { - intptr_t* fp; - request._sample_pc = os::fetch_frame_from_context(ucontext, reinterpret_cast(&request._sample_sp), &fp); - assert(sp_in_stack(request, jt), "invariant"); - if (!build(request, fp, jt)) { - set_cpu_time_biased_sample(request, jt); - } - } -} diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp index 20e737e0cbf..6567e7f8bff 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp @@ -81,11 +81,6 @@ class JfrSampleRequestBuilder : AllStatic { static JfrSampleResult build_java_sample_request(const void* ucontext, JfrThreadLocal* tl, JavaThread* jt); - static void build_cpu_time_sample_request(JfrSampleRequest &request, - void* ucontext, - JavaThread* jt, - JfrThreadLocal* tl, - JfrTicks& now); }; #endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEREQUEST_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index ddc9d59b295..aa72c29cf50 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -28,7 +28,6 @@ #include "code/nmethod.hpp" #include "interpreter/interpreter.hpp" #include "jfr/jfrEvents.hpp" -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrSampleMonitor.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" #include "jfr/periodic/sampling/jfrThreadSampling.hpp" @@ -162,7 +161,7 @@ static inline bool is_valid(const PcDesc* pc_desc) { return pc_desc != nullptr && pc_desc->scope_decode_offset() != DebugInformationRecorder::serialized_null; } -static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt, bool& biased) { +static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt) { assert(jt != nullptr, "invariant"); if (!jt->has_last_Java_frame()) { @@ -179,7 +178,6 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, // A biased sample is requested or no code blob. top_frame = jt->last_frame(); in_continuation = is_in_continuation(top_frame, jt); - biased = true; return true; } @@ -229,8 +227,6 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, assert(!stream.current()->is_safepoint_blob_frame(), "invariant"); - biased = true; - // Search the first frame that is above the sampled sp. for (; !stream.is_done(); stream.next()) { frame* const current = stream.current(); @@ -254,7 +250,6 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc); if (is_valid(pc_desc)) { current->adjust_pc(pc_desc->real_pc(sampled_nm)); - biased = false; } } } @@ -275,9 +270,8 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick assert(current != nullptr, "invariant"); frame top_frame; - bool biased = false; bool in_continuation; - if (!compute_top_frame(request, top_frame, in_continuation, jt, biased)) { + if (!compute_top_frame(request, top_frame, in_continuation, jt)) { return; } @@ -299,42 +293,6 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick } } -#ifdef LINUX -static void record_cpu_time_thread(const JfrCPUTimeSampleRequest& request, const JfrTicks& now, const JfrThreadLocal* tl, JavaThread* jt, Thread* current) { - assert(jt != nullptr, "invariant"); - assert(tl != nullptr, "invariant"); - assert(current != nullptr, "invariant"); - frame top_frame; - bool biased = false; - bool in_continuation = false; - bool could_compute_top_frame = compute_top_frame(request._request, top_frame, in_continuation, jt, biased); - const traceid tid = in_continuation ? tl->vthread_id_with_epoch_update(jt) : JfrThreadLocal::jvm_thread_id(jt); - - if (!could_compute_top_frame) { - JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); - return; - } - traceid sid; - { - ResourceMark rm(current); - JfrStackTrace stacktrace; - if (!stacktrace.record(jt, top_frame, in_continuation, request._request)) { - // Unable to record stacktrace. Fail. - JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); - return; - } - sid = JfrStackTraceRepository::add(stacktrace); - } - assert(sid != 0, "invariant"); - - - JfrCPUTimeThreadSampling::send_event(request._request._sample_ticks, sid, tid, request._cpu_time_period, biased); - if (current == jt) { - send_safepoint_latency_event(request._request, now, sid, jt); - } -} -#endif - static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { assert(tl != nullptr, "invariant"); assert(jt != nullptr, "invariant"); @@ -350,49 +308,6 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav assert(!tl->has_enqueued_requests(), "invariant"); } -static void drain_enqueued_cpu_time_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current, bool lock) { - assert(tl != nullptr, "invariant"); - assert(jt != nullptr, "invariant"); - assert(current != nullptr, "invariant"); -#ifdef LINUX - tl->set_do_async_processing_of_cpu_time_jfr_requests(false); - if (lock) { - tl->acquire_cpu_time_jfr_dequeue_lock(); - } - JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); - for (u4 i = 0; i < queue.size(); i++) { - record_cpu_time_thread(queue.at(i), now, tl, jt, current); - } - queue.clear(); - assert(queue.is_empty(), "invariant"); - tl->set_has_cpu_time_jfr_requests(false); - if (queue.lost_samples() > 0) { - JfrCPUTimeThreadSampling::send_lost_event( now, JfrThreadLocal::thread_id(jt), queue.get_and_reset_lost_samples()); - } - if (lock) { - tl->release_cpu_time_jfr_queue_lock(); - } -#endif -} - -// Entry point for a thread that has been sampled in native code and has a pending JFR CPU time request. -void JfrThreadSampling::process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock) { - assert(jt != nullptr, "invariant"); - - const JfrTicks now = JfrTicks::now(); - drain_enqueued_cpu_time_requests(now, tl, jt, current, lock); -} - -static void drain_all_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { - assert(tl != nullptr, "invariant"); - assert(jt != nullptr, "invariant"); - assert(current != nullptr, "invariant"); - drain_enqueued_requests(now, tl, jt, current); - if (tl->has_cpu_time_jfr_requests()) { - drain_enqueued_cpu_time_requests(now, tl, jt, current, true); - } -} - // Only entered by the JfrSampler thread. bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) { assert(tl != nullptr, "invairant"); @@ -467,6 +382,5 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) { break; } } - drain_all_enqueued_requests(now, tl, jt, jt); + drain_enqueued_requests(now, tl, jt, jt); } - diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp index 3c4d1a126b0..d4c4bc0d873 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp @@ -33,10 +33,8 @@ class Thread; class JfrThreadSampling : AllStatic { friend class JfrSamplerThread; - friend class JfrCPUSamplerThread; private: static bool process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread); - static void process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock); public: static void process_sample_request(JavaThread* jt); }; diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index dd75cb2929f..384305862ca 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -29,7 +29,6 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/sampling/objectSampler.hpp" #include "jfr/periodic/jfrOSInterface.hpp" -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" @@ -305,9 +304,6 @@ bool JfrRecorder::create_components() { if (!create_thread_sampler()) { return false; } - if (!create_cpu_time_thread_sampling()) { - return false; - } if (!create_event_throttler()) { return false; } @@ -322,7 +318,6 @@ static JfrStackTraceRepository* _stack_trace_repository; static JfrStringPool* _stringpool = nullptr; static JfrOSInterface* _os_interface = nullptr; static JfrThreadSampler* _thread_sampler = nullptr; -static JfrCPUTimeThreadSampling* _cpu_time_thread_sampling = nullptr; static JfrCheckpointManager* _checkpoint_manager = nullptr; bool JfrRecorder::create_java_event_writer() { @@ -395,12 +390,6 @@ bool JfrRecorder::create_thread_sampler() { return _thread_sampler != nullptr; } -bool JfrRecorder::create_cpu_time_thread_sampling() { - assert(_cpu_time_thread_sampling == nullptr, "invariant"); - _cpu_time_thread_sampling = JfrCPUTimeThreadSampling::create(); - return _cpu_time_thread_sampling != nullptr; -} - bool JfrRecorder::create_event_throttler() { return JfrEventThrottler::create(); } @@ -439,10 +428,6 @@ void JfrRecorder::destroy_components() { JfrThreadSampler::destroy(); _thread_sampler = nullptr; } - if (_cpu_time_thread_sampling != nullptr) { - JfrCPUTimeThreadSampling::destroy(); - _cpu_time_thread_sampling = nullptr; - } JfrEventThrottler::destroy(); } diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp index 3099c8ad344..b917904c5fb 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp @@ -54,7 +54,6 @@ class JfrRecorder : public JfrCHeapObj { static bool create_storage(); static bool create_stringpool(); static bool create_thread_sampler(); - static bool create_cpu_time_thread_sampling(); static bool create_event_throttler(); static bool create_components(); static void destroy_components(); diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp index f660a01c04c..0befaae7751 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp @@ -34,7 +34,6 @@ constexpr static const JfrSamplerParams _disabled_params = { false // reconfigure }; -static JfrEventThrottler* _disabled_cpu_time_sample_throttler = nullptr; static JfrEventThrottler* _object_allocation_throttler = nullptr; static JfrEventThrottler* _safepoint_latency_throttler = nullptr; @@ -49,9 +48,6 @@ JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) : _update(false) {} bool JfrEventThrottler::create() { - assert(_disabled_cpu_time_sample_throttler == nullptr, "invariant"); - _disabled_cpu_time_sample_throttler = new JfrEventThrottler(JfrCPUTimeSampleEvent); - _disabled_cpu_time_sample_throttler->_disabled = true; assert(_object_allocation_throttler == nullptr, "invariant"); _object_allocation_throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent); if (_object_allocation_throttler == nullptr || !_object_allocation_throttler->initialize()) { @@ -63,8 +59,6 @@ bool JfrEventThrottler::create() { } void JfrEventThrottler::destroy() { - delete _disabled_cpu_time_sample_throttler; - _disabled_cpu_time_sample_throttler = nullptr; delete _object_allocation_throttler; _object_allocation_throttler = nullptr; delete _safepoint_latency_throttler; @@ -75,19 +69,15 @@ void JfrEventThrottler::destroy() { // and another for the SamplingLatency event. // When introducing many more throttlers, consider adding a lookup map keyed by event id. JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) { - assert(_disabled_cpu_time_sample_throttler != nullptr, "Disabled CPU time throttler has not been properly initialized"); assert(_object_allocation_throttler != nullptr, "ObjectAllocation throttler has not been properly initialized"); assert(_safepoint_latency_throttler != nullptr, "SafepointLatency throttler has not been properly initialized"); - assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent || event_id == JfrCPUTimeSampleEvent, "Event type has an unconfigured throttler"); + assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent, "Event type has an unconfigured throttler"); if (event_id == JfrObjectAllocationSampleEvent) { return _object_allocation_throttler; } if (event_id == JfrSafepointLatencyEvent) { return _safepoint_latency_throttler; } - if (event_id == JfrCPUTimeSampleEvent) { - return _disabled_cpu_time_sample_throttler; - } return nullptr; } diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index 4b805a98a32..503aa85e02f 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -26,7 +26,6 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -79,15 +78,7 @@ JfrThreadLocal::JfrThreadLocal() : _enqueued_requests(false), _vthread(false), _notified(false), - _dead(false) -#ifdef LINUX - ,_cpu_timer(nullptr), - _cpu_time_jfr_locked(UNLOCKED), - _has_cpu_time_jfr_requests(false), - _cpu_time_jfr_queue(0), - _do_async_processing_of_cpu_time_jfr_requests(false) -#endif - { + _dead(false) { Thread* thread = Thread::current_or_null(); _parent_trace_id = thread != nullptr ? jvm_thread_id(thread) : (traceid)0; } @@ -138,9 +129,7 @@ void JfrThreadLocal::on_start(Thread* t) { if (JfrRecorder::is_recording()) { JfrCheckpointManager::write_checkpoint(t); if (t->is_Java_thread()) { - JavaThread *const jt = JavaThread::cast(t); - JfrCPUTimeThreadSampling::on_javathread_create(jt); - send_java_thread_start_event(jt); + send_java_thread_start_event(JavaThread::cast(t)); } } if (t->jfr_thread_local()->has_cached_stack_trace()) { @@ -232,7 +221,6 @@ void JfrThreadLocal::on_exit(Thread* t) { if (t->is_Java_thread()) { JavaThread* const jt = JavaThread::cast(t); send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); - JfrCPUTimeThreadSampling::on_javathread_terminate(jt); JfrThreadCPULoadEvent::send_event_for_thread(jt); } release(tl, Thread::current()); // because it could be that Thread::current() != t @@ -549,85 +537,3 @@ Arena* JfrThreadLocal::dcmd_arena(JavaThread* jt) { tl->_dcmd_arena = arena; return arena; } - - -#ifdef LINUX - -void JfrThreadLocal::set_cpu_timer(timer_t* timer) { - if (_cpu_timer == nullptr) { - _cpu_timer = JfrCHeapObj::new_array(1); - } - *_cpu_timer = *timer; -} - -void JfrThreadLocal::unset_cpu_timer() { - if (_cpu_timer != nullptr) { - timer_delete(*_cpu_timer); - JfrCHeapObj::free(_cpu_timer, sizeof(timer_t)); - _cpu_timer = nullptr; - } -} - -timer_t* JfrThreadLocal::cpu_timer() const { - return _cpu_timer; -} - -bool JfrThreadLocal::is_cpu_time_jfr_enqueue_locked() { - return Atomic::load_acquire(&_cpu_time_jfr_locked) == ENQUEUE; -} - -bool JfrThreadLocal::is_cpu_time_jfr_dequeue_locked() { - return Atomic::load_acquire(&_cpu_time_jfr_locked) == DEQUEUE; -} - -bool JfrThreadLocal::try_acquire_cpu_time_jfr_enqueue_lock() { - return Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, ENQUEUE) == UNLOCKED; -} - -bool JfrThreadLocal::try_acquire_cpu_time_jfr_dequeue_lock() { - CPUTimeLockState got; - while (true) { - CPUTimeLockState got = Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE); - if (got == UNLOCKED) { - return true; // successfully locked for dequeue - } - if (got == DEQUEUE) { - return false; // already locked for dequeue - } - // else wait for the lock to be released from a signal handler - } -} - -void JfrThreadLocal::acquire_cpu_time_jfr_dequeue_lock() { - while (Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE) != UNLOCKED); -} - -void JfrThreadLocal::release_cpu_time_jfr_queue_lock() { - Atomic::release_store(&_cpu_time_jfr_locked, UNLOCKED); -} - -void JfrThreadLocal::set_has_cpu_time_jfr_requests(bool has_requests) { - Atomic::release_store(&_has_cpu_time_jfr_requests, has_requests); -} - -bool JfrThreadLocal::has_cpu_time_jfr_requests() { - return Atomic::load_acquire(&_has_cpu_time_jfr_requests); -} - -JfrCPUTimeTraceQueue& JfrThreadLocal::cpu_time_jfr_queue() { - return _cpu_time_jfr_queue; -} - -void JfrThreadLocal::deallocate_cpu_time_jfr_queue() { - cpu_time_jfr_queue().resize(0); -} - -void JfrThreadLocal::set_do_async_processing_of_cpu_time_jfr_requests(bool wants) { - Atomic::release_store(&_do_async_processing_of_cpu_time_jfr_requests, wants); -} - -bool JfrThreadLocal::wants_async_processing_of_cpu_time_jfr_requests() { - return Atomic::load_acquire(&_do_async_processing_of_cpu_time_jfr_requests); -} - -#endif diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 715a2c44f93..8e545d9c429 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -33,10 +33,6 @@ #include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" -#ifdef LINUX -#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" -#endif - class Arena; class JavaThread; class JfrBuffer; @@ -83,22 +79,6 @@ class JfrThreadLocal { bool _dead; bool _sampling_critical_section; -#ifdef LINUX - timer_t* _cpu_timer; - - enum CPUTimeLockState { - UNLOCKED, - // locked for enqueuing - ENQUEUE, - // locked for dequeuing - DEQUEUE - }; - volatile CPUTimeLockState _cpu_time_jfr_locked; - volatile bool _has_cpu_time_jfr_requests; - JfrCPUTimeTraceQueue _cpu_time_jfr_queue; - volatile bool _do_async_processing_of_cpu_time_jfr_requests; -#endif - JfrBuffer* install_native_buffer() const; JfrBuffer* install_java_buffer() const; void release(Thread* t); @@ -362,39 +342,6 @@ class JfrThreadLocal { void set_thread_blob(const JfrBlobHandle& handle); const JfrBlobHandle& thread_blob() const; - // CPU time sampling -#ifdef LINUX - void set_cpu_timer(timer_t* timer); - void unset_cpu_timer(); - timer_t* cpu_timer() const; - - // The CPU time JFR lock has three different states: - // - ENQUEUE: lock for enqueuing CPU time requests - // - DEQUEUE: lock for dequeuing CPU time requests - // - UNLOCKED: no lock held - // This ensures that we can safely enqueue and dequeue CPU time requests, - // without interleaving - - bool is_cpu_time_jfr_enqueue_locked(); - bool is_cpu_time_jfr_dequeue_locked(); - - bool try_acquire_cpu_time_jfr_enqueue_lock(); - bool try_acquire_cpu_time_jfr_dequeue_lock(); - void acquire_cpu_time_jfr_dequeue_lock(); - void release_cpu_time_jfr_queue_lock(); - - void set_has_cpu_time_jfr_requests(bool has_events); - bool has_cpu_time_jfr_requests(); - - JfrCPUTimeTraceQueue& cpu_time_jfr_queue(); - void deallocate_cpu_time_jfr_queue(); - - void set_do_async_processing_of_cpu_time_jfr_requests(bool wants); - bool wants_async_processing_of_cpu_time_jfr_requests(); -#else - bool has_cpu_time_jfr_requests() { return false; } -#endif - // Hooks static void on_start(Thread* t); static void on_exit(Thread* t); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 772ef7bbe82..f3a06d5efd2 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -78,7 +78,6 @@ class JavaThread; // - WorkerThread // - WatcherThread // - JfrThreadSampler -// - JfrCPUSamplerThread // - LogAsyncWriter // // All Thread subclasses must be either JavaThread or NonJavaThread. diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index ac5d37381f9..50d85944485 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -115,8 +115,6 @@ template(JFROldObject) \ template(JvmtiPostObjectFree) \ template(RendezvousGCThreads) \ - template(JFRInitializeCPUTimeSampler) \ - template(JFRTerminateCPUTimeSampler) \ template(ReinitializeMDO) class Thread; diff --git a/src/hotspot/share/utilities/ticks.hpp b/src/hotspot/share/utilities/ticks.hpp index 88dc9a787f9..8d2bbc7ce1f 100644 --- a/src/hotspot/share/utilities/ticks.hpp +++ b/src/hotspot/share/utilities/ticks.hpp @@ -236,7 +236,6 @@ class TimeInstant : public Rep { friend class TimePartitionsTest; friend class GCTimerTest; friend class CompilerEvent; - friend class JfrCPUSamplerThread; }; #if INCLUDE_JFR diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index 2ea4725abc8..f43e45b724e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -51,7 +51,6 @@ import jdk.jfr.internal.settings.EnabledSetting; import jdk.jfr.internal.settings.LevelSetting; import jdk.jfr.internal.settings.MethodSetting; import jdk.jfr.internal.settings.PeriodSetting; -import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; @@ -327,9 +326,6 @@ public final class EventControl { private static Control defineThrottle(PlatformEventType type) { String def = type.getAnnotationValue(Throttle.class, ThrottleSetting.DEFAULT_VALUE); type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList())); - if (type.getName().equals("jdk.CPUTimeSample")) { - return new Control(new CPUThrottleSetting(type), def); - } return new Control(new ThrottleSetting(type, def), def); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index b91f0c337b2..e0eaef74b4c 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -270,16 +270,6 @@ public final class JVM { */ public static native void setMethodSamplingPeriod(long type, long periodMillis); - /** - * Set the maximum event emission rate for the CPU time sampler - * - * Setting rate to 0 turns off the CPU time sampler. - * - * @param rate the new rate in events per second - * @param autoAdapt true if the rate should be adapted automatically - */ - public static native void setCPUThrottle(double rate, boolean autoAdapt); - /** * Sets the file where data should be written. * diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 769e7055305..32b59bca4c0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -30,10 +30,8 @@ import java.util.List; import java.util.Objects; import jdk.jfr.SettingDescriptor; -import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; -import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.tracing.Modification; @@ -47,7 +45,6 @@ public final class PlatformEventType extends Type { private final boolean isJVM; private final boolean isJDK; private final boolean isMethodSampling; - private final boolean isCPUTimeMethodSampling; private final List settings = new ArrayList<>(5); private final boolean dynamicSettings; private final int stackTraceOffset; @@ -59,7 +56,6 @@ public final class PlatformEventType extends Type { private boolean stackTraceEnabled = true; private long thresholdTicks = 0; private long period = 0; - private TimespanRate cpuRate; private boolean hasHook; private boolean beginChunk; @@ -79,7 +75,6 @@ public final class PlatformEventType extends Type { this.dynamicSettings = dynamicSettings; this.isJVM = Type.isDefinedByJVM(id); this.isMethodSampling = determineMethodSampling(); - this.isCPUTimeMethodSampling = isJVM && name.equals(Type.EVENT_NAME_PREFIX + "CPUTimeSample"); this.isJDK = isJDK; this.stackTraceOffset = determineStackTraceOffset(); } @@ -196,13 +191,6 @@ public final class PlatformEventType extends Type { } } - public void setCPUThrottle(TimespanRate rate) { - if (isCPUTimeMethodSampling) { - this.cpuRate = rate; - JVM.setCPUThrottle(rate.rate(), rate.autoAdapt()); - } - } - public void setHasPeriod(boolean hasPeriod) { this.hasPeriod = hasPeriod; } @@ -263,9 +251,6 @@ public final class PlatformEventType extends Type { if (isMethodSampling) { long p = enabled ? period : 0; JVM.setMethodSamplingPeriod(getId(), p); - } else if (isCPUTimeMethodSampling) { - TimespanRate r = enabled ? cpuRate : new TimespanRate(0, false); - JVM.setCPUThrottle(r.rate(), r.autoAdapt()); } else { JVM.setEnabled(getId(), enabled); } @@ -403,10 +388,6 @@ public final class PlatformEventType extends Type { return isMethodSampling; } - public boolean isCPUTimeMethodSampling() { - return isCPUTimeMethodSampling; - } - public void setStackFilterId(long id) { startFilterId = id; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index a2ac74142f4..a6192db1ab0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -39,8 +39,7 @@ label = "Active Settings" table = "COLUMN 'Event Type', 'Enabled', 'Threshold', 'Stack Trace','Period','Cutoff', 'Throttle' FORMAT none, missing:whitespace, missing:whitespace, missing:whitespace, - missing:whitespace, missing:whitespace, missing:whitespace, - missing:whitespace + missing:whitespace, missing:whitespace, missing:whitespace SELECT E.id, LAST_BATCH(E.value), LAST_BATCH(T.value), LAST_BATCH(S.value), LAST_BATCH(P.value), LAST_BATCH(C.value), LAST_BATCH(U.value) @@ -401,28 +400,6 @@ table = "COLUMN 'Method', 'Samples', 'Percent' SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) FROM ExecutionSample GROUP BY T LIMIT 25" -[application.cpu-time-hot-methods] -label = "Java Methods that Execute the Most from CPU Time Sampler" -table = "COLUMN 'Method', 'Samples', 'Percent' - FORMAT none, none, normalized - SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) - FROM CPUTimeSample GROUP BY T LIMIT 25" - -[application.cpu-time-statistics] -label = "CPU Time Sample Statistics" -form = "COLUMN 'Successful Samples', 'Failed Samples', 'Biased Samples', 'Total Samples', 'Lost Samples' - SELECT COUNT(S.startTime), COUNT(F.startTime), COUNT(B.startTime), Count(A.startTime), SUM(L.lostSamples) - FROM - CPUTimeSample AS S, - CPUTimeSample AS F, - CPUTimeSample AS A, - CPUTimeSample AS B, - CPUTimeSamplesLost AS L - WHERE - S.failed = 'false' AND - F.failed = 'true' AND - B.biased = 'true'" - [jvm.jdk-agents] label = "JDK Agents" table = "COLUMN 'Time', 'Initialization', 'Name', 'Options' diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java deleted file mode 100644 index c18aeef2132..00000000000 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2020, Datadog, Inc. All rights reserved. - * Copyright (c) 2025 SAP SE. 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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.jfr.internal.settings; - -import static jdk.jfr.internal.util.TimespanUnit.SECONDS; -import static jdk.jfr.internal.util.TimespanUnit.MILLISECONDS; - -import java.util.Objects; -import java.util.Set; - -import jdk.jfr.Description; -import jdk.jfr.SettingControl; -import jdk.jfr.Label; -import jdk.jfr.MetadataDefinition; -import jdk.jfr.Name; -import jdk.jfr.internal.PlatformEventType; -import jdk.jfr.internal.Type; -import jdk.jfr.internal.util.TimespanRate; -import jdk.jfr.internal.util.Utils; - -@MetadataDefinition -@Label("CPUThrottleSetting") -@Description("Upper bounds the emission rate for CPU time samples") -@Name(Type.SETTINGS_PREFIX + "Rate") -public final class CPUThrottleSetting extends SettingControl { - public static final String DEFAULT_VALUE = "0/s"; - private final PlatformEventType eventType; - private String value = DEFAULT_VALUE; - - public CPUThrottleSetting(PlatformEventType eventType) { - this.eventType = Objects.requireNonNull(eventType); - } - - @Override - public String combine(Set values) { - TimespanRate max = null; - for (String value : values) { - TimespanRate rate = TimespanRate.of(value); - if (rate != null) { - if (max == null || rate.isHigher(max)) { - max = rate; - } - max = new TimespanRate(max.rate(), max.autoAdapt() || rate.autoAdapt()); - } - } - // "off" is not supported - return Objects.requireNonNullElse(max.toString(), DEFAULT_VALUE); - } - - @Override - public void setValue(String value) { - TimespanRate rate = TimespanRate.of(value); - if (rate != null) { - eventType.setCPUThrottle(rate); - this.value = value; - } - } - - @Override - public String getValue() { - return value; - } -} - diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java index 2632cd63848..f32436a5e0f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java @@ -55,8 +55,4 @@ public record Rate(long amount, TimespanUnit unit) { private double inNanos() { return (double) amount / unit.nanos; } - - public double perSecond() { - return inNanos() * 1_000_000_000.0; - } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java deleted file mode 100644 index 5d671310e3c..00000000000 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. All rights reserved. - * Copyright (c) 2024, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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.jfr.internal.util; - -import jdk.jfr.internal.settings.CPUThrottleSetting; - -/** - * A rate or fixed period, see {@link jdk.jfr.internal.Rate} - */ -public record TimespanRate(double rate, boolean autoAdapt) { - - public static TimespanRate of(String text) { - if (text.equals("off")) { - text = CPUThrottleSetting.DEFAULT_VALUE; - } - boolean isPeriod = !text.contains("/"); - if (isPeriod) { - var period = ValueParser.parseTimespanWithInfinity(text, Long.MAX_VALUE); - if (period == Long.MAX_VALUE) { - return null; - } - if (period == 0) { - return new TimespanRate(0, false); - } - return new TimespanRate(Runtime.getRuntime().availableProcessors() / (period / 1_000_000_000.0), false); - } - Rate r = Rate.of(text); - if (r == null) { - return null; - } - return new TimespanRate(r.perSecond(), true); - } - - public boolean isHigher(TimespanRate that) { - return rate() > that.rate(); - } - - @Override - public String toString() { - if (autoAdapt) { - return String.format("%d/ns", (long)(rate * 1_000_000_000L)); - } - return String.format("%dns", (long)(Runtime.getRuntime().availableProcessors() / rate * 1_000_000_000L)); - } -} diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 541d1d3aa2f..293af26746f 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -226,16 +226,6 @@ off - - false - 500/s - true - - - - true - - true 10 ms diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 9cec2d9a70f..89a9022d11e 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -206,16 +206,6 @@ 20 ms - - false - 10ms - true - - - - true - - true diff --git a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java index d6e126a493f..5b8aacfb1d2 100644 --- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java +++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java @@ -89,10 +89,7 @@ public class TestLookForUntestedEvents { // Experimental events private static final Set experimentalEvents = Set.of( - "Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost"); - - // Subset of the experimental events that should have tests - private static final Set experimentalButTestedEvents = Set.of("CPUTimeSample"); + "Flush", "SyncOnValueBasedClass"); public static void main(String[] args) throws Exception { for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) { @@ -113,9 +110,7 @@ public class TestLookForUntestedEvents { .collect(Collectors.toList()); Set eventsNotCoveredByTest = new HashSet<>(jfrEventTypes); - Set checkedEvents = new HashSet<>(jfrEventTypes); - checkedEvents.addAll(experimentalButTestedEvents); - for (String event : checkedEvents) { + for (String event : jfrEventTypes) { for (Path p : paths) { if (findStringInFile(p, event)) { eventsNotCoveredByTest.remove(event); diff --git a/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java deleted file mode 100644 index de211b8c454..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2013, 2023, 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.jfr.event.profiling; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedFrame; -import jdk.jfr.consumer.RecordedStackTrace; -import jdk.test.lib.Asserts; -import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.Events; -import jdk.test.lib.jfr.RecurseThread; - -public class BaseTestFullStackTrace { - private final static int MAX_DEPTH = 64; // currently hardcoded in jvm - - private final String eventName; - private final String threadFieldName; - - public BaseTestFullStackTrace(String eventName, String threadFieldName) { - this.eventName = eventName; - this.threadFieldName = threadFieldName; - } - - public void run() throws Throwable { - RecurseThread[] threads = new RecurseThread[3]; - for (int i = 0; i < threads.length; ++i) { - int depth = MAX_DEPTH - 1 + i; - threads[i] = new RecurseThread(depth); - threads[i].setName("recursethread-" + depth); - threads[i].start(); - } - - for (RecurseThread thread : threads) { - while (!thread.isInRunLoop()) { - Thread.sleep(20); - } - } - - assertStackTraces(threads); - - for (RecurseThread thread : threads) { - thread.quit(); - thread.join(); - } - } - - private void assertStackTraces(RecurseThread[] threads) throws Throwable { - while (true) { - try (Recording recording = new Recording()) { - if (eventName.equals(EventNames.CPUTimeSample)) { - recording.enable(eventName).with("throttle", "50ms"); - } else { - recording.enable(eventName).withPeriod(Duration.ofMillis(50)); - } - recording.start(); - Thread.sleep(500); - recording.stop(); - if (hasValidStackTraces(recording, threads)) { - break; - } - } - }; - } - - private boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { - boolean[] isEventFound = new boolean[threads.length]; - - for (RecordedEvent event : Events.fromRecording(recording)) { - System.out.println("Event: " + event); - String threadName = Events.assertField(event, threadFieldName + ".javaName").getValue(); - long threadId = Events.assertField(event, threadFieldName + ".javaThreadId").getValue(); - - for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { - RecurseThread currThread = threads[threadIndex]; - if (threadId == currThread.getId()) { - System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); - Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); - if ("recurseEnd".equals(getTopMethodName(event))) { - isEventFound[threadIndex] = true; - checkEvent(event, currThread.totalDepth); - break; - } - } - } - } - - for (int i = 0; i < threads.length; ++i) { - String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; - System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); - } - for (int i = 0; i < threads.length; ++i) { - if(!isEventFound[i]) { - // no assertion, let's retry. - // Could be race condition, i.e safe point during Thread.sleep - System.out.println("Failed to validate all threads, will retry."); - return false; - } - } - return true; - } - - public String getTopMethodName(RecordedEvent event) { - List frames = event.getStackTrace().getFrames(); - Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); - return frames.getFirst().getMethod().getName(); - } - - private void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { - RecordedStackTrace stacktrace = null; - try { - stacktrace = event.getStackTrace(); - List frames = stacktrace.getFrames(); - Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); - List expectedMethods = getExpectedMethods(expectedDepth); - Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); - - for (int i = 0; i < frames.size(); ++i) { - String name = frames.get(i).getMethod().getName(); - String expectedName = expectedMethods.get(i); - System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); - Asserts.assertEquals(name, expectedName, "Wrong method name"); - } - - boolean isTruncated = stacktrace.isTruncated(); - boolean isTruncateExpected = expectedDepth > MAX_DEPTH; - Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); - - String firstMethod = frames.getLast().getMethod().getName(); - boolean isFullTrace = "run".equals(firstMethod); - String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); - Asserts.assertTrue(isTruncated != isFullTrace, msg); - } catch (Throwable t) { - System.out.println(String.format("stacktrace:%n%s", stacktrace)); - throw t; - } - } - - private List getExpectedMethods(int depth) { - List methods = new ArrayList<>(); - methods.add("recurseEnd"); - for (int i = 0; i < depth - 2; ++i) { - methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); - } - methods.add("run"); - if (depth > MAX_DEPTH) { - methods = methods.subList(0, MAX_DEPTH); - } - return methods; - } -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java deleted file mode 100644 index eb8d33832b5..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; - -import java.time.Duration; - -import jdk.jfr.consumer.RecordingStream; -import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.RecurseThread; - -/* - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @modules jdk.jfr/jdk.jfr.internal - * @run main/timeout=30 jdk.jfr.event.profiling.TestCPUTimeAndExecutionSample - */ -public class TestCPUTimeAndExecutionSample { - - static String sampleEvent = EventNames.CPUTimeSample; - - // The period is set to 1100 ms to provoke the 1000 ms - // threshold in the JVM for os::naked_short_sleep(). - public static void main(String[] args) throws Exception { - run(EventNames.ExecutionSample); - run(EventNames.CPUTimeSample); - run(EventNames.ExecutionSample); - run(EventNames.CPUTimeSample); - } - - private static void run(String eventType) { - RecurseThread t = new RecurseThread(50); - t.setDaemon(true); - try (RecordingStream rs = new RecordingStream()) { - rs.enable(sampleEvent).with("throttle", "1000/s"); - rs.onEvent(sampleEvent, e -> { - t.quit(); - rs.close(); - }); - t.start(); - rs.start(); - } - } -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java deleted file mode 100644 index 0d1108346ab..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.jfr.event.profiling; - -import jdk.test.lib.jfr.EventNames; - -/** - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @build jdk.jfr.event.profiling.BaseTestFullStackTrace - * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleFullStackTrace - */ -public class TestCPUTimeSampleFullStackTrace { - - public static void main(String[] args) throws Throwable { - new BaseTestFullStackTrace(EventNames.CPUTimeSample, "eventThread").run(); - } - -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java deleted file mode 100644 index 133df36684c..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; - -import java.time.Duration; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordingStream; -import jdk.jfr.internal.JVM; -import jdk.test.lib.jfr.EventNames; - -/* - * Tests that creating multiple recordings after another is possible. - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @modules jdk.jfr/jdk.jfr.internal - * @run main jdk.jfr.event.profiling.TestCPUTimeSampleMultipleRecordings - */ -public class TestCPUTimeSampleMultipleRecordings { - - static String nativeEvent = EventNames.CPUTimeSample; - - static volatile boolean alive = true; - - public static void main(String[] args) throws Exception { - Thread t = new Thread(TestCPUTimeSampleMultipleRecordings::nativeMethod); - t.setDaemon(true); - t.start(); - for (int i = 0; i < 2; i++) { - try (RecordingStream rs = new RecordingStream()) { - rs.enable(nativeEvent).with("throttle", "1ms"); - rs.onEvent(nativeEvent, e -> { - alive = false; - rs.close(); - }); - - rs.start(); - } - } - alive = false; - } - - public static void nativeMethod() { - while (alive) { - JVM.getPid(); - } - } -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java deleted file mode 100644 index 1617bce4ba3..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2017, 2023, 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.jfr.event.profiling; - -import java.time.Duration; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordingStream; -import jdk.jfr.internal.JVM; -import jdk.test.lib.jfr.EventNames; - -/* - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @modules jdk.jfr/jdk.jfr.internal - * @run main jdk.jfr.event.profiling.TestCPUTimeSampleNative - */ -public class TestCPUTimeSampleNative { - - static String nativeEvent = EventNames.CPUTimeSample; - - static volatile boolean alive = true; - - public static void main(String[] args) throws Exception { - try (RecordingStream rs = new RecordingStream()) { - rs.enable(nativeEvent).with("throttle", "1ms"); - rs.onEvent(nativeEvent, e -> { - alive = false; - rs.close(); - }); - Thread t = new Thread(TestCPUTimeSampleNative::nativeMethod); - t.setDaemon(true); - t.start(); - rs.start(); - } - - } - - public static void nativeMethod() { - while (alive) { - JVM.getPid(); - } - } -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java deleted file mode 100644 index 55b350ad096..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; -import java.lang.management.ManagementFactory; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Comparator; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; -import jdk.test.lib.Asserts; -import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.Events; - -/** - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleThrottling - */ -public class TestCPUTimeSampleThrottling { - - public static void main(String[] args) throws Exception { - testZeroPerSecond(); - testThrottleSettings(); - testThrottleSettingsPeriod(); - } - - private static void testZeroPerSecond() throws Exception { - Asserts.assertTrue(0L == countEvents(1000, "0/s").count()); - } - - private static void testThrottleSettings() throws Exception { - long count = countEvents(1000, - Runtime.getRuntime().availableProcessors() * 2 + "/s").count(); - Asserts.assertTrue(count > 0 && count < 3, - "Expected between 0 and 3 events, got " + count); - } - - private static void testThrottleSettingsPeriod() throws Exception { - float rate = countEvents(1000, "10ms").rate(); - Asserts.assertTrue(rate > 90 && rate < 110, "Expected around 100 events per second, got " + rate); - } - - private record EventCount(long count, float time) { - float rate() { - return count / time; - } - } - - private static EventCount countEvents(int timeMs, String rate) throws Exception { - try(Recording recording = new Recording()) { - recording.enable(EventNames.CPUTimeSample) - .with("throttle", rate); - - var bean = ManagementFactory.getThreadMXBean(); - - recording.start(); - - long startThreadCpuTime = bean.getCurrentThreadCpuTime(); - - wasteCPU(timeMs); - - long spendCPUTime = bean.getCurrentThreadCpuTime() - startThreadCpuTime; - - recording.stop(); - - long eventCount = Events.fromRecording(recording).stream() - .filter(e -> e.getThread().getJavaName() - .equals(Thread.currentThread().getName())) - .count(); - - System.out.println("Event count: " + eventCount + ", CPU time: " + spendCPUTime / 1_000_000_000f + "s"); - - return new EventCount(eventCount, spendCPUTime / 1_000_000_000f); - } - } - - private static void wasteCPU(int durationMs) { - long start = System.currentTimeMillis(); - double i = 0; - while (System.currentTimeMillis() - start < durationMs) { - for (int j = 0; j < 100000; j++) { - i = Math.sqrt(i * Math.pow(Math.sqrt(Math.random()), Math.random())); - } - } - } - -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java deleted file mode 100644 index 69d32d48282..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017, 2022, 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.jfr.event.profiling; - -import java.time.Duration; - -import jdk.jfr.consumer.RecordingStream; -import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.RecurseThread; - -/* - * @test - * @requires vm.hasJFR & os.family == "linux" - * @library /test/lib - * @modules jdk.jfr/jdk.jfr.internal - * @run main jdk.jfr.event.profiling.TestCPUTimeSamplingLongPeriod - */ -public class TestCPUTimeSamplingLongPeriod { - - static String sampleEvent = EventNames.CPUTimeSample; - - // The period is set to 1100 ms to provoke the 1000 ms - // threshold in the JVM for os::naked_short_sleep(). - public static void main(String[] args) throws Exception { - RecurseThread t = new RecurseThread(50); - t.setDaemon(true); - try (RecordingStream rs = new RecordingStream()) { - rs.enable(sampleEvent).with("throttle", "1100ms"); - rs.onEvent(sampleEvent, e -> { - t.quit(); - rs.close(); - }); - t.start(); - rs.start(); - } - } -} diff --git a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java index c337a8128ab..b06f9eed03d 100644 --- a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java +++ b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java @@ -23,20 +23,147 @@ package jdk.jfr.event.profiling; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.test.lib.Asserts; import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.RecurseThread; /** * @test * @requires vm.hasJFR * @requires vm.opt.DeoptimizeALot != true * @library /test/lib - * @build jdk.jfr.event.profiling.BaseTestFullStackTrace * @run main/othervm jdk.jfr.event.profiling.TestFullStackTrace */ public class TestFullStackTrace { + private final static String EVENT_NAME = EventNames.ExecutionSample; + private final static int MAX_DEPTH = 64; // currently hardcoded in jvm public static void main(String[] args) throws Throwable { - new BaseTestFullStackTrace(EventNames.ExecutionSample, "sampledThread").run(); + RecurseThread[] threads = new RecurseThread[3]; + for (int i = 0; i < threads.length; ++i) { + int depth = MAX_DEPTH - 1 + i; + threads[i] = new RecurseThread(depth); + threads[i].setName("recursethread-" + depth); + threads[i].start(); + } + + for (RecurseThread thread : threads) { + while (!thread.isInRunLoop()) { + Thread.sleep(20); + } + } + + assertStackTraces(threads); + + for (RecurseThread thread : threads) { + thread.quit(); + thread.join(); + } } + private static void assertStackTraces( RecurseThread[] threads) throws Throwable { + Recording recording= null; + do { + recording = new Recording(); + recording.enable(EVENT_NAME).withPeriod(Duration.ofMillis(50)); + recording.start(); + Thread.sleep(500); + recording.stop(); + } while (!hasValidStackTraces(recording, threads)); + } + + private static boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { + boolean[] isEventFound = new boolean[threads.length]; + + for (RecordedEvent event : Events.fromRecording(recording)) { + //System.out.println("Event: " + event); + String threadName = Events.assertField(event, "sampledThread.javaName").getValue(); + long threadId = Events.assertField(event, "sampledThread.javaThreadId").getValue(); + + for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { + RecurseThread currThread = threads[threadIndex]; + if (threadId == currThread.getId()) { + System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); + Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); + if ("recurseEnd".equals(getTopMethodName(event))) { + isEventFound[threadIndex] = true; + checkEvent(event, currThread.totalDepth); + break; + } + } + } + } + + for (int i = 0; i < threads.length; ++i) { + String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; + System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); + } + for (int i = 0; i < threads.length; ++i) { + if(!isEventFound[i]) { + // no assertion, let's retry. + // Could be race condition, i.e safe point during Thread.sleep + System.out.println("Failed to validate all threads, will retry."); + return false; + } + } + return true; + } + + public static String getTopMethodName(RecordedEvent event) { + List frames = event.getStackTrace().getFrames(); + Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); + return frames.getFirst().getMethod().getName(); + } + + private static void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { + RecordedStackTrace stacktrace = null; + try { + stacktrace = event.getStackTrace(); + List frames = stacktrace.getFrames(); + Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); + List expectedMethods = getExpectedMethods(expectedDepth); + Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); + + for (int i = 0; i < frames.size(); ++i) { + String name = frames.get(i).getMethod().getName(); + String expectedName = expectedMethods.get(i); + System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); + Asserts.assertEquals(name, expectedName, "Wrong method name"); + } + + boolean isTruncated = stacktrace.isTruncated(); + boolean isTruncateExpected = expectedDepth > MAX_DEPTH; + Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); + + String firstMethod = frames.getLast().getMethod().getName(); + boolean isFullTrace = "run".equals(firstMethod); + String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); + Asserts.assertTrue(isTruncated != isFullTrace, msg); + } catch (Throwable t) { + System.out.println(String.format("stacktrace:%n%s", stacktrace)); + throw t; + } + } + + private static List getExpectedMethods(int depth) { + List methods = new ArrayList<>(); + methods.add("recurseEnd"); + for (int i = 0; i < depth - 2; ++i) { + methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); + } + methods.add("run"); + if (depth > MAX_DEPTH) { + methods = methods.subList(0, MAX_DEPTH); + } + return methods; + } } diff --git a/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java deleted file mode 100644 index 8dc77fd38da..00000000000 --- a/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2025 SAP SE. 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 test; - - import java.time.Duration; - - /* - * A class used to create a simple deep call stack for testing purposes - */ - public class RecursiveMethods { - - /** Method that uses recursion to produce a call stack of at least {@code depth} depth */ - public static int entry(int depth) { - return method2(--depth); - } - - private static int method2(int depth) { - return method3(--depth); - } - - private static int method3(int depth) { - return method4(--depth); - } - - private static int method4(int depth) { - return method5(--depth); - } - - private static int method5(int depth) { - return method6(--depth); - } - - private static int method6(int depth) { - return method7(--depth); - } - - private static int method7(int depth) { - return method8(--depth); - } - - private static int method8(int depth) { - return method9(--depth); - } - - private static int method9(int depth) { - return method10(--depth); - } - - private static int method10(int depth) { - if (depth > 0) { - return entry(--depth); - } - return depth; - } -} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index a00898358a8..904abe8e3e2 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -77,8 +77,6 @@ public class EventNames { public static final String ThreadAllocationStatistics = PREFIX + "ThreadAllocationStatistics"; public static final String ExecutionSample = PREFIX + "ExecutionSample"; public static final String NativeMethodSample = PREFIX + "NativeMethodSample"; - public static final String CPUTimeSample = PREFIX + "CPUTimeSample"; - public static final String CPUTimeSamplesLost = PREFIX + "CPUTimeSamplesLost"; public static final String ThreadDump = PREFIX + "ThreadDump"; public static final String OldObjectSample = PREFIX + "OldObjectSample"; public static final String SymbolTableStatistics = PREFIX + "SymbolTableStatistics"; From 8f8b367ae3c9afca3581f6aced7f9855ef0d541d Mon Sep 17 00:00:00 2001 From: David Holmes Date: Thu, 5 Jun 2025 00:35:26 +0000 Subject: [PATCH 057/143] 8350029: Illegal invokespecial interface not caught by verification Reviewed-by: coleenp, matsaave --- .../share/classfile/verificationType.cpp | 56 ++++++----- .../share/classfile/verificationType.hpp | 25 +++-- src/hotspot/share/classfile/verifier.cpp | 55 +++++++---- .../share/interpreter/linkResolver.cpp | 4 +- .../runtime/verifier/invokespecial/Run.java | 29 ++++++ .../TestInvokeSpecialInterface.java | 99 +++++++++++++++++++ .../invokespecial/UseInterfaceMethodRef.jasm | 37 +++++++ .../verifier/invokespecial/UseMethodRef.jasm | 37 +++++++ 8 files changed, 289 insertions(+), 53 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/verifier/invokespecial/Run.java create mode 100644 test/hotspot/jtreg/runtime/verifier/invokespecial/TestInvokeSpecialInterface.java create mode 100644 test/hotspot/jtreg/runtime/verifier/invokespecial/UseInterfaceMethodRef.jasm create mode 100644 test/hotspot/jtreg/runtime/verifier/invokespecial/UseMethodRef.jasm diff --git a/src/hotspot/share/classfile/verificationType.cpp b/src/hotspot/share/classfile/verificationType.cpp index e6115dfce9b..aedb620aabe 100644 --- a/src/hotspot/share/classfile/verificationType.cpp +++ b/src/hotspot/share/classfile/verificationType.cpp @@ -48,41 +48,50 @@ VerificationType VerificationType::from_tag(u1 tag) { } } -bool VerificationType::resolve_and_check_assignability(InstanceKlass* klass, Symbol* name, - Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object, TRAPS) { +// Potentially resolve the target class and from class, and check whether the from class is assignable +// to the target class. The current_klass is the class being verified - it could also be the target in some +// cases, and otherwise is needed to obtain the correct classloader for resolving the other classes. +bool VerificationType::resolve_and_check_assignability(InstanceKlass* current_klass, Symbol* target_name, Symbol* from_name, + bool from_field_is_protected, bool from_is_array, + bool from_is_object, bool* target_is_interface, TRAPS) { HandleMark hm(THREAD); - Klass* this_class; - if (klass->is_hidden() && klass->name() == name) { - this_class = klass; + Klass* target_klass; + if (current_klass->is_hidden() && current_klass->name() == target_name) { + target_klass = current_klass; } else { - this_class = SystemDictionary::resolve_or_fail( - name, Handle(THREAD, klass->class_loader()), true, CHECK_false); + target_klass = SystemDictionary::resolve_or_fail( + target_name, Handle(THREAD, current_klass->class_loader()), true, CHECK_false); if (log_is_enabled(Debug, class, resolve)) { - Verifier::trace_class_resolution(this_class, klass); + Verifier::trace_class_resolution(target_klass, current_klass); } } - if (this_class->is_interface() && (!from_field_is_protected || + bool is_intf = target_klass->is_interface(); + if (target_is_interface != nullptr) { + *target_is_interface = is_intf; + } + + if (is_intf && (!from_field_is_protected || from_name != vmSymbols::java_lang_Object())) { // If we are not trying to access a protected field or method in // java.lang.Object then, for arrays, we only allow assignability // to interfaces java.lang.Cloneable and java.io.Serializable. // Otherwise, we treat interfaces as java.lang.Object. return !from_is_array || - this_class == vmClasses::Cloneable_klass() || - this_class == vmClasses::Serializable_klass(); + target_klass == vmClasses::Cloneable_klass() || + target_klass == vmClasses::Serializable_klass(); } else if (from_is_object) { - Klass* from_class; - if (klass->is_hidden() && klass->name() == from_name) { - from_class = klass; + Klass* from_klass; + if (current_klass->is_hidden() && current_klass->name() == from_name) { + from_klass = current_klass; } else { - from_class = SystemDictionary::resolve_or_fail( - from_name, Handle(THREAD, klass->class_loader()), true, CHECK_false); + from_klass = SystemDictionary::resolve_or_fail( + from_name, Handle(THREAD, current_klass->class_loader()), true, CHECK_false); if (log_is_enabled(Debug, class, resolve)) { - Verifier::trace_class_resolution(from_class, klass); + Verifier::trace_class_resolution(from_klass, current_klass); } } - return from_class->is_subclass_of(this_class); + return from_klass->is_subclass_of(target_klass); } return false; @@ -90,8 +99,8 @@ bool VerificationType::resolve_and_check_assignability(InstanceKlass* klass, Sym bool VerificationType::is_reference_assignable_from( const VerificationType& from, ClassVerifier* context, - bool from_field_is_protected, TRAPS) const { - InstanceKlass* klass = context->current_class(); + bool from_field_is_protected, bool* this_is_interface, TRAPS) const { + if (from.is_null()) { // null is assignable to any reference return true; @@ -109,7 +118,7 @@ bool VerificationType::is_reference_assignable_from( #if INCLUDE_CDS if (CDSConfig::is_dumping_archive()) { bool skip_assignability_check = false; - SystemDictionaryShared::add_verification_constraint(klass, + SystemDictionaryShared::add_verification_constraint(context->current_class(), name(), from.name(), from_field_is_protected, from.is_array(), from.is_object(), &skip_assignability_check); if (skip_assignability_check) { @@ -119,8 +128,9 @@ bool VerificationType::is_reference_assignable_from( } } #endif - return resolve_and_check_assignability(klass, name(), from.name(), - from_field_is_protected, from.is_array(), from.is_object(), THREAD); + return resolve_and_check_assignability(context->current_class(), name(), from.name(), + from_field_is_protected, from.is_array(), + from.is_object(), this_is_interface, THREAD); } else if (is_array() && from.is_array()) { VerificationType comp_this = get_component(context); VerificationType comp_from = from.get_component(context); diff --git a/src/hotspot/share/classfile/verificationType.hpp b/src/hotspot/share/classfile/verificationType.hpp index 4f0609f0cce..788e0029fad 100644 --- a/src/hotspot/share/classfile/verificationType.hpp +++ b/src/hotspot/share/classfile/verificationType.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -288,7 +288,7 @@ class VerificationType { if (is_reference() && from.is_reference()) { return is_reference_assignable_from(from, context, from_field_is_protected, - THREAD); + nullptr, THREAD); } else { return false; } @@ -327,17 +327,24 @@ class VerificationType { void print_on(outputStream* st) const; - private: + bool is_reference_assignable_from(const VerificationType& from, ClassVerifier* context, + bool from_field_is_protected, bool* this_is_interface, TRAPS) const; - bool is_reference_assignable_from( - const VerificationType&, ClassVerifier*, bool from_field_is_protected, - TRAPS) const; - - public: - static bool resolve_and_check_assignability(InstanceKlass* klass, Symbol* name, + static bool resolve_and_check_assignability(InstanceKlass* current_klass, Symbol* target_name, Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object, + TRAPS) { + return resolve_and_check_assignability(current_klass, target_name, from_name, from_field_is_protected, + from_is_array, from_is_object, nullptr, THREAD); + } + + private: + static bool resolve_and_check_assignability(InstanceKlass* current_klass, Symbol* target_name, + Symbol* from_name, bool from_field_is_protected, + bool from_is_array, bool from_is_object, + bool* target_is_interface, TRAPS); + }; #endif // SHARE_CLASSFILE_VERIFICATIONTYPE_HPP diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index 98c05909b82..0f1468f0309 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -2891,26 +2891,43 @@ void ClassVerifier::verify_invoke_instructions( "Illegal call to internal method"); return; } - } else if (opcode == Bytecodes::_invokespecial - && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) - && !ref_class_type.equals(VerificationType::reference_type( - current_class()->super()->name()))) { - bool subtype = false; - bool have_imr_indirect = cp->tag_at(index).value() == JVM_CONSTANT_InterfaceMethodref; - subtype = ref_class_type.is_assignable_from( - current_type(), this, false, CHECK_VERIFY(this)); - if (!subtype) { - verify_error(ErrorContext::bad_code(bci), - "Bad invokespecial instruction: " - "current class isn't assignable to reference class."); - return; - } else if (have_imr_indirect) { - verify_error(ErrorContext::bad_code(bci), - "Bad invokespecial instruction: " - "interface method reference is in an indirect superinterface."); - return; - } + } + // invokespecial, when not , must be to a method in the current class, a direct superinterface, + // or any superclass (including Object). + else if (opcode == Bytecodes::_invokespecial + && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) + && !ref_class_type.equals(VerificationType::reference_type(current_class()->super()->name()))) { + // We know it is not current class, direct superinterface or immediate superclass. That means it + // could be: + // - a totally unrelated class or interface + // - an indirect superinterface + // - an indirect superclass (including Object) + // We use the assignability test to see if it is a superclass, or else an interface, and keep track + // of the latter. Note that subtype can be true if we are dealing with an interface that is not actually + // implemented as assignability treats all interfaces as Object. + + bool is_interface = false; // This can only be set true if the assignability check will return true + // and we loaded the class. For any other "true" returns (e.g. same class + // or Object) we either can't get here (same class already excluded above) + // or we know it is not an interface (i.e. Object). + bool subtype = ref_class_type.is_reference_assignable_from(current_type(), this, false, + &is_interface, CHECK_VERIFY(this)); + if (!subtype) { // Totally unrelated class + verify_error(ErrorContext::bad_code(bci), + "Bad invokespecial instruction: " + "current class isn't assignable to reference class."); + return; + } else { + // Indirect superclass (including Object), indirect interface, or unrelated interface. + // Any interface use is an error. + if (is_interface) { + verify_error(ErrorContext::bad_code(bci), + "Bad invokespecial instruction: " + "interface method to invoke is not in a direct superinterface."); + return; + } + } } // Get the verification types for the method's arguments. diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp index b26c643d15f..22199baef8e 100644 --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -1200,7 +1200,7 @@ Method* LinkResolver::linktime_resolve_special_method(const LinkInfo& link_info, } // ensure that invokespecial's interface method reference is in - // a direct superinterface, not an indirect superinterface + // a direct superinterface, not an indirect superinterface or unrelated interface Klass* current_klass = link_info.current_klass(); if (current_klass != nullptr && resolved_klass->is_interface()) { InstanceKlass* klass_to_check = InstanceKlass::cast(current_klass); @@ -1209,7 +1209,7 @@ Method* LinkResolver::linktime_resolve_special_method(const LinkInfo& link_info, stringStream ss; ss.print("Interface method reference: '"); resolved_method->print_external_name(&ss); - ss.print("', is in an indirect superinterface of %s", + ss.print("', is not in a direct superinterface of %s", current_klass->external_name()); THROW_MSG_NULL(vmSymbols::java_lang_IncompatibleClassChangeError(), ss.as_string()); } diff --git a/test/hotspot/jtreg/runtime/verifier/invokespecial/Run.java b/test/hotspot/jtreg/runtime/verifier/invokespecial/Run.java new file mode 100644 index 00000000000..df9a1d157b0 --- /dev/null +++ b/test/hotspot/jtreg/runtime/verifier/invokespecial/Run.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * A class with a run()V method that doesn't implement Runnable. + */ +public class Run { + public void run() { } +} diff --git a/test/hotspot/jtreg/runtime/verifier/invokespecial/TestInvokeSpecialInterface.java b/test/hotspot/jtreg/runtime/verifier/invokespecial/TestInvokeSpecialInterface.java new file mode 100644 index 00000000000..caced8b3b51 --- /dev/null +++ b/test/hotspot/jtreg/runtime/verifier/invokespecial/TestInvokeSpecialInterface.java @@ -0,0 +1,99 @@ +/* + * 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. + */ + +/* + * @test id=verified + * @compile Run.java UseMethodRef.jasm UseInterfaceMethodRef.jasm TestInvokeSpecialInterface.java + * @run main/othervm TestInvokeSpecialInterface true + */ + +/* + * @test id=unverified + * @compile Run.java UseMethodRef.jasm UseInterfaceMethodRef.jasm TestInvokeSpecialInterface.java + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-BytecodeVerificationRemote TestInvokeSpecialInterface false + */ + +public class TestInvokeSpecialInterface { + public static void main(String[] args) throws Throwable { + if (args[0].equals("true")) { + check_verified(); + } else { + check_unverified(); + } + } + + static void check_verified() { + String veMsg = "interface method to invoke is not in a direct superinterface"; + try { + UseMethodRef t = new UseMethodRef(); + UseMethodRef.test(t); + } + catch(VerifyError ve) { + if (ve.getMessage().contains(veMsg)) { + System.out.println("Got expected: " + ve); + } else { + throw new RuntimeException("Unexpected VerifyError thrown", ve); + } + } + + try { + UseInterfaceMethodRef t = new UseInterfaceMethodRef(); + UseInterfaceMethodRef.test(t); + } + catch(VerifyError ve) { + if (ve.getMessage().contains(veMsg)) { + System.out.println("Got expected: " + ve); + } else { + throw new RuntimeException("Unexpected VerifyError thrown", ve); + } + } + } + + static void check_unverified() { + try { + UseMethodRef t = new UseMethodRef(); + UseMethodRef.test(t); + } + catch(IncompatibleClassChangeError icce) { + String icceMsg = "Method 'void java.lang.Runnable.run()' must be InterfaceMethodref constant"; + if (icce.getMessage().contains(icceMsg)) { + System.out.println("Got expected: " + icce); + } else { + throw new RuntimeException("Unexpected IncompatibleClassChangeError", icce); + } + } + + try { + UseInterfaceMethodRef t = new UseInterfaceMethodRef(); + UseInterfaceMethodRef.test(t); + } + catch(IncompatibleClassChangeError icce) { + String icceMsg = "Interface method reference: 'void java.lang.Runnable.run()', is not in a direct superinterface of UseInterfaceMethodRef"; + if (icce.getMessage().contains(icceMsg)) { + System.out.println("Got expected: " + icce); + } else { + throw new RuntimeException("Unexpected IncompatibleClassChangeError", icce); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/verifier/invokespecial/UseInterfaceMethodRef.jasm b/test/hotspot/jtreg/runtime/verifier/invokespecial/UseInterfaceMethodRef.jasm new file mode 100644 index 00000000000..49290b3c1bc --- /dev/null +++ b/test/hotspot/jtreg/runtime/verifier/invokespecial/UseInterfaceMethodRef.jasm @@ -0,0 +1,37 @@ +/* + * 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. + */ + +class UseInterfaceMethodRef extends Run version 52:0 { + + public Method "":"()V" stack 1 locals 1 { + aload_0; + invokespecial Method Run."":"()V"; + return; + } + + public static Method test:"(LUseInterfaceMethodRef;)V" stack 2 { + aload_0; + invokespecial InterfaceMethod java/lang/Runnable.run:()V; // VerifyError + return; + } +} diff --git a/test/hotspot/jtreg/runtime/verifier/invokespecial/UseMethodRef.jasm b/test/hotspot/jtreg/runtime/verifier/invokespecial/UseMethodRef.jasm new file mode 100644 index 00000000000..cf1ebec115b --- /dev/null +++ b/test/hotspot/jtreg/runtime/verifier/invokespecial/UseMethodRef.jasm @@ -0,0 +1,37 @@ +/* + * 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. + */ + +class UseMethodRef extends Run version 52:0 { + + public Method "":"()V" stack 1 locals 1 { + aload_0; + invokespecial Method Run."":"()V"; + return; + } + + public static Method test:"(LUseMethodRef;)V" stack 2 { + aload_0; + invokespecial Method java/lang/Runnable.run:()V; // VerifyError + return; + } +} From 575806c0e5584ea24cda80158070579b88c477f7 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Thu, 5 Jun 2025 01:41:21 +0000 Subject: [PATCH 058/143] 8358078: javap crashes with NPE on preview class file Reviewed-by: liach --- .../com/sun/tools/javap/ClassWriter.java | 6 +- .../tools/javap/ClassFileVersionTest.java | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 test/langtools/tools/javap/ClassFileVersionTest.java diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java index 4036a185ab1..c5c75d8848c 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java @@ -118,9 +118,11 @@ public class ClassWriter extends BasicWriter { protected ClassFileFormatVersion cffv() { var major = classModel.majorVersion(); if (major < JAVA_1_VERSION || major > ClassFile.latestMajorVersion()) - return null; + // something not representable by CFFV, let's fall back + return ClassFileFormatVersion.latest(); if (major >= JAVA_12_VERSION && classModel.minorVersion() != 0) { - return null; + // preview versions aren't explicitly supported, but latest is good enough for now + return ClassFileFormatVersion.latest(); } return ClassFileFormatVersion.fromMajor(major); } diff --git a/test/langtools/tools/javap/ClassFileVersionTest.java b/test/langtools/tools/javap/ClassFileVersionTest.java new file mode 100644 index 00000000000..c5968dfd84e --- /dev/null +++ b/test/langtools/tools/javap/ClassFileVersionTest.java @@ -0,0 +1,98 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8358078 + * @summary javap should not crash due to class file versions + * @library /tools/lib + * @modules jdk.jdeps/com.sun.tools.javap + * @run junit ClassFileVersionTest + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import toolbox.JavapTask; +import toolbox.Task; +import toolbox.ToolBox; + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.reflect.AccessFlag; +import java.lang.reflect.ClassFileFormatVersion; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.of; + +public class ClassFileVersionTest { + + final ToolBox toolBox = new ToolBox(); + + public static Stream classFiles() { + int major17 = ClassFileFormatVersion.RELEASE_17.major(); + int preview = Character.MAX_VALUE; + int majorLatest = ClassFileFormatVersion.latest().major(); + AccessFlag[] noFlags = {}; + return Stream.of( + of(false, major17, 0, noFlags), + of(false, major17, preview, noFlags), + of(false, 0, 0, noFlags), + of(false, major17, 0, new AccessFlag[]{AccessFlag.PUBLIC}), + of(false, major17, preview, new AccessFlag[]{AccessFlag.PUBLIC}), + of(false, majorLatest, preview, new AccessFlag[]{AccessFlag.PUBLIC}), + of(true, majorLatest, 0, new AccessFlag[]{AccessFlag.BRIDGE}), // misplaced access flag + of(true, majorLatest, preview, new AccessFlag[]{AccessFlag.BRIDGE}) // misplaced access flag + ); + } + + private static byte[] createClassFile(int major, int minor, AccessFlag[] classFlags) { + return ClassFile.of().build(ClassDesc.of("Test"), (builder) -> { + // manually assemble flag bits to avoid exception in ClassFile api + int flags = 0; + for (AccessFlag classFlag : classFlags) { + flags |= classFlag.mask(); + } + builder.withVersion(major, minor).withFlags(flags); + }); + } + + @ParameterizedTest + @MethodSource("classFiles") + void test(boolean shouldError, int major, int minor, AccessFlag[] classFlags) throws Throwable { + + Files.write(Path.of("cf.class"), createClassFile(major, minor, classFlags)); + + var lines = new JavapTask(toolBox) + .classes("cf.class") + .options("-c", "-p", "-v") + .run(shouldError ? Task.Expect.FAIL : Task.Expect.SUCCESS) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + assertEquals(shouldError, lines.stream().anyMatch(l -> l.startsWith("Error: Access Flags:")), "printed error"); + } +} From 849655a145a40b056a751528cebc78a11481514c Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Thu, 5 Jun 2025 03:25:46 +0000 Subject: [PATCH 059/143] 8358632: [asan] reports heap-buffer-overflow in AOTCodeCache::copy_bytes Reviewed-by: vlivanov, iveresov --- src/hotspot/share/code/aotCodeCache.cpp | 15 +++------------ .../appcds/aotCode/AOTCodeCompressedOopsTest.java | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 6d8faa10a38..d0692ee678b 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -460,18 +460,9 @@ AOTCodeCache* AOTCodeCache::open_for_dump() { } void copy_bytes(const char* from, address to, uint size) { - assert(size > 0, "sanity"); - bool by_words = true; - if ((size > 2 * HeapWordSize) && (((intptr_t)from | (intptr_t)to) & (HeapWordSize - 1)) == 0) { - // Use wordwise copies if possible: - Copy::disjoint_words((HeapWord*)from, - (HeapWord*)to, - ((size_t)size + HeapWordSize-1) / HeapWordSize); - } else { - by_words = false; - Copy::conjoint_jbytes(from, to, (size_t)size); - } - log_trace(aot, codecache)("Copied %d bytes as %s from " INTPTR_FORMAT " to " INTPTR_FORMAT, size, (by_words ? "HeapWord" : "bytes"), p2i(from), p2i(to)); + assert((int)size > 0, "sanity"); + memcpy(to, from, size); + log_trace(aot, codecache)("Copied %d bytes from " INTPTR_FORMAT " to " INTPTR_FORMAT, size, p2i(from), p2i(to)); } AOTCodeReader::AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry) { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java index 267b9a267b2..4587eeae5e5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java @@ -173,7 +173,7 @@ public class AOTCodeCompressedOopsTest { * [0.022s][info][cds] narrow_oop_mode = 3, narrow_oop_base = 0x0000000300000000, narrow_oop_shift = 3 * [0.022s][info][cds] heap range = [0x0000000301000000 - 0x0000000ac1000000] */ - Pattern p = Pattern.compile("narrow_oop_base = 0x(\\d+), narrow_oop_shift = (\\d)"); + Pattern p = Pattern.compile("narrow_oop_base = 0x([0-9a-fA-F]+), narrow_oop_shift = (\\d)"); for (int i = 0; i < list.size(); i++) { String line = list.get(i); if (line.indexOf("CDS archive was created with max heap size") != -1) { From 08023481edec4c0cacc79a608c573856f0718e58 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Thu, 5 Jun 2025 04:24:05 +0000 Subject: [PATCH 060/143] 8358558: (zipfs) Reorder the listing of "accessMode" property in the ZIP file system's documentation Reviewed-by: dfuchs, vyazici, alanb, lancea --- src/jdk.zipfs/share/classes/module-info.java | 83 ++++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/jdk.zipfs/share/classes/module-info.java b/src/jdk.zipfs/share/classes/module-info.java index db7ae3eec81..0716fdf8266 100644 --- a/src/jdk.zipfs/share/classes/module-info.java +++ b/src/jdk.zipfs/share/classes/module-info.java @@ -149,12 +149,52 @@ import java.util.Set; * *
* + * + * + * + * + * + * * * * * * * @@ -266,47 +306,6 @@ import java.util.Set; * * * - * - * - * - * - * - * * *
accessMode{@link java.lang.String}null/unset + * A value defining the desired access mode of the file system. + * ZIP file systems can be created to allow for read-write or + * read-only access. + *
    + *
  • + * If no value is set, the file system is created as read-write + * if possible. Use {@link java.nio.file.FileSystem#isReadOnly() + * isReadOnly()} to determine the actual access mode. + *
  • + *
  • + * If the value is {@code "readOnly"}, the file system is created + * read-only, and {@link java.nio.file.FileSystem#isReadOnly() + * isReadOnly()} will always return {@code true}. Creating a + * read-only file system requires the underlying ZIP file to + * already exist. + *
  • + *
  • + * If the value is {@code "readWrite"}, the file system is created + * read-write, and {@link java.nio.file.FileSystem#isReadOnly() + * isReadOnly()} will always return {@code false}. If a writable file + * system cannot be created, an {@code IOException} will be thrown + * when creating the ZIP file system. + *
  • + *
  • + * Any other values will cause an {@code IllegalArgumentException} + * to be thrown when creating the ZIP file system. + *
  • + *
+ * The {@code accessMode} property has no effect on reported POSIX file + * permissions (in cases where POSIX support is enabled). + *
create{@link java.lang.String} or {@link java.lang.Boolean}false * If the value is {@code true}, the ZIP file system provider creates a - * new ZIP or JAR file if it does not exist. + * new ZIP or JAR file if it does not exist. Specifying the {@code create} + * property as {@code true} with the {@code accessMode} as {@code "readOnly"} + * will cause an {@code IllegalArgumentException} to be thrown when creating + * the ZIP file system. *
accessMode{@link java.lang.String}null/unset - * A value defining the desired access mode of the file system. - * ZIP file systems can be created to allow for read-write or - * read-only access. - *
    - *
  • - * If no value is set, the file system is created as read-write - * if possible. Use {@link java.nio.file.FileSystem#isReadOnly() - * isReadOnly()} to determine the actual access mode. - *
  • - *
  • - * If the value is {@code "readOnly"}, the file system is created - * read-only, and {@link java.nio.file.FileSystem#isReadOnly() - * isReadOnly()} will always return {@code true}. Creating a - * read-only file system requires the underlying ZIP file to - * already exist. - * Specifying the {@code create} property as {@code true} with the - * {@code accessMode} as {@code readOnly} will cause an {@code - * IllegalArgumentException} to be thrown when creating the ZIP file - * system. - *
  • - *
  • - * If the value is {@code "readWrite"}, the file system is created - * read-write, and {@link java.nio.file.FileSystem#isReadOnly() - * isReadOnly()} will always return {@code false}. If a writable file - * system cannot be created, an {@code IOException} will be thrown - * when creating the ZIP file system. - *
  • - *
  • - * Any other values will cause an {@code IllegalArgumentException} - * to be thrown when creating the ZIP file system. - *
  • - *
- * The {@code accessMode} property has no effect on reported POSIX file - * permissions (in cases where POSIX support is enabled). - *
* From 48b97ac0e006362528423ffd657b2ea3afa46a6e Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Thu, 5 Jun 2025 07:34:48 +0000 Subject: [PATCH 061/143] 8358634: RISC-V: Fix several broken documentation web-links Reviewed-by: fyang --- src/hotspot/cpu/riscv/macroAssembler_riscv.hpp | 2 +- src/hotspot/cpu/riscv/vm_version_riscv.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index f302b3602ad..7fa7f931044 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -666,7 +666,7 @@ class MacroAssembler: public Assembler { // We try to follow risc-v asm menomics. // But as we don't layout a reachable GOT, // we often need to resort to movptr, li <48imm>. - // https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md + // https://github.com/riscv-non-isa/riscv-asm-manual/blob/main/src/asm-manual.adoc // Hotspot only use the standard calling convention using x1/ra. // The alternative calling convection using x5/t0 is not used. diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 4214d6c53dc..a0a42fb5463 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -221,13 +221,13 @@ class VM_Version : public Abstract_VM_Version { FLAG_SET_DEFAULT(UseExtension, true); \ } \ - // https://github.com/riscv/riscv-profiles/blob/main/profiles.adoc#rva20-profiles + // https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva20-profiles #define RV_USE_RVA20U64 \ RV_ENABLE_EXTENSION(UseRVC) \ static void useRVA20U64Profile(); - // https://github.com/riscv/riscv-profiles/blob/main/profiles.adoc#rva22-profiles + // https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva22-profiles #define RV_USE_RVA22U64 \ RV_ENABLE_EXTENSION(UseRVC) \ RV_ENABLE_EXTENSION(UseZba) \ @@ -241,7 +241,7 @@ class VM_Version : public Abstract_VM_Version { static void useRVA22U64Profile(); - // https://github.com/riscv/riscv-profiles/blob/main/rva23-profile.adoc#rva23u64-profile + // https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc#rva23u64-profile #define RV_USE_RVA23U64 \ RV_ENABLE_EXTENSION(UseRVC) \ RV_ENABLE_EXTENSION(UseRVV) \ From ace70a6d6aca619da34b2f9cac2586cc88cefb5a Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Thu, 5 Jun 2025 08:18:18 +0000 Subject: [PATCH 062/143] 8358666: [REDO] Implement JEP 509: JFR CPU-Time Profiling Reviewed-by: mgronlun --- src/hotspot/os/posix/signals_posix.cpp | 8 + src/hotspot/os/posix/signals_posix.hpp | 2 + src/hotspot/share/jfr/jfr.inline.hpp | 3 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 6 + src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 2 + .../jfr/jni/jfrJniMethodRegistration.cpp | 1 + src/hotspot/share/jfr/metadata/metadata.xml | 16 + .../sampling/jfrCPUTimeThreadSampler.cpp | 780 ++++++++++++++++++ .../sampling/jfrCPUTimeThreadSampler.hpp | 152 ++++ .../periodic/sampling/jfrSampleRequest.cpp | 33 +- .../periodic/sampling/jfrSampleRequest.hpp | 5 + .../periodic/sampling/jfrThreadSampling.cpp | 92 ++- .../periodic/sampling/jfrThreadSampling.hpp | 2 + .../share/jfr/recorder/jfrRecorder.cpp | 15 + .../share/jfr/recorder/jfrRecorder.hpp | 1 + .../recorder/service/jfrEventThrottler.cpp | 12 +- .../share/jfr/support/jfrThreadLocal.cpp | 98 ++- .../share/jfr/support/jfrThreadLocal.hpp | 53 ++ src/hotspot/share/runtime/thread.hpp | 1 + src/hotspot/share/runtime/vmOperation.hpp | 2 + src/hotspot/share/utilities/ticks.hpp | 1 + .../jdk/jfr/internal/EventControl.java | 4 + .../share/classes/jdk/jfr/internal/JVM.java | 10 + .../jdk/jfr/internal/PlatformEventType.java | 21 + .../classes/jdk/jfr/internal/query/view.ini | 25 +- .../internal/settings/CPUThrottleSetting.java | 89 ++ .../classes/jdk/jfr/internal/util/Rate.java | 4 + .../jdk/jfr/internal/util/TimespanRate.java | 68 ++ src/jdk.jfr/share/conf/jfr/default.jfc | 10 + src/jdk.jfr/share/conf/jfr/profile.jfc | 10 + .../metadata/TestLookForUntestedEvents.java | 9 +- .../profiling/BaseTestFullStackTrace.java | 176 ++++ .../TestCPUTimeAndExecutionSample.java | 65 ++ .../TestCPUTimeSampleFullStackTrace.java | 41 + .../TestCPUTimeSampleMultipleRecordings.java | 70 ++ .../profiling/TestCPUTimeSampleNative.java | 66 ++ .../TestCPUTimeSampleThrottling.java | 111 +++ .../TestCPUTimeSamplingLongPeriod.java | 58 ++ .../event/profiling/TestFullStackTrace.java | 131 +-- .../classes/test/RecursiveMethods.java | 76 ++ test/lib/jdk/test/lib/jfr/EventNames.java | 2 + 41 files changed, 2191 insertions(+), 140 deletions(-) create mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp create mode 100644 src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java create mode 100644 test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java create mode 100644 test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java create mode 100644 test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp index e900d5695ae..0157d354f40 100644 --- a/src/hotspot/os/posix/signals_posix.cpp +++ b/src/hotspot/os/posix/signals_posix.cpp @@ -1505,6 +1505,14 @@ bool PosixSignals::is_sig_ignored(int sig) { } } +void* PosixSignals::get_signal_handler_for_signal(int sig) { + struct sigaction oact; + if (sigaction(sig, (struct sigaction*)nullptr, &oact) == -1) { + return nullptr; + } + return get_signal_handler(&oact); +} + static void signal_sets_init() { sigemptyset(&preinstalled_sigs); diff --git a/src/hotspot/os/posix/signals_posix.hpp b/src/hotspot/os/posix/signals_posix.hpp index 9deade4db18..c1cb70df153 100644 --- a/src/hotspot/os/posix/signals_posix.hpp +++ b/src/hotspot/os/posix/signals_posix.hpp @@ -52,6 +52,8 @@ public: static bool is_sig_ignored(int sig); + static void* get_signal_handler_for_signal(int sig); + static void hotspot_sigmask(Thread* thread); static void print_signal_handler(outputStream* st, int sig, char* buf, size_t buflen); diff --git a/src/hotspot/share/jfr/jfr.inline.hpp b/src/hotspot/share/jfr/jfr.inline.hpp index bdb47f600e6..5b6fc62d0ea 100644 --- a/src/hotspot/share/jfr/jfr.inline.hpp +++ b/src/hotspot/share/jfr/jfr.inline.hpp @@ -32,7 +32,8 @@ inline bool Jfr::has_sample_request(JavaThread* jt) { assert(jt != nullptr, "invariant"); - return jt->jfr_thread_local()->has_sample_request(); + JfrThreadLocal* tl = jt->jfr_thread_local(); + return tl->has_sample_request() || tl->has_cpu_time_jfr_requests(); } inline void Jfr::check_and_process_sample_request(JavaThread* jt) { diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 6f1c1936574..bc2412a90c1 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -24,6 +24,7 @@ #include "jfr/jfr.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrEventSetting.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -169,6 +170,11 @@ NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_ty return JNI_TRUE; NO_TRANSITION_END +JVM_ENTRY_NO_ENV(void, jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt)) + JfrEventSetting::set_enabled(JfrCPUTimeSampleEvent, rate > 0); + JfrCPUTimeThreadSampling::set_rate(rate, auto_adapt == JNI_TRUE); +JVM_END + NO_TRANSITION(void, jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong event_type_id, jlong value)) JfrEventSetting::set_miscellaneous(event_type_id, value); const JfrEventId typed_event_id = (JfrEventId)event_type_id; diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 9c78c6239d4..dbea7f0180d 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -129,6 +129,8 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jclass jvm); jboolean JNICALL jfr_set_throttle(JNIEnv* env, jclass jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms); +void JNICALL jfr_set_cpu_throttle(JNIEnv* env, jclass jvm, jdouble rate, jboolean auto_adapt); + void JNICALL jfr_set_miscellaneous(JNIEnv* env, jclass jvm, jlong id, jlong value); void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jclass jvm, jlong cutoff_ticks, jboolean, jboolean); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 33a564dee2f..82ef93d95b2 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -83,6 +83,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count, (char*)"setMiscellaneous", (char*)"(JJ)V", (void*)jfr_set_miscellaneous, (char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_throttle, + (char*)"setCPUThrottle", (char*)"(DZ)V", (void*)jfr_set_cpu_throttle, (char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples, (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk, (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread, diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 9c04ec3dca1..03daca946f6 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -962,6 +962,22 @@ + + + + + + + + + + + + + diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp new file mode 100644 index 00000000000..2f063123a3d --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.cpp @@ -0,0 +1,780 @@ +/* + * Copyright (c) 2025 SAP SE. 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. + * + */ + +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" +#include "logging/log.hpp" + + +#if defined(LINUX) +#include "jfr/periodic/sampling/jfrThreadSampling.hpp" +#include "jfr/support/jfrThreadLocal.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/utilities/jfrThreadIterator.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "jfrfiles/jfrEventClasses.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/atomic.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/osThread.hpp" +#include "runtime/safepointMechanism.inline.hpp" +#include "runtime/threadSMR.hpp" +#include "runtime/vmOperation.hpp" +#include "runtime/vmThread.hpp" +#include "utilities/ticks.hpp" + +#include "signals_posix.hpp" + +static const int64_t AUTOADAPT_INTERVAL_MS = 100; + +static bool is_excluded(JavaThread* jt) { + return jt->is_hidden_from_external_view() || + jt->jfr_thread_local()->is_excluded() || + jt->is_JfrRecorder_thread(); +} + +static JavaThread* get_java_thread_if_valid() { + Thread* raw_thread = Thread::current_or_null_safe(); + if (raw_thread == nullptr) { + // probably while shutting down + return nullptr; + } + assert(raw_thread->is_Java_thread(), "invariant"); + JavaThread* jt = JavaThread::cast(raw_thread); + if (is_excluded(jt) || jt->is_exiting()) { + return nullptr; + } + return jt; +} + +JfrCPUTimeTraceQueue::JfrCPUTimeTraceQueue(u4 capacity) : + _data(nullptr), _capacity(capacity), _head(0), _lost_samples(0) { + if (capacity != 0) { + _data = JfrCHeapObj::new_array(capacity); + } +} + +JfrCPUTimeTraceQueue::~JfrCPUTimeTraceQueue() { + if (_data != nullptr) { + assert(_capacity != 0, "invariant"); + JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); + } +} + +bool JfrCPUTimeTraceQueue::enqueue(JfrCPUTimeSampleRequest& request) { + assert(JavaThread::current()->jfr_thread_local()->is_cpu_time_jfr_enqueue_locked(), "invariant"); + assert(&JavaThread::current()->jfr_thread_local()->cpu_time_jfr_queue() == this, "invariant"); + u4 elementIndex; + do { + elementIndex = Atomic::load_acquire(&_head); + if (elementIndex >= _capacity) { + return false; + } + } while (Atomic::cmpxchg(&_head, elementIndex, elementIndex + 1) != elementIndex); + _data[elementIndex] = request; + return true; +} + +JfrCPUTimeSampleRequest& JfrCPUTimeTraceQueue::at(u4 index) { + assert(index < _head, "invariant"); + return _data[index]; +} + +static volatile u4 _lost_samples_sum = 0; + +u4 JfrCPUTimeTraceQueue::size() const { + return Atomic::load_acquire(&_head); +} + +void JfrCPUTimeTraceQueue::set_size(u4 size) { + Atomic::release_store(&_head, size); +} + +u4 JfrCPUTimeTraceQueue::capacity() const { + return _capacity; +} + +void JfrCPUTimeTraceQueue::set_capacity(u4 capacity) { + _head = 0; + if (_data != nullptr) { + assert(_capacity != 0, "invariant"); + JfrCHeapObj::free(_data, _capacity * sizeof(JfrCPUTimeSampleRequest)); + } + if (capacity != 0) { + _data = JfrCHeapObj::new_array(capacity); + } else { + _data = nullptr; + } + _capacity = capacity; +} + +bool JfrCPUTimeTraceQueue::is_empty() const { + return Atomic::load_acquire(&_head) == 0; +} + +u4 JfrCPUTimeTraceQueue::lost_samples() const { + return Atomic::load(&_lost_samples); +} + +void JfrCPUTimeTraceQueue::increment_lost_samples() { + Atomic::inc(&_lost_samples_sum); + Atomic::inc(&_lost_samples); +} + +u4 JfrCPUTimeTraceQueue::get_and_reset_lost_samples() { + return Atomic::xchg(&_lost_samples, (u4)0); +} + +void JfrCPUTimeTraceQueue::resize(u4 capacity) { + if (capacity != _capacity) { + set_capacity(capacity); + } +} + +void JfrCPUTimeTraceQueue::resize_for_period(u4 period_millis) { + u4 capacity = CPU_TIME_QUEUE_CAPACITY; + if (period_millis > 0 && period_millis < 10) { + capacity = (u4) ((double) capacity * 10 / period_millis); + } + resize(capacity); +} + +void JfrCPUTimeTraceQueue::clear() { + Atomic::release_store(&_head, (u4)0); +} + +static int64_t compute_sampling_period(double rate) { + if (rate == 0) { + return 0; + } + return os::active_processor_count() * 1000000000.0 / rate; +} + +class JfrCPUSamplerThread : public NonJavaThread { + friend class JfrCPUTimeThreadSampling; + private: + Semaphore _sample; + NonJavaThread* _sampler_thread; + double _rate; + bool _auto_adapt; + volatile int64_t _current_sampling_period_ns; + volatile bool _disenrolled; + // top bit is used to indicate that no signal handler should proceed + volatile u4 _active_signal_handlers; + volatile bool _is_async_processing_of_cpu_time_jfr_requests_triggered; + volatile bool _warned_about_timer_creation_failure; + volatile bool _signal_handler_installed; + + static const u4 STOP_SIGNAL_BIT = 0x80000000; + + JfrCPUSamplerThread(double rate, bool auto_adapt); + + void start_thread(); + + void enroll(); + void disenroll(); + void update_all_thread_timers(); + + void auto_adapt_period_if_needed(); + + void set_rate(double rate, bool auto_adapt); + int64_t get_sampling_period() const { return Atomic::load(&_current_sampling_period_ns); }; + + void sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now); + + // process the queues for all threads that are in native state (and requested to be processed) + void stackwalk_threads_in_native(); + bool create_timer_for_thread(JavaThread* thread, timer_t &timerid); + + void stop_signal_handlers(); + + // returns false if the stop signal bit was set, true otherwise + bool increment_signal_handler_count(); + + void decrement_signal_handler_count(); + + void initialize_active_signal_handler_counter(); + +protected: + virtual void post_run(); +public: + virtual const char* name() const { return "JFR CPU Sampler Thread"; } + virtual const char* type_name() const { return "JfrCPUTimeSampler"; } + void run(); + void on_javathread_create(JavaThread* thread); + void on_javathread_terminate(JavaThread* thread); + + void handle_timer_signal(siginfo_t* info, void* context); + bool init_timers(); + void stop_timer(); + + void trigger_async_processing_of_cpu_time_jfr_requests(); +}; + +JfrCPUSamplerThread::JfrCPUSamplerThread(double rate, bool auto_adapt) : + _sample(), + _sampler_thread(nullptr), + _rate(rate), + _auto_adapt(auto_adapt), + _current_sampling_period_ns(compute_sampling_period(rate)), + _disenrolled(true), + _active_signal_handlers(STOP_SIGNAL_BIT), + _is_async_processing_of_cpu_time_jfr_requests_triggered(false), + _warned_about_timer_creation_failure(false), + _signal_handler_installed(false) { + assert(rate >= 0, "invariant"); +} + +void JfrCPUSamplerThread::trigger_async_processing_of_cpu_time_jfr_requests() { + Atomic::release_store(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true); +} + +void JfrCPUSamplerThread::on_javathread_create(JavaThread* thread) { + if (thread->is_hidden_from_external_view() || thread->is_JfrRecorder_thread() || + !Atomic::load_acquire(&_signal_handler_installed)) { + return; + } + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + tl->cpu_time_jfr_queue().resize_for_period(_current_sampling_period_ns / 1000000); + timer_t timerid; + if (create_timer_for_thread(thread, timerid)) { + tl->set_cpu_timer(&timerid); + } else { + if (!Atomic::or_then_fetch(&_warned_about_timer_creation_failure, true)) { + log_warning(jfr)("Failed to create timer for a thread"); + } + tl->deallocate_cpu_time_jfr_queue(); + } +} + +void JfrCPUSamplerThread::on_javathread_terminate(JavaThread* thread) { + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + timer_t* timer = tl->cpu_timer(); + if (timer == nullptr) { + return; // no timer was created for this thread + } + tl->unset_cpu_timer(); + tl->deallocate_cpu_time_jfr_queue(); + s4 lost_samples = tl->cpu_time_jfr_queue().lost_samples(); + if (lost_samples > 0) { + JfrCPUTimeThreadSampling::send_lost_event(JfrTicks::now(), JfrThreadLocal::thread_id(thread), lost_samples); + } +} + +void JfrCPUSamplerThread::start_thread() { + if (os::create_thread(this, os::os_thread)) { + os::start_thread(this); + } else { + log_error(jfr)("Failed to create thread for thread sampling"); + } +} + +void JfrCPUSamplerThread::enroll() { + if (Atomic::cmpxchg(&_disenrolled, true, false)) { + Atomic::store(&_warned_about_timer_creation_failure, false); + initialize_active_signal_handler_counter(); + log_trace(jfr)("Enrolling CPU thread sampler"); + _sample.signal(); + if (!init_timers()) { + log_error(jfr)("Failed to initialize timers for CPU thread sampler"); + disenroll(); + return; + } + log_trace(jfr)("Enrolled CPU thread sampler"); + } +} + +void JfrCPUSamplerThread::disenroll() { + if (!Atomic::cmpxchg(&_disenrolled, false, true)) { + log_trace(jfr)("Disenrolling CPU thread sampler"); + if (Atomic::load_acquire(&_signal_handler_installed)) { + stop_timer(); + stop_signal_handlers(); + } + _sample.wait(); + log_trace(jfr)("Disenrolled CPU thread sampler"); + } +} + +void JfrCPUSamplerThread::run() { + assert(_sampler_thread == nullptr, "invariant"); + _sampler_thread = this; + int64_t last_auto_adapt_check = os::javaTimeNanos(); + while (true) { + if (!_sample.trywait()) { + // disenrolled + _sample.wait(); + } + _sample.signal(); + + if (os::javaTimeNanos() - last_auto_adapt_check > AUTOADAPT_INTERVAL_MS * 1000000) { + auto_adapt_period_if_needed(); + last_auto_adapt_check = os::javaTimeNanos(); + } + + if (Atomic::cmpxchg(&_is_async_processing_of_cpu_time_jfr_requests_triggered, true, false)) { + stackwalk_threads_in_native(); + } + os::naked_sleep(100); + } +} + +void JfrCPUSamplerThread::stackwalk_threads_in_native() { + ResourceMark rm; + // Required to prevent JFR from sampling through an ongoing safepoint + MutexLocker tlock(Threads_lock); + ThreadsListHandle tlh; + Thread* current = Thread::current(); + for (size_t i = 0; i < tlh.list()->length(); i++) { + JavaThread* jt = tlh.list()->thread_at(i); + JfrThreadLocal* tl = jt->jfr_thread_local(); + if (tl->wants_async_processing_of_cpu_time_jfr_requests()) { + if (jt->thread_state() != _thread_in_native || !tl->try_acquire_cpu_time_jfr_dequeue_lock()) { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + continue; + } + if (jt->has_last_Java_frame()) { + JfrThreadSampling::process_cpu_time_request(jt, tl, current, false); + } else { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + } + tl->release_cpu_time_jfr_queue_lock(); + } + } +} + +static volatile size_t count = 0; + +void JfrCPUTimeThreadSampling::send_empty_event(const JfrTicks &start_time, traceid tid, Tickspan cpu_time_period) { + EventCPUTimeSample event(UNTIMED); + event.set_failed(true); + event.set_starttime(start_time); + event.set_eventThread(tid); + event.set_stackTrace(0); + event.set_samplingPeriod(cpu_time_period); + event.set_biased(false); + event.commit(); +} + + +static volatile size_t biased_count = 0; + +void JfrCPUTimeThreadSampling::send_event(const JfrTicks &start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased) { + EventCPUTimeSample event(UNTIMED); + event.set_failed(false); + event.set_starttime(start_time); + event.set_eventThread(tid); + event.set_stackTrace(sid); + event.set_samplingPeriod(cpu_time_period); + event.set_biased(biased); + event.commit(); + Atomic::inc(&count); + if (biased) { + Atomic::inc(&biased_count); + } + if (Atomic::load(&count) % 1000 == 0) { + log_debug(jfr)("CPU thread sampler sent %zu events, lost %d, biased %zu\n", Atomic::load(&count), Atomic::load(&_lost_samples_sum), Atomic::load(&biased_count)); + } +} + +void JfrCPUTimeThreadSampling::send_lost_event(const JfrTicks &time, traceid tid, s4 lost_samples) { + if (!EventCPUTimeSamplesLost::is_enabled()) { + return; + } + EventCPUTimeSamplesLost event(UNTIMED); + event.set_starttime(time); + event.set_lostSamples(lost_samples); + event.set_eventThread(tid); + event.commit(); +} + +void JfrCPUSamplerThread::post_run() { + this->NonJavaThread::post_run(); + delete this; +} + +static JfrCPUTimeThreadSampling* _instance = nullptr; + +JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { + return *_instance; +} + +JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { + assert(_instance == nullptr, "invariant"); + _instance = new JfrCPUTimeThreadSampling(); + return _instance; +} + +void JfrCPUTimeThreadSampling::destroy() { + if (_instance != nullptr) { + delete _instance; + _instance = nullptr; + } +} + +JfrCPUTimeThreadSampling::JfrCPUTimeThreadSampling() : _sampler(nullptr) {} + +JfrCPUTimeThreadSampling::~JfrCPUTimeThreadSampling() { + if (_sampler != nullptr) { + _sampler->disenroll(); + } +} + +void JfrCPUTimeThreadSampling::create_sampler(double rate, bool auto_adapt) { + assert(_sampler == nullptr, "invariant"); + _sampler = new JfrCPUSamplerThread(rate, auto_adapt); + _sampler->start_thread(); + _sampler->enroll(); +} + +void JfrCPUTimeThreadSampling::update_run_state(double rate, bool auto_adapt) { + if (rate != 0) { + if (_sampler == nullptr) { + create_sampler(rate, auto_adapt); + } else { + _sampler->set_rate(rate, auto_adapt); + _sampler->enroll(); + } + return; + } + if (_sampler != nullptr) { + _sampler->set_rate(rate /* 0 */, auto_adapt); + _sampler->disenroll(); + } +} + +void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { + assert(rate >= 0, "invariant"); + if (_instance == nullptr) { + return; + } + instance().set_rate_value(rate, auto_adapt); +} + +void JfrCPUTimeThreadSampling::set_rate_value(double rate, bool auto_adapt) { + if (_sampler != nullptr) { + _sampler->set_rate(rate, auto_adapt); + } + update_run_state(rate, auto_adapt); +} + +void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread *thread) { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->on_javathread_create(thread); + } +} + +void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread *thread) { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->on_javathread_terminate(thread); + } +} + +void JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests() { + if (_instance != nullptr && _instance->_sampler != nullptr) { + _instance->_sampler->trigger_async_processing_of_cpu_time_jfr_requests(); + } +} + +void handle_timer_signal(int signo, siginfo_t* info, void* context) { + assert(_instance != nullptr, "invariant"); + _instance->handle_timer_signal(info, context); +} + + +void JfrCPUTimeThreadSampling::handle_timer_signal(siginfo_t* info, void* context) { + if (info->si_code != SI_TIMER) { + // not the signal we are interested in + return; + } + assert(_sampler != nullptr, "invariant"); + + if (!_sampler->increment_signal_handler_count()) { + return; + } + _sampler->handle_timer_signal(info, context); + _sampler->decrement_signal_handler_count(); +} + +void JfrCPUSamplerThread::sample_thread(JfrSampleRequest& request, void* ucontext, JavaThread* jt, JfrThreadLocal* tl, JfrTicks& now) { + JfrSampleRequestBuilder::build_cpu_time_sample_request(request, ucontext, jt, jt->jfr_thread_local(), now); +} + +static bool check_state(JavaThread* thread) { + switch (thread->thread_state()) { + case _thread_in_Java: + case _thread_in_native: + return true; + default: + return false; + } +} + +void JfrCPUSamplerThread::handle_timer_signal(siginfo_t* info, void* context) { + JfrTicks now = JfrTicks::now(); + JavaThread* jt = get_java_thread_if_valid(); + if (jt == nullptr) { + return; + } + JfrThreadLocal* tl = jt->jfr_thread_local(); + JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); + if (!check_state(jt)) { + queue.increment_lost_samples(); + return; + } + if (!tl->try_acquire_cpu_time_jfr_enqueue_lock()) { + queue.increment_lost_samples(); + return; + } + + JfrCPUTimeSampleRequest request; + // the sampling period might be too low for the current Linux configuration + // so samples might be skipped and we have to compute the actual period + int64_t period = get_sampling_period() * (info->si_overrun + 1); + request._cpu_time_period = Ticks(period / 1000000000.0 * JfrTime::frequency()) - Ticks(0); + sample_thread(request._request, context, jt, tl, now); + + if (queue.enqueue(request)) { + if (queue.size() == 1) { + tl->set_has_cpu_time_jfr_requests(true); + SafepointMechanism::arm_local_poll_release(jt); + } + } else { + queue.increment_lost_samples(); + } + + if (jt->thread_state() == _thread_in_native) { + if (!tl->wants_async_processing_of_cpu_time_jfr_requests()) { + tl->set_do_async_processing_of_cpu_time_jfr_requests(true); + JfrCPUTimeThreadSampling::trigger_async_processing_of_cpu_time_jfr_requests(); + } + } else { + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + } + + tl->release_cpu_time_jfr_queue_lock(); +} + +static const int SIG = SIGPROF; + +static void set_timer_time(timer_t timerid, int64_t period_nanos) { + struct itimerspec its; + if (period_nanos == 0) { + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + } else { + its.it_interval.tv_sec = period_nanos / NANOSECS_PER_SEC; + its.it_interval.tv_nsec = period_nanos % NANOSECS_PER_SEC; + } + its.it_value = its.it_interval; + if (timer_settime(timerid, 0, &its, nullptr) == -1) { + warning("Failed to set timer for thread sampling: %s", os::strerror(os::get_last_error())); + } +} + +bool JfrCPUSamplerThread::create_timer_for_thread(JavaThread* thread, timer_t& timerid) { + struct sigevent sev; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_signo = SIG; + sev.sigev_value.sival_ptr = nullptr; + ((int*)&sev.sigev_notify)[1] = thread->osthread()->thread_id(); + clockid_t clock; + int err = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clock); + if (err != 0) { + log_error(jfr)("Failed to get clock for thread sampling: %s", os::strerror(err)); + return false; + } + if (timer_create(clock, &sev, &timerid) < 0) { + return false; + } + int64_t period = get_sampling_period(); + if (period != 0) { + set_timer_time(timerid, period); + } + return true; +} + + +void JfrCPUSamplerThread::stop_signal_handlers() { + // set the stop signal bit + Atomic::or_then_fetch(&_active_signal_handlers, STOP_SIGNAL_BIT, memory_order_acq_rel); + while (Atomic::load_acquire(&_active_signal_handlers) > STOP_SIGNAL_BIT) { + // wait for all signal handlers to finish + os::naked_short_nanosleep(1000); + } +} + +// returns false if the stop signal bit was set, true otherwise +bool JfrCPUSamplerThread::increment_signal_handler_count() { + // increment the count of active signal handlers + u4 old_value = Atomic::fetch_then_add(&_active_signal_handlers, (u4)1, memory_order_acq_rel); + if ((old_value & STOP_SIGNAL_BIT) != 0) { + // if the stop signal bit was set, we are not allowed to increment + Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); + return false; + } + return true; +} + +void JfrCPUSamplerThread::decrement_signal_handler_count() { + Atomic::dec(&_active_signal_handlers, memory_order_acq_rel); +} + +void JfrCPUSamplerThread::initialize_active_signal_handler_counter() { + Atomic::release_store(&_active_signal_handlers, (u4)0); +} + +class VM_JFRInitializeCPUTimeSampler : public VM_Operation { + private: + JfrCPUSamplerThread* const _sampler; + + public: + VM_JFRInitializeCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} + + VMOp_Type type() const { return VMOp_JFRInitializeCPUTimeSampler; } + void doit() { + JfrJavaThreadIterator iter; + while (iter.has_next()) { + _sampler->on_javathread_create(iter.next()); + } + }; +}; + +bool JfrCPUSamplerThread::init_timers() { + // install sig handler for sig + void* prev_handler = PosixSignals::get_signal_handler_for_signal(SIG); + if ((prev_handler != SIG_DFL && prev_handler != SIG_IGN && prev_handler != (void*)::handle_timer_signal) || + PosixSignals::install_generic_signal_handler(SIG, (void*)::handle_timer_signal) == (void*)-1) { + log_error(jfr)("Conflicting SIGPROF handler found: %p. CPUTimeSample events will not be recorded", prev_handler); + return false; + } + Atomic::release_store(&_signal_handler_installed, true); + VM_JFRInitializeCPUTimeSampler op(this); + VMThread::execute(&op); + return true; +} + +class VM_JFRTerminateCPUTimeSampler : public VM_Operation { + private: + JfrCPUSamplerThread* const _sampler; + + public: + VM_JFRTerminateCPUTimeSampler(JfrCPUSamplerThread* sampler) : _sampler(sampler) {} + + VMOp_Type type() const { return VMOp_JFRTerminateCPUTimeSampler; } + void doit() { + JfrJavaThreadIterator iter; + while (iter.has_next()) { + JavaThread *thread = iter.next(); + JfrThreadLocal* tl = thread->jfr_thread_local(); + timer_t* timer = tl->cpu_timer(); + if (timer == nullptr) { + continue; + } + tl->deallocate_cpu_time_jfr_queue(); + tl->unset_cpu_timer(); + } + }; +}; + +void JfrCPUSamplerThread::stop_timer() { + VM_JFRTerminateCPUTimeSampler op(this); + VMThread::execute(&op); +} + +void JfrCPUSamplerThread::auto_adapt_period_if_needed() { + int64_t current_period = get_sampling_period(); + if (_auto_adapt || current_period == -1) { + int64_t period = compute_sampling_period(_rate); + if (period != current_period) { + Atomic::store(&_current_sampling_period_ns, period); + update_all_thread_timers(); + } + } +} + +void JfrCPUSamplerThread::set_rate(double rate, bool auto_adapt) { + _rate = rate; + _auto_adapt = auto_adapt; + if (_rate > 0 && Atomic::load_acquire(&_disenrolled) == false) { + auto_adapt_period_if_needed(); + } else { + Atomic::store(&_current_sampling_period_ns, compute_sampling_period(rate)); + } +} + +void JfrCPUSamplerThread::update_all_thread_timers() { + int64_t period_millis = get_sampling_period(); + ThreadsListHandle tlh; + for (size_t i = 0; i < tlh.length(); i++) { + JavaThread* thread = tlh.thread_at(i); + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + timer_t* timer = tl->cpu_timer(); + if (timer != nullptr) { + set_timer_time(*timer, period_millis); + } + } +} + +#else + +static void warn() { + static bool displayed_warning = false; + if (!displayed_warning) { + warning("CPU time method sampling not supported in JFR on your platform"); + displayed_warning = true; + } +} + +static JfrCPUTimeThreadSampling* _instance = nullptr; + +JfrCPUTimeThreadSampling& JfrCPUTimeThreadSampling::instance() { + return *_instance; +} + +JfrCPUTimeThreadSampling* JfrCPUTimeThreadSampling::create() { + _instance = new JfrCPUTimeThreadSampling(); + return _instance; +} + +void JfrCPUTimeThreadSampling::destroy() { + delete _instance; + _instance = nullptr; +} + +void JfrCPUTimeThreadSampling::set_rate(double rate, bool auto_adapt) { + if (rate != 0) { + warn(); + } +} + +void JfrCPUTimeThreadSampling::on_javathread_create(JavaThread* thread) { +} + +void JfrCPUTimeThreadSampling::on_javathread_terminate(JavaThread* thread) { +} + +#endif // defined(LINUX) && defined(INCLUDE_JFR) diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp new file mode 100644 index 00000000000..7c0545f4772 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025 SAP SE. 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. + * + */ + +#ifndef SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP +#define SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP + +#include "jfr/utilities/jfrAllocation.hpp" + +class JavaThread; + +#if defined(LINUX) + +#include "jfr/periodic/sampling/jfrSampleRequest.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +struct JfrCPUTimeSampleRequest { + JfrSampleRequest _request; + Tickspan _cpu_time_period; + + JfrCPUTimeSampleRequest() {} +}; + +// Fixed size async-signal-safe SPSC linear queue backed by an array. +// Designed to be only used under lock and read linearly +class JfrCPUTimeTraceQueue { + + // the default queue capacity, scaled if the sampling period is smaller than 10ms + // when the thread is started + static const u4 CPU_TIME_QUEUE_CAPACITY = 500; + + JfrCPUTimeSampleRequest* _data; + u4 _capacity; + // next unfilled index + volatile u4 _head; + + volatile u4 _lost_samples; + +public: + JfrCPUTimeTraceQueue(u4 capacity); + + ~JfrCPUTimeTraceQueue(); + + // signal safe, but can't be interleaved with dequeue + bool enqueue(JfrCPUTimeSampleRequest& trace); + + JfrCPUTimeSampleRequest& at(u4 index); + + u4 size() const; + + void set_size(u4 size); + + u4 capacity() const; + + // deletes all samples in the queue + void set_capacity(u4 capacity); + + bool is_empty() const; + + u4 lost_samples() const; + + void increment_lost_samples(); + + // returns the previous lost samples count + u4 get_and_reset_lost_samples(); + + void resize(u4 capacity); + + void resize_for_period(u4 period_millis); + + void clear(); + +}; + + +class JfrCPUSamplerThread; + +class JfrCPUTimeThreadSampling : public JfrCHeapObj { + friend class JfrRecorder; + private: + + JfrCPUSamplerThread* _sampler; + + void create_sampler(double rate, bool auto_adapt); + void set_rate_value(double rate, bool auto_adapt); + + JfrCPUTimeThreadSampling(); + ~JfrCPUTimeThreadSampling(); + + static JfrCPUTimeThreadSampling& instance(); + static JfrCPUTimeThreadSampling* create(); + static void destroy(); + + void update_run_state(double rate, bool auto_adapt); + + public: + static void set_rate(double rate, bool auto_adapt); + + static void on_javathread_create(JavaThread* thread); + static void on_javathread_terminate(JavaThread* thread); + void handle_timer_signal(siginfo_t* info, void* context); + + static void send_empty_event(const JfrTicks& start_time, traceid tid, Tickspan cpu_time_period); + static void send_event(const JfrTicks& start_time, traceid sid, traceid tid, Tickspan cpu_time_period, bool biased); + static void send_lost_event(const JfrTicks& time, traceid tid, s4 lost_samples); + + static void trigger_async_processing_of_cpu_time_jfr_requests(); +}; + +#else + +// a basic implementation on other platforms that +// emits warnings + +class JfrCPUTimeThreadSampling : public JfrCHeapObj { + friend class JfrRecorder; +private: + static JfrCPUTimeThreadSampling& instance(); + static JfrCPUTimeThreadSampling* create(); + static void destroy(); + + public: + static void set_rate(double rate, bool auto_adapt); + + static void on_javathread_create(JavaThread* thread); + static void on_javathread_terminate(JavaThread* thread); +}; + +#endif // defined(LINUX) + + +#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRCPUTIMETHREADSAMPLER_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp index f8e63e2e344..7049df0198b 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.cpp @@ -24,6 +24,7 @@ #include "asm/codeBuffer.hpp" #include "interpreter/interpreter.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" +#include "jfr/utilities/jfrTime.hpp" #include "runtime/continuationEntry.hpp" #include "runtime/frame.inline.hpp" #include "runtime/javaThread.inline.hpp" @@ -171,7 +172,7 @@ static bool build(JfrSampleRequest& request, intptr_t* fp, JavaThread* jt) { assert(request._sample_sp != nullptr, "invariant"); assert(request._sample_pc != nullptr, "invariant"); assert(jt != nullptr, "invariant"); - assert(jt->thread_state() == _thread_in_Java, "invariant"); + assert(jt->thread_state() == _thread_in_Java || jt->thread_state() == _thread_in_native, "invariant"); // 1. Interpreter frame? if (is_interpreter(request)) { @@ -303,3 +304,33 @@ JfrSampleResult JfrSampleRequestBuilder::build_java_sample_request(const void* u } return set_biased_java_sample(request, tl, jt); } + + +// A biased sample request is denoted by an empty bcp and an empty pc. +static inline void set_cpu_time_biased_sample(JfrSampleRequest& request, JavaThread* jt) { + if (request._sample_bcp != nullptr) { + request._sample_bcp = nullptr; + } + assert(request._sample_bcp == nullptr, "invariant"); + request._sample_pc = nullptr; +} + +void JfrSampleRequestBuilder::build_cpu_time_sample_request(JfrSampleRequest& request, + void* ucontext, + JavaThread* jt, + JfrThreadLocal* tl, + JfrTicks& now) { + assert(jt != nullptr, "invariant"); + request._sample_ticks = now; + + // Prioritize the ljf, if one exists. + request._sample_sp = jt->last_Java_sp(); + if (request._sample_sp == nullptr || !build_from_ljf(request, tl, jt)) { + intptr_t* fp; + request._sample_pc = os::fetch_frame_from_context(ucontext, reinterpret_cast(&request._sample_sp), &fp); + assert(sp_in_stack(request, jt), "invariant"); + if (!build(request, fp, jt)) { + set_cpu_time_biased_sample(request, jt); + } + } +} diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp index 6567e7f8bff..20e737e0cbf 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrSampleRequest.hpp @@ -81,6 +81,11 @@ class JfrSampleRequestBuilder : AllStatic { static JfrSampleResult build_java_sample_request(const void* ucontext, JfrThreadLocal* tl, JavaThread* jt); + static void build_cpu_time_sample_request(JfrSampleRequest &request, + void* ucontext, + JavaThread* jt, + JfrThreadLocal* tl, + JfrTicks& now); }; #endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEREQUEST_HPP diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index aa72c29cf50..ddc9d59b295 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -28,6 +28,7 @@ #include "code/nmethod.hpp" #include "interpreter/interpreter.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrSampleMonitor.hpp" #include "jfr/periodic/sampling/jfrSampleRequest.hpp" #include "jfr/periodic/sampling/jfrThreadSampling.hpp" @@ -161,7 +162,7 @@ static inline bool is_valid(const PcDesc* pc_desc) { return pc_desc != nullptr && pc_desc->scope_decode_offset() != DebugInformationRecorder::serialized_null; } -static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt) { +static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, bool& in_continuation, JavaThread* jt, bool& biased) { assert(jt != nullptr, "invariant"); if (!jt->has_last_Java_frame()) { @@ -178,6 +179,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, // A biased sample is requested or no code blob. top_frame = jt->last_frame(); in_continuation = is_in_continuation(top_frame, jt); + biased = true; return true; } @@ -227,6 +229,8 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, assert(!stream.current()->is_safepoint_blob_frame(), "invariant"); + biased = true; + // Search the first frame that is above the sampled sp. for (; !stream.is_done(); stream.next()) { frame* const current = stream.current(); @@ -250,6 +254,7 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc); if (is_valid(pc_desc)) { current->adjust_pc(pc_desc->real_pc(sampled_nm)); + biased = false; } } } @@ -270,8 +275,9 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick assert(current != nullptr, "invariant"); frame top_frame; + bool biased = false; bool in_continuation; - if (!compute_top_frame(request, top_frame, in_continuation, jt)) { + if (!compute_top_frame(request, top_frame, in_continuation, jt, biased)) { return; } @@ -293,6 +299,42 @@ static void record_thread_in_java(const JfrSampleRequest& request, const JfrTick } } +#ifdef LINUX +static void record_cpu_time_thread(const JfrCPUTimeSampleRequest& request, const JfrTicks& now, const JfrThreadLocal* tl, JavaThread* jt, Thread* current) { + assert(jt != nullptr, "invariant"); + assert(tl != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + frame top_frame; + bool biased = false; + bool in_continuation = false; + bool could_compute_top_frame = compute_top_frame(request._request, top_frame, in_continuation, jt, biased); + const traceid tid = in_continuation ? tl->vthread_id_with_epoch_update(jt) : JfrThreadLocal::jvm_thread_id(jt); + + if (!could_compute_top_frame) { + JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); + return; + } + traceid sid; + { + ResourceMark rm(current); + JfrStackTrace stacktrace; + if (!stacktrace.record(jt, top_frame, in_continuation, request._request)) { + // Unable to record stacktrace. Fail. + JfrCPUTimeThreadSampling::send_empty_event(request._request._sample_ticks, tid, request._cpu_time_period); + return; + } + sid = JfrStackTraceRepository::add(stacktrace); + } + assert(sid != 0, "invariant"); + + + JfrCPUTimeThreadSampling::send_event(request._request._sample_ticks, sid, tid, request._cpu_time_period, biased); + if (current == jt) { + send_safepoint_latency_event(request._request, now, sid, jt); + } +} +#endif + static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { assert(tl != nullptr, "invariant"); assert(jt != nullptr, "invariant"); @@ -308,6 +350,49 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav assert(!tl->has_enqueued_requests(), "invariant"); } +static void drain_enqueued_cpu_time_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current, bool lock) { + assert(tl != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); +#ifdef LINUX + tl->set_do_async_processing_of_cpu_time_jfr_requests(false); + if (lock) { + tl->acquire_cpu_time_jfr_dequeue_lock(); + } + JfrCPUTimeTraceQueue& queue = tl->cpu_time_jfr_queue(); + for (u4 i = 0; i < queue.size(); i++) { + record_cpu_time_thread(queue.at(i), now, tl, jt, current); + } + queue.clear(); + assert(queue.is_empty(), "invariant"); + tl->set_has_cpu_time_jfr_requests(false); + if (queue.lost_samples() > 0) { + JfrCPUTimeThreadSampling::send_lost_event( now, JfrThreadLocal::thread_id(jt), queue.get_and_reset_lost_samples()); + } + if (lock) { + tl->release_cpu_time_jfr_queue_lock(); + } +#endif +} + +// Entry point for a thread that has been sampled in native code and has a pending JFR CPU time request. +void JfrThreadSampling::process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock) { + assert(jt != nullptr, "invariant"); + + const JfrTicks now = JfrTicks::now(); + drain_enqueued_cpu_time_requests(now, tl, jt, current, lock); +} + +static void drain_all_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, JavaThread* jt, Thread* current) { + assert(tl != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + drain_enqueued_requests(now, tl, jt, current); + if (tl->has_cpu_time_jfr_requests()) { + drain_enqueued_cpu_time_requests(now, tl, jt, current, true); + } +} + // Only entered by the JfrSampler thread. bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) { assert(tl != nullptr, "invairant"); @@ -382,5 +467,6 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) { break; } } - drain_enqueued_requests(now, tl, jt, jt); + drain_all_enqueued_requests(now, tl, jt, jt); } + diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp index d4c4bc0d873..3c4d1a126b0 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.hpp @@ -33,8 +33,10 @@ class Thread; class JfrThreadSampling : AllStatic { friend class JfrSamplerThread; + friend class JfrCPUSamplerThread; private: static bool process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread); + static void process_cpu_time_request(JavaThread* jt, JfrThreadLocal* tl, Thread* current, bool lock); public: static void process_sample_request(JavaThread* jt); }; diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index 384305862ca..dd75cb2929f 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -29,6 +29,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/sampling/objectSampler.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" @@ -304,6 +305,9 @@ bool JfrRecorder::create_components() { if (!create_thread_sampler()) { return false; } + if (!create_cpu_time_thread_sampling()) { + return false; + } if (!create_event_throttler()) { return false; } @@ -318,6 +322,7 @@ static JfrStackTraceRepository* _stack_trace_repository; static JfrStringPool* _stringpool = nullptr; static JfrOSInterface* _os_interface = nullptr; static JfrThreadSampler* _thread_sampler = nullptr; +static JfrCPUTimeThreadSampling* _cpu_time_thread_sampling = nullptr; static JfrCheckpointManager* _checkpoint_manager = nullptr; bool JfrRecorder::create_java_event_writer() { @@ -390,6 +395,12 @@ bool JfrRecorder::create_thread_sampler() { return _thread_sampler != nullptr; } +bool JfrRecorder::create_cpu_time_thread_sampling() { + assert(_cpu_time_thread_sampling == nullptr, "invariant"); + _cpu_time_thread_sampling = JfrCPUTimeThreadSampling::create(); + return _cpu_time_thread_sampling != nullptr; +} + bool JfrRecorder::create_event_throttler() { return JfrEventThrottler::create(); } @@ -428,6 +439,10 @@ void JfrRecorder::destroy_components() { JfrThreadSampler::destroy(); _thread_sampler = nullptr; } + if (_cpu_time_thread_sampling != nullptr) { + JfrCPUTimeThreadSampling::destroy(); + _cpu_time_thread_sampling = nullptr; + } JfrEventThrottler::destroy(); } diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp index b917904c5fb..3099c8ad344 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp @@ -54,6 +54,7 @@ class JfrRecorder : public JfrCHeapObj { static bool create_storage(); static bool create_stringpool(); static bool create_thread_sampler(); + static bool create_cpu_time_thread_sampling(); static bool create_event_throttler(); static bool create_components(); static void destroy_components(); diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp index 0befaae7751..f660a01c04c 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp @@ -34,6 +34,7 @@ constexpr static const JfrSamplerParams _disabled_params = { false // reconfigure }; +static JfrEventThrottler* _disabled_cpu_time_sample_throttler = nullptr; static JfrEventThrottler* _object_allocation_throttler = nullptr; static JfrEventThrottler* _safepoint_latency_throttler = nullptr; @@ -48,6 +49,9 @@ JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) : _update(false) {} bool JfrEventThrottler::create() { + assert(_disabled_cpu_time_sample_throttler == nullptr, "invariant"); + _disabled_cpu_time_sample_throttler = new JfrEventThrottler(JfrCPUTimeSampleEvent); + _disabled_cpu_time_sample_throttler->_disabled = true; assert(_object_allocation_throttler == nullptr, "invariant"); _object_allocation_throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent); if (_object_allocation_throttler == nullptr || !_object_allocation_throttler->initialize()) { @@ -59,6 +63,8 @@ bool JfrEventThrottler::create() { } void JfrEventThrottler::destroy() { + delete _disabled_cpu_time_sample_throttler; + _disabled_cpu_time_sample_throttler = nullptr; delete _object_allocation_throttler; _object_allocation_throttler = nullptr; delete _safepoint_latency_throttler; @@ -69,15 +75,19 @@ void JfrEventThrottler::destroy() { // and another for the SamplingLatency event. // When introducing many more throttlers, consider adding a lookup map keyed by event id. JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) { + assert(_disabled_cpu_time_sample_throttler != nullptr, "Disabled CPU time throttler has not been properly initialized"); assert(_object_allocation_throttler != nullptr, "ObjectAllocation throttler has not been properly initialized"); assert(_safepoint_latency_throttler != nullptr, "SafepointLatency throttler has not been properly initialized"); - assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent, "Event type has an unconfigured throttler"); + assert(event_id == JfrObjectAllocationSampleEvent || event_id == JfrSafepointLatencyEvent || event_id == JfrCPUTimeSampleEvent, "Event type has an unconfigured throttler"); if (event_id == JfrObjectAllocationSampleEvent) { return _object_allocation_throttler; } if (event_id == JfrSafepointLatencyEvent) { return _safepoint_latency_throttler; } + if (event_id == JfrCPUTimeSampleEvent) { + return _disabled_cpu_time_sample_throttler; + } return nullptr; } diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index 503aa85e02f..4b805a98a32 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -26,6 +26,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -78,7 +79,15 @@ JfrThreadLocal::JfrThreadLocal() : _enqueued_requests(false), _vthread(false), _notified(false), - _dead(false) { + _dead(false) +#ifdef LINUX + ,_cpu_timer(nullptr), + _cpu_time_jfr_locked(UNLOCKED), + _has_cpu_time_jfr_requests(false), + _cpu_time_jfr_queue(0), + _do_async_processing_of_cpu_time_jfr_requests(false) +#endif + { Thread* thread = Thread::current_or_null(); _parent_trace_id = thread != nullptr ? jvm_thread_id(thread) : (traceid)0; } @@ -129,7 +138,9 @@ void JfrThreadLocal::on_start(Thread* t) { if (JfrRecorder::is_recording()) { JfrCheckpointManager::write_checkpoint(t); if (t->is_Java_thread()) { - send_java_thread_start_event(JavaThread::cast(t)); + JavaThread *const jt = JavaThread::cast(t); + JfrCPUTimeThreadSampling::on_javathread_create(jt); + send_java_thread_start_event(jt); } } if (t->jfr_thread_local()->has_cached_stack_trace()) { @@ -221,6 +232,7 @@ void JfrThreadLocal::on_exit(Thread* t) { if (t->is_Java_thread()) { JavaThread* const jt = JavaThread::cast(t); send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); + JfrCPUTimeThreadSampling::on_javathread_terminate(jt); JfrThreadCPULoadEvent::send_event_for_thread(jt); } release(tl, Thread::current()); // because it could be that Thread::current() != t @@ -537,3 +549,85 @@ Arena* JfrThreadLocal::dcmd_arena(JavaThread* jt) { tl->_dcmd_arena = arena; return arena; } + + +#ifdef LINUX + +void JfrThreadLocal::set_cpu_timer(timer_t* timer) { + if (_cpu_timer == nullptr) { + _cpu_timer = JfrCHeapObj::new_array(1); + } + *_cpu_timer = *timer; +} + +void JfrThreadLocal::unset_cpu_timer() { + if (_cpu_timer != nullptr) { + timer_delete(*_cpu_timer); + JfrCHeapObj::free(_cpu_timer, sizeof(timer_t)); + _cpu_timer = nullptr; + } +} + +timer_t* JfrThreadLocal::cpu_timer() const { + return _cpu_timer; +} + +bool JfrThreadLocal::is_cpu_time_jfr_enqueue_locked() { + return Atomic::load_acquire(&_cpu_time_jfr_locked) == ENQUEUE; +} + +bool JfrThreadLocal::is_cpu_time_jfr_dequeue_locked() { + return Atomic::load_acquire(&_cpu_time_jfr_locked) == DEQUEUE; +} + +bool JfrThreadLocal::try_acquire_cpu_time_jfr_enqueue_lock() { + return Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, ENQUEUE) == UNLOCKED; +} + +bool JfrThreadLocal::try_acquire_cpu_time_jfr_dequeue_lock() { + CPUTimeLockState got; + while (true) { + CPUTimeLockState got = Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE); + if (got == UNLOCKED) { + return true; // successfully locked for dequeue + } + if (got == DEQUEUE) { + return false; // already locked for dequeue + } + // else wait for the lock to be released from a signal handler + } +} + +void JfrThreadLocal::acquire_cpu_time_jfr_dequeue_lock() { + while (Atomic::cmpxchg(&_cpu_time_jfr_locked, UNLOCKED, DEQUEUE) != UNLOCKED); +} + +void JfrThreadLocal::release_cpu_time_jfr_queue_lock() { + Atomic::release_store(&_cpu_time_jfr_locked, UNLOCKED); +} + +void JfrThreadLocal::set_has_cpu_time_jfr_requests(bool has_requests) { + Atomic::release_store(&_has_cpu_time_jfr_requests, has_requests); +} + +bool JfrThreadLocal::has_cpu_time_jfr_requests() { + return Atomic::load_acquire(&_has_cpu_time_jfr_requests); +} + +JfrCPUTimeTraceQueue& JfrThreadLocal::cpu_time_jfr_queue() { + return _cpu_time_jfr_queue; +} + +void JfrThreadLocal::deallocate_cpu_time_jfr_queue() { + cpu_time_jfr_queue().resize(0); +} + +void JfrThreadLocal::set_do_async_processing_of_cpu_time_jfr_requests(bool wants) { + Atomic::release_store(&_do_async_processing_of_cpu_time_jfr_requests, wants); +} + +bool JfrThreadLocal::wants_async_processing_of_cpu_time_jfr_requests() { + return Atomic::load_acquire(&_do_async_processing_of_cpu_time_jfr_requests); +} + +#endif diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 8e545d9c429..715a2c44f93 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -33,6 +33,10 @@ #include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" +#ifdef LINUX +#include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" +#endif + class Arena; class JavaThread; class JfrBuffer; @@ -79,6 +83,22 @@ class JfrThreadLocal { bool _dead; bool _sampling_critical_section; +#ifdef LINUX + timer_t* _cpu_timer; + + enum CPUTimeLockState { + UNLOCKED, + // locked for enqueuing + ENQUEUE, + // locked for dequeuing + DEQUEUE + }; + volatile CPUTimeLockState _cpu_time_jfr_locked; + volatile bool _has_cpu_time_jfr_requests; + JfrCPUTimeTraceQueue _cpu_time_jfr_queue; + volatile bool _do_async_processing_of_cpu_time_jfr_requests; +#endif + JfrBuffer* install_native_buffer() const; JfrBuffer* install_java_buffer() const; void release(Thread* t); @@ -342,6 +362,39 @@ class JfrThreadLocal { void set_thread_blob(const JfrBlobHandle& handle); const JfrBlobHandle& thread_blob() const; + // CPU time sampling +#ifdef LINUX + void set_cpu_timer(timer_t* timer); + void unset_cpu_timer(); + timer_t* cpu_timer() const; + + // The CPU time JFR lock has three different states: + // - ENQUEUE: lock for enqueuing CPU time requests + // - DEQUEUE: lock for dequeuing CPU time requests + // - UNLOCKED: no lock held + // This ensures that we can safely enqueue and dequeue CPU time requests, + // without interleaving + + bool is_cpu_time_jfr_enqueue_locked(); + bool is_cpu_time_jfr_dequeue_locked(); + + bool try_acquire_cpu_time_jfr_enqueue_lock(); + bool try_acquire_cpu_time_jfr_dequeue_lock(); + void acquire_cpu_time_jfr_dequeue_lock(); + void release_cpu_time_jfr_queue_lock(); + + void set_has_cpu_time_jfr_requests(bool has_events); + bool has_cpu_time_jfr_requests(); + + JfrCPUTimeTraceQueue& cpu_time_jfr_queue(); + void deallocate_cpu_time_jfr_queue(); + + void set_do_async_processing_of_cpu_time_jfr_requests(bool wants); + bool wants_async_processing_of_cpu_time_jfr_requests(); +#else + bool has_cpu_time_jfr_requests() { return false; } +#endif + // Hooks static void on_start(Thread* t); static void on_exit(Thread* t); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index f3a06d5efd2..772ef7bbe82 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -78,6 +78,7 @@ class JavaThread; // - WorkerThread // - WatcherThread // - JfrThreadSampler +// - JfrCPUSamplerThread // - LogAsyncWriter // // All Thread subclasses must be either JavaThread or NonJavaThread. diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 50d85944485..ac5d37381f9 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -115,6 +115,8 @@ template(JFROldObject) \ template(JvmtiPostObjectFree) \ template(RendezvousGCThreads) \ + template(JFRInitializeCPUTimeSampler) \ + template(JFRTerminateCPUTimeSampler) \ template(ReinitializeMDO) class Thread; diff --git a/src/hotspot/share/utilities/ticks.hpp b/src/hotspot/share/utilities/ticks.hpp index 8d2bbc7ce1f..88dc9a787f9 100644 --- a/src/hotspot/share/utilities/ticks.hpp +++ b/src/hotspot/share/utilities/ticks.hpp @@ -236,6 +236,7 @@ class TimeInstant : public Rep { friend class TimePartitionsTest; friend class GCTimerTest; friend class CompilerEvent; + friend class JfrCPUSamplerThread; }; #if INCLUDE_JFR diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index f43e45b724e..2ea4725abc8 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -51,6 +51,7 @@ import jdk.jfr.internal.settings.EnabledSetting; import jdk.jfr.internal.settings.LevelSetting; import jdk.jfr.internal.settings.MethodSetting; import jdk.jfr.internal.settings.PeriodSetting; +import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; @@ -326,6 +327,9 @@ public final class EventControl { private static Control defineThrottle(PlatformEventType type) { String def = type.getAnnotationValue(Throttle.class, ThrottleSetting.DEFAULT_VALUE); type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList())); + if (type.getName().equals("jdk.CPUTimeSample")) { + return new Control(new CPUThrottleSetting(type), def); + } return new Control(new ThrottleSetting(type, def), def); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index e0eaef74b4c..b91f0c337b2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -270,6 +270,16 @@ public final class JVM { */ public static native void setMethodSamplingPeriod(long type, long periodMillis); + /** + * Set the maximum event emission rate for the CPU time sampler + * + * Setting rate to 0 turns off the CPU time sampler. + * + * @param rate the new rate in events per second + * @param autoAdapt true if the rate should be adapted automatically + */ + public static native void setCPUThrottle(double rate, boolean autoAdapt); + /** * Sets the file where data should be written. * diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 32b59bca4c0..b65a26f3aad 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -30,8 +30,10 @@ import java.util.List; import java.util.Objects; import jdk.jfr.SettingDescriptor; +import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; +import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.tracing.Modification; @@ -45,6 +47,7 @@ public final class PlatformEventType extends Type { private final boolean isJVM; private final boolean isJDK; private final boolean isMethodSampling; + private final boolean isCPUTimeMethodSampling; private final List settings = new ArrayList<>(5); private final boolean dynamicSettings; private final int stackTraceOffset; @@ -56,6 +59,7 @@ public final class PlatformEventType extends Type { private boolean stackTraceEnabled = true; private long thresholdTicks = 0; private long period = 0; + private TimespanRate cpuRate; private boolean hasHook; private boolean beginChunk; @@ -75,6 +79,7 @@ public final class PlatformEventType extends Type { this.dynamicSettings = dynamicSettings; this.isJVM = Type.isDefinedByJVM(id); this.isMethodSampling = determineMethodSampling(); + this.isCPUTimeMethodSampling = isJVM && name.equals(Type.EVENT_NAME_PREFIX + "CPUTimeSample"); this.isJDK = isJDK; this.stackTraceOffset = determineStackTraceOffset(); } @@ -191,6 +196,15 @@ public final class PlatformEventType extends Type { } } + public void setCPUThrottle(TimespanRate rate) { + if (isCPUTimeMethodSampling) { + this.cpuRate = rate; + if (isEnabled()) { + JVM.setCPUThrottle(rate.rate(), rate.autoAdapt()); + } + } + } + public void setHasPeriod(boolean hasPeriod) { this.hasPeriod = hasPeriod; } @@ -251,6 +265,9 @@ public final class PlatformEventType extends Type { if (isMethodSampling) { long p = enabled ? period : 0; JVM.setMethodSamplingPeriod(getId(), p); + } else if (isCPUTimeMethodSampling) { + TimespanRate r = enabled ? cpuRate : new TimespanRate(0, false); + JVM.setCPUThrottle(r.rate(), r.autoAdapt()); } else { JVM.setEnabled(getId(), enabled); } @@ -388,6 +405,10 @@ public final class PlatformEventType extends Type { return isMethodSampling; } + public boolean isCPUTimeMethodSampling() { + return isCPUTimeMethodSampling; + } + public void setStackFilterId(long id) { startFilterId = id; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index a6192db1ab0..a2ac74142f4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -39,7 +39,8 @@ label = "Active Settings" table = "COLUMN 'Event Type', 'Enabled', 'Threshold', 'Stack Trace','Period','Cutoff', 'Throttle' FORMAT none, missing:whitespace, missing:whitespace, missing:whitespace, - missing:whitespace, missing:whitespace, missing:whitespace + missing:whitespace, missing:whitespace, missing:whitespace, + missing:whitespace SELECT E.id, LAST_BATCH(E.value), LAST_BATCH(T.value), LAST_BATCH(S.value), LAST_BATCH(P.value), LAST_BATCH(C.value), LAST_BATCH(U.value) @@ -400,6 +401,28 @@ table = "COLUMN 'Method', 'Samples', 'Percent' SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) FROM ExecutionSample GROUP BY T LIMIT 25" +[application.cpu-time-hot-methods] +label = "Java Methods that Execute the Most from CPU Time Sampler" +table = "COLUMN 'Method', 'Samples', 'Percent' + FORMAT none, none, normalized + SELECT stackTrace.topFrame AS T, COUNT(*), COUNT(*) + FROM CPUTimeSample GROUP BY T LIMIT 25" + +[application.cpu-time-statistics] +label = "CPU Time Sample Statistics" +form = "COLUMN 'Successful Samples', 'Failed Samples', 'Biased Samples', 'Total Samples', 'Lost Samples' + SELECT COUNT(S.startTime), COUNT(F.startTime), COUNT(B.startTime), Count(A.startTime), SUM(L.lostSamples) + FROM + CPUTimeSample AS S, + CPUTimeSample AS F, + CPUTimeSample AS A, + CPUTimeSample AS B, + CPUTimeSamplesLost AS L + WHERE + S.failed = 'false' AND + F.failed = 'true' AND + B.biased = 'true'" + [jvm.jdk-agents] label = "JDK Agents" table = "COLUMN 'Time', 'Initialization', 'Name', 'Options' diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java new file mode 100644 index 00000000000..c18aeef2132 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/CPUThrottleSetting.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, Datadog, Inc. All rights reserved. + * Copyright (c) 2025 SAP SE. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.settings; + +import static jdk.jfr.internal.util.TimespanUnit.SECONDS; +import static jdk.jfr.internal.util.TimespanUnit.MILLISECONDS; + +import java.util.Objects; +import java.util.Set; + +import jdk.jfr.Description; +import jdk.jfr.SettingControl; +import jdk.jfr.Label; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.Type; +import jdk.jfr.internal.util.TimespanRate; +import jdk.jfr.internal.util.Utils; + +@MetadataDefinition +@Label("CPUThrottleSetting") +@Description("Upper bounds the emission rate for CPU time samples") +@Name(Type.SETTINGS_PREFIX + "Rate") +public final class CPUThrottleSetting extends SettingControl { + public static final String DEFAULT_VALUE = "0/s"; + private final PlatformEventType eventType; + private String value = DEFAULT_VALUE; + + public CPUThrottleSetting(PlatformEventType eventType) { + this.eventType = Objects.requireNonNull(eventType); + } + + @Override + public String combine(Set values) { + TimespanRate max = null; + for (String value : values) { + TimespanRate rate = TimespanRate.of(value); + if (rate != null) { + if (max == null || rate.isHigher(max)) { + max = rate; + } + max = new TimespanRate(max.rate(), max.autoAdapt() || rate.autoAdapt()); + } + } + // "off" is not supported + return Objects.requireNonNullElse(max.toString(), DEFAULT_VALUE); + } + + @Override + public void setValue(String value) { + TimespanRate rate = TimespanRate.of(value); + if (rate != null) { + eventType.setCPUThrottle(rate); + this.value = value; + } + } + + @Override + public String getValue() { + return value; + } +} + diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java index f32436a5e0f..2632cd63848 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Rate.java @@ -55,4 +55,8 @@ public record Rate(long amount, TimespanUnit unit) { private double inNanos() { return (double) amount / unit.nanos; } + + public double perSecond() { + return inNanos() * 1_000_000_000.0; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java new file mode 100644 index 00000000000..5d671310e3c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/TimespanRate.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 SAP SE. All rights reserved. + * Copyright (c) 2024, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.util; + +import jdk.jfr.internal.settings.CPUThrottleSetting; + +/** + * A rate or fixed period, see {@link jdk.jfr.internal.Rate} + */ +public record TimespanRate(double rate, boolean autoAdapt) { + + public static TimespanRate of(String text) { + if (text.equals("off")) { + text = CPUThrottleSetting.DEFAULT_VALUE; + } + boolean isPeriod = !text.contains("/"); + if (isPeriod) { + var period = ValueParser.parseTimespanWithInfinity(text, Long.MAX_VALUE); + if (period == Long.MAX_VALUE) { + return null; + } + if (period == 0) { + return new TimespanRate(0, false); + } + return new TimespanRate(Runtime.getRuntime().availableProcessors() / (period / 1_000_000_000.0), false); + } + Rate r = Rate.of(text); + if (r == null) { + return null; + } + return new TimespanRate(r.perSecond(), true); + } + + public boolean isHigher(TimespanRate that) { + return rate() > that.rate(); + } + + @Override + public String toString() { + if (autoAdapt) { + return String.format("%d/ns", (long)(rate * 1_000_000_000L)); + } + return String.format("%dns", (long)(Runtime.getRuntime().availableProcessors() / rate * 1_000_000_000L)); + } +} diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 293af26746f..541d1d3aa2f 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -226,6 +226,16 @@ off + + false + 500/s + true + + + + true + + true 10 ms diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 89a9022d11e..9cec2d9a70f 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -206,6 +206,16 @@ 20 ms + + false + 10ms + true + + + + true + + true diff --git a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java index 5b8aacfb1d2..d6e126a493f 100644 --- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java +++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java @@ -89,7 +89,10 @@ public class TestLookForUntestedEvents { // Experimental events private static final Set experimentalEvents = Set.of( - "Flush", "SyncOnValueBasedClass"); + "Flush", "SyncOnValueBasedClass", "CPUTimeSample", "CPUTimeSamplesLost"); + + // Subset of the experimental events that should have tests + private static final Set experimentalButTestedEvents = Set.of("CPUTimeSample"); public static void main(String[] args) throws Exception { for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) { @@ -110,7 +113,9 @@ public class TestLookForUntestedEvents { .collect(Collectors.toList()); Set eventsNotCoveredByTest = new HashSet<>(jfrEventTypes); - for (String event : jfrEventTypes) { + Set checkedEvents = new HashSet<>(jfrEventTypes); + checkedEvents.addAll(experimentalButTestedEvents); + for (String event : checkedEvents) { for (Path p : paths) { if (findStringInFile(p, event)) { eventsNotCoveredByTest.remove(event); diff --git a/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java new file mode 100644 index 00000000000..de211b8c454 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/BaseTestFullStackTrace.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013, 2023, 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.jfr.event.profiling; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.RecurseThread; + +public class BaseTestFullStackTrace { + private final static int MAX_DEPTH = 64; // currently hardcoded in jvm + + private final String eventName; + private final String threadFieldName; + + public BaseTestFullStackTrace(String eventName, String threadFieldName) { + this.eventName = eventName; + this.threadFieldName = threadFieldName; + } + + public void run() throws Throwable { + RecurseThread[] threads = new RecurseThread[3]; + for (int i = 0; i < threads.length; ++i) { + int depth = MAX_DEPTH - 1 + i; + threads[i] = new RecurseThread(depth); + threads[i].setName("recursethread-" + depth); + threads[i].start(); + } + + for (RecurseThread thread : threads) { + while (!thread.isInRunLoop()) { + Thread.sleep(20); + } + } + + assertStackTraces(threads); + + for (RecurseThread thread : threads) { + thread.quit(); + thread.join(); + } + } + + private void assertStackTraces(RecurseThread[] threads) throws Throwable { + while (true) { + try (Recording recording = new Recording()) { + if (eventName.equals(EventNames.CPUTimeSample)) { + recording.enable(eventName).with("throttle", "50ms"); + } else { + recording.enable(eventName).withPeriod(Duration.ofMillis(50)); + } + recording.start(); + Thread.sleep(500); + recording.stop(); + if (hasValidStackTraces(recording, threads)) { + break; + } + } + }; + } + + private boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { + boolean[] isEventFound = new boolean[threads.length]; + + for (RecordedEvent event : Events.fromRecording(recording)) { + System.out.println("Event: " + event); + String threadName = Events.assertField(event, threadFieldName + ".javaName").getValue(); + long threadId = Events.assertField(event, threadFieldName + ".javaThreadId").getValue(); + + for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { + RecurseThread currThread = threads[threadIndex]; + if (threadId == currThread.getId()) { + System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); + Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); + if ("recurseEnd".equals(getTopMethodName(event))) { + isEventFound[threadIndex] = true; + checkEvent(event, currThread.totalDepth); + break; + } + } + } + } + + for (int i = 0; i < threads.length; ++i) { + String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; + System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); + } + for (int i = 0; i < threads.length; ++i) { + if(!isEventFound[i]) { + // no assertion, let's retry. + // Could be race condition, i.e safe point during Thread.sleep + System.out.println("Failed to validate all threads, will retry."); + return false; + } + } + return true; + } + + public String getTopMethodName(RecordedEvent event) { + List frames = event.getStackTrace().getFrames(); + Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); + return frames.getFirst().getMethod().getName(); + } + + private void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { + RecordedStackTrace stacktrace = null; + try { + stacktrace = event.getStackTrace(); + List frames = stacktrace.getFrames(); + Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); + List expectedMethods = getExpectedMethods(expectedDepth); + Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); + + for (int i = 0; i < frames.size(); ++i) { + String name = frames.get(i).getMethod().getName(); + String expectedName = expectedMethods.get(i); + System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); + Asserts.assertEquals(name, expectedName, "Wrong method name"); + } + + boolean isTruncated = stacktrace.isTruncated(); + boolean isTruncateExpected = expectedDepth > MAX_DEPTH; + Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); + + String firstMethod = frames.getLast().getMethod().getName(); + boolean isFullTrace = "run".equals(firstMethod); + String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); + Asserts.assertTrue(isTruncated != isFullTrace, msg); + } catch (Throwable t) { + System.out.println(String.format("stacktrace:%n%s", stacktrace)); + throw t; + } + } + + private List getExpectedMethods(int depth) { + List methods = new ArrayList<>(); + methods.add("recurseEnd"); + for (int i = 0; i < depth - 2; ++i) { + methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); + } + methods.add("run"); + if (depth > MAX_DEPTH) { + methods = methods.subList(0, MAX_DEPTH); + } + return methods; + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java new file mode 100644 index 00000000000..eb8d33832b5 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeAndExecutionSample.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.RecurseThread; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main/timeout=30 jdk.jfr.event.profiling.TestCPUTimeAndExecutionSample + */ +public class TestCPUTimeAndExecutionSample { + + static String sampleEvent = EventNames.CPUTimeSample; + + // The period is set to 1100 ms to provoke the 1000 ms + // threshold in the JVM for os::naked_short_sleep(). + public static void main(String[] args) throws Exception { + run(EventNames.ExecutionSample); + run(EventNames.CPUTimeSample); + run(EventNames.ExecutionSample); + run(EventNames.CPUTimeSample); + } + + private static void run(String eventType) { + RecurseThread t = new RecurseThread(50); + t.setDaemon(true); + try (RecordingStream rs = new RecordingStream()) { + rs.enable(sampleEvent).with("throttle", "1000/s"); + rs.onEvent(sampleEvent, e -> { + t.quit(); + rs.close(); + }); + t.start(); + rs.start(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java new file mode 100644 index 00000000000..0d1108346ab --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleFullStackTrace.java @@ -0,0 +1,41 @@ +/* + * 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.jfr.event.profiling; + +import jdk.test.lib.jfr.EventNames; + +/** + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @build jdk.jfr.event.profiling.BaseTestFullStackTrace + * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleFullStackTrace + */ +public class TestCPUTimeSampleFullStackTrace { + + public static void main(String[] args) throws Throwable { + new BaseTestFullStackTrace(EventNames.CPUTimeSample, "eventThread").run(); + } + +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java new file mode 100644 index 00000000000..133df36684c --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleMultipleRecordings.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.internal.JVM; +import jdk.test.lib.jfr.EventNames; + +/* + * Tests that creating multiple recordings after another is possible. + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSampleMultipleRecordings + */ +public class TestCPUTimeSampleMultipleRecordings { + + static String nativeEvent = EventNames.CPUTimeSample; + + static volatile boolean alive = true; + + public static void main(String[] args) throws Exception { + Thread t = new Thread(TestCPUTimeSampleMultipleRecordings::nativeMethod); + t.setDaemon(true); + t.start(); + for (int i = 0; i < 2; i++) { + try (RecordingStream rs = new RecordingStream()) { + rs.enable(nativeEvent).with("throttle", "1ms"); + rs.onEvent(nativeEvent, e -> { + alive = false; + rs.close(); + }); + + rs.start(); + } + } + alive = false; + } + + public static void nativeMethod() { + while (alive) { + JVM.getPid(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java new file mode 100644 index 00000000000..1617bce4ba3 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleNative.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017, 2023, 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.internal.JVM; +import jdk.test.lib.jfr.EventNames; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSampleNative + */ +public class TestCPUTimeSampleNative { + + static String nativeEvent = EventNames.CPUTimeSample; + + static volatile boolean alive = true; + + public static void main(String[] args) throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.enable(nativeEvent).with("throttle", "1ms"); + rs.onEvent(nativeEvent, e -> { + alive = false; + rs.close(); + }); + Thread t = new Thread(TestCPUTimeSampleNative::nativeMethod); + t.setDaemon(true); + t.start(); + rs.start(); + } + + } + + public static void nativeMethod() { + while (alive) { + JVM.getPid(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java new file mode 100644 index 00000000000..55b350ad096 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 SAP SE. 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.jfr.event.profiling; +import java.lang.management.ManagementFactory; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Comparator; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @run main/othervm jdk.jfr.event.profiling.TestCPUTimeSampleThrottling + */ +public class TestCPUTimeSampleThrottling { + + public static void main(String[] args) throws Exception { + testZeroPerSecond(); + testThrottleSettings(); + testThrottleSettingsPeriod(); + } + + private static void testZeroPerSecond() throws Exception { + Asserts.assertTrue(0L == countEvents(1000, "0/s").count()); + } + + private static void testThrottleSettings() throws Exception { + long count = countEvents(1000, + Runtime.getRuntime().availableProcessors() * 2 + "/s").count(); + Asserts.assertTrue(count > 0 && count < 3, + "Expected between 0 and 3 events, got " + count); + } + + private static void testThrottleSettingsPeriod() throws Exception { + float rate = countEvents(1000, "10ms").rate(); + Asserts.assertTrue(rate > 90 && rate < 110, "Expected around 100 events per second, got " + rate); + } + + private record EventCount(long count, float time) { + float rate() { + return count / time; + } + } + + private static EventCount countEvents(int timeMs, String rate) throws Exception { + try(Recording recording = new Recording()) { + recording.enable(EventNames.CPUTimeSample) + .with("throttle", rate); + + var bean = ManagementFactory.getThreadMXBean(); + + recording.start(); + + long startThreadCpuTime = bean.getCurrentThreadCpuTime(); + + wasteCPU(timeMs); + + long spendCPUTime = bean.getCurrentThreadCpuTime() - startThreadCpuTime; + + recording.stop(); + + long eventCount = Events.fromRecording(recording).stream() + .filter(e -> e.getThread().getJavaName() + .equals(Thread.currentThread().getName())) + .count(); + + System.out.println("Event count: " + eventCount + ", CPU time: " + spendCPUTime / 1_000_000_000f + "s"); + + return new EventCount(eventCount, spendCPUTime / 1_000_000_000f); + } + } + + private static void wasteCPU(int durationMs) { + long start = System.currentTimeMillis(); + double i = 0; + while (System.currentTimeMillis() - start < durationMs) { + for (int j = 0; j < 100000; j++) { + i = Math.sqrt(i * Math.pow(Math.sqrt(Math.random()), Math.random())); + } + } + } + +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java new file mode 100644 index 00000000000..69d32d48282 --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSamplingLongPeriod.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 2022, 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.jfr.event.profiling; + +import java.time.Duration; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.RecurseThread; + +/* + * @test + * @requires vm.hasJFR & os.family == "linux" + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal + * @run main jdk.jfr.event.profiling.TestCPUTimeSamplingLongPeriod + */ +public class TestCPUTimeSamplingLongPeriod { + + static String sampleEvent = EventNames.CPUTimeSample; + + // The period is set to 1100 ms to provoke the 1000 ms + // threshold in the JVM for os::naked_short_sleep(). + public static void main(String[] args) throws Exception { + RecurseThread t = new RecurseThread(50); + t.setDaemon(true); + try (RecordingStream rs = new RecordingStream()) { + rs.enable(sampleEvent).with("throttle", "1100ms"); + rs.onEvent(sampleEvent, e -> { + t.quit(); + rs.close(); + }); + t.start(); + rs.start(); + } + } +} diff --git a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java index b06f9eed03d..c337a8128ab 100644 --- a/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java +++ b/test/jdk/jdk/jfr/event/profiling/TestFullStackTrace.java @@ -23,147 +23,20 @@ package jdk.jfr.event.profiling; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedFrame; -import jdk.jfr.consumer.RecordedStackTrace; -import jdk.test.lib.Asserts; import jdk.test.lib.jfr.EventNames; -import jdk.test.lib.jfr.Events; -import jdk.test.lib.jfr.RecurseThread; /** * @test * @requires vm.hasJFR * @requires vm.opt.DeoptimizeALot != true * @library /test/lib + * @build jdk.jfr.event.profiling.BaseTestFullStackTrace * @run main/othervm jdk.jfr.event.profiling.TestFullStackTrace */ public class TestFullStackTrace { - private final static String EVENT_NAME = EventNames.ExecutionSample; - private final static int MAX_DEPTH = 64; // currently hardcoded in jvm public static void main(String[] args) throws Throwable { - RecurseThread[] threads = new RecurseThread[3]; - for (int i = 0; i < threads.length; ++i) { - int depth = MAX_DEPTH - 1 + i; - threads[i] = new RecurseThread(depth); - threads[i].setName("recursethread-" + depth); - threads[i].start(); - } - - for (RecurseThread thread : threads) { - while (!thread.isInRunLoop()) { - Thread.sleep(20); - } - } - - assertStackTraces(threads); - - for (RecurseThread thread : threads) { - thread.quit(); - thread.join(); - } + new BaseTestFullStackTrace(EventNames.ExecutionSample, "sampledThread").run(); } - private static void assertStackTraces( RecurseThread[] threads) throws Throwable { - Recording recording= null; - do { - recording = new Recording(); - recording.enable(EVENT_NAME).withPeriod(Duration.ofMillis(50)); - recording.start(); - Thread.sleep(500); - recording.stop(); - } while (!hasValidStackTraces(recording, threads)); - } - - private static boolean hasValidStackTraces(Recording recording, RecurseThread[] threads) throws Throwable { - boolean[] isEventFound = new boolean[threads.length]; - - for (RecordedEvent event : Events.fromRecording(recording)) { - //System.out.println("Event: " + event); - String threadName = Events.assertField(event, "sampledThread.javaName").getValue(); - long threadId = Events.assertField(event, "sampledThread.javaThreadId").getValue(); - - for (int threadIndex = 0; threadIndex < threads.length; ++threadIndex) { - RecurseThread currThread = threads[threadIndex]; - if (threadId == currThread.getId()) { - System.out.println("ThreadName=" + currThread.getName() + ", depth=" + currThread.totalDepth); - Asserts.assertEquals(threadName, currThread.getName(), "Wrong thread name"); - if ("recurseEnd".equals(getTopMethodName(event))) { - isEventFound[threadIndex] = true; - checkEvent(event, currThread.totalDepth); - break; - } - } - } - } - - for (int i = 0; i < threads.length; ++i) { - String msg = "threadIndex=%d, recurseDepth=%d, isEventFound=%b%n"; - System.out.printf(msg, i, threads[i].totalDepth, isEventFound[i]); - } - for (int i = 0; i < threads.length; ++i) { - if(!isEventFound[i]) { - // no assertion, let's retry. - // Could be race condition, i.e safe point during Thread.sleep - System.out.println("Failed to validate all threads, will retry."); - return false; - } - } - return true; - } - - public static String getTopMethodName(RecordedEvent event) { - List frames = event.getStackTrace().getFrames(); - Asserts.assertFalse(frames.isEmpty(), "JavaFrames was empty"); - return frames.getFirst().getMethod().getName(); - } - - private static void checkEvent(RecordedEvent event, int expectedDepth) throws Throwable { - RecordedStackTrace stacktrace = null; - try { - stacktrace = event.getStackTrace(); - List frames = stacktrace.getFrames(); - Asserts.assertEquals(Math.min(MAX_DEPTH, expectedDepth), frames.size(), "Wrong stacktrace depth. Expected:" + expectedDepth); - List expectedMethods = getExpectedMethods(expectedDepth); - Asserts.assertEquals(expectedMethods.size(), frames.size(), "Wrong expectedMethods depth. Test error."); - - for (int i = 0; i < frames.size(); ++i) { - String name = frames.get(i).getMethod().getName(); - String expectedName = expectedMethods.get(i); - System.out.printf("method[%d]=%s, expected=%s%n", i, name, expectedName); - Asserts.assertEquals(name, expectedName, "Wrong method name"); - } - - boolean isTruncated = stacktrace.isTruncated(); - boolean isTruncateExpected = expectedDepth > MAX_DEPTH; - Asserts.assertEquals(isTruncated, isTruncateExpected, "Wrong value for isTruncated. Expected:" + isTruncateExpected); - - String firstMethod = frames.getLast().getMethod().getName(); - boolean isFullTrace = "run".equals(firstMethod); - String msg = String.format("Wrong values for isTruncated=%b, isFullTrace=%b", isTruncated, isFullTrace); - Asserts.assertTrue(isTruncated != isFullTrace, msg); - } catch (Throwable t) { - System.out.println(String.format("stacktrace:%n%s", stacktrace)); - throw t; - } - } - - private static List getExpectedMethods(int depth) { - List methods = new ArrayList<>(); - methods.add("recurseEnd"); - for (int i = 0; i < depth - 2; ++i) { - methods.add((i % 2) == 0 ? "recurseA" : "recurseB"); - } - methods.add("run"); - if (depth > MAX_DEPTH) { - methods = methods.subList(0, MAX_DEPTH); - } - return methods; - } } diff --git a/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java new file mode 100644 index 00000000000..8dc77fd38da --- /dev/null +++ b/test/jdk/jdk/jfr/event/profiling/classes/test/RecursiveMethods.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 SAP SE. 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 test; + + import java.time.Duration; + + /* + * A class used to create a simple deep call stack for testing purposes + */ + public class RecursiveMethods { + + /** Method that uses recursion to produce a call stack of at least {@code depth} depth */ + public static int entry(int depth) { + return method2(--depth); + } + + private static int method2(int depth) { + return method3(--depth); + } + + private static int method3(int depth) { + return method4(--depth); + } + + private static int method4(int depth) { + return method5(--depth); + } + + private static int method5(int depth) { + return method6(--depth); + } + + private static int method6(int depth) { + return method7(--depth); + } + + private static int method7(int depth) { + return method8(--depth); + } + + private static int method8(int depth) { + return method9(--depth); + } + + private static int method9(int depth) { + return method10(--depth); + } + + private static int method10(int depth) { + if (depth > 0) { + return entry(--depth); + } + return depth; + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 904abe8e3e2..a00898358a8 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -77,6 +77,8 @@ public class EventNames { public static final String ThreadAllocationStatistics = PREFIX + "ThreadAllocationStatistics"; public static final String ExecutionSample = PREFIX + "ExecutionSample"; public static final String NativeMethodSample = PREFIX + "NativeMethodSample"; + public static final String CPUTimeSample = PREFIX + "CPUTimeSample"; + public static final String CPUTimeSamplesLost = PREFIX + "CPUTimeSamplesLost"; public static final String ThreadDump = PREFIX + "ThreadDump"; public static final String OldObjectSample = PREFIX + "OldObjectSample"; public static final String SymbolTableStatistics = PREFIX + "SymbolTableStatistics"; From dc949003ded278805d10c7b630e82348a7d998fe Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 5 Jun 2025 09:02:23 +0000 Subject: [PATCH 063/143] 8358588: ThreadSnapshot.ThreadLock should be static nested class Reviewed-by: alanb, sspitsyn, amenkov --- src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java index b5607059abc..e0dd4bbc508 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -175,7 +175,7 @@ class ThreadSnapshot { /** * Represents a locking operation of a thread at a specific stack depth. */ - private class ThreadLock { + private static class ThreadLock { private static final OwnedLockType[] lockTypeValues = OwnedLockType.values(); // cache // set by the VM From 66feb490bdf670c9b101f36b2fa1d0a923c0c3df Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Thu, 5 Jun 2025 09:30:44 +0000 Subject: [PATCH 064/143] 8358543: Remove CommentChecker.java and DirDiff.java Reviewed-by: erikj --- .../tools/commentchecker/CommentChecker.java | 217 -------------- .../src/build/tools/dirdiff/DirDiff.java | 275 ------------------ 2 files changed, 492 deletions(-) delete mode 100644 src/utils/src/build/tools/commentchecker/CommentChecker.java delete mode 100644 src/utils/src/build/tools/dirdiff/DirDiff.java diff --git a/src/utils/src/build/tools/commentchecker/CommentChecker.java b/src/utils/src/build/tools/commentchecker/CommentChecker.java deleted file mode 100644 index 5de89e00646..00000000000 --- a/src/utils/src/build/tools/commentchecker/CommentChecker.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 1998, 2013, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 build.tools.commentchecker; - -import java.io.*; -import java.util.StringTokenizer; - -/** - * CommentChecker is a utility which verifies that there aren't - * "/*" or "/**" tokens inside any comment blocks in one or more - * Java source files. Although it is legal to have beginning - * comment delimiters inside of a comment block (JLS 3.7), there - * have been errors where a dropped end-comment delimiter in a - * method'd doc-comment effectively "erased" that method. We're - * therefore restricting beginning comment delimiters inside of - * JDK source (at least the Swing team is for their portion). - * - * To scan a few files, run CommentChecker as follows: - * - * java CommentChecker file1.java file2.java ... - * - * There are too many Java files in the JDK base for most shells - * to support listing in a single command, so CommentChecker also - * supports cpio and tar-style filename passing, where "-" - * indicates that the list of files is read from stdin: - * - * find . -name SCCS -prune -o -name '*.java' -print | \ - * java CommentChecker - - * - * @author Thomas Ball - */ -public class CommentChecker { - - static int errors = 0; - - // Turn on this flag and recompile to dump this tool's state changes. - static final boolean verbose = false; - - static void check(String fileName) { - BufferedReader in = null; - boolean inComment = false; - boolean inLineComment = false; - boolean inQuote = false; - boolean inEscape = false; - int lastChar = -1; - int lineNumber = 1; - - try { - in = new BufferedReader(new FileReader(fileName)); - while (true) { - int ch = in.read(); - if (ch == -1) { - if (inQuote || inComment) { - error(fileName + ": premature EOF."); - } - return; - } - - if (verbose) { - System.out.print((char)ch); - } - - switch (ch) { - case '\n': - if (inQuote && !inComment) { - error(fileName + ":" + lineNumber + - " dangling quote."); - inQuote = false; - } - if (inLineComment) { - inLineComment = false; - if (verbose) { - System.out.println("\ninLineComment=false"); - } - } - lineNumber++; - break; - - case '\"': - if (!inComment && !inLineComment && !inEscape && - !(!inQuote && lastChar == '\'')) { - inQuote = !inQuote; - if (verbose) { - System.out.println("\ninQuote=" + inQuote); - } - } - break; - - case '/': - if (!inQuote && lastChar == '*') { - inComment = false; - if (verbose) { - System.out.println("\ninComment=false"); - } - } - if (!inQuote && lastChar == '/') { - inLineComment = true; - if (verbose) { - System.out.println("\ninLineComment=true"); - } - } - break; - - case '*': - if (!inQuote && lastChar == '/') { - if (inComment) { - error(fileName + ":" + lineNumber + - " nested comment."); - } - inComment = true; - if (verbose) { - System.out.println("\ninComment=true"); - } - } - break; - } - - lastChar = ch; - - // Watch for escaped characters, such as '\"'. - if (ch == '\\' && !inEscape) { - inEscape = true; - if (verbose) { - System.out.println("\ninEscape set"); - } - } else { - inEscape = false; - } - } - } catch (FileNotFoundException fnfe) { - error(fileName + " not found."); - } catch (IOException ioe) { - error(fileName + ": " + ioe); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - error(fileName + ": " + e); - } - } - } - } - - static void error(String description) { - System.err.println(description); - errors++; - } - - static void exit() { - if (errors != 1) { - System.out.println("There were " + errors + " errors."); - } else { - System.out.println("There was 1 error."); - } - System.exit(errors); - } - - public static void main(String[] args) { - if (args.length == 0) { - System.err.println("usage: java CommentChecker [-] file.java ..."); - System.exit(1); - } - - if (args.length == 1 && args[0].equals("-")) { - /* read filenames in one per line from stdin, ala cpio. - * This is good for checking the whole JDK in one pass: - * - * cpio . -name SCCS -prune -o -name '*.java' -print | \ - * java CommentChecker - - */ - try { - BufferedReader br = - new BufferedReader(new InputStreamReader(System.in)); - while (true) { - String fileName = br.readLine(); - if (fileName == null) { - break; - } - check(fileName); - } - br.close(); - } catch (Exception e) { - error("error reading System.in: " + e); - } - } else { - for (int i = 0; i < args.length; i++) { - check(args[i]); - } - } - - exit(); - } -} diff --git a/src/utils/src/build/tools/dirdiff/DirDiff.java b/src/utils/src/build/tools/dirdiff/DirDiff.java deleted file mode 100644 index 149f4eba3af..00000000000 --- a/src/utils/src/build/tools/dirdiff/DirDiff.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (c) 2002, 2013, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 build.tools.dirdiff; - -import java.io.File; -import java.util.TreeSet; - -public class DirDiff implements Runnable { - private static final String FILE_SEPARATOR = System.getProperty("file.separator"); - private static final boolean traversSccsDirs; - private static final boolean recurseExtraDirs; - private static final boolean verboseMode; - private static final boolean checkSizes; - private static long SizeTolerance = 0; - private File goldenDir = null; - private File testDir = null; - - // static initializer: - static { - String traversePropertyValue = System.getProperty("sccs"); - traversSccsDirs = (traversePropertyValue != null && - traversePropertyValue.toLowerCase().equals("true"))? true : false; - if (traversSccsDirs) { - System.err.println("traversing SCCS directories..."); - } - - String verbosePropertyValue = System.getProperty("verbose"); - verboseMode = (verbosePropertyValue != null && - verbosePropertyValue.toLowerCase().equals("true"))? true : false; - if (verboseMode) { - System.err.println("verbose mode truned on..."); - } - - String noRecurseExtraDirsPropertyValue = System.getProperty("recurse"); - recurseExtraDirs = (noRecurseExtraDirsPropertyValue != null && - noRecurseExtraDirsPropertyValue.toLowerCase().equals("true"))? true : false; - if (recurseExtraDirs) { - System.err.println("recursing extra directories..."); - } - - String sizeToleranceValue = System.getProperty("sizeTolerance"); - checkSizes = (sizeToleranceValue != null); - if (checkSizes) { - try { - SizeTolerance = Long.parseLong(sizeToleranceValue); - } - catch (NumberFormatException e) { - System.err.println("Invlalid sizeTolerance value: " + sizeToleranceValue); - System.err.println("Expecting a long value. Exiting."); - System.exit(1); - } - System.err.println("checking matching files for size differences of at least " + SizeTolerance); - } - } - - public DirDiff(File inGoldenDir, File inTestDir) { - goldenDir = inGoldenDir; - testDir = inTestDir; - } - - private void whatToDoWithMatchingFiles(File goldenChild, File testChild) { - if (verboseMode) { - System.out.println("Files Match:\t" + goldenChild.getAbsolutePath() + - " and " + testChild.getAbsolutePath()); - } - if (checkSizes) { - // compare file sizes... - long goldenLength = 0; - long testLength = 0; - try { - goldenLength = goldenChild.length(); - testLength = testChild.length(); - } - catch (Exception e) { - System.err.println("Error: exception thrown and caught:"); - e.printStackTrace(); - } - if (java.lang.Math.abs(goldenLength - testLength) > SizeTolerance) { - if (goldenLength > testLength) { - System.out.println("File short [" + (testLength - goldenLength) + "]:\t" + testChild.getAbsolutePath()); - } else { - System.out.println("File long [" + (testLength - goldenLength) + "]:\t" + testChild.getAbsolutePath()); - } - } - } - } - - - private void whatToDoWithMatchingDirs(File goldenChild, File testChild) { - if (verboseMode) { - System.out.println("Dirs Match:\t" + goldenChild.getAbsolutePath() + - " and " + testChild.getAbsolutePath()); - } - } - - private void whatToDoWithMissingFiles(File missingFile) { - long length = 0; - try { - length = missingFile.length(); - } - catch (Exception e) { - System.err.println("Error: exception thrown and caught:"); - e.printStackTrace(); - } - - System.out.println("Missing File [" + length + "]:\t" + missingFile.getAbsolutePath()); - } - - private void whatToDoWithExtraFiles(File extraFile) { - long length = 0; - try { - length = extraFile.length(); - } - catch (Exception e) { - System.err.println("Error: exception thrown and caught:"); - e.printStackTrace(); - } - - System.out.println("Extra File [" + length + "]:\t" + extraFile.getAbsolutePath()); - } - - private void whatToDoWithMissingDirs(File missingDir) { - System.out.println("Missing Dir:\t" + missingDir.getAbsolutePath()); - } - - private void whatToDoWithExtraDirs(File extraDir) { - System.out.println("Extra Dir:\t" + extraDir.getAbsolutePath()); - } - - private void whatToDoWithNonMatchingChildren(File goldenChild, File testChild) { - System.out.println("Type Mismatch:\t" + goldenChild.getAbsolutePath() + " is a " + - (goldenChild.isDirectory()? "directory" : "file") + - " and " + testChild.getAbsolutePath() + " is a " + - (testChild.isDirectory()? "directory" : "file")); - } - - public void run() { - File[] currentTestDirs = null; - if (testDir != null) { - currentTestDirs = testDir.listFiles(); - } - - File[] currentGoldenDirs = null; - TreeSet goldDirSet = new TreeSet<>(); - if (goldenDir != null) { - currentGoldenDirs = goldenDir.listFiles(); - for (int i=0; i Date: Thu, 5 Jun 2025 10:14:41 +0000 Subject: [PATCH 065/143] 8357962: JFR Cooperative Sampling reveals inconsistent interpreter frames as part of JVMTI PopFrame Reviewed-by: dholmes, eosterlund --- src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp | 1 + src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp | 1 + src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp index 0ed064f48ba..710970d1ea2 100644 --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -1893,6 +1893,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { Interpreter::_remove_activation_preserving_args_entry = __ pc(); __ empty_expression_stack(); + __ restore_bcp(); // We could have returned from deoptimizing this frame, so restore rbcp. // Set the popframe_processing bit in pending_popframe_condition // indicating that we are currently handling popframe, so that // call_VMs that may happen later do not trigger new popframe diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index 75469ea40e9..b8de3547c83 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -1646,6 +1646,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { Interpreter::_remove_activation_preserving_args_entry = __ pc(); __ empty_expression_stack(); + __ restore_bcp(); // We could have returned from deoptimizing this frame, so restore rbcp. // Set the popframe_processing bit in pending_popframe_condition // indicating that we are currently handling popframe, so that // call_VMs that may happen later do not trigger new popframe diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp index d4691c9874c..bd061d45fbd 100644 --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp @@ -1441,6 +1441,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { Interpreter::_remove_activation_preserving_args_entry = __ pc(); __ empty_expression_stack(); + __ restore_bcp(); // We could have returned from deoptimizing this frame, so restore rbcp. // Set the popframe_processing bit in pending_popframe_condition // indicating that we are currently handling popframe, so that // call_VMs that may happen later do not trigger new popframe From bd08932d5b9d1a363d8229ea72df4c6dbfd4571d Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Thu, 5 Jun 2025 10:31:23 +0000 Subject: [PATCH 066/143] 8356633: Incorrect use of {@link} in jdk.jshell Reviewed-by: rgiulietti, vyazici --- src/jdk.jshell/share/classes/jdk/jshell/Snippet.java | 4 ++-- .../share/classes/jdk/jshell/SourceCodeAnalysis.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java b/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java index 8e5971bedf8..cfbd4fe4ef5 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, 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 @@ -524,7 +524,7 @@ public abstract class Snippet { /** * The snippet is inactive because it does not yet exist. - * Used only in {@link SnippetEvent#previousStatus} for new + * Used only in {@link SnippetEvent#previousStatus()} for new * snippets. * {@link jdk.jshell.JShell#status(jdk.jshell.Snippet) JShell.status(Snippet)} * will never return this {@code Status}. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 0375a2ead65..10da664cd03 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -150,7 +150,7 @@ public abstract class SourceCodeAnalysis { * They will not appear in queries for snippets -- * for example, {@link JShell#snippets() }. *

- * Restrictions on the input are as in {@link JShell#eval}. + * Restrictions on the input are as in {@link JShell#eval(String)}. *

* Only preliminary compilation is performed, sufficient to build the * {@code Snippet}. Snippets known to be erroneous, are returned as From c5daf890534dfdbe5f66189ef6b08af8ffd4de47 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Thu, 5 Jun 2025 11:05:52 +0000 Subject: [PATCH 067/143] 8349369: test/docs/jdk/javadoc/doccheck/checks/jdkCheckLinks.java did not report on missing man page files Reviewed-by: hannesw --- .../doccheck/checks/jdkCheckLinks.java | 4 +-- .../doccheck/doccheckutils/FileProcessor.java | 8 ++--- .../doccheckutils/checkers/LinkChecker.java | 30 ++++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/test/docs/jdk/javadoc/doccheck/checks/jdkCheckLinks.java b/test/docs/jdk/javadoc/doccheck/checks/jdkCheckLinks.java index bd818923d9f..3db1cdf6cb9 100644 --- a/test/docs/jdk/javadoc/doccheck/checks/jdkCheckLinks.java +++ b/test/docs/jdk/javadoc/doccheck/checks/jdkCheckLinks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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,7 +23,7 @@ /* * @test - * @bug 8337109 + * @bug 8337109 8349369 * @summary Check Links in the generated documentation * @library /test/langtools/tools/lib ../../doccheck /test/lib ../../../../tools/tester * @build DocTester toolbox.TestRunner diff --git a/test/docs/jdk/javadoc/doccheck/doccheckutils/FileProcessor.java b/test/docs/jdk/javadoc/doccheck/doccheckutils/FileProcessor.java index 6ae3a473bdb..2291d649c3a 100644 --- a/test/docs/jdk/javadoc/doccheck/doccheckutils/FileProcessor.java +++ b/test/docs/jdk/javadoc/doccheck/doccheckutils/FileProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -44,16 +44,16 @@ public class FileProcessor { public void processFiles(Path directory) { try { - Files.walkFileTree(directory, new SimpleFileVisitor() { + Files.walkFileTree(directory, new SimpleFileVisitor<>() { @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.toString().endsWith(".html")) files.add(file); return FileVisitResult.CONTINUE; } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { return FileVisitResult.CONTINUE; } }); diff --git a/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/LinkChecker.java b/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/LinkChecker.java index 62ead5a25d3..ed0098d2714 100644 --- a/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/LinkChecker.java +++ b/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/LinkChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -220,20 +220,14 @@ public class LinkChecker implements HtmlChecker { @Override public boolean isOK() { - return duplicateIds == 0 - && missingIds == 0 - && missingFiles == 0 - && badSchemes == 0; + return log.noErrors() && (missingFiles == 0); } @Override public void close() { - report(); - if (!isOK()) { - throw new RuntimeException( - "LinkChecker encountered errors. Duplicate IDs: " - + duplicateIds + ", Missing IDs: " + missingIds - + ", Missing Files: " + missingFiles + ", Bad Schemes: " + badSchemes); + if (!log.noErrors()) { + report(); + throw new RuntimeException("LinkChecker encountered errors; see log above."); } } @@ -276,6 +270,11 @@ public class LinkChecker implements HtmlChecker { p = currFile.getParent().resolve(resolvedUriPath).normalize(); } + if (!Files.exists(p)) { + log.log(currFile, line, "missing file reference: " + log.relativize(p)); + return; + } + if (fragment != null && !fragment.isEmpty()) { foundReference(line, p, fragment); } @@ -392,7 +391,7 @@ public class LinkChecker implements HtmlChecker { void addID(int line, String name) { if (checked) { - throw new IllegalStateException("Adding ID after file has been"); + throw new IllegalStateException("Adding ID after file has been checked"); } Objects.requireNonNull(name); IDInfo info = map.computeIfAbsent(name, _ -> new IDInfo()); @@ -413,7 +412,9 @@ public class LinkChecker implements HtmlChecker { if (name != null) { IDInfo id = map.get(name); if (id == null || !id.declared) { - log.log(log.relativize(from), line, "id not found: " + this.pathOrURI + "#" + name); + log.log(log.relativize(from), line, + "id not found: " + this.pathOrURI + "#" + name); + LinkChecker.this.missingIds++; } } } else { @@ -429,7 +430,8 @@ public class LinkChecker implements HtmlChecker { map.forEach((name, id) -> { if (name != null && !id.declared) { for (Position ref : id.references) { - log.log(log.relativize(ref.path), ref.line, "id not found: " + this.pathOrURI + "#" + name); + log.log(log.relativize(ref.path), ref.line, + "id not found: " + this.pathOrURI + "#" + name); } missingIds++; } From eb770a060ad86d69b38df7d11622e9e25a528e1d Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 5 Jun 2025 11:36:08 +0000 Subject: [PATCH 068/143] 8351594: JFR: Rate-limited sampling of Java events Reviewed-by: mgronlun, alanb --- .../classes/java/io/FileInputStream.java | 25 +- .../classes/java/io/FileOutputStream.java | 18 +- .../classes/java/io/RandomAccessFile.java | 41 +-- .../share/classes/java/lang/Throwable.java | 4 +- .../share/classes/java/net/Socket.java | 10 +- .../internal/event/ExceptionThrownEvent.java | 7 +- .../jdk/internal/event/FileReadEvent.java | 26 +- .../jdk/internal/event/FileWriteEvent.java | 25 +- .../jdk/internal/event/SocketReadEvent.java | 10 +- .../jdk/internal/event/SocketWriteEvent.java | 14 +- .../jdk/internal/event/ThrowableTracer.java | 22 +- .../classes/sun/nio/ch/FileChannelImpl.java | 65 +--- .../jdk/jfr/{internal => }/Throttle.java | 52 ++-- .../jdk/jfr/events/ExceptionThrownEvent.java | 4 +- .../classes/jdk/jfr/events/FileReadEvent.java | 4 +- .../jdk/jfr/events/FileWriteEvent.java | 4 +- .../jdk/jfr/events/SocketReadEvent.java | 4 +- .../jdk/jfr/events/SocketWriteEvent.java | 4 +- .../jdk/jfr/internal/ClassInspector.java | 16 + .../jdk/jfr/internal/EventControl.java | 3 + .../jfr/internal/EventInstrumentation.java | 107 ++++++- .../classes/jdk/jfr/internal/JVMSupport.java | 2 +- .../jdk/jfr/internal/MetadataLoader.java | 5 +- .../jdk/jfr/internal/MetadataRepository.java | 6 +- .../jdk/jfr/internal/PlatformEventType.java | 16 +- .../internal/event/EventConfiguration.java | 29 ++ .../internal/settings/ThrottleSetting.java | 3 +- .../jdk/jfr/internal/settings/Throttler.java | 233 ++++++++++++++ .../settings/ThrottlerParameters.java | 94 ++++++ .../internal/settings/ThrottlerWindow.java | 99 ++++++ .../share/classes/jdk/jfr/package-info.java | 24 +- src/jdk.jfr/share/conf/jfr/default.jfc | 36 ++- src/jdk.jfr/share/conf/jfr/profile.jfc | 38 ++- .../metadata/annotations/TestThrottle.java | 290 ++++++++++++++++++ .../settings/TestSettingsAvailability.java | 14 +- .../jfr/startupargs/TestEventSettings.java | 4 +- 36 files changed, 1120 insertions(+), 238 deletions(-) rename src/jdk.jfr/share/classes/jdk/jfr/{internal => }/Throttle.java (58%) create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java create mode 100644 test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java diff --git a/src/java.base/share/classes/java/io/FileInputStream.java b/src/java.base/share/classes/java/io/FileInputStream.java index ab312fc8c5b..b5b4813cbb5 100644 --- a/src/java.base/share/classes/java/io/FileInputStream.java +++ b/src/java.base/share/classes/java/io/FileInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -205,22 +205,17 @@ public class FileInputStream extends InputStream private int traceRead0() throws IOException { int result = 0; - boolean endOfFile = false; long bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); result = read0(); if (result < 0) { - endOfFile = true; + bytesRead = -1; } else { bytesRead = 1; } } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - FileReadEvent.commit(start, duration, path, bytesRead, endOfFile); - } + FileReadEvent.offer(start, path, bytesRead); } return result; } @@ -236,19 +231,11 @@ public class FileInputStream extends InputStream private int traceReadBytes(byte b[], int off, int len) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = readBytes(b, off, len); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } diff --git a/src/java.base/share/classes/java/io/FileOutputStream.java b/src/java.base/share/classes/java/io/FileOutputStream.java index 6c5a30ea432..022aa44397a 100644 --- a/src/java.base/share/classes/java/io/FileOutputStream.java +++ b/src/java.base/share/classes/java/io/FileOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -266,16 +266,12 @@ public class FileOutputStream extends OutputStream private void traceWrite(int b, boolean append) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); write(b, append); bytesWritten = 1; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } @@ -310,16 +306,12 @@ public class FileOutputStream extends OutputStream private void traceWriteBytes(byte b[], int off, int len, boolean append) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); writeBytes(b, off, len, append); bytesWritten = len; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } diff --git a/src/java.base/share/classes/java/io/RandomAccessFile.java b/src/java.base/share/classes/java/io/RandomAccessFile.java index c09f87afcdc..339030e022c 100644 --- a/src/java.base/share/classes/java/io/RandomAccessFile.java +++ b/src/java.base/share/classes/java/io/RandomAccessFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -367,21 +367,16 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private int traceRead0() throws IOException { int result = 0; long bytesRead = 0; - boolean endOfFile = false; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); result = read0(); if (result < 0) { - endOfFile = true; + bytesRead = -1; } else { bytesRead = 1; } } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - FileReadEvent.commit(start, duration, path, bytesRead, endOfFile); - } + FileReadEvent.offer(start, path, bytesRead); } return result; } @@ -404,19 +399,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private int traceReadBytes0(byte b[], int off, int len) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = readBytes0(b, off, len); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -582,16 +569,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private void traceImplWrite(int b) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); implWrite(b); bytesWritten = 1; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } @@ -624,16 +607,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { private void traceImplWriteBytes(byte b[], int off, int len) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); implWriteBytes(b, off, len); bytesWritten = len; } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - FileWriteEvent.commit(start, duration, path, bytesWritten); - } + FileWriteEvent.offer(start, path, bytesWritten); } } diff --git a/src/java.base/share/classes/java/lang/Throwable.java b/src/java.base/share/classes/java/lang/Throwable.java index 8c0ce29dbee..ac3325fc514 100644 --- a/src/java.base/share/classes/java/lang/Throwable.java +++ b/src/java.base/share/classes/java/lang/Throwable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -121,7 +121,7 @@ public class Throwable implements Serializable { * Flag set by jdk.internal.event.JFRTracing to indicate if * exceptions should be traced by JFR. */ - static volatile boolean jfrTracing; + static boolean jfrTracing; /** * The JVM saves some indication of the stack backtrace in this slot. diff --git a/src/java.base/share/classes/java/net/Socket.java b/src/java.base/share/classes/java/net/Socket.java index d2afa166a88..2905a51b402 100644 --- a/src/java.base/share/classes/java/net/Socket.java +++ b/src/java.base/share/classes/java/net/Socket.java @@ -965,10 +965,7 @@ public class Socket implements java.io.Closeable { } long start = SocketReadEvent.timestamp(); int nbytes = implRead(b, off, len); - long duration = SocketReadEvent.timestamp() - start; - if (SocketReadEvent.shouldCommit(duration)) { - SocketReadEvent.emit(start, duration, nbytes, parent.getRemoteSocketAddress(), getSoTimeout()); - } + SocketReadEvent.offer(start, nbytes, parent.getRemoteSocketAddress(), getSoTimeout()); return nbytes; } @@ -1081,10 +1078,7 @@ public class Socket implements java.io.Closeable { } long start = SocketWriteEvent.timestamp(); implWrite(b, off, len); - long duration = SocketWriteEvent.timestamp() - start; - if (SocketWriteEvent.shouldCommit(duration)) { - SocketWriteEvent.emit(start, duration, len, parent.getRemoteSocketAddress()); - } + SocketWriteEvent.offer(start, len, parent.getRemoteSocketAddress()); } private void implWrite(byte[] b, int off, int len) throws IOException { diff --git a/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java b/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java index 45c13e3010d..355ee81c0fc 100644 --- a/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/ExceptionThrownEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -31,6 +31,11 @@ public final class ExceptionThrownEvent extends Event { public String message; public Class thrownClass; + public static boolean shouldThrottleCommit(long timestamp) { + // Generated by JFR + return false; + } + public static void commit(long start, String message, Class thrownClass) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java b/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java index 34ff4e9d4ed..77b2568802e 100644 --- a/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/FileReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -45,11 +45,33 @@ public final class FileReadEvent extends Event { return 0L; } - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration meets + * or exceeds the configured value and is not throttled (determined by calling the + * generated method {@link #shouldThrottleCommit(long, long)}), an event will be + * emitted by calling {@link #commit(long, long, String, long, boolean)} + * + * @param start the start time + * @param path the path + * @param bytesRead the number of bytes that were read, or -1 if the end of the file was reached + */ + public static void offer(long start, String path, long bytesRead) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { + boolean endOfFile = bytesRead < 0; + long bytes = endOfFile ? 0 : bytesRead; + commit(start, duration, path, bytes, endOfFile); + } + } + public static void commit(long start, long duration, String path, long bytesRead, boolean endOfFile) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java b/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java index f6c27960430..3fa7dbe9822 100644 --- a/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/FileWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -44,11 +44,32 @@ public final class FileWriteEvent extends Event { return 0L; } - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration meets + * or exceeds the configured value and is not throttled (determined by calling the + * generated method {@link #shouldThrottleCommit(long, long)}), an event will be + * emitted by calling {@link #commit(long, long, String, long)} + * + * @param start the start time + * @param path the path + * @param bytesRead the number of bytes that were written, or -1 if the end of the file was reached + */ + public static void offer(long start, String path, long bytesWritten) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { + long bytes = bytesWritten > 0 ? bytesWritten : 0; + commit(start, duration, path, bytes); + } + } + public static void commit(long start, long duration, String path, long bytesWritten) { // Generated by JFR } diff --git a/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java index d5f6c3241d9..09b3f23b07f 100644 --- a/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/SocketReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -74,9 +74,10 @@ public class SocketReadEvent extends Event { * of this method is generated automatically if jfr is enabled. * * @param duration time in nanoseconds to complete the operation + * @param end timestamp at the end of the operation * @return true if the event should be commited */ - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } @@ -118,8 +119,9 @@ public class SocketReadEvent extends Event { * @param timeout maximum time to wait */ public static void offer(long start, long nbytes, SocketAddress remote, long timeout) { - long duration = timestamp() - start; - if (shouldCommit(duration)) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { emit(start, duration, nbytes, remote, timeout); } } diff --git a/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java b/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java index 7c56ef826a5..12d8ffbf65b 100644 --- a/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/SocketWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -68,10 +68,11 @@ public class SocketWriteEvent extends Event { * must exceed some threshold in order to commit the event. The implementation * of this method is generated automatically if jfr is enabled. * - * @param duration time in nanoseconds to complete the operation + * @param duration time to complete the operation + * @param end timestamp at the end of the operation * @return true if the event should be commited */ - public static boolean shouldCommit(long duration) { + public static boolean shouldThrottleCommit(long duration, long end) { // Generated by JFR return false; } @@ -104,7 +105,7 @@ public class SocketWriteEvent extends Event { * The duration of the operation is computed using the current * timestamp and the given start time. If the duration is meets * or exceeds the configured value (determined by calling the generated method - * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #shouldThrottleCommit(long)}), an event will be emitted by calling * {@link #emit(long, long, long, SocketAddress)}. * * @param start the start time @@ -112,8 +113,9 @@ public class SocketWriteEvent extends Event { * @param remote the address of the remote socket being written to */ public static void offer(long start, long bytesWritten, SocketAddress remote) { - long duration = timestamp() - start; - if (shouldCommit(duration)) { + long end = timestamp(); + long duration = end - start; + if (shouldThrottleCommit(duration, end)) { emit(start, duration, bytesWritten, remote); } } diff --git a/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java b/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java index f7076b44e90..6502cbc8002 100644 --- a/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java +++ b/src/java.base/share/classes/jdk/internal/event/ThrowableTracer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -37,22 +37,24 @@ public final class ThrowableTracer { if (OutOfMemoryError.class.isAssignableFrom(clazz)) { return; } - - if (ErrorThrownEvent.enabled()) { + if (ErrorThrownEvent.enabled() || ExceptionThrownEvent.enabled()) { long timestamp = ErrorThrownEvent.timestamp(); - ErrorThrownEvent.commit(timestamp, message, clazz); - } - if (ExceptionThrownEvent.enabled()) { - long timestamp = ExceptionThrownEvent.timestamp(); - ExceptionThrownEvent.commit(timestamp, message, clazz); + if (ErrorThrownEvent.enabled()) { + ErrorThrownEvent.commit(timestamp, message, clazz); + } + if (ExceptionThrownEvent.shouldThrottleCommit(timestamp)) { + ExceptionThrownEvent.commit(timestamp, message, clazz); + } } numThrowables.incrementAndGet(); } public static void traceThrowable(Class clazz, String message) { if (ExceptionThrownEvent.enabled()) { - long timestamp = ExceptionThrownEvent.timestamp(); - ExceptionThrownEvent.commit(timestamp, message, clazz); + long timestamp = ErrorThrownEvent.timestamp(); + if (ExceptionThrownEvent.shouldThrottleCommit(timestamp)) { + ExceptionThrownEvent.commit(timestamp, message, clazz); + } } numThrowables.incrementAndGet(); } diff --git a/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java index 72de217a83c..240405c2f7c 100644 --- a/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -264,19 +264,11 @@ public class FileChannelImpl private int traceImplRead(ByteBuffer dst) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dst); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -326,19 +318,11 @@ public class FileChannelImpl private long traceImplRead(ByteBuffer[] dsts, int offset, int length) throws IOException { long bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dsts, offset, length); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -385,16 +369,11 @@ public class FileChannelImpl private int traceImplWrite(ByteBuffer src) throws IOException { int bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(src); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } @@ -441,16 +420,11 @@ public class FileChannelImpl private long traceImplWrite(ByteBuffer[] srcs, int offset, int length) throws IOException { long bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(srcs, offset, length); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } @@ -1199,19 +1173,11 @@ public class FileChannelImpl private int traceImplRead(ByteBuffer dst, long position) throws IOException { int bytesRead = 0; - long start = 0; + long start = FileReadEvent.timestamp(); try { - start = FileReadEvent.timestamp(); bytesRead = implRead(dst, position); } finally { - long duration = FileReadEvent.timestamp() - start; - if (FileReadEvent.shouldCommit(duration)) { - if (bytesRead < 0) { - FileReadEvent.commit(start, duration, path, 0L, true); - } else { - FileReadEvent.commit(start, duration, path, bytesRead, false); - } - } + FileReadEvent.offer(start, path, bytesRead); } return bytesRead; } @@ -1271,16 +1237,11 @@ public class FileChannelImpl private int traceImplWrite(ByteBuffer src, long position) throws IOException { int bytesWritten = 0; - long start = 0; + long start = FileWriteEvent.timestamp(); try { - start = FileWriteEvent.timestamp(); bytesWritten = implWrite(src, position); } finally { - long duration = FileWriteEvent.timestamp() - start; - if (FileWriteEvent.shouldCommit(duration)) { - long bytes = bytesWritten > 0 ? bytesWritten : 0; - FileWriteEvent.commit(start, duration, path, bytes); - } + FileWriteEvent.offer(start, path, bytesWritten); } return bytesWritten; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java b/src/jdk.jfr/share/classes/jdk/jfr/Throttle.java similarity index 58% rename from src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java rename to src/jdk.jfr/share/classes/jdk/jfr/Throttle.java index 39409d008ab..7b9771eba2b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/Throttle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Datadog, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -24,7 +24,7 @@ * questions. */ -package jdk.jfr.internal; +package jdk.jfr; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -32,14 +32,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import jdk.jfr.MetadataDefinition; - /** - * Event annotation, determines the event emission rate in events per time unit. + * Event annotation, specifies the maximum rate of events per time unit, (for + * example, {@code "100/s"}). + *

+ * If the event class annotated with {@code Throttle} is filtered by other + * settings, such as a {@link jdk.jfr.Threshold} or a user-defined setting, the + * throttling will happen after those settings have been applied. * - * This setting is only supported for JVM events. - * - * @since 16 + * @since 25 */ @MetadataDefinition @Target({ ElementType.TYPE }) @@ -47,30 +48,33 @@ import jdk.jfr.MetadataDefinition; @Retention(RetentionPolicy.RUNTIME) public @interface Throttle { /** - * Settings name {@code "throttle"} for configuring an event emission rate in events per time unit. + * Setting name {@code "throttle"} for configuring throttled events. */ public static final String NAME = "throttle"; - public static final String DEFAULT = "off"; /** - * Throttle, for example {@code "100/s"}. + * The throttle rate, for example {@code "100/s"}. *

- * String representation of a non-negative {@code Long} value followed by a slash ("/") - * and one of the following units
- * {@code "ns"} (nanoseconds)
- * {@code "us"} (microseconds)
- * {@code "ms"} (milliseconds)
- * {@code "s"} (seconds)
- * {@code "m"} (minutes)
- * {@code "h"} (hours)
- * {@code "d"} (days)
+ * String representation of a non-negative {@code long} value followed by a + * forward slash ("/") and one of the following units:
+ *

    + *
  • {@code "ns"} (nanoseconds)
  • + *
  • {@code "us"} (microseconds)
  • + *
  • {@code "ms"} (milliseconds)
  • + *
  • {@code "s"} (seconds)
  • + *
  • {@code "m"} (minutes)
  • + *
  • {@code "h"} (hours)
  • + *
  • {@code "d"} (days)
  • + *
*

* Example values, {@code "6000/m"}, {@code "10/ms"} and {@code "200/s"}. - * When zero is specified, for example {@code "0/s"}, no events are emitted. - * When {@code "off"} is specified, all events are emitted. + *

+ * Specifying zero, for example {@code "0/s"}, results in no events being + * emitted. + *

+ * Specifying {@code "off"} (case-sensitive) results in all events being emitted. * * @return the throttle value, default {@code "off"} not {@code null} - * */ - String value() default DEFAULT; + String value() default "off"; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java index 22c87f3132d..41945aaf66c 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ExceptionThrownEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -29,6 +29,7 @@ import jdk.jfr.Category; import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.RemoveFields; import jdk.jfr.internal.Type; @@ -38,6 +39,7 @@ import jdk.jfr.internal.Type; @Category("Java Application") @Description("An object derived from java.lang.Exception has been created") @RemoveFields("duration") +@Throttle public final class ExceptionThrownEvent extends MirrorEvent { @Label("Message") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java index 84886d2493a..bd9926c08d3 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.MirrorEvent; @@ -38,6 +39,7 @@ import jdk.jfr.internal.MirrorEvent; @Category("Java Application") @Description("Reading data from a file") @StackFilter({"java.io.FileInputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) +@Throttle public final class FileReadEvent extends MirrorEvent { @Label("Path") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java index 990f1845168..e7861eef1b6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FileWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.MirrorEvent; @@ -38,6 +39,7 @@ import jdk.jfr.internal.MirrorEvent; @Category("Java Application") @Description("Writing data to a file") @StackFilter({"java.io.FileOutputStream", "java.io.RandomAccessFile", "sun.nio.ch.FileChannelImpl"}) +@Throttle public final class FileWriteEvent extends MirrorEvent { @Label("Path") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java index 917eabd9206..b2cdee4f8dc 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketReadEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -31,6 +31,7 @@ import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; import jdk.jfr.Timespan; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.Type; @@ -38,6 +39,7 @@ import jdk.jfr.internal.Type; @Label("Socket Read") @Category("Java Application") @Description("Reading data from a socket") +@Throttle public final class SocketReadEvent extends MirrorEvent { @Label("Remote Host") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java index 92c85c132d4..661a4d1a68e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/SocketWriteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -30,6 +30,7 @@ import jdk.jfr.Description; import jdk.jfr.Label; import jdk.jfr.DataAmount; import jdk.jfr.Name; +import jdk.jfr.Throttle; import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.Type; @@ -37,6 +38,7 @@ import jdk.jfr.internal.Type; @Label("Socket Write") @Category("Java Application") @Description("Writing data to a socket") +@Throttle public final class SocketWriteEvent extends MirrorEvent { @Label("Remote Host") diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java index d310c505da6..3646162e8f7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java @@ -51,6 +51,7 @@ import jdk.jfr.Name; import jdk.jfr.Registered; import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; +import jdk.jfr.Throttle; import jdk.jfr.internal.util.Bytecode; import jdk.jfr.internal.util.ImplicitFields; import jdk.jfr.internal.util.Bytecode.FieldDesc; @@ -64,6 +65,7 @@ final class ClassInspector { private static final ClassDesc ANNOTATION_NAME = classDesc(Name.class); private static final ClassDesc ANNOTATION_ENABLED = classDesc(Enabled.class); private static final ClassDesc ANNOTATION_REMOVE_FIELDS = classDesc(RemoveFields.class); + private static final ClassDesc ANNOTATION_THROTTLE = classDesc(Throttle.class); private static final String[] EMPTY_STRING_ARRAY = {}; private final ClassModel classModel; @@ -138,6 +140,20 @@ final class ClassInspector { return true; } + boolean isThrottled() { + String result = annotationValue(ANNOTATION_THROTTLE, String.class, "off"); + if (result != null) { + return true; + } + if (superClass != null) { + Throttle t = superClass.getAnnotation(Throttle.class); + if (t != null) { + return true; + } + } + return false; + } + boolean hasStaticMethod(MethodDesc method) { for (MethodModel m : classModel.methods()) { if (Modifier.isStatic(m.flags().flagsMask())) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index 2ea4725abc8..02775b7707a 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -44,6 +44,7 @@ import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; import jdk.jfr.StackTrace; import jdk.jfr.Threshold; +import jdk.jfr.Throttle; import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.events.StackFilter; import jdk.jfr.internal.settings.CutoffSetting; @@ -55,6 +56,7 @@ import jdk.jfr.internal.settings.CPUThrottleSetting; import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThrottleSetting; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.tracing.Modification; import jdk.jfr.internal.util.Utils; @@ -95,6 +97,7 @@ public final class EventControl { } if (eventType.hasThrottle()) { addControl(Throttle.NAME, defineThrottle(eventType)); + eventType.setThrottler(new Throttler(eventType)); } if (eventType.hasLevel()) { addControl(Level.NAME, defineLevel(eventType)); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java index 96e6f36e5c8..7f0ed76586a 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -60,7 +60,14 @@ import jdk.jfr.internal.util.ImplicitFields; * Class responsible for adding instrumentation to a subclass of {@link Event}. * */ -final class EventInstrumentation { +public final class EventInstrumentation { + public static final long MASK_THROTTLE = 1 << 62; + public static final long MASK_THROTTLE_CHECK = 1 << 63; + public static final long MASK_THROTTLE_BITS = MASK_THROTTLE | MASK_THROTTLE_CHECK; + public static final long MASK_THROTTLE_CHECK_SUCCESS = MASK_THROTTLE_CHECK | MASK_THROTTLE; + public static final long MASK_THROTTLE_CHECK_FAIL = MASK_THROTTLE_CHECK | 0; + public static final long MASK_NON_THROTTLE_BITS = ~MASK_THROTTLE_BITS; + private static final FieldDesc FIELD_EVENT_CONFIGURATION = FieldDesc.of(Object.class, "eventConfiguration"); private static final ClassDesc TYPE_EVENT_CONFIGURATION = classDesc(EventConfiguration.class); @@ -71,11 +78,17 @@ final class EventInstrumentation { private static final MethodDesc METHOD_BEGIN = MethodDesc.of("begin", "()V"); private static final MethodDesc METHOD_COMMIT = MethodDesc.of("commit", "()V"); private static final MethodDesc METHOD_DURATION = MethodDesc.of("duration", "(J)J"); + private static final MethodDesc METHOD_THROTTLE = MethodDesc.of("throttle", "(JJ)J"); private static final MethodDesc METHOD_ENABLED = MethodDesc.of("enabled", "()Z"); private static final MethodDesc METHOD_END = MethodDesc.of("end", "()V"); - private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG = MethodDesc.of("shouldCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG_LONG = MethodDesc.of("shouldThrottleCommit", "(JJ)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG = MethodDesc.of("shouldThrottleCommit", "(J)Z"); + private static final MethodDesc METHOD_EVENT_CONFIGURATION_GET_SETTING = MethodDesc.of("getSetting", SettingControl.class, int.class); private static final MethodDesc METHOD_EVENT_SHOULD_COMMIT = MethodDesc.of("shouldCommit", "()Z"); + private static final MethodDesc METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG = MethodDesc.of("shouldThrottleCommit", "(JJ)Z"); + private static final MethodDesc METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG = MethodDesc.of("shouldThrottleCommit", "(J)Z"); private static final MethodDesc METHOD_GET_EVENT_WRITER = MethodDesc.of("getEventWriter", "()" + TYPE_EVENT_WRITER.descriptorString()); private static final MethodDesc METHOD_IS_ENABLED = MethodDesc.of("isEnabled", "()Z"); private static final MethodDesc METHOD_RESET = MethodDesc.of("reset", "()V"); @@ -88,6 +101,7 @@ final class EventInstrumentation { private final MethodDesc staticCommitMethod; private final boolean untypedEventConfiguration; private final boolean guardEventConfiguration; + private final boolean throttled; /** * Creates an EventInstrumentation object. @@ -110,6 +124,11 @@ final class EventInstrumentation { this.eventClassDesc = inspector.getClassDesc(); this.staticCommitMethod = inspector.findStaticCommitMethod(); this.untypedEventConfiguration = hasUntypedConfiguration(); + if (inspector.isJDK()) { + this.throttled = inspector.hasStaticMethod(METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG); + } else { + this.throttled = inspector.isThrottled(); + } } byte[] buildInstrumented() { @@ -147,6 +166,12 @@ final class EventInstrumentation { if (isMethod(method, METHOD_SHOULD_COMMIT_LONG)) { return this::methodShouldCommitStatic; } + if (isMethod(method, METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG)) { + return this::methodShouldCommitThrottleStaticLongLong; + } + if (isMethod(method, METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG)) { + return this::methodShouldCommitThrottleStaticLong; + } if (isMethod(method, METHOD_TIME_STAMP)) { return this::methodTimestamp; } @@ -188,11 +213,11 @@ final class EventInstrumentation { if (!inspector.hasDuration()) { throwMissingDuration(codeBuilder, "end"); } else { - codeBuilder.aload(0); - codeBuilder.aload(0); - getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); - invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION); - putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + setDuration(codeBuilder, cb -> { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); + invokestatic(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_DURATION); + }); codeBuilder.return_(); } } @@ -205,9 +230,8 @@ final class EventInstrumentation { } // if (!eventConfiguration.shouldCommit(duration) goto fail; getEventConfiguration(codeBuilder); - codeBuilder.aload(0); - getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); - invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT); + getDuration(codeBuilder); + invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG); codeBuilder.ifeq(fail); List settingDescs = inspector.getSettings(); for (int index = 0; index < settingDescs.size(); index++) { @@ -222,6 +246,30 @@ final class EventInstrumentation { codeBuilder.invokevirtual(eventClassDesc, sd.methodName(), mdesc); codeBuilder.ifeq(fail); } + if (throttled) { + // long d = eventConfiguration.throttle(this.duration); + // this.duration = d; + // if (d & MASK_THROTTLE_BIT == 0) { + // goto fail; + // } + getEventConfiguration(codeBuilder); + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_START_TIME); + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + Bytecode.invokevirtual(codeBuilder, TYPE_EVENT_CONFIGURATION, METHOD_THROTTLE); + int result = codeBuilder.allocateLocal(TypeKind.LONG); + codeBuilder.lstore(result); + codeBuilder.aload(0); + codeBuilder.lload(result); + putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + codeBuilder.lload(result); + codeBuilder.ldc(MASK_THROTTLE); + codeBuilder.land(); + codeBuilder.lconst_0(); + codeBuilder.lcmp(); + codeBuilder.ifeq(fail); + } // return true codeBuilder.iconst_1(); codeBuilder.ireturn(); @@ -294,6 +342,17 @@ final class EventInstrumentation { } private void methodShouldCommitStatic(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT_LONG); + } + + private void methodShouldCommitThrottleStaticLongLong(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG_LONG); + } + private void methodShouldCommitThrottleStaticLong(CodeBuilder codeBuilder) { + methodShouldCommitStatic(codeBuilder, METHOD_EVENT_CONFIGURATION_SHOULD_THROTTLE_COMMIT_LONG); + } + + private void methodShouldCommitStatic(CodeBuilder codeBuilder, MethodDesc method) { Label fail = codeBuilder.newLabel(); if (guardEventConfiguration) { // if (eventConfiguration == null) goto fail; @@ -302,8 +361,10 @@ final class EventInstrumentation { } // return eventConfiguration.shouldCommit(duration); getEventConfiguration(codeBuilder); - codeBuilder.lload(0); - codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.name(), METHOD_EVENT_CONFIGURATION_SHOULD_COMMIT.descriptor()); + for (int i = 0 ; i < method.descriptor().parameterCount(); i++) { + codeBuilder.lload(2 * i); + } + codeBuilder.invokevirtual(TYPE_EVENT_CONFIGURATION, method.name(), method.descriptor()); codeBuilder.ireturn(); // fail: codeBuilder.labelBinding(fail); @@ -515,6 +576,28 @@ final class EventInstrumentation { return desc.matches(m); } + private void getDuration(CodeBuilder codeBuilder) { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + if (throttled) { + codeBuilder.loadConstant(MASK_NON_THROTTLE_BITS); + codeBuilder.land(); + } + } + + private void setDuration(CodeBuilder codeBuilder, Consumer expression) { + codeBuilder.aload(0); + expression.accept(codeBuilder); + if (throttled) { + codeBuilder.aload(0); + getfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + codeBuilder.loadConstant(MASK_THROTTLE_BITS); + codeBuilder.land(); + codeBuilder.lor(); + } + putfield(codeBuilder, eventClassDesc, ImplicitFields.FIELD_DURATION); + } + private static void getEventWriter(CodeBuilder codeBuilder) { invokestatic(codeBuilder, TYPE_EVENT_WRITER, METHOD_GET_EVENT_WRITER); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java index ded1f78b76e..8314437ce72 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java @@ -98,7 +98,7 @@ public final class JVMSupport { public static void tryToInitializeJVM() { } - static long nanosToTicks(long nanos) { + public static long nanosToTicks(long nanos) { return (long) (nanos * JVM.getTimeConversionFactor()); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java index 60db375c876..2d1332aed06 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java @@ -45,11 +45,12 @@ import jdk.jfr.Period; import jdk.jfr.Relational; import jdk.jfr.StackTrace; import jdk.jfr.Threshold; +import jdk.jfr.Throttle; import jdk.jfr.TransitionFrom; import jdk.jfr.TransitionTo; import jdk.jfr.Unsigned; import jdk.jfr.internal.util.Utils; - +import jdk.jfr.internal.settings.ThrottleSetting; public final class MetadataLoader { // Caching to reduce allocation pressure and heap usage @@ -320,7 +321,7 @@ public final class MetadataLoader { aes.add(new AnnotationElement(Cutoff.class, Cutoff.INFINITY)); } if (t.throttle) { - aes.add(new AnnotationElement(Throttle.class, Throttle.DEFAULT)); + aes.add(new AnnotationElement(Throttle.class, ThrottleSetting.DEFAULT_VALUE)); } } if (t.experimental) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java index 5dcf07f719d..e574ab47992 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -45,11 +45,13 @@ import jdk.jfr.EventType; import jdk.jfr.Name; import jdk.jfr.Period; import jdk.jfr.SettingControl; +import jdk.jfr.Throttle; import jdk.jfr.ValueDescriptor; import jdk.jfr.internal.consumer.RepositoryFiles; import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.management.HiddenWait; import jdk.jfr.internal.periodic.PeriodicEvents; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.util.Utils; public final class MetadataRepository { @@ -214,9 +216,11 @@ public final class MetadataRepository { } } EventType eventType = PrivateAccess.getInstance().newEventType(pEventType); + pEventType.setHasThrottle(pEventType.getAnnotation(Throttle.class) != null); EventControl ec = new EventControl(pEventType, eventClass); SettingControl[] settings = ec.getSettingControls().toArray(new SettingControl[0]); - EventConfiguration configuration = new EventConfiguration(pEventType, eventType, ec, settings, eventType.getId()); + Throttler throttler = pEventType.getThrottler(); + EventConfiguration configuration = new EventConfiguration(pEventType, eventType, ec, settings, throttler, eventType.getId()); pEventType.setRegistered(true); // If class is instrumented or should not be instrumented, mark as instrumented. if (JVM.isInstrumented(eventClass) || !JVMSupport.shouldInstrument(pEventType.isJDK(), pEventType.getName())) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index b65a26f3aad..ff3e0238cf0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -35,6 +35,7 @@ import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.util.ImplicitFields; import jdk.jfr.internal.util.TimespanRate; import jdk.jfr.internal.util.Utils; +import jdk.jfr.internal.settings.Throttler; import jdk.jfr.internal.tracing.Modification; /** @@ -72,6 +73,7 @@ public final class PlatformEventType extends Type { private boolean registered = true; private boolean committable = enabled && registered; private boolean hasLevel = false; + private Throttler throttler; // package private PlatformEventType(String name, long id, boolean isJDK, boolean dynamicSettings) { @@ -190,9 +192,11 @@ public final class PlatformEventType extends Type { } } - public void setThrottle(long eventSampleSize, long period_ms) { + public void setThrottle(long eventSampleSize, long periodInMillis) { if (isJVM) { - JVM.setThrottle(getId(), eventSampleSize, period_ms); + JVM.setThrottle(getId(), eventSampleSize, periodInMillis); + } else { + throttler.configure(eventSampleSize, periodInMillis); } } @@ -420,4 +424,12 @@ public final class PlatformEventType extends Type { public long getStackFilterId() { return startFilterId; } + + public Throttler getThrottler() { + return throttler; + } + + public void setThrottler(Throttler throttler) { + this.throttler = throttler; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java index 4b0f2fba3da..f16e153fe43 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventConfiguration.java @@ -28,14 +28,17 @@ package jdk.jfr.internal.event; import jdk.jfr.EventType; import jdk.jfr.SettingControl; import jdk.jfr.internal.EventControl; +import jdk.jfr.internal.EventInstrumentation; import jdk.jfr.internal.JVM; import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.settings.Throttler; public record EventConfiguration( PlatformEventType platformEventType, EventType eventType, EventControl eventControl, SettingControl[] settings, + Throttler throttler, long id) { // Accessed by generated code in event class @@ -43,6 +46,19 @@ public record EventConfiguration( return isEnabled() && duration >= platformEventType.getThresholdTicks(); } + // Accessed by generated code in event class. Used by: + // static boolean shouldThrottleCommit(long duration, long timestamp) + public boolean shouldThrottleCommit(long duration, long timestamp) { + return isEnabled() && duration >= platformEventType.getThresholdTicks() && throttler.sample(timestamp); + } + + // Caller must of Event::shouldThrottleCommit must check enablement. + // Accessed by generated code in event class. Used by: + // static boolean shouldThrottleCommit(long timestamp) + public boolean shouldThrottleCommit(long timestamp) { + return throttler.sample(timestamp); + } + // Accessed by generated code in event class public SettingControl getSetting(int index) { return settings[index]; @@ -53,6 +69,19 @@ public record EventConfiguration( return platformEventType.isCommittable(); } + public long throttle(long startTime, long rawDuration) { + // We have already tried to throttle, return as is + if ((rawDuration & EventInstrumentation.MASK_THROTTLE_BITS) != 0) { + return rawDuration; + } + long endTime = startTime + rawDuration; + if (throttler.sample(endTime)) { + return rawDuration | EventInstrumentation.MASK_THROTTLE_CHECK_SUCCESS; + } else { + return rawDuration | EventInstrumentation.MASK_THROTTLE_CHECK_FAIL; + } + } + // Not really part of the configuration, but the method // needs to be somewhere the event class can access, // but not part of the public API. diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java index 800443cfaed..ab26b5fcd31 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java @@ -38,7 +38,6 @@ import jdk.jfr.MetadataDefinition; import jdk.jfr.Name; import jdk.jfr.SettingControl; import jdk.jfr.internal.PlatformEventType; -import jdk.jfr.internal.Throttle; import jdk.jfr.internal.Type; import jdk.jfr.internal.util.Rate; import jdk.jfr.internal.util.TimespanUnit; @@ -49,7 +48,7 @@ import jdk.jfr.internal.util.Utils; @Description("Throttles the emission rate for an event") @Name(Type.SETTINGS_PREFIX + "Throttle") public final class ThrottleSetting extends SettingControl { - public static final String DEFAULT_VALUE = Throttle.DEFAULT; + public static final String DEFAULT_VALUE = "off"; private final PlatformEventType eventType; private final String defaultValue; private String value; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java new file mode 100644 index 00000000000..df8f5a58700 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java @@ -0,0 +1,233 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.settings; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import jdk.jfr.internal.PlatformEventType; +public final class Throttler { + private static final ThrottlerParameters DISABLED_PARAMETERS = new ThrottlerParameters(0, 0, 0); + private static final long MILLIUNITS = 1000; + private static final long MINUTE = 60 * MILLIUNITS; + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + private static final long EVENT_THROTTLER_OFF = -2; + + private final ReentrantLock lock = new ReentrantLock(); + private final Random randomGenerator = new Random(); + private final ThrottlerWindow window0 = new ThrottlerWindow(); + private final ThrottlerWindow window1 = new ThrottlerWindow(); + + private volatile ThrottlerWindow activeWindow = window0; + + // Guarded by lock + private double averagePopulationSize; + private double ewmaPopulationSize; + private long accumulatedDebtCarryLimit; + private long accumulatedDebtCarryCount; + private ThrottlerParameters lastParameters = new ThrottlerParameters(0, 0, 0); + private long sampleSize; + private long periodMillis; + private boolean disabled; + private boolean update = true; + + public Throttler(PlatformEventType t) { + } + // Not synchronized in fast path, but uses volatile reads. + public boolean sample(long ticks) { + if (disabled) { + return true; + } + ThrottlerWindow current = activeWindow; + if (current.isExpired(ticks)) { + if (lock.tryLock()) { + try { + rotateWindow(ticks); + } finally { + lock.unlock(); + } + } + return activeWindow.sample(); + } + return current.sample(); + } + + public void configure(long sampleSize, long periodMillis) { + lock.lock(); + try { + this.sampleSize = sampleSize; + this.periodMillis = periodMillis; + this.update = true; + this.activeWindow = configure(nextWindowParameters(), activeWindow); + } finally { + lock.unlock(); + } + } + + private ThrottlerWindow configure(ThrottlerParameters parameters, ThrottlerWindow expired) { + if (parameters.reconfigure) { + // Store updated params once to both windows + expired.parameters = parameters; + nextWindow(expired).parameters = parameters; + configure(parameters); + } + ThrottlerWindow next = setRate(parameters, expired); + next.initialize(parameters); + return next; + } + + private void configure(ThrottlerParameters parameters) { + averagePopulationSize = 0; + ewmaPopulationSize = computeEwmaAlphaCoefficient(parameters.windowLookBackCount); + accumulatedDebtCarryLimit = computeAccumulatedDebtCarryLimit(parameters); + accumulatedDebtCarryCount = accumulatedDebtCarryLimit; + parameters.reconfigure = false; + } + + private void rotateWindow(long ticks) { + ThrottlerWindow current = activeWindow; + if (current.isExpired(ticks)) { + activeWindow = configure(current.parameters.copy(), current); + } + } + + private ThrottlerWindow setRate(ThrottlerParameters parameters, ThrottlerWindow expired) { + ThrottlerWindow next = nextWindow(expired); + long projectedSampleSize = parameters.samplePointsPerWindow + amortizeDebt(expired); + if (projectedSampleSize == 0) { + next.projectedPopulationSize = 0; + return next; + } + next.samplingInterval = deriveSamplingInterval(projectedSampleSize, expired); + next.projectedPopulationSize = projectedSampleSize * next.samplingInterval; + return next; + } + + private long amortizeDebt(ThrottlerWindow expired) { + long accumulatedDebt = expired.accumulatedDebt(); + if (accumulatedDebtCarryCount == accumulatedDebtCarryLimit) { + accumulatedDebtCarryCount = 1; + return 0; + } + accumulatedDebtCarryCount++; + return -accumulatedDebt; + } + + private long deriveSamplingInterval(double sampleSize, ThrottlerWindow expired) { + double populationSize = projectPopulationSize(expired); + if (populationSize <= sampleSize) { + return 1; + } + double projectProbability = sampleSize / populationSize; + return nextGeometric(projectProbability, randomGenerator.nextDouble()); + } + + private double projectPopulationSize(ThrottlerWindow expired) { + averagePopulationSize = exponentiallyWeightedMovingAverage(expired.populationSize(), ewmaPopulationSize, averagePopulationSize); + return averagePopulationSize; + } + + private static long nextGeometric(double p, double u) { + return (long) Math.ceil(Math.log(1.0 - adjustBoundary(u)) / Math.log(1.0 - p)); + } + + private static double adjustBoundary(double u) { + if (u == 0.0) { + return 0.01; + } + if (u == 1.0) { + return 0.99; + } + return u; + } + + private void normalize() { + if (periodMillis == MILLIUNITS) { + return; + } + if (periodMillis == MINUTE) { + if (sampleSize >= TEN_PER_1000_MS_IN_MINUTES) { + sampleSize /= 60; + periodMillis /= 60; + } + return; + } + if (periodMillis == HOUR) { + if (sampleSize >= TEN_PER_1000_MS_IN_HOURS) { + sampleSize /= 3600; + periodMillis /= 3600; + } + return; + } + if (sampleSize >= TEN_PER_1000_MS_IN_DAYS) { + sampleSize /= 86400; + periodMillis /= 86400; + } + } + + private ThrottlerParameters nextWindowParameters() { + if (update) { + return updateParameters(); + } + return disabled ? DISABLED_PARAMETERS : lastParameters; + } + + private ThrottlerParameters updateParameters() { + disabled = is_disabled(sampleSize); + if (disabled) { + return DISABLED_PARAMETERS; + } + normalize(); + lastParameters.setSamplePointsAndWindowDuration(sampleSize, periodMillis); + lastParameters.reconfigure = true; + update = false; + return lastParameters; + } + + private boolean is_disabled(long eventSampleSize) { + return eventSampleSize == EVENT_THROTTLER_OFF; + } + + private double exponentiallyWeightedMovingAverage(double y, double alpha, double s) { + return alpha * y + (1 - alpha) * s; + } + + private double computeEwmaAlphaCoefficient(long lookBackCount) { + return lookBackCount <= 1 ? 1.0 : 1.0 / lookBackCount; + } + + private long computeAccumulatedDebtCarryLimit(ThrottlerParameters parameters) { + if (parameters.windowDurationMillis == 0 || parameters.windowDurationMillis >= 1000) { + return 1; + } + return 1000 / parameters.windowDurationMillis; + } + + private ThrottlerWindow nextWindow(ThrottlerWindow expired) { + return expired == window0 ? window1 : window0; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java new file mode 100644 index 00000000000..68908fda2a6 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerParameters.java @@ -0,0 +1,94 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.settings; + +final class ThrottlerParameters { + private static final long LOW_RATE_UPPER_BOUND = 9; + private static final long WINDOW_DIVISOR = 5; + private static final long MILLIUNITS = 1000; + private static final long MINUTE = 60 * MILLIUNITS; + private static final long TEN_PER_1000_MS_IN_MINUTES = 600; + private static final long HOUR = 60 * MINUTE; + private static final long TEN_PER_1000_MS_IN_HOURS = 36000; + private static final long DAY = 24 * HOUR; + private static final long TEN_PER_1000_MS_IN_DAYS = 864000; + private static final long DEFAULT_WINDOWS_LOOKBACK_COUNT = 25; // 25 windows == 5 seconds (for default window duration of 200 ms) + + long samplePointsPerWindow; + long windowDurationMillis; + long windowLookBackCount; + boolean reconfigure; + + ThrottlerParameters(long samplePointsPerWindow, long windowDuration, long windowLockBackCount) { + this.samplePointsPerWindow = samplePointsPerWindow; + this.windowDurationMillis = windowDuration; + this.windowLookBackCount = windowLockBackCount; + } + + public ThrottlerParameters copy() { + return new ThrottlerParameters(samplePointsPerWindow, windowDurationMillis, windowLookBackCount); + } + + void setSamplePointsAndWindowDuration(long sampleSize, long periodMillis) { + try { + if (sampleSize <= LOW_RATE_UPPER_BOUND) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == MINUTE && sampleSize < TEN_PER_1000_MS_IN_MINUTES) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == HOUR && sampleSize < TEN_PER_1000_MS_IN_HOURS) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + if (periodMillis == DAY && sampleSize < TEN_PER_1000_MS_IN_DAYS) { + samplePointsPerWindow = sampleSize; + windowDurationMillis = periodMillis; + return; + } + samplePointsPerWindow = sampleSize / WINDOW_DIVISOR; + windowDurationMillis = periodMillis / WINDOW_DIVISOR; + } finally { + updateWindowLookback(); + } + } + + private void updateWindowLookback() { + if (windowDurationMillis <= MILLIUNITS) { + windowLookBackCount = DEFAULT_WINDOWS_LOOKBACK_COUNT; // 5 seconds + return; + } + if (windowDurationMillis == MINUTE) { + windowLookBackCount = 5; // 5 windows == 5 minutes + return; + } + windowLookBackCount = 1; // 1 window == 1 hour or 1 day + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java new file mode 100644 index 00000000000..31fb6cab63c --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottlerWindow.java @@ -0,0 +1,99 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.jfr.internal.settings; + +import jdk.jfr.internal.JVMSupport; + +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import jdk.jfr.internal.JVM; + +final class ThrottlerWindow { + private final AtomicLong measuredPopulationSize = new AtomicLong(); + // Guarded by Throttler.lock. + ThrottlerParameters parameters = new ThrottlerParameters(0, 0, 0); + long samplingInterval = 1; + long projectedPopulationSize; + + private volatile long endTicks; + + void initialize(ThrottlerParameters parameters) { + if (parameters.windowDurationMillis == 0) { + endTicks = 0; + return; + } + measuredPopulationSize.set(0); + endTicks = JVM.counterTime() + JVMSupport.nanosToTicks(1_000_000L * parameters.windowDurationMillis); + } + + boolean isExpired(long timestamp) { + long endTicks = this.endTicks; + if (timestamp == 0) { + return JVM.counterTime() >= endTicks; + } else { + return timestamp >= endTicks; + } + } + + boolean sample() { + long ordinal = measuredPopulationSize.incrementAndGet(); + return ordinal <= projectedPopulationSize && ordinal % samplingInterval == 0; + } + + long maxSampleSize() { + return samplingInterval == 0 ? 0 : projectedPopulationSize / samplingInterval; + } + + long sampleSize() { + long size = populationSize(); + return size > projectedPopulationSize ? maxSampleSize() : size / samplingInterval; + } + + long populationSize() { + return measuredPopulationSize.get(); + } + + long accumulatedDebt() { + if (projectedPopulationSize == 0) { + return 0; + } + return parameters.samplePointsPerWindow - maxSampleSize() + debt(); + } + + long debt() { + if (projectedPopulationSize == 0) { + return 0; + } + return sampleSize() - parameters.samplePointsPerWindow; + } + + public String toString() { + StringJoiner sb = new StringJoiner(", "); + sb.add("measuredPopulationSize=" + measuredPopulationSize); + sb.add("samplingInterval=" + samplingInterval); + sb.add("projectedPopulationSize=" + projectedPopulationSize); + return sb.toString(); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java index bd5b197d7fc..54866ec508f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/package-info.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -177,6 +177,28 @@ * {@code "false"} * * + * {@code throttle} + * Specifies the maximum rate of events per time unit. + * {@code "off"} (no throttling) + * + * "off", if events should not be throttled, otherwise a string representation of a positive {@code Long} value followed by forward slash ("/") and one of the following units: + *

    + *
  • {@code "ns"} (nanoseconds) + *
  • {@code "us"} (microseconds) + *
  • {@code "ms"} (milliseconds)
  • + *
  • {@code "s"} (seconds)
  • + *
  • {@code "m"} (minutes)
  • + *
  • {@code "h"} (hours)
  • + *
  • {@code "d"} (days)
  • + *
+ * + * + * {@code "off"}
+ * {@code "100/s"}
+ * {@code "1000/m"} + * + * + * * {@code filter} * Specifies the filter for the event * {@code ""} (empty string) diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 541d1d3aa2f..565dfbdee05 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -769,31 +769,35 @@ true true - 20 ms + 20 ms true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s true true - 20 ms + 1 ms + 100/s @@ -836,8 +840,9 @@ - false + true true + 100/s @@ -1136,20 +1141,27 @@ - + - - + + - + + + + + + + + @@ -1177,10 +1189,6 @@ 20 ms - 20 ms - - 20 ms - diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 9cec2d9a70f..2c0812e75fe 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -769,31 +769,35 @@ true true - 10 ms + 10 ms true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s true true - 10 ms + 1 ms + 300/s @@ -836,8 +840,9 @@ - false + true true + 300/s @@ -1135,21 +1140,28 @@ - + - - + + - + - + + + + + + + + @@ -1176,10 +1188,6 @@ 10 ms - 10 ms - - 10 ms - diff --git a/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java new file mode 100644 index 00000000000..da3bd311abb --- /dev/null +++ b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java @@ -0,0 +1,290 @@ +/* + * 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.jfr.api.metadata.annotations; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.AnnotationElement; +import jdk.jfr.Event; +import jdk.jfr.EventType; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; +import jdk.jfr.Threshold; +import jdk.jfr.Enabled; +import jdk.jfr.Recording; +import jdk.jfr.SettingDefinition; +import jdk.jfr.SettingDescriptor; +import jdk.jfr.Throttle; +import jdk.jfr.ValueDescriptor; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.Events; +import jdk.jfr.SettingControl; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.metadata.annotations.TestThrottle + */ +public class TestThrottle { + + @Throttle("off") + @Enabled(false) + public static class ThrottledDisabledEvent extends Event { + } + + @Throttle("off") + public static class ThrottledOffEvent extends Event { + } + + @Throttle("0/s") + public static class ThrottledZeroRateEvent extends Event { + } + + @Throttle("10000000/s") + public static class ThrottledHighRateEvent extends Event { + } + + @Throttle("off") + @Threshold("5 h") + public static class ThrottledThresholdedEvent extends Event { + } + + @Throttle("50/s") + public static class ThrottledNormalRateEvent extends Event { + public int index; + } + + static class TestSetting extends SettingControl { + private boolean value; + + @Override + public String combine(Set values) { + if (values.contains("true")) { + return "true"; + } + if (values.contains("false")) { + return "false"; + } + return "true"; + } + + @Override + public void setValue(String text) { + value = Boolean.parseBoolean(text); + } + + @Override + public String getValue() { + return "" + value; + } + } + + @Throttle("10000000/s") + public static class ThrottledUserdefinedEvent extends Event { + @SettingDefinition + public boolean test(TestSetting control) { + return control.value; + } + } + + @Throttle("50/s") + public static class ThrottledReuseEvent extends Event { + public int index; + } + + public static void main(String[] args) throws Exception { + testThrottleDisabled(); + testThrottledOff(); + testThottleZeroRate(); + testThrottleHighRate(); + testThrottleThresholded(); + testThrottleNormalRate(); + testThrottleUserdefined(); + } + + private static void testThrottleDisabled() throws Exception { + testEvent(ThrottledDisabledEvent.class, false); + } + + private static void testThrottledOff() throws Exception { + testEvent(ThrottledOffEvent.class, true); + } + + private static void testThottleZeroRate() throws Exception { + testEvent(ThrottledZeroRateEvent.class, false); + } + + private static void testThrottleHighRate() throws Exception { + testEvent(ThrottledHighRateEvent.class, true); + } + + private static void testThrottleThresholded() throws Exception { + testEvent(ThrottledThresholdedEvent.class, false); + } + + private static void testThrottleNormalRate() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + AtomicInteger lastIndex = new AtomicInteger(); + AtomicInteger throttled = new AtomicInteger(); + rs.onEvent(ThrottledNormalRateEvent.class.getName(), e -> { + int index = e.getInt("index"); + if (lastIndex.get() + 1 != index) { + throttled.incrementAndGet(); + } + lastIndex.set(index); + }); + rs.startAsync(); + int index = 1; + while (throttled.get() < 30) { + ThrottledNormalRateEvent e = new ThrottledNormalRateEvent(); + e.index = index; + e.commit(); + index++; + Thread.sleep(3); + } + } + } + + private static void testThrottleUserdefined() throws Exception { + testThrottleUserdefined("false", "1000000/s", false); + testThrottleUserdefined("true", "10000000/s", true); + testThrottleUserdefined("true", "0/s", false); + testThrottleUserdefined("true", "off", true); + testThrottleUserdefined("false", "off", false); + } + + private static void testThrottleUserdefined(String test, String throttle, boolean emit) throws Exception { + String eventName = ThrottledUserdefinedEvent.class.getName(); + try (Recording r = new Recording()) { + r.enable(eventName).with("test", test).with("throttle", throttle); + r.start(); + + ThrottledUserdefinedEvent e1 = new ThrottledUserdefinedEvent(); + e1.commit(); + + ThrottledUserdefinedEvent e2 = new ThrottledUserdefinedEvent(); + e2.begin(); + e2.commit(); + + ThrottledUserdefinedEvent e3 = new ThrottledUserdefinedEvent(); + e3.begin(); + e3.end(); + e3.commit(); + + ThrottledUserdefinedEvent e4 = new ThrottledUserdefinedEvent(); + if (e4.shouldCommit()) { + e4.commit(); + } + assertShouldCommit(e4, emit); + + ThrottledUserdefinedEvent e5 = new ThrottledUserdefinedEvent(); + assertShouldCommit(e5, emit); + if (e5.shouldCommit()) { + e5.commit(); + } + + r.stop(); + assertEvents(r, eventName, emit ? 5 : 0); + } + } + + @SuppressWarnings("unchecked") + private static void testEvent(Class eventClass, boolean shouldCommit) throws Exception { + try (Recording r = new Recording()) { + r.start(); + Constructor c = (Constructor) eventClass.getConstructor(); + for (int i = 0; i < 17; i++) { + Event e = c.newInstance(); + if (i % 5 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.commit(); + if (i % 3 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + for (int i = 0; i < 50; i++) { + Event e = c.newInstance(); + e.begin(); + if (i % 5 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.end(); + if (i % 3 == 0) { + assertShouldCommit(e, shouldCommit); + } + e.commit(); + if (i % 7 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + for (int i = 0; i < 11; i++) { + Event e = c.newInstance(); + e.begin(); + e.commit(); + if (i % 7 == 0) { + assertShouldCommit(e, shouldCommit); + } + } + if (shouldCommit) { + assertEvents(r, eventClass.getName(), 17 + 50 + 11); + } + } + } + + private static void assertEvents(Recording r, String name, int expected) throws Exception { + int count = 0; + for (RecordedEvent event : Events.fromRecording(r)) { + if (event.getEventType().getName().equals(name)) { + count++; + } + } + if (count != expected) { + throw new Exception("Expected " + expected + " " + name + " events, but found " + count); + } + } + + private static void assertShouldCommit(Event e, boolean expected) throws Exception { + if (e.shouldCommit() != expected) { + throw new Exception("Expected " + e.getClass() + "::shouldCommit() to return " + expected); + } + } +} diff --git a/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java b/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java index dc22644b4d0..7c6e6fa276b 100644 --- a/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java +++ b/test/jdk/jdk/jfr/api/recording/settings/TestSettingsAvailability.java @@ -66,7 +66,7 @@ public class TestSettingsAvailability { for (EventType parsedType : rf.readEventTypes()) { EventType inMem = inMemoryTypes.get(parsedType.getName()); if (inMem == null) { - throw new Exception("Superflous event type " + parsedType.getName() + " in recording"); + throw new Exception("Superfluous event type " + parsedType.getName() + " in recording"); } Set inMemsettings = new HashSet<>(); for (SettingDescriptor sd : inMem.getSettingDescriptors()) { @@ -75,7 +75,7 @@ public class TestSettingsAvailability { for (SettingDescriptor parsedSetting : parsedType.getSettingDescriptors()) { if (!inMemsettings.contains(parsedSetting.getName())) { - throw new Exception("Superflous setting " + parsedSetting.getName() + " in " + parsedType.getName()); + throw new Exception("Superfluous setting " + parsedSetting.getName() + " in " + parsedType.getName()); } inMemsettings.remove(parsedSetting.getName()); } @@ -89,14 +89,14 @@ public class TestSettingsAvailability { private static void testKnownSettings() throws Exception { testSetting(EventNames.JVMInformation, "enabled", "period"); - testSetting(EventNames.FileRead, "enabled", "threshold", "stackTrace"); - testSetting(EventNames.FileWrite, "enabled", "threshold","stackTrace"); + testSetting(EventNames.FileRead, "enabled", "threshold", "stackTrace", "throttle"); + testSetting(EventNames.FileWrite, "enabled", "threshold", "stackTrace", "throttle"); testSetting(EventNames.ExceptionStatistics, "enabled", "period"); - testSetting(EventNames.SocketRead, "enabled", "threshold", "stackTrace"); - testSetting(EventNames.SocketWrite, "enabled", "threshold", "stackTrace"); + testSetting(EventNames.SocketRead, "enabled", "threshold", "stackTrace", "throttle"); + testSetting(EventNames.SocketWrite, "enabled", "threshold", "stackTrace", "throttle"); testSetting(EventNames.ActiveRecording, "enabled"); testSetting(EventNames.ActiveSetting, "enabled"); - testSetting(EventNames.JavaExceptionThrow, "enabled", "stackTrace"); + testSetting(EventNames.JavaExceptionThrow, "enabled", "stackTrace", "throttle"); } private static void testSetting(String eventName, String... settingNames) throws Exception { diff --git a/test/jdk/jdk/jfr/startupargs/TestEventSettings.java b/test/jdk/jdk/jfr/startupargs/TestEventSettings.java index 37af394affd..c86a6331c52 100644 --- a/test/jdk/jdk/jfr/startupargs/TestEventSettings.java +++ b/test/jdk/jdk/jfr/startupargs/TestEventSettings.java @@ -51,7 +51,7 @@ import jdk.jfr.Recording; * jdk.jfr.startupargs.TestEventSettings multipleSettings * * @run main/othervm - * -XX:StartFlightRecording:class-loading=true,socket-threshold=100ms + * -XX:StartFlightRecording:class-loading=true,locking-threshold=100ms * jdk.jfr.startupargs.TestEventSettings jfcOptions */ public class TestEventSettings { @@ -70,7 +70,7 @@ public class TestEventSettings { } case "jfcOptions" -> { assertSetting("jdk.ClassDefine#enabled","true"); - assertSetting("jdk.SocketRead#threshold", "100 ms"); + assertSetting("jdk.JavaMonitorEnter#threshold", "100 ms"); } default -> throw new Exception("Uknown tes " + subTest); } From 6cdfd36ac80ad889ddbcfc702115b750a32d9645 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 5 Jun 2025 11:42:31 +0000 Subject: [PATCH 069/143] 8358590: JFR: Include min and max in MethodTiming event Reviewed-by: mgronlun --- src/hotspot/share/jfr/metadata/metadata.xml | 4 ++- .../jdk/jfr/events/MethodTimingEvent.java | 10 ++++++- .../classes/jdk/jfr/internal/query/view.ini | 4 +-- .../jfr/internal/tracing/PlatformTracer.java | 2 ++ .../jdk/jfr/internal/tracing/TimedClass.java | 7 ++++- .../jdk/jfr/internal/tracing/TimedMethod.java | 29 +++++++++++++++++-- 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 03daca946f6..d7287adb1a6 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1315,7 +1315,9 @@ - + + + diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java index cc1d8f681c7..4998966939b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/MethodTimingEvent.java @@ -42,11 +42,19 @@ public final class MethodTimingEvent extends AbstractJDKEvent { @Label("Invocations") public long invocations; + @Label("Minimum Time") + @Timespan(Timespan.TICKS) + public long minimum; + @Label("Average Time") @Timespan(Timespan.TICKS) public long average; - public static void commit(long start, long method, long invocations, long average) { + @Label("Maximum Time") + @Timespan(Timespan.TICKS) + public long maximum; + + public static void commit(long start, long method, long invocations, long minimum, long average, long maximum) { // Generated by JFR } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index a2ac74142f4..018f41bbf22 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -471,9 +471,9 @@ table = "COLUMN 'Alloc. Time', 'Application Method', 'Object Age', 'Heap Usage' [application.method-timing] label = "Method Timing" -table = "COLUMN 'Timed Method', 'Invocations', 'Average Time' +table = "COLUMN 'Timed Method', 'Invocations', 'Min. Tim', 'Max. Time', 'Average Time' FORMAT none, none, ms-precision:6 - SELECT LAST_BATCH(method) AS M, LAST_BATCH(invocations), LAST_BATCH(average) + SELECT LAST_BATCH(method) AS M, LAST_BATCH(invocations), LAST_BATCH(minimum), LAST_BATCH(maximum), LAST_BATCH(average) FROM jdk.MethodTiming GROUP BY method ORDER BY average" [application.method-calls] diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java index 608fe865147..abc3b0f8cd0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/PlatformTracer.java @@ -146,6 +146,7 @@ public final class PlatformTracer { public static void addObjectTiming(long duration) { OBJECT.invocations().getAndIncrement(); OBJECT.time().addAndGet(duration); + OBJECT.updateMinMax(duration); } public static void addTiming(long id, long duration) { @@ -153,6 +154,7 @@ public final class PlatformTracer { if (entry != null) { entry.invocations().getAndIncrement(); entry.time().addAndGet(duration); + entry.updateMinMax(duration); } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java index 936a8cf835d..ed592fec645 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedClass.java @@ -61,7 +61,12 @@ public final class TimedClass { long invocations = tm.invocations().get(); long time = tm.time().get(); long average = invocations == 0 ? Long.MIN_VALUE : time / invocations; - MethodTimingEvent.commit(timestamp, methodId, invocations, average); + long min = tm.minimum().get(); + if (min == Long.MAX_VALUE) { + min = Long.MIN_VALUE; // Signals that the value is missing + } + long max = tm.maximum().get(); + MethodTimingEvent.commit(timestamp, methodId, invocations, min, average, max); tm.method().log("Emitted event"); } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java index 185fab62421..be7f0c8f084 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/TimedMethod.java @@ -33,8 +33,33 @@ import java.util.concurrent.atomic.AtomicLong; *

* Fields in record classes are truly final so might help to have a record here. */ -record TimedMethod(AtomicLong invocations, AtomicLong time, Method method, AtomicBoolean published) { +record TimedMethod(AtomicLong invocations, AtomicLong time, AtomicLong minimum, AtomicLong maximum, Method method, AtomicBoolean published) { TimedMethod(Method method) { - this(new AtomicLong(), new AtomicLong(), method, new AtomicBoolean()); + this(new AtomicLong(), new AtomicLong(), new AtomicLong(Long.MAX_VALUE), new AtomicLong(Long.MIN_VALUE), method, new AtomicBoolean()); + } + + public void updateMinMax(long duration) { + if (duration > maximum.getPlain()) { + while (true) { + long max = maximum.get(); + if (duration <= max) { + return; + } + if (maximum.weakCompareAndSetVolatile(max, duration)) { + return; + } + } + } + if (duration < minimum.getPlain()) { + while (true) { + long min = minimum.get(); + if (duration >= min) { + return; + } + if (minimum.weakCompareAndSetVolatile(min, duration)) { + return; + } + } + } } } From 782bbca439cd0d6db9366b4bd8d4861b8f780203 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 5 Jun 2025 12:04:57 +0000 Subject: [PATCH 070/143] 8358633: Test ThreadPoolExecutorTest::testTimedInvokeAnyNullTimeUnit is broken by JDK-8347491 Reviewed-by: alanb --- test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java index d9ce643a26d..2bc66b1e9e3 100644 --- a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java +++ b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorTest.java @@ -1727,7 +1727,7 @@ public class ThreadPoolExecutorTest extends JSR166TestCase { e.invokeAny(l, randomTimeout(), null); shouldThrow(); } catch (NullPointerException success) { - assertEquals("Cannot invoke \"java.util.concurrent.TimeUnit.toNanos(long)\" because \"unit\" is null", success.getMessage()); + // Do not check the message, as ThreadPoolExecutor does not override invokeAny } } } From 33ed7c1842e61664c1ad0ea4d29f20728c89e06c Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 5 Jun 2025 13:08:48 +0000 Subject: [PATCH 071/143] 8358689: test/micro/org/openjdk/bench/java/net/SocketEventOverhead.java does not build after JDK-8351594 Reviewed-by: alanb --- .../bench/java/net/SocketEventOverhead.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/net/SocketEventOverhead.java b/test/micro/org/openjdk/bench/java/net/SocketEventOverhead.java index c405de0af3c..0ba577f49d7 100644 --- a/test/micro/org/openjdk/bench/java/net/SocketEventOverhead.java +++ b/test/micro/org/openjdk/bench/java/net/SocketEventOverhead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -65,7 +65,7 @@ public class SocketEventOverhead { @Fork(value = 1, jvmArgs = { "--add-exports", "java.base/jdk.internal.event=ALL-UNNAMED", - "-XX:StartFlightRecording:jdk.SocketWrite#enabled=true,jdk.SocketWrite#threshold=1s"}) + "-XX:StartFlightRecording:jdk.SocketWrite#enabled=true,jdk.SocketWrite#threshold=1s,jdk.SocketWrite#throttle=off"}) @Benchmark public int socketWriteJFREnabledEventNotEmitted(SkeletonFixture fixture) { return fixture.write(); @@ -73,7 +73,7 @@ public class SocketEventOverhead { @Fork(value = 1, jvmArgs = { "--add-exports","java.base/jdk.internal.event=ALL-UNNAMED", - "-XX:StartFlightRecording:jdk.SocketWrite#enabled=true,jdk.SocketWrite#threshold=0ms,disk=false,jdk.SocketWrite#stackTrace=false"}) + "-XX:StartFlightRecording:jdk.SocketWrite#enabled=true,jdk.SocketWrite#threshold=0ms,disk=false,jdk.SocketWrite#stackTrace=false,jdk.SocketWrite#throttle=off"}) @Benchmark public int socketWriteJFREnabledEventEmitted(SkeletonFixture fixture) { return fixture.write(); @@ -99,7 +99,7 @@ public class SocketEventOverhead { @Fork(value = 1, jvmArgs = { "--add-exports", "java.base/jdk.internal.event=ALL-UNNAMED", - "-XX:StartFlightRecording:jdk.SocketRead#enabled=true,jdk.SocketRead#threshold=1s"}) + "-XX:StartFlightRecording:jdk.SocketRead#enabled=true,jdk.SocketRead#threshold=1s,jdk.SocketRead#throttle=off"}) @Benchmark public int socketReadJFREnabledEventNotEmitted(SkeletonFixture fixture) { return fixture.read(); @@ -107,7 +107,7 @@ public class SocketEventOverhead { @Fork(value = 1, jvmArgs = { "--add-exports","java.base/jdk.internal.event=ALL-UNNAMED", - "-XX:StartFlightRecording:jdk.SocketRead#enabled=true,jdk.SocketRead#threshold=0ms,disk=false,jdk.SocketRead#stackTrace=false"}) + "-XX:StartFlightRecording:jdk.SocketRead#enabled=true,jdk.SocketRead#threshold=0ms,disk=false,jdk.SocketRead#stackTrace=false,jdk.SocketRead#throttle=off"}) @Benchmark public int socketReadJFREnabledEventEmitted(SkeletonFixture fixture) { return fixture.read(); @@ -137,10 +137,7 @@ public class SocketEventOverhead { try { nbytes = write0(); } finally { - long duration = start - SocketWriteEvent.timestamp(); - if (SocketWriteEvent.shouldCommit(duration)) { - SocketWriteEvent.emit(start, duration, nbytes, getRemoteAddress()); - } + SocketWriteEvent.offer(start, nbytes, getRemoteAddress()); } return nbytes; } @@ -158,10 +155,7 @@ public class SocketEventOverhead { try { nbytes = read0(); } finally { - long duration = start - SocketReadEvent.timestamp(); - if (SocketReadEvent.shouldCommit(duration)) { - SocketReadEvent.emit(start, duration, nbytes, getRemoteAddress(), 0); - } + SocketReadEvent.offer(start, nbytes, getRemoteAddress(), 0); } return nbytes; } From 23f1d4f9a993033596ff17751c877f2bb3f792ed Mon Sep 17 00:00:00 2001 From: Dmitry Chuyko Date: Thu, 5 Jun 2025 14:28:27 +0000 Subject: [PATCH 072/143] 8337666: AArch64: SHA3 GPR intrinsic Reviewed-by: aph --- src/hotspot/cpu/aarch64/globals_aarch64.hpp | 4 +- .../cpu/aarch64/macroAssembler_aarch64.hpp | 21 + .../cpu/aarch64/stubGenerator_aarch64.cpp | 370 +++++++++++++++++- .../cpu/aarch64/vm_version_aarch64.cpp | 2 +- .../sha/sanity/TestSHA3Intrinsics.java | 128 +++++- .../sanity/TestSHA3MultiBlockIntrinsics.java | 230 ++++++++++- 6 files changed, 749 insertions(+), 6 deletions(-) diff --git a/src/hotspot/cpu/aarch64/globals_aarch64.hpp b/src/hotspot/cpu/aarch64/globals_aarch64.hpp index 632bba728a1..b316103d656 100644 --- a/src/hotspot/cpu/aarch64/globals_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/globals_aarch64.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2019, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -95,6 +95,8 @@ define_pd_global(intx, InlineSmallCode, 1000); "Use simplest and shortest implementation for array equals") \ product(bool, UseSIMDForBigIntegerShiftIntrinsics, true, \ "Use SIMD instructions for left/right shift of BigInteger") \ + product(bool, UseSIMDForSHA3Intrinsic, true, \ + "Use SIMD SHA3 instructions for SHA3 intrinsic") \ product(bool, AvoidUnalignedAccesses, false, \ "Avoid generating unaligned memory accesses") \ product(bool, UseLSE, false, \ diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 32506c49cfa..d77bc92875f 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -323,6 +323,27 @@ class MacroAssembler: public Assembler { extr(Rd, Rn, Rn, imm); } + inline void rolw(Register Rd, Register Rn, unsigned imm) { + extrw(Rd, Rn, Rn, (32 - imm)); + } + + inline void rol(Register Rd, Register Rn, unsigned imm) { + extr(Rd, Rn, Rn, (64 - imm)); + } + + using Assembler::rax1; + using Assembler::eor3; + + inline void rax1(Register Rd, Register Rn, Register Rm) { + eor(Rd, Rn, Rm, ROR, 63); // Rd = Rn ^ rol(Rm, 1) + } + + inline void eor3(Register Rd, Register Rn, Register Rm, Register Rk) { + assert(Rd != Rn, "Use tmp register"); + eor(Rd, Rm, Rk); + eor(Rd, Rd, Rn); + } + inline void sxtbw(Register Rd, Register Rn) { sbfmw(Rd, Rn, 0, 7); } diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 1b6f6d489f3..a0d1e22ff96 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -7081,6 +7081,366 @@ class StubGenerator: public StubCodeGenerator { return start; } + void bcax5(Register a0, Register a1, Register a2, Register a3, Register a4, + Register tmp0, Register tmp1, Register tmp2) { + __ bic(tmp0, a2, a1); // for a0 + __ bic(tmp1, a3, a2); // for a1 + __ bic(tmp2, a4, a3); // for a2 + __ eor(a2, a2, tmp2); + __ bic(tmp2, a0, a4); // for a3 + __ eor(a3, a3, tmp2); + __ bic(tmp2, a1, a0); // for a4 + __ eor(a0, a0, tmp0); + __ eor(a1, a1, tmp1); + __ eor(a4, a4, tmp2); + } + + void keccak_round_gpr(bool can_use_fp, bool can_use_r18, Register rc, + Register a0, Register a1, Register a2, Register a3, Register a4, + Register a5, Register a6, Register a7, Register a8, Register a9, + Register a10, Register a11, Register a12, Register a13, Register a14, + Register a15, Register a16, Register a17, Register a18, Register a19, + Register a20, Register a21, Register a22, Register a23, Register a24, + Register tmp0, Register tmp1, Register tmp2) { + __ eor3(tmp1, a4, a9, a14); + __ eor3(tmp0, tmp1, a19, a24); // tmp0 = a4^a9^a14^a19^a24 = c4 + __ eor3(tmp2, a1, a6, a11); + __ eor3(tmp1, tmp2, a16, a21); // tmp1 = a1^a6^a11^a16^a21 = c1 + __ rax1(tmp2, tmp0, tmp1); // d0 + { + + Register tmp3, tmp4; + if (can_use_fp && can_use_r18) { + tmp3 = rfp; + tmp4 = r18_tls; + } else { + tmp3 = a4; + tmp4 = a9; + __ stp(tmp3, tmp4, __ pre(sp, -16)); + } + + __ eor3(tmp3, a0, a5, a10); + __ eor3(tmp4, tmp3, a15, a20); // tmp4 = a0^a5^a10^a15^a20 = c0 + __ eor(a0, a0, tmp2); + __ eor(a5, a5, tmp2); + __ eor(a10, a10, tmp2); + __ eor(a15, a15, tmp2); + __ eor(a20, a20, tmp2); // d0(tmp2) + __ eor3(tmp3, a2, a7, a12); + __ eor3(tmp2, tmp3, a17, a22); // tmp2 = a2^a7^a12^a17^a22 = c2 + __ rax1(tmp3, tmp4, tmp2); // d1 + __ eor(a1, a1, tmp3); + __ eor(a6, a6, tmp3); + __ eor(a11, a11, tmp3); + __ eor(a16, a16, tmp3); + __ eor(a21, a21, tmp3); // d1(tmp3) + __ rax1(tmp3, tmp2, tmp0); // d3 + __ eor3(tmp2, a3, a8, a13); + __ eor3(tmp0, tmp2, a18, a23); // tmp0 = a3^a8^a13^a18^a23 = c3 + __ eor(a3, a3, tmp3); + __ eor(a8, a8, tmp3); + __ eor(a13, a13, tmp3); + __ eor(a18, a18, tmp3); + __ eor(a23, a23, tmp3); + __ rax1(tmp2, tmp1, tmp0); // d2 + __ eor(a2, a2, tmp2); + __ eor(a7, a7, tmp2); + __ eor(a12, a12, tmp2); + __ rax1(tmp0, tmp0, tmp4); // d4 + if (!can_use_fp || !can_use_r18) { + __ ldp(tmp3, tmp4, __ post(sp, 16)); + } + __ eor(a17, a17, tmp2); + __ eor(a22, a22, tmp2); + __ eor(a4, a4, tmp0); + __ eor(a9, a9, tmp0); + __ eor(a14, a14, tmp0); + __ eor(a19, a19, tmp0); + __ eor(a24, a24, tmp0); + } + + __ rol(tmp0, a10, 3); + __ rol(a10, a1, 1); + __ rol(a1, a6, 44); + __ rol(a6, a9, 20); + __ rol(a9, a22, 61); + __ rol(a22, a14, 39); + __ rol(a14, a20, 18); + __ rol(a20, a2, 62); + __ rol(a2, a12, 43); + __ rol(a12, a13, 25); + __ rol(a13, a19, 8) ; + __ rol(a19, a23, 56); + __ rol(a23, a15, 41); + __ rol(a15, a4, 27); + __ rol(a4, a24, 14); + __ rol(a24, a21, 2); + __ rol(a21, a8, 55); + __ rol(a8, a16, 45); + __ rol(a16, a5, 36); + __ rol(a5, a3, 28); + __ rol(a3, a18, 21); + __ rol(a18, a17, 15); + __ rol(a17, a11, 10); + __ rol(a11, a7, 6); + __ mov(a7, tmp0); + + bcax5(a0, a1, a2, a3, a4, tmp0, tmp1, tmp2); + bcax5(a5, a6, a7, a8, a9, tmp0, tmp1, tmp2); + bcax5(a10, a11, a12, a13, a14, tmp0, tmp1, tmp2); + bcax5(a15, a16, a17, a18, a19, tmp0, tmp1, tmp2); + bcax5(a20, a21, a22, a23, a24, tmp0, tmp1, tmp2); + + __ ldr(tmp1, __ post(rc, 8)); + __ eor(a0, a0, tmp1); + + } + + // Arguments: + // + // Inputs: + // c_rarg0 - byte[] source+offset + // c_rarg1 - byte[] SHA.state + // c_rarg2 - int block_size + // c_rarg3 - int offset + // c_rarg4 - int limit + // + address generate_sha3_implCompress_gpr(StubGenStubId stub_id) { + bool multi_block; + switch (stub_id) { + case sha3_implCompress_id: + multi_block = false; + break; + case sha3_implCompressMB_id: + multi_block = true; + break; + default: + ShouldNotReachHere(); + } + + static const uint64_t round_consts[24] = { + 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, + 0x8000000080008000L, 0x000000000000808BL, 0x0000000080000001L, + 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008AL, + 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL, + 0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, + 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, + 0x000000000000800AL, 0x800000008000000AL, 0x8000000080008081L, + 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L + }; + + __ align(CodeEntryAlignment); + StubCodeMark mark(this, stub_id); + address start = __ pc(); + + Register buf = c_rarg0; + Register state = c_rarg1; + Register block_size = c_rarg2; + Register ofs = c_rarg3; + Register limit = c_rarg4; + + // use r3.r17,r19..r28 to keep a0..a24. + // a0..a24 are respective locals from SHA3.java + Register a0 = r25, + a1 = r26, + a2 = r27, + a3 = r3, + a4 = r4, + a5 = r5, + a6 = r6, + a7 = r7, + a8 = rscratch1, // r8 + a9 = rscratch2, // r9 + a10 = r10, + a11 = r11, + a12 = r12, + a13 = r13, + a14 = r14, + a15 = r15, + a16 = r16, + a17 = r17, + a18 = r28, + a19 = r19, + a20 = r20, + a21 = r21, + a22 = r22, + a23 = r23, + a24 = r24; + + Register tmp0 = block_size, tmp1 = buf, tmp2 = state, tmp3 = r30; + + Label sha3_loop, rounds24_preloop, loop_body; + Label sha3_512_or_sha3_384, shake128; + + bool can_use_r18 = false; +#ifndef R18_RESERVED + can_use_r18 = true; +#endif + bool can_use_fp = !PreserveFramePointer; + + __ enter(); + + // save almost all yet unsaved gpr registers on stack + __ str(block_size, __ pre(sp, -128)); + if (multi_block) { + __ stpw(ofs, limit, Address(sp, 8)); + } + // 8 bytes at sp+16 will be used to keep buf + __ stp(r19, r20, Address(sp, 32)); + __ stp(r21, r22, Address(sp, 48)); + __ stp(r23, r24, Address(sp, 64)); + __ stp(r25, r26, Address(sp, 80)); + __ stp(r27, r28, Address(sp, 96)); + if (can_use_r18 && can_use_fp) { + __ stp(r18_tls, state, Address(sp, 112)); + } else { + __ str(state, Address(sp, 112)); + } + + // begin sha3 calculations: loading a0..a24 from state arrary + __ ldp(a0, a1, state); + __ ldp(a2, a3, Address(state, 16)); + __ ldp(a4, a5, Address(state, 32)); + __ ldp(a6, a7, Address(state, 48)); + __ ldp(a8, a9, Address(state, 64)); + __ ldp(a10, a11, Address(state, 80)); + __ ldp(a12, a13, Address(state, 96)); + __ ldp(a14, a15, Address(state, 112)); + __ ldp(a16, a17, Address(state, 128)); + __ ldp(a18, a19, Address(state, 144)); + __ ldp(a20, a21, Address(state, 160)); + __ ldp(a22, a23, Address(state, 176)); + __ ldr(a24, Address(state, 192)); + + __ BIND(sha3_loop); + + // load input + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a0, a0, tmp3); + __ eor(a1, a1, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a2, a2, tmp3); + __ eor(a3, a3, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a4, a4, tmp3); + __ eor(a5, a5, tmp2); + __ ldr(tmp3, __ post(buf, 8)); + __ eor(a6, a6, tmp3); + + // block_size == 72, SHA3-512; block_size == 104, SHA3-384 + __ tbz(block_size, 7, sha3_512_or_sha3_384); + + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a7, a7, tmp3); + __ eor(a8, a8, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a9, a9, tmp3); + __ eor(a10, a10, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a11, a11, tmp3); + __ eor(a12, a12, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a13, a13, tmp3); + __ eor(a14, a14, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a15, a15, tmp3); + __ eor(a16, a16, tmp2); + + // block_size == 136, bit4 == 0 and bit5 == 0, SHA3-256 or SHAKE256 + __ andw(tmp2, block_size, 48); + __ cbzw(tmp2, rounds24_preloop); + __ tbnz(block_size, 5, shake128); + // block_size == 144, bit5 == 0, SHA3-244 + __ ldr(tmp3, __ post(buf, 8)); + __ eor(a17, a17, tmp3); + __ b(rounds24_preloop); + + __ BIND(shake128); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a17, a17, tmp3); + __ eor(a18, a18, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a19, a19, tmp3); + __ eor(a20, a20, tmp2); + __ b(rounds24_preloop); // block_size == 168, SHAKE128 + + __ BIND(sha3_512_or_sha3_384); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a7, a7, tmp3); + __ eor(a8, a8, tmp2); + __ tbz(block_size, 5, rounds24_preloop); // SHA3-512 + + // SHA3-384 + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a9, a9, tmp3); + __ eor(a10, a10, tmp2); + __ ldp(tmp3, tmp2, __ post(buf, 16)); + __ eor(a11, a11, tmp3); + __ eor(a12, a12, tmp2); + + __ BIND(rounds24_preloop); + __ fmovs(v0, 24.0); // float loop counter, + __ fmovs(v1, 1.0); // exact representation + + __ str(buf, Address(sp, 16)); + __ lea(tmp3, ExternalAddress((address) round_consts)); + + __ BIND(loop_body); + keccak_round_gpr(can_use_fp, can_use_r18, tmp3, + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, + tmp0, tmp1, tmp2); + __ fsubs(v0, v0, v1); + __ fcmps(v0, 0.0); + __ br(__ NE, loop_body); + + if (multi_block) { + __ ldrw(block_size, sp); // block_size + __ ldpw(tmp2, tmp1, Address(sp, 8)); // offset, limit + __ addw(tmp2, tmp2, block_size); + __ cmpw(tmp2, tmp1); + __ strw(tmp2, Address(sp, 8)); // store offset in case we're jumping + __ ldr(buf, Address(sp, 16)); // restore buf in case we're jumping + __ br(Assembler::LE, sha3_loop); + __ movw(c_rarg0, tmp2); // return offset + } + if (can_use_fp && can_use_r18) { + __ ldp(r18_tls, state, Address(sp, 112)); + } else { + __ ldr(state, Address(sp, 112)); + } + // save calculated sha3 state + __ stp(a0, a1, Address(state)); + __ stp(a2, a3, Address(state, 16)); + __ stp(a4, a5, Address(state, 32)); + __ stp(a6, a7, Address(state, 48)); + __ stp(a8, a9, Address(state, 64)); + __ stp(a10, a11, Address(state, 80)); + __ stp(a12, a13, Address(state, 96)); + __ stp(a14, a15, Address(state, 112)); + __ stp(a16, a17, Address(state, 128)); + __ stp(a18, a19, Address(state, 144)); + __ stp(a20, a21, Address(state, 160)); + __ stp(a22, a23, Address(state, 176)); + __ str(a24, Address(state, 192)); + + // restore required registers from stack + __ ldp(r19, r20, Address(sp, 32)); + __ ldp(r21, r22, Address(sp, 48)); + __ ldp(r23, r24, Address(sp, 64)); + __ ldp(r25, r26, Address(sp, 80)); + __ ldp(r27, r28, Address(sp, 96)); + if (can_use_fp && can_use_r18) { + __ add(rfp, sp, 128); // leave() will copy rfp to sp below + } // else no need to recalculate rfp, since it wasn't changed + + __ leave(); + + __ ret(lr); + + return start; + } + /** * Arguments: * @@ -11512,9 +11872,15 @@ class StubGenerator: public StubCodeGenerator { StubRoutines::_sha512_implCompressMB = generate_sha512_implCompress(StubGenStubId::sha512_implCompressMB_id); } if (UseSHA3Intrinsics) { - StubRoutines::_sha3_implCompress = generate_sha3_implCompress(StubGenStubId::sha3_implCompress_id); + StubRoutines::_double_keccak = generate_double_keccak(); - StubRoutines::_sha3_implCompressMB = generate_sha3_implCompress(StubGenStubId::sha3_implCompressMB_id); + if (UseSIMDForSHA3Intrinsic) { + StubRoutines::_sha3_implCompress = generate_sha3_implCompress(StubGenStubId::sha3_implCompress_id); + StubRoutines::_sha3_implCompressMB = generate_sha3_implCompress(StubGenStubId::sha3_implCompressMB_id); + } else { + StubRoutines::_sha3_implCompress = generate_sha3_implCompress_gpr(StubGenStubId::sha3_implCompress_id); + StubRoutines::_sha3_implCompressMB = generate_sha3_implCompress_gpr(StubGenStubId::sha3_implCompressMB_id); + } } if (UsePoly1305Intrinsics) { diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 6ed7a6be585..941cb254532 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -379,7 +379,7 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseSHA3Intrinsics, true); } } - } else if (UseSHA3Intrinsics) { + } else if (UseSHA3Intrinsics && UseSIMDForSHA3Intrinsic) { warning("Intrinsics for SHA3-224, SHA3-256, SHA3-384 and SHA3-512 crypto hash functions not available on this CPU."); FLAG_SET_DEFAULT(UseSHA3Intrinsics, false); } diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3Intrinsics.java b/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3Intrinsics.java index f4d16af854e..44bfa10e36d 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3Intrinsics.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3Intrinsics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -40,6 +40,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -Dalgorithm=SHA3-224 * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions @@ -58,6 +59,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -Dalgorithm=SHA3-256 * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions @@ -76,6 +78,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -Dalgorithm=SHA3-384 * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions @@ -94,6 +97,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -Dalgorithm=SHA3-512 * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions @@ -110,6 +114,128 @@ * negative_224.log negative_256.log negative_384.log negative_512.log */ +/** + * @test + * @bug 8337666 + * @requires os.arch == "aarch64" + * @summary Verify that SHA3-224, SHA3-256, SHA3-384, SHA3-512 intrinsic is actually used. + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -Dalgorithm=SHA3-224 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -Dalgorithm=SHA3-256 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -Dalgorithm=SHA3-384 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -Dalgorithm=SHA3-512 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -DverificationStrategy=VERIFY_INTRINSIC_USAGE + * compiler.testlibrary.intrinsics.Verifier positive_224.log positive_256.log positive_384.log positive_512.log + */ + +/** + * @test + * @bug 8337666 + * @requires os.arch == "aarch64" + * @summary Verify that SHA3-224, SHA3-256, SHA3-384, SHA3-512 intrinsic is actually used. + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -Dalgorithm=SHA3-224 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -Dalgorithm=SHA3-256 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -Dalgorithm=SHA3-384 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -Dalgorithm=SHA3-512 + * compiler.intrinsics.sha.sanity.TestSHA3Intrinsics + * @run main/othervm -DverificationStrategy=VERIFY_INTRINSIC_USAGE + * compiler.testlibrary.intrinsics.Verifier positive_224.log positive_256.log positive_384.log positive_512.log + */ + package compiler.intrinsics.sha.sanity; import compiler.testlibrary.sha.predicate.IntrinsicPredicates; diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3MultiBlockIntrinsics.java b/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3MultiBlockIntrinsics.java index d350a5f0b13..b6b3fb22dd5 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3MultiBlockIntrinsics.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/sanity/TestSHA3MultiBlockIntrinsics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -40,6 +40,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-224 * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics @@ -50,6 +51,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-224 + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 @@ -67,6 +69,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-256 * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics @@ -77,6 +80,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-256 + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 @@ -94,6 +98,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-384 * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics @@ -104,6 +109,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-384 + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 @@ -121,6 +127,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-512 * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics @@ -131,6 +138,7 @@ * -XX:CompileOnly=sun.security.provider.DigestBase::* * -XX:CompileOnly=sun.security.provider.SHA3::* * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-512 + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UseSIMDForSHA3Intrinsic * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 @@ -147,6 +155,226 @@ * negative_384.log negative_512.log */ +/** + * @test + * @bug 8337666 + * @requires os.arch == "aarch64" + * @summary Verify that SHA3-224, SHA3-256, SHA3-384, SHA3-512 multi block intrinsic is actually used. + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-224 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-224 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-256 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-256 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-384 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-384 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-512 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-512 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:-PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -DverificationStrategy=VERIFY_INTRINSIC_USAGE + * compiler.testlibrary.intrinsics.Verifier positive_224.log positive_256.log + * positive_384.log positive_512.log positive_224_def.log positive_256_def.log + * positive_384_def.log positive_512_def.log + */ + +/** + * @test + * @bug 8337666 + * @requires os.arch == "aarch64" + * @summary Verify that SHA3-224, SHA3-256, SHA3-384, SHA3-512 multi block intrinsic is actually used. + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-224 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_224_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-224 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-256 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_256_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-256 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-384 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_384_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-384 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -XX:-UseMD5Intrinsics + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * -XX:-UseSHA1Intrinsics -XX:-UseSHA256Intrinsics + * -XX:-UseSHA512Intrinsics -Dalgorithm=SHA3-512 + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:CompileThreshold=500 + * -XX:Tier4InvocationThreshold=500 + * -XX:+LogCompilation -XX:LogFile=positive_512_def.log + * -XX:CompileOnly=sun.security.provider.DigestBase::* + * -XX:CompileOnly=sun.security.provider.SHA3::* + * -XX:+UseSHA3Intrinsics -Dalgorithm=SHA3-512 + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-UseSIMDForSHA3Intrinsic -XX:+PreserveFramePointer + * compiler.intrinsics.sha.sanity.TestSHA3MultiBlockIntrinsics + * @run main/othervm -DverificationStrategy=VERIFY_INTRINSIC_USAGE + * compiler.testlibrary.intrinsics.Verifier positive_224.log positive_256.log + * positive_384.log positive_512.log positive_224_def.log positive_256_def.log + * positive_384_def.log positive_512_def.log + */ + package compiler.intrinsics.sha.sanity; import compiler.testlibrary.sha.predicate.IntrinsicPredicates; From c59e44a7aa2aeff0823830b698d524523b996650 Mon Sep 17 00:00:00 2001 From: Patricio Chilano Mateo Date: Thu, 5 Jun 2025 15:02:02 +0000 Subject: [PATCH 073/143] 8357914: TestEmptyBootstrapMethodsAttr.java fails when run with TEST_THREAD_FACTORY=Virtual Reviewed-by: lmesnik, dholmes, sspitsyn, syan --- test/hotspot/jtreg/ProblemList-Virtual.txt | 1 - .../TestEmptyBootstrapMethodsAttr.java | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index dbd555c1cdd..4216eddb885 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -90,7 +90,6 @@ vmTestbase/nsk/jdi/ThreadReference/isSuspended/issuspended002/TestDescription.ja gc/arguments/TestNewSizeThreadIncrease.java 0000000 generic-all gc/g1/TestSkipRebuildRemsetPhase.java 0000000 generic-all -runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java 8346442 generic-all runtime/ErrorHandling/MachCodeFramesInErrorFile.java 0000000 generic-all runtime/Thread/AsyncExceptionOnMonitorEnter.java 0000000 generic-all runtime/Thread/StopAtExit.java 0000000 generic-all diff --git a/test/hotspot/jtreg/runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java b/test/hotspot/jtreg/runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java index 53c85d53c44..33d42a31a04 100644 --- a/test/hotspot/jtreg/runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java +++ b/test/hotspot/jtreg/runtime/classFileParserBug/TestEmptyBootstrapMethodsAttr.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -54,8 +54,13 @@ public class TestEmptyBootstrapMethodsAttr { "-Duser.language=en", "-Duser.country=US", className); OutputAnalyzer output = new OutputAnalyzer(pb.start()); output.shouldNotContain("java.lang.ClassFormatError"); - output.shouldContain("Main method not found in class " + className); output.shouldHaveExitValue(1); + boolean vthreadMode = pb.command().toString().contains("test.thread.factory=Virtual"); + if (vthreadMode) { + output.shouldContain("java.lang.NoSuchMethodException: " + className + ".main"); + } else { + output.shouldContain("Main method not found in class " + className); + } // Test case #2: // Try loading class with empty bootstrap_methods table where an @@ -69,7 +74,12 @@ public class TestEmptyBootstrapMethodsAttr { "-Duser.language=en", "-Duser.country=US", className); output = new OutputAnalyzer(pb.start()); output.shouldNotContain("java.lang.ClassFormatError"); - output.shouldContain("Main method not found in class " + className); output.shouldHaveExitValue(1); + vthreadMode = pb.command().toString().contains("test.thread.factory=Virtual"); + if (vthreadMode) { + output.shouldContain("java.lang.NoSuchMethodException: " + className + ".main"); + } else { + output.shouldContain("Main method not found in class " + className); + } } } From af87035b713f8bfe05a007a4d4670cefc6a6aaf2 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Thu, 5 Jun 2025 16:01:32 +0000 Subject: [PATCH 074/143] 8355746: Start of release updates for JDK 26 8355748: Add SourceVersion.RELEASE_26 8355751: Add source 26 and target 26 to javac Co-authored-by: Joe Darcy Reviewed-by: iris, coleenp, darcy --- .jcheck/conf | 2 +- make/conf/version-numbers.conf | 10 +- .../share/classfile/classFileParser.cpp | 2 + .../java/lang/classfile/ClassFile.java | 10 +- .../lang/reflect/ClassFileFormatVersion.java | 14 +- .../javax/lang/model/SourceVersion.java | 16 +- .../AbstractAnnotationValueVisitor14.java | 4 +- ...AbstractAnnotationValueVisitorPreview.java | 4 +- .../model/util/AbstractElementVisitor14.java | 4 +- .../util/AbstractElementVisitorPreview.java | 4 +- .../model/util/AbstractTypeVisitor14.java | 4 +- .../util/AbstractTypeVisitorPreview.java | 4 +- .../lang/model/util/ElementKindVisitor14.java | 4 +- .../model/util/ElementKindVisitorPreview.java | 4 +- .../lang/model/util/ElementScanner14.java | 4 +- .../model/util/ElementScannerPreview.java | 4 +- .../util/SimpleAnnotationValueVisitor14.java | 4 +- .../SimpleAnnotationValueVisitorPreview.java | 4 +- .../model/util/SimpleElementVisitor14.java | 4 +- .../util/SimpleElementVisitorPreview.java | 4 +- .../lang/model/util/SimpleTypeVisitor14.java | 4 +- .../model/util/SimpleTypeVisitorPreview.java | 4 +- .../lang/model/util/TypeKindVisitor14.java | 4 +- .../model/util/TypeKindVisitorPreview.java | 4 +- .../com/sun/tools/javac/code/Source.java | 7 + .../com/sun/tools/javac/jvm/ClassFile.java | 3 +- .../com/sun/tools/javac/jvm/Target.java | 3 + .../javac/processing/PrintingProcessor.java | 2 +- .../share/data/symbols/java.base-P.sym.txt | 829 ++++++++++++++++++ .../data/symbols/java.compiler-P.sym.txt | 85 ++ .../share/data/symbols/java.desktop-P.sym.txt | 43 + .../share/data/symbols/java.logging-P.sym.txt | 44 + .../data/symbols/java.management-P.sym.txt | 57 ++ .../data/symbols/java.net.http-P.sym.txt | 37 + .../data/symbols/java.security.jgss-P.sym.txt | 36 + .../data/symbols/java.xml.crypto-P.sym.txt | 34 + .../share/data/symbols/jdk.attach-P.sym.txt | 32 + .../share/data/symbols/jdk.compiler-P.sym.txt | 32 + .../symbols/jdk.crypto.cryptoki-P.sym.txt | 31 + .../symbols/jdk.incubator.foreign-P.sym.txt | 105 +++ .../symbols/jdk.incubator.vector-P.sym.txt | 113 +++ .../share/data/symbols/jdk.jdi-P.sym.txt | 32 + .../share/data/symbols/jdk.jfr-P.sym.txt | 31 + .../share/data/symbols/jdk.jpackage-P.sym.txt | 31 + .../share/data/symbols/jdk.jshell-P.sym.txt | 46 + .../share/data/symbols/jdk.net-P.sym.txt | 31 + .../data/symbols/jdk.security.jgss-P.sym.txt | 31 + src/jdk.compiler/share/data/symbols/symbols | 5 +- test/hotspot/jtreg/ProblemList.txt | 5 + .../CommandLine/VMDeprecatedOptions.java | 17 +- .../asm/org/objectweb/asm/ClassReader.java | 2 +- .../asm/org/objectweb/asm/Opcodes.java | 1 + test/jdk/ProblemList.txt | 6 + .../javac/api/TestGetSourceVersions.java | 6 +- .../javac/classfiles/ClassVersionChecker.java | 5 +- .../lib/JavacTestingAbstractProcessor.java | 20 +- .../options/HelpOutputColumnWidthTest.java | 4 +- .../classReaderTest/Client.nopreview.out | 2 +- .../classReaderTest/Client.preview.out | 2 +- .../tools/javac/versions/Versions.java | 8 +- 60 files changed, 1814 insertions(+), 90 deletions(-) create mode 100644 src/jdk.compiler/share/data/symbols/java.base-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.compiler-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.desktop-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.logging-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.management-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.net.http-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.security.jgss-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/java.xml.crypto-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.attach-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.compiler-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.crypto.cryptoki-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.incubator.foreign-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.incubator.vector-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.jdi-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.jfr-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.jpackage-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.jshell-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.net-P.sym.txt create mode 100644 src/jdk.compiler/share/data/symbols/jdk.security.jgss-P.sym.txt diff --git a/.jcheck/conf b/.jcheck/conf index 6ab5c2d64c2..60881e74d2a 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,7 +1,7 @@ [general] project=jdk jbs=JDK -version=25 +version=26 [checks] error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index ce9e32315a9..38d6e42dff9 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -26,17 +26,17 @@ # Default version, product, and vendor information to use, # unless overridden by configure -DEFAULT_VERSION_FEATURE=25 +DEFAULT_VERSION_FEATURE=26 DEFAULT_VERSION_INTERIM=0 DEFAULT_VERSION_UPDATE=0 DEFAULT_VERSION_PATCH=0 DEFAULT_VERSION_EXTRA1=0 DEFAULT_VERSION_EXTRA2=0 DEFAULT_VERSION_EXTRA3=0 -DEFAULT_VERSION_DATE=2025-09-16 -DEFAULT_VERSION_CLASSFILE_MAJOR=69 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" +DEFAULT_VERSION_DATE=2026-03-17 +DEFAULT_VERSION_CLASSFILE_MAJOR=70 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 -DEFAULT_ACCEPTABLE_BOOT_VERSIONS="24 25" -DEFAULT_JDK_SOURCE_TARGET_VERSION=25 +DEFAULT_ACCEPTABLE_BOOT_VERSIONS="24 25 26" +DEFAULT_JDK_SOURCE_TARGET_VERSION=26 DEFAULT_PROMOTED_VERSION_PRE=ea diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 0f021c0ef4d..bfb41f8384a 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -154,6 +154,8 @@ #define JAVA_25_VERSION 69 +#define JAVA_26_VERSION 70 + void ClassFileParser::set_class_bad_constant_seen(short bad_constant) { assert((bad_constant == JVM_CONSTANT_Module || bad_constant == JVM_CONSTANT_Package) && _major_version >= JAVA_9_VERSION, diff --git a/src/java.base/share/classes/java/lang/classfile/ClassFile.java b/src/java.base/share/classes/java/lang/classfile/ClassFile.java index 4b529c39099..216facbdddf 100644 --- a/src/java.base/share/classes/java/lang/classfile/ClassFile.java +++ b/src/java.base/share/classes/java/lang/classfile/ClassFile.java @@ -1030,6 +1030,14 @@ public sealed interface ClassFile */ int JAVA_25_VERSION = 69; + /** + * The class major version introduced by Java SE 26, {@value}. + * + * @see ClassFileFormatVersion#RELEASE_26 + * @since 26 + */ + int JAVA_26_VERSION = 70; + /** * A minor version number {@value} indicating a class uses preview features * of a Java SE release since 12, for major versions {@value @@ -1041,7 +1049,7 @@ public sealed interface ClassFile * {@return the latest class major version supported by the current runtime} */ static int latestMajorVersion() { - return JAVA_25_VERSION; + return JAVA_26_VERSION; } /** diff --git a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java index 70eb921a124..4a63dd157f8 100644 --- a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java +++ b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java @@ -371,6 +371,18 @@ public enum ClassFileFormatVersion { * The Java Virtual Machine Specification, Java SE 25 Edition */ RELEASE_25(69), + + /** + * The version introduced by the Java Platform, Standard Edition + * 26. + * + * @since 26 + * + * @see + * The Java Virtual Machine Specification, Java SE 26 Edition + */ + RELEASE_26(70), ; // Reduce code churn when appending new constants // Note to maintainers: when adding constants for newer releases, @@ -386,7 +398,7 @@ public enum ClassFileFormatVersion { * {@return the latest class file format version} */ public static ClassFileFormatVersion latest() { - return RELEASE_25; + return RELEASE_26; } /** diff --git a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java index 46d1a2cf9c9..6b4ae119504 100644 --- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java +++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java @@ -468,6 +468,18 @@ public enum SourceVersion { * JEP 513: Flexible Constructor Bodies */ RELEASE_25, + + /** + * The version introduced by the Java Platform, Standard Edition + * 26. + * + * @since 26 + * + * @see + * The Java Language Specification, Java SE 26 Edition + */ + RELEASE_26, ; // Reduce code churn when appending new constants // Note that when adding constants for newer releases, the @@ -477,7 +489,7 @@ public enum SourceVersion { * {@return the latest source version that can be modeled} */ public static SourceVersion latest() { - return RELEASE_25; + return RELEASE_26; } private static final SourceVersion latestSupported = getLatestSupported(); @@ -492,7 +504,7 @@ public enum SourceVersion { private static SourceVersion getLatestSupported() { int intVersion = Runtime.version().feature(); return (intVersion >= 11) ? - valueOf("RELEASE_" + Math.min(25, intVersion)): + valueOf("RELEASE_" + Math.min(26, intVersion)): RELEASE_10; } diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java index 4d36781c584..2b2f80358b0 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -44,7 +44,7 @@ import javax.annotation.processing.SupportedSourceVersion; * @see AbstractAnnotationValueVisitor9 * @since 14 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public abstract class AbstractAnnotationValueVisitor14 extends AbstractAnnotationValueVisitor9 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java index 4afcc7fbf1d..59d1eb28282 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractAnnotationValueVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -50,7 +50,7 @@ import javax.annotation.processing.ProcessingEnvironment; * @see AbstractAnnotationValueVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public abstract class AbstractAnnotationValueVisitorPreview extends AbstractAnnotationValueVisitor14 { diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java index 3a57ef18c5b..b624bb9f999 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -50,7 +50,7 @@ import static javax.lang.model.SourceVersion.*; * @see AbstractElementVisitor9 * @since 16 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public abstract class AbstractElementVisitor14 extends AbstractElementVisitor9 { /** * Constructor for concrete subclasses to call. diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java index b9642b203fe..761916a1434 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -53,7 +53,7 @@ import static javax.lang.model.SourceVersion.*; * @see AbstractElementVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public abstract class AbstractElementVisitorPreview extends AbstractElementVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java index eb96f4abb99..1860cd09ac5 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -47,7 +47,7 @@ import static javax.lang.model.SourceVersion.*; * @see AbstractTypeVisitor9 * @since 14 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public abstract class AbstractTypeVisitor14 extends AbstractTypeVisitor9 { /** * Constructor for concrete subclasses to call. diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java index 74b007356d4..6b701262a4f 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractTypeVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -53,7 +53,7 @@ import static javax.lang.model.SourceVersion.*; * @see AbstractTypeVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public abstract class AbstractTypeVisitorPreview extends AbstractTypeVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java index d7941d6e153..cb0e1cfbc9f 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -61,7 +61,7 @@ import javax.lang.model.SourceVersion; * @see ElementKindVisitor9 * @since 16 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class ElementKindVisitor14 extends ElementKindVisitor9 { /** * Constructor for concrete subclasses; uses {@code null} for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java index 868e89c23eb..0f436e6d1e5 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -67,7 +67,7 @@ import static javax.lang.model.SourceVersion.*; * @see ElementKindVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class ElementKindVisitorPreview extends ElementKindVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java index e0c05ab228e..946cf97b092 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -77,7 +77,7 @@ import static javax.lang.model.SourceVersion.*; * @see ElementScanner9 * @since 16 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class ElementScanner14 extends ElementScanner9 { /** * Constructor for concrete subclasses; uses {@code null} for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java index 6d80aa8c661..a9d8e7c1f3f 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -81,7 +81,7 @@ import static javax.lang.model.SourceVersion.*; * @see ElementScanner14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class ElementScannerPreview extends ElementScanner14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java index 4682e2a7ee8..9320e18d5dd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -52,7 +52,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleAnnotationValueVisitor9 * @since 14 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class SimpleAnnotationValueVisitor14 extends SimpleAnnotationValueVisitor9 { /** * Constructor for concrete subclasses; uses {@code null} for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java index a477f33017c..01509c0c4a5 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleAnnotationValueVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -58,7 +58,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleAnnotationValueVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class SimpleAnnotationValueVisitorPreview extends SimpleAnnotationValueVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java index db97e59152f..dff1731d3c9 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -58,7 +58,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleElementVisitor9 * @since 16 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class SimpleElementVisitor14 extends SimpleElementVisitor9 { /** * Constructor for concrete subclasses; uses {@code null} for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java index 0d9914f3852..c3896a794c0 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -61,7 +61,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleElementVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class SimpleElementVisitorPreview extends SimpleElementVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java index 3f962137987..d06f5d673d3 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -56,7 +56,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleTypeVisitor9 * @since 14 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class SimpleTypeVisitor14 extends SimpleTypeVisitor9 { /** * Constructor for concrete subclasses; uses {@code null} for the diff --git a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java index 13a0ad41d7e..11e2e8a50d5 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleTypeVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -62,7 +62,7 @@ import static javax.lang.model.SourceVersion.*; * @see SimpleTypeVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class SimpleTypeVisitorPreview extends SimpleTypeVisitor14 { /** diff --git a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java index 57d43e77500..295ceb200b8 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitor14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -61,7 +61,7 @@ import static javax.lang.model.SourceVersion.*; * @see TypeKindVisitor9 * @since 14 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) public class TypeKindVisitor14 extends TypeKindVisitor9 { /** * Constructor for concrete subclasses to call; uses {@code null} diff --git a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java index 3ae19353a0b..c0ca3a0450a 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/TypeKindVisitorPreview.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -66,7 +66,7 @@ import static javax.lang.model.SourceVersion.*; * @see TypeKindVisitor14 * @since 23 */ -@SupportedSourceVersion(RELEASE_25) +@SupportedSourceVersion(RELEASE_26) @PreviewFeature(feature=PreviewFeature.Feature.LANGUAGE_MODEL, reflective=true) public class TypeKindVisitorPreview extends TypeKindVisitor14 { /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index b0b82fa5734..063d37be4e2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -153,6 +153,11 @@ public enum Source { * 25, tbd */ JDK25("25"), + + /** + * 26, tbd + */ + JDK26("26"), ; // Reduce code churn when appending new constants private static final Context.Key sourceKey = new Context.Key<>(); @@ -205,6 +210,7 @@ public enum Source { public Target requiredTarget() { return switch(this) { + case JDK26 -> Target.JDK1_26; case JDK25 -> Target.JDK1_25; case JDK24 -> Target.JDK1_24; case JDK23 -> Target.JDK1_23; @@ -358,6 +364,7 @@ public enum Source { case JDK23 -> RELEASE_23; case JDK24 -> RELEASE_24; case JDK25 -> RELEASE_25; + case JDK26 -> RELEASE_26; default -> null; }; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java index 202d92f63bc..e5adecdb107 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -129,6 +129,7 @@ public class ClassFile { V67(67, 0), // JDK 23 V68(68, 0), // JDK 24 V69(69, 0), // JDK 25 + V70(70, 0), // JDK 26 ; // Reduce code churn when appending new constants Version(int major, int minor) { this.major = major; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java index eeca1dadc1b..f60adcb3b80 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java @@ -110,6 +110,9 @@ public enum Target { /** JDK 25. */ JDK1_25("25", 69, 0), + + /** JDK 26. */ + JDK1_26("26", 70, 0), ; // Reduce code churn when appending new constants private static final Context.Key targetKey = new Context.Key<>(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java index 72a2b564ec5..7aaf035fd36 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java @@ -55,7 +55,7 @@ import com.sun.tools.javac.util.StringUtils; * deletion without notice. */ @SupportedAnnotationTypes("*") -@SupportedSourceVersion(SourceVersion.RELEASE_25) +@SupportedSourceVersion(SourceVersion.RELEASE_26) public class PrintingProcessor extends AbstractProcessor { PrintWriter writer; diff --git a/src/jdk.compiler/share/data/symbols/java.base-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.base-P.sym.txt new file mode 100644 index 00000000000..95544fc3cac --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.base-P.sym.txt @@ -0,0 +1,829 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +module name java.base +header exports java/io,java/lang,java/lang/annotation,java/lang/classfile,java/lang/classfile/attribute,java/lang/classfile/constantpool,java/lang/classfile/instruction,java/lang/constant,java/lang/foreign,java/lang/invoke,java/lang/module,java/lang/ref,java/lang/reflect,java/lang/runtime,java/math,java/net,java/net/spi,java/nio,java/nio/channels,java/nio/channels/spi,java/nio/charset,java/nio/charset/spi,java/nio/file,java/nio/file/attribute,java/nio/file/spi,java/security,java/security/cert,java/security/interfaces,java/security/spec,java/text,java/text/spi,java/time,java/time/chrono,java/time/format,java/time/temporal,java/time/zone,java/util,java/util/concurrent,java/util/concurrent/atomic,java/util/concurrent/locks,java/util/function,java/util/jar,java/util/random,java/util/regex,java/util/spi,java/util/stream,java/util/zip,javax/crypto,javax/crypto/interfaces,javax/crypto/spec,javax/net,javax/net/ssl,javax/security/auth,javax/security/auth/callback,javax/security/auth/login,javax/security/auth/spi,javax/security/auth/x500,javax/security/cert,jdk/internal/event[jdk.jfr],jdk/internal/javac[java.compiler\u005C;u002C;java.desktop\u005C;u002C;jdk.compiler\u005C;u002C;jdk.incubator.vector\u005C;u002C;jdk.jartool\u005C;u002C;jdk.jdeps\u005C;u002C;jdk.jfr\u005C;u002C;jdk.jlink\u005C;u002C;jdk.jshell],jdk/internal/vm/vector[jdk.incubator.vector] extraModulePackages jdk/internal/access/foreign,jdk/internal/classfile/impl,jdk/internal/constant,jdk/internal/foreign/abi,jdk/internal/foreign/abi/aarch64/linux,jdk/internal/foreign/abi/aarch64/macos,jdk/internal/foreign/abi/aarch64/windows,jdk/internal/foreign/abi/fallback,jdk/internal/foreign/abi/ppc64/aix,jdk/internal/foreign/abi/ppc64/linux,jdk/internal/foreign/abi/riscv64/linux,jdk/internal/foreign/abi/s390/linux,jdk/internal/foreign/abi/x64/sysv,jdk/internal/foreign/abi/x64/windows,jdk/internal/foreign/layout,jdk/internal/lang/stable,sun/nio/ch,sun/net,jdk/internal/foreign,jdk/internal/foreign,sun/net,sun/nio/ch uses java/lang/System$LoggerFinder,java/net/ContentHandlerFactory,java/net/spi/InetAddressResolverProvider,java/net/spi/URLStreamHandlerProvider,java/nio/channels/spi/AsynchronousChannelProvider,java/nio/channels/spi/SelectorProvider,java/nio/charset/spi/CharsetProvider,java/nio/file/spi/FileSystemProvider,java/nio/file/spi/FileTypeDetector,java/security/Provider,java/text/spi/BreakIteratorProvider,java/text/spi/CollatorProvider,java/text/spi/DateFormatProvider,java/text/spi/DateFormatSymbolsProvider,java/text/spi/DecimalFormatSymbolsProvider,java/text/spi/NumberFormatProvider,java/time/chrono/AbstractChronology,java/time/chrono/Chronology,java/time/zone/ZoneRulesProvider,java/util/spi/CalendarDataProvider,java/util/spi/CalendarNameProvider,java/util/spi/CurrencyNameProvider,java/util/spi/LocaleNameProvider,java/util/spi/ResourceBundleControlProvider,java/util/spi/ResourceBundleProvider,java/util/spi/TimeZoneNameProvider,java/util/spi/ToolProvider,javax/security/auth/spi/LoginModule,jdk/internal/io/JdkConsoleProvider,jdk/internal/logger/DefaultLoggerFinder,sun/text/spi/JavaTimeDateTimePatternProvider,sun/util/locale/provider/LocaleDataMetaInfo,sun/util/resources/LocaleData$CommonResourceBundleProvider,sun/util/resources/LocaleData$SupplementaryResourceBundleProvider,sun/util/spi/CalendarProvider provides interface\u0020;java/nio/file/spi/FileSystemProvider\u0020;impls\u0020;jdk/internal/jrtfs/JrtFileSystemProvider target macos-aarch64 flags 8000 + +class name java/io/Console +-method name println descriptor (Ljava/lang/Object;)Ljava/io/Console; +-method name print descriptor (Ljava/lang/Object;)Ljava/io/Console; +-method name readln descriptor (Ljava/lang/String;)Ljava/lang/String; +-method name println descriptor ()Ljava/io/Console; +-method name readln descriptor ()Ljava/lang/String; + +class name java/io/FilePermission +header extends java/security/Permission implements java/io/Serializable flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +-class name java/io/IO + +class name java/io/OptionalDataException +header extends java/io/ObjectStreamException flags 31 + +class name java/io/ProxyingConsole +-method name println descriptor (Ljava/lang/Object;)Ljava/io/Console; +-method name print descriptor (Ljava/lang/Object;)Ljava/io/Console; +-method name readln descriptor (Ljava/lang/String;)Ljava/lang/String; +-method name readln descriptor ()Ljava/lang/String; + +class name java/io/SerializablePermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/lang/Boolean +-method name descriptor (Z)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (Z)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/Byte +-method name descriptor (B)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (B)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/CharSequence +method name getChars descriptor (II[CI)V flags 1 + +class name java/lang/Character +-method name descriptor (C)V +method name descriptor (C)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/Class +-method name isInterface descriptor ()Z +-method name isArray descriptor ()Z +-method name isPrimitive descriptor ()Z +-method name getModifiers descriptor ()I +method name isInterface descriptor ()Z flags 1 +method name isArray descriptor ()Z flags 1 +method name isPrimitive descriptor ()Z flags 1 +method name getModifiers descriptor ()I flags 1 + +class name java/lang/Double +-method name descriptor (D)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (D)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/Float +-method name descriptor (F)V +-method name descriptor (D)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (F)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (D)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/IO +header extends java/lang/Object flags 31 +method name println descriptor (Ljava/lang/Object;)V flags 9 +method name println descriptor ()V flags 9 +method name print descriptor (Ljava/lang/Object;)V flags 9 +method name readln descriptor ()Ljava/lang/String; flags 9 +method name readln descriptor (Ljava/lang/String;)Ljava/lang/String; flags 9 + +class name java/lang/Integer +-method name descriptor (I)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (I)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/Long +-method name descriptor (J)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (J)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/Math +-method name max descriptor (JJ)J +-method name min descriptor (JJ)J +method name max descriptor (JJ)J flags 9 runtimeAnnotations @Ljdk/internal/vm/annotation/IntrinsicCandidate; +method name min descriptor (JJ)J flags 9 runtimeAnnotations @Ljdk/internal/vm/annotation/IntrinsicCandidate; +method name unsignedMultiplyExact descriptor (II)I flags 9 +method name unsignedMultiplyExact descriptor (JI)J flags 9 +method name unsignedMultiplyExact descriptor (JJ)J flags 9 +method name powExact descriptor (II)I flags 9 +method name unsignedPowExact descriptor (II)I flags 9 +method name powExact descriptor (JI)J flags 9 +method name unsignedPowExact descriptor (JI)J flags 9 + +class name java/lang/Package +header extends java/lang/NamedPackage implements java/lang/reflect/AnnotatedElement flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/lang/ProcessHandle +header extends java/lang/Object implements java/lang/Comparable nestMembers java/lang/ProcessHandle$Info flags 601 signature Ljava/lang/Object;Ljava/lang/Comparable; +innerclass innerClass java/lang/ProcessHandle$Info outerClass java/lang/ProcessHandle innerClassName Info flags 609 + +class name java/lang/Runtime +header extends java/lang/Object nestMembers java/lang/Runtime$Version flags 31 +innerclass innerClass java/lang/Runtime$Version outerClass java/lang/Runtime innerClassName Version flags 19 + +class name java/lang/RuntimePermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/lang/Short +-method name descriptor (S)V +-method name descriptor (Ljava/lang/String;)V +method name descriptor (S)V flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") +method name descriptor (Ljava/lang/String;)V thrownTypes java/lang/NumberFormatException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(since="9") + +class name java/lang/StableValue +header extends java/lang/Object sealed true permittedSubclasses jdk/internal/lang/stable/StableValueImpl flags 601 signature Ljava/lang/Object; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STABLE_VALUES;) +method name trySet descriptor (Ljava/lang/Object;)Z flags 401 signature (TT;)Z +method name orElse descriptor (Ljava/lang/Object;)Ljava/lang/Object; flags 401 signature (TT;)TT; +method name orElseThrow descriptor ()Ljava/lang/Object; flags 401 signature ()TT; +method name isSet descriptor ()Z flags 401 +method name orElseSet descriptor (Ljava/util/function/Supplier;)Ljava/lang/Object; flags 401 signature (Ljava/util/function/Supplier<+TT;>;)TT; +method name setOrThrow descriptor (Ljava/lang/Object;)V flags 401 signature (TT;)V +method name equals descriptor (Ljava/lang/Object;)Z flags 401 +method name hashCode descriptor ()I flags 401 +method name of descriptor ()Ljava/lang/StableValue; flags 9 signature ()Ljava/lang/StableValue; +method name of descriptor (Ljava/lang/Object;)Ljava/lang/StableValue; flags 9 signature (TT;)Ljava/lang/StableValue; +method name supplier descriptor (Ljava/util/function/Supplier;)Ljava/util/function/Supplier; flags 9 signature (Ljava/util/function/Supplier<+TT;>;)Ljava/util/function/Supplier; +method name intFunction descriptor (ILjava/util/function/IntFunction;)Ljava/util/function/IntFunction; flags 9 signature (ILjava/util/function/IntFunction<+TR;>;)Ljava/util/function/IntFunction; +method name function descriptor (Ljava/util/Set;Ljava/util/function/Function;)Ljava/util/function/Function; flags 9 signature (Ljava/util/Set<+TT;>;Ljava/util/function/Function<-TT;+TR;>;)Ljava/util/function/Function; +method name list descriptor (ILjava/util/function/IntFunction;)Ljava/util/List; flags 9 signature (ILjava/util/function/IntFunction<+TE;>;)Ljava/util/List; +method name map descriptor (Ljava/util/Set;Ljava/util/function/Function;)Ljava/util/Map; flags 9 signature (Ljava/util/Set;Ljava/util/function/Function<-TK;+TV;>;)Ljava/util/Map; + +class name java/lang/StrictMath +method name unsignedMultiplyExact descriptor (II)I flags 9 +method name unsignedMultiplyExact descriptor (JI)J flags 9 +method name unsignedMultiplyExact descriptor (JJ)J flags 9 +method name powExact descriptor (II)I flags 9 +method name unsignedPowExact descriptor (II)I flags 9 +method name powExact descriptor (JI)J flags 9 +method name unsignedPowExact descriptor (JI)J flags 9 + +class name java/lang/classfile/ClassFile +field name JAVA_25_VERSION descriptor I constantValue 69 flags 19 + +class name java/lang/classfile/CodeElement +header extends java/lang/Object implements java/lang/classfile/ClassFileElement sealed true permittedSubclasses java/lang/classfile/Instruction,java/lang/classfile/PseudoInstruction,java/lang/classfile/CustomAttribute,java/lang/classfile/attribute/RuntimeVisibleTypeAnnotationsAttribute,java/lang/classfile/attribute/RuntimeInvisibleTypeAnnotationsAttribute,java/lang/classfile/attribute/StackMapTableAttribute,java/lang/classfile/attribute/UnknownAttribute flags 601 + +class name java/lang/classfile/attribute/UnknownAttribute +header extends java/lang/Object implements java/lang/classfile/Attribute,java/lang/classfile/ClassElement,java/lang/classfile/MethodElement,java/lang/classfile/FieldElement,java/lang/classfile/CodeElement sealed true permittedSubclasses jdk/internal/classfile/impl/BoundAttribute$BoundUnknownAttribute flags 601 signature Ljava/lang/Object;Ljava/lang/classfile/Attribute;Ljava/lang/classfile/ClassElement;Ljava/lang/classfile/MethodElement;Ljava/lang/classfile/FieldElement;Ljava/lang/classfile/CodeElement; +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundUnknownAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundUnknownAttribute flags 19 + +class name java/lang/classfile/constantpool/ClassEntry +method name matches descriptor (Ljava/lang/constant/ClassDesc;)Z flags 401 + +class name java/lang/classfile/constantpool/ConstantPoolBuilder +header extends java/lang/Object implements java/lang/classfile/constantpool/ConstantPool sealed true permittedSubclasses jdk/internal/classfile/impl/SplitConstantPool,jdk/internal/classfile/impl/TemporaryConstantPool flags 601 +innerclass innerClass jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl outerClass jdk/internal/classfile/impl/AbstractPoolEntry innerClassName ClassEntryImpl flags 19 +innerclass innerClass java/lang/constant/DirectMethodHandleDesc$Kind outerClass java/lang/constant/DirectMethodHandleDesc innerClassName Kind flags 4019 +innerclass innerClass jdk/internal/classfile/impl/AbstractPoolEntry$MethodHandleEntryImpl outerClass jdk/internal/classfile/impl/AbstractPoolEntry innerClassName MethodHandleEntryImpl flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractPoolEntry$InvokeDynamicEntryImpl outerClass jdk/internal/classfile/impl/AbstractPoolEntry innerClassName InvokeDynamicEntryImpl flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractPoolEntry$ConstantDynamicEntryImpl outerClass jdk/internal/classfile/impl/AbstractPoolEntry innerClassName ConstantDynamicEntryImpl flags 19 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/lang/classfile/constantpool/MethodTypeEntry +method name matches descriptor (Ljava/lang/constant/MethodTypeDesc;)Z flags 401 + +class name java/lang/classfile/constantpool/ModuleEntry +method name matches descriptor (Ljava/lang/constant/ModuleDesc;)Z flags 401 + +class name java/lang/classfile/constantpool/PackageEntry +method name matches descriptor (Ljava/lang/constant/PackageDesc;)Z flags 401 + +class name java/lang/classfile/constantpool/StringEntry +method name equalsString descriptor (Ljava/lang/String;)Z flags 401 + +class name java/lang/classfile/constantpool/Utf8Entry +method name isFieldType descriptor (Ljava/lang/constant/ClassDesc;)Z flags 401 +method name isMethodType descriptor (Ljava/lang/constant/MethodTypeDesc;)Z flags 401 + +class name java/lang/constant/DynamicCallSiteDesc +header extends java/lang/Object flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/lang/foreign/Linker$Option +header extends java/lang/Object nestHost java/lang/foreign/Linker sealed true permittedSubclasses jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl flags 601 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName FirstVariadicArg flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$CaptureCallState outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName CaptureCallState flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$Critical outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName Critical flags 4019 +innerclass innerClass java/lang/foreign/Linker$Option outerClass java/lang/foreign/Linker innerClassName Option flags 609 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName LinkerOptionImpl flags 609 + +class name java/lang/invoke/MethodHandleProxies +header extends java/lang/Object flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +innerclass innerClass java/lang/classfile/ClassFile$Option outerClass java/lang/classfile/ClassFile innerClassName Option flags 609 +innerclass innerClass java/lang/classfile/ClassFile$ClassHierarchyResolverOption outerClass java/lang/classfile/ClassFile innerClassName ClassHierarchyResolverOption flags 609 +innerclass innerClass java/lang/module/ModuleDescriptor$Modifier outerClass java/lang/module/ModuleDescriptor innerClassName Modifier flags 4019 +innerclass innerClass java/lang/module/ModuleDescriptor$Builder outerClass java/lang/module/ModuleDescriptor innerClassName Builder flags 19 +innerclass innerClass java/lang/classfile/CodeBuilder$CatchBuilder outerClass java/lang/classfile/CodeBuilder innerClassName CatchBuilder flags 609 +innerclass innerClass java/lang/classfile/CodeBuilder$BlockCodeBuilder outerClass java/lang/classfile/CodeBuilder innerClassName BlockCodeBuilder flags 609 +innerclass innerClass java/lang/invoke/TypeDescriptor$OfField outerClass java/lang/invoke/TypeDescriptor innerClassName OfField flags 609 + +class name java/lang/invoke/MethodHandles +header extends java/lang/Object nestMembers java/lang/invoke/MethodHandles$Lookup,java/lang/invoke/MethodHandles$Lookup$ClassOption flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +innerclass innerClass java/lang/invoke/VarHandle$AccessMode outerClass java/lang/invoke/VarHandle innerClassName AccessMode flags 4019 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup$ClassOption outerClass java/lang/invoke/MethodHandles$Lookup innerClassName ClassOption flags 4019 + +class name java/lang/invoke/SegmentVarHandle +header extends java/lang/invoke/VarHandle flags 30 +method name withInvokeExactBehavior descriptor ()Ljava/lang/invoke/SegmentVarHandle; flags 1 +method name withInvokeBehavior descriptor ()Ljava/lang/invoke/SegmentVarHandle; flags 1 +method name withInvokeBehavior descriptor ()Ljava/lang/invoke/VarHandle; flags 1041 +method name withInvokeExactBehavior descriptor ()Ljava/lang/invoke/VarHandle; flags 1041 + +class name java/lang/invoke/VarHandle +header extends java/lang/Object implements java/lang/constant/Constable nestMembers java/lang/invoke/VarHandle$VarHandleDesc,java/lang/invoke/VarHandle$AccessMode sealed true permittedSubclasses java/lang/invoke/IndirectVarHandle,java/lang/invoke/LazyInitializingVarHandle,java/lang/invoke/SegmentVarHandle,java/lang/invoke/VarHandleByteArrayAsChars$ByteArrayViewVarHandle,java/lang/invoke/VarHandleByteArrayAsDoubles$ByteArrayViewVarHandle,java/lang/invoke/VarHandleByteArrayAsFloats$ByteArrayViewVarHandle,java/lang/invoke/VarHandleByteArrayAsInts$ByteArrayViewVarHandle,java/lang/invoke/VarHandleByteArrayAsLongs$ByteArrayViewVarHandle,java/lang/invoke/VarHandleByteArrayAsShorts$ByteArrayViewVarHandle,java/lang/invoke/VarHandleBooleans$Array,java/lang/invoke/VarHandleBooleans$FieldInstanceReadOnly,java/lang/invoke/VarHandleBooleans$FieldStaticReadOnly,java/lang/invoke/VarHandleBytes$Array,java/lang/invoke/VarHandleBytes$FieldInstanceReadOnly,java/lang/invoke/VarHandleBytes$FieldStaticReadOnly,java/lang/invoke/VarHandleChars$Array,java/lang/invoke/VarHandleChars$FieldInstanceReadOnly,java/lang/invoke/VarHandleChars$FieldStaticReadOnly,java/lang/invoke/VarHandleDoubles$Array,java/lang/invoke/VarHandleDoubles$FieldInstanceReadOnly,java/lang/invoke/VarHandleDoubles$FieldStaticReadOnly,java/lang/invoke/VarHandleFloats$Array,java/lang/invoke/VarHandleFloats$FieldInstanceReadOnly,java/lang/invoke/VarHandleFloats$FieldStaticReadOnly,java/lang/invoke/VarHandleInts$Array,java/lang/invoke/VarHandleInts$FieldInstanceReadOnly,java/lang/invoke/VarHandleInts$FieldStaticReadOnly,java/lang/invoke/VarHandleLongs$Array,java/lang/invoke/VarHandleLongs$FieldInstanceReadOnly,java/lang/invoke/VarHandleLongs$FieldStaticReadOnly,java/lang/invoke/VarHandleReferences$Array,java/lang/invoke/VarHandleReferences$FieldInstanceReadOnly,java/lang/invoke/VarHandleReferences$FieldStaticReadOnly,java/lang/invoke/VarHandleShorts$Array,java/lang/invoke/VarHandleShorts$FieldInstanceReadOnly,java/lang/invoke/VarHandleShorts$FieldStaticReadOnly flags 421 +innerclass innerClass java/lang/invoke/VarHandle$AccessMode outerClass java/lang/invoke/VarHandle innerClassName AccessMode flags 4019 +innerclass innerClass java/lang/invoke/VarHandle$VarHandleDesc outerClass java/lang/invoke/VarHandle innerClassName VarHandleDesc flags 19 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsChars$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsChars innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsDoubles$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsDoubles innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsFloats$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsFloats innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsInts$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsInts innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsLongs$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsLongs innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleByteArrayAsShorts$ByteArrayViewVarHandle outerClass java/lang/invoke/VarHandleByteArrayAsShorts innerClassName ByteArrayViewVarHandle flags 408 +innerclass innerClass java/lang/invoke/VarHandleBooleans$Array outerClass java/lang/invoke/VarHandleBooleans innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleBooleans$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleBooleans innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleBooleans$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleBooleans innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleBytes$Array outerClass java/lang/invoke/VarHandleBytes innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleBytes$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleBytes innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleBytes$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleBytes innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleChars$Array outerClass java/lang/invoke/VarHandleChars innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleChars$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleChars innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleChars$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleChars innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleDoubles$Array outerClass java/lang/invoke/VarHandleDoubles innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleDoubles$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleDoubles innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleDoubles$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleDoubles innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleFloats$Array outerClass java/lang/invoke/VarHandleFloats innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleFloats$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleFloats innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleFloats$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleFloats innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleInts$Array outerClass java/lang/invoke/VarHandleInts innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleInts$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleInts innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleInts$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleInts innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleLongs$Array outerClass java/lang/invoke/VarHandleLongs innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleLongs$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleLongs innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleLongs$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleLongs innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleReferences$Array outerClass java/lang/invoke/VarHandleReferences innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleReferences$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleReferences innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleReferences$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleReferences innerClassName FieldStaticReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleShorts$Array outerClass java/lang/invoke/VarHandleShorts innerClassName Array flags 18 +innerclass innerClass java/lang/invoke/VarHandleShorts$FieldInstanceReadOnly outerClass java/lang/invoke/VarHandleShorts innerClassName FieldInstanceReadOnly flags 8 +innerclass innerClass java/lang/invoke/VarHandleShorts$FieldStaticReadOnly outerClass java/lang/invoke/VarHandleShorts innerClassName FieldStaticReadOnly flags 8 + +-class name java/lang/invoke/VarHandleSegmentAsBytes + +-class name java/lang/invoke/VarHandleSegmentAsChars + +-class name java/lang/invoke/VarHandleSegmentAsDoubles + +-class name java/lang/invoke/VarHandleSegmentAsFloats + +-class name java/lang/invoke/VarHandleSegmentAsInts + +-class name java/lang/invoke/VarHandleSegmentAsLongs + +-class name java/lang/invoke/VarHandleSegmentAsShorts + +-class name java/lang/invoke/VarHandleSegmentViewBase + +class name java/lang/module/ModuleDescriptor +header extends java/lang/Object implements java/lang/Comparable nestMembers java/lang/module/ModuleDescriptor$Builder,java/lang/module/ModuleDescriptor$Version,java/lang/module/ModuleDescriptor$Provides,java/lang/module/ModuleDescriptor$Opens,java/lang/module/ModuleDescriptor$Opens$Modifier,java/lang/module/ModuleDescriptor$Exports,java/lang/module/ModuleDescriptor$Exports$Modifier,java/lang/module/ModuleDescriptor$Requires,java/lang/module/ModuleDescriptor$Requires$Modifier,java/lang/module/ModuleDescriptor$Modifier flags 31 signature Ljava/lang/Object;Ljava/lang/Comparable; +innerclass innerClass java/lang/module/ModuleDescriptor$Version outerClass java/lang/module/ModuleDescriptor innerClassName Version flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Modifier outerClass java/lang/module/ModuleDescriptor innerClassName Modifier flags 4019 +innerclass innerClass java/lang/reflect/AccessFlag$Location outerClass java/lang/reflect/AccessFlag innerClassName Location flags 4019 +innerclass innerClass java/lang/module/ModuleDescriptor$Builder outerClass java/lang/module/ModuleDescriptor innerClassName Builder flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Provides outerClass java/lang/module/ModuleDescriptor innerClassName Provides flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Opens outerClass java/lang/module/ModuleDescriptor innerClassName Opens flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Exports outerClass java/lang/module/ModuleDescriptor innerClassName Exports flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Requires outerClass java/lang/module/ModuleDescriptor innerClassName Requires flags 19 +innerclass innerClass java/lang/module/ModuleDescriptor$Opens$Modifier outerClass java/lang/module/ModuleDescriptor$Opens innerClassName Modifier flags 4019 +innerclass innerClass java/lang/module/ModuleDescriptor$Exports$Modifier outerClass java/lang/module/ModuleDescriptor$Exports innerClassName Modifier flags 4019 +innerclass innerClass java/lang/module/ModuleDescriptor$Requires$Modifier outerClass java/lang/module/ModuleDescriptor$Requires innerClassName Modifier flags 4019 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/lang/ref/Cleaner +-method name register descriptor (Ljava/lang/Object;Ljava/lang/Runnable;)Ljava/lang/ref/Cleaner$Cleanable; +method name register descriptor (Ljava/lang/Object;Ljava/lang/Runnable;)Ljava/lang/ref/Cleaner$Cleanable; flags 1 runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;;; + +class name java/lang/ref/PhantomReference +header extends java/lang/ref/Reference flags 21 signature Ljava/lang/ref/Reference; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} +-method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V +method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V flags 1 signature (TT;Ljava/lang/ref/ReferenceQueue<-TT;>;)V runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;;; + +class name java/lang/ref/Reference +header extends java/lang/Object sealed true permittedSubclasses java/lang/ref/PhantomReference,java/lang/ref/SoftReference,java/lang/ref/WeakReference,java/lang/ref/FinalReference flags 421 signature Ljava/lang/Object; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} + +class name java/lang/ref/ReferenceQueue +header extends java/lang/Object flags 21 signature Ljava/lang/Object; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} + +class name java/lang/ref/SoftReference +header extends java/lang/ref/Reference flags 21 signature Ljava/lang/ref/Reference; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} +-method name descriptor (Ljava/lang/Object;)V +-method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V +method name descriptor (Ljava/lang/Object;)V flags 1 signature (TT;)V runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;; +method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V flags 1 signature (TT;Ljava/lang/ref/ReferenceQueue<-TT;>;)V runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;;; + +class name java/lang/ref/WeakReference +header extends java/lang/ref/Reference flags 21 signature Ljava/lang/ref/Reference; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} +-method name descriptor (Ljava/lang/Object;)V +-method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V +method name descriptor (Ljava/lang/Object;)V flags 1 signature (TT;)V runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;; +method name descriptor (Ljava/lang/Object;Ljava/lang/ref/ReferenceQueue;)V flags 1 signature (TT;Ljava/lang/ref/ReferenceQueue<-TT;>;)V runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;;; + +class name java/lang/reflect/AccessFlag +header extends java/lang/Enum nestMembers java/lang/reflect/AccessFlag$Location flags 4031 signature Ljava/lang/Enum; +innerclass innerClass java/lang/reflect/AccessFlag$Location outerClass java/lang/reflect/AccessFlag innerClassName Location flags 4019 +innerclass innerClass java/util/Map$Entry outerClass java/util/Map innerClassName Entry flags 609 +method name maskToAccessFlags descriptor (ILjava/lang/reflect/AccessFlag$Location;Ljava/lang/reflect/ClassFileFormatVersion;)Ljava/util/Set; flags 9 signature (ILjava/lang/reflect/AccessFlag$Location;Ljava/lang/reflect/ClassFileFormatVersion;)Ljava/util/Set; + +class name java/lang/reflect/AccessFlag$Location +header extends java/lang/Enum nestHost java/lang/reflect/AccessFlag flags 4031 signature Ljava/lang/Enum; +innerclass innerClass java/lang/reflect/AccessFlag$Location outerClass java/lang/reflect/AccessFlag innerClassName Location flags 4019 +innerclass innerClass java/util/Map$Entry outerClass java/util/Map innerClassName Entry flags 609 +method name flagsMask descriptor ()I flags 1 +method name flagsMask descriptor (Ljava/lang/reflect/ClassFileFormatVersion;)I flags 1 +method name flags descriptor ()Ljava/util/Set; flags 1 signature ()Ljava/util/Set; +method name flags descriptor (Ljava/lang/reflect/ClassFileFormatVersion;)Ljava/util/Set; flags 1 signature (Ljava/lang/reflect/ClassFileFormatVersion;)Ljava/util/Set; + +class name java/lang/reflect/ClassFileFormatVersion +field name RELEASE_25 descriptor Ljava/lang/reflect/ClassFileFormatVersion; flags 4019 + +class name java/lang/reflect/Modifier +header extends java/lang/Object flags 31 + +class name java/lang/reflect/ReflectPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/lang/runtime/ObjectMethods +header extends java/lang/Object flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/lang/runtime/SwitchBootstraps +header extends java/lang/Object flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +innerclass innerClass java/lang/Enum$EnumDesc outerClass java/lang/Enum innerClassName EnumDesc flags 19 +innerclass innerClass java/lang/classfile/ClassFile$Option outerClass java/lang/classfile/ClassFile innerClassName Option flags 609 +innerclass innerClass java/lang/classfile/ClassFile$StackMapsOption outerClass java/lang/classfile/ClassFile innerClassName StackMapsOption flags 4019 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup$ClassOption outerClass java/lang/invoke/MethodHandles$Lookup innerClassName ClassOption flags 4019 +innerclass innerClass java/lang/classfile/attribute/StackMapFrameInfo$SimpleVerificationTypeInfo outerClass java/lang/classfile/attribute/StackMapFrameInfo innerClassName SimpleVerificationTypeInfo flags 4019 +innerclass innerClass java/lang/classfile/attribute/StackMapFrameInfo$ObjectVerificationTypeInfo outerClass java/lang/classfile/attribute/StackMapFrameInfo innerClassName ObjectVerificationTypeInfo flags 609 +innerclass innerClass java/lang/classfile/attribute/StackMapFrameInfo$VerificationTypeInfo outerClass java/lang/classfile/attribute/StackMapFrameInfo innerClassName VerificationTypeInfo flags 609 + +class name java/net/HttpURLConnection +-method name getPermission descriptor ()Ljava/security/Permission; +method name getPermission descriptor ()Ljava/security/Permission; thrownTypes java/io/IOException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/net/InterfaceAddress +header extends java/lang/Object flags 31 + +class name java/net/NetPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/net/URLConnection +-method name getPermission descriptor ()Ljava/security/Permission; +method name getPermission descriptor ()Ljava/security/Permission; thrownTypes java/io/IOException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/net/URLDecoder +header extends java/lang/Object flags 31 + +class name java/net/URLEncoder +header extends java/lang/Object flags 31 + +class name java/net/URLPermission +header extends java/security/Permission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/io/ObjectInputStream$GetField outerClass java/io/ObjectInputStream innerClassName GetField flags 409 + +class name java/nio/CharBuffer +method name getChars descriptor (II[CI)V flags 1 + +class name java/nio/charset/CoderResult +header extends java/lang/Object flags 31 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/nio/charset/CodingErrorAction +header extends java/lang/Object flags 31 + +class name java/nio/file/LinkPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/security/DrbgParameters +header extends java/lang/Object nestMembers java/security/DrbgParameters$Reseed,java/security/DrbgParameters$NextBytes,java/security/DrbgParameters$Instantiation,java/security/DrbgParameters$Capability flags 31 +innerclass innerClass java/security/DrbgParameters$Instantiation outerClass java/security/DrbgParameters innerClassName Instantiation flags 19 +innerclass innerClass java/security/DrbgParameters$Capability outerClass java/security/DrbgParameters innerClassName Capability flags 4019 +innerclass innerClass java/security/DrbgParameters$NextBytes outerClass java/security/DrbgParameters innerClassName NextBytes flags 19 +innerclass innerClass java/security/DrbgParameters$Reseed outerClass java/security/DrbgParameters innerClassName Reseed flags 19 + +class name java/security/SecurityPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/security/UnresolvedPermission +header extends java/security/Permission implements java/io/Serializable flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/time/ZoneOffset +header extends java/time/ZoneId implements java/time/temporal/TemporalAccessor,java/time/temporal/TemporalAdjuster,java/lang/Comparable,java/io/Serializable flags 31 signature Ljava/time/ZoneId;Ljava/time/temporal/TemporalAccessor;Ljava/time/temporal/TemporalAdjuster;Ljava/lang/Comparable;Ljava/io/Serializable; runtimeAnnotations @Ljdk/internal/ValueBased; + +class name java/time/format/DecimalStyle +header extends java/lang/Object flags 31 +innerclass innerClass java/util/Locale$Category outerClass java/util/Locale innerClassName Category flags 4019 + +class name java/util/Base64 +header extends java/lang/Object nestMembers java/util/Base64$Decoder,java/util/Base64$Encoder flags 31 +innerclass innerClass java/util/Base64$Encoder outerClass java/util/Base64 innerClassName Encoder flags 9 +innerclass innerClass java/util/Base64$Decoder outerClass java/util/Base64 innerClassName Decoder flags 9 + +class name java/util/Collections +header extends java/lang/Object flags 31 + +class name java/util/Currency +method name availableCurrencies descriptor ()Ljava/util/stream/Stream; flags 9 signature ()Ljava/util/stream/Stream; + +class name java/util/FormattableFlags +header extends java/lang/Object flags 31 + +class name java/util/PropertyPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name java/util/TimeZone +-method name getTimeZone descriptor (Ljava/lang/String;)Ljava/util/TimeZone; +-method name getAvailableIDs descriptor (I)[Ljava/lang/String; +-method name getAvailableIDs descriptor ()[Ljava/lang/String; +method name getTimeZone descriptor (Ljava/lang/String;)Ljava/util/TimeZone; flags 9 +method name getAvailableIDs descriptor (I)[Ljava/lang/String; flags 9 +method name getAvailableIDs descriptor ()[Ljava/lang/String; flags 9 +method name availableIDs descriptor (I)Ljava/util/stream/Stream; flags 9 signature (I)Ljava/util/stream/Stream; +method name availableIDs descriptor ()Ljava/util/stream/Stream; flags 9 signature ()Ljava/util/stream/Stream; + +class name java/util/WeakHashMap +header extends java/util/AbstractMap implements java/util/Map flags 21 signature Ljava/util/AbstractMap;Ljava/util/Map; runtimeTypeAnnotations @Ljdk/internal/RequiresIdentity;{typeParameterIndex=I0,targetType="CLASS_TYPE_PARAMETER"} +innerclass innerClass java/util/Map$Entry outerClass java/util/Map innerClassName Entry flags 609 +-method name put descriptor (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +method name put descriptor (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; flags 1 signature (TK;TV;)TV; runtimeParameterAnnotations @Ljdk/internal/RequiresIdentity;;; + +class name java/util/concurrent/Executors +header extends java/lang/Object flags 31 +innerclass innerClass java/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory outerClass java/util/concurrent/ForkJoinPool innerClassName ForkJoinWorkerThreadFactory flags 609 +innerclass innerClass java/lang/Thread$UncaughtExceptionHandler outerClass java/lang/Thread innerClassName UncaughtExceptionHandler flags 609 +innerclass innerClass java/lang/Thread$Builder outerClass java/lang/Thread innerClassName Builder flags 609 +innerclass innerClass java/lang/Thread$Builder$OfVirtual outerClass java/lang/Thread$Builder innerClassName OfVirtual flags 609 + +class name java/util/concurrent/ForkJoinPool +header extends java/util/concurrent/AbstractExecutorService implements java/util/concurrent/ScheduledExecutorService nestMembers java/util/concurrent/ForkJoinPool$ManagedBlocker,java/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory flags 21 +innerclass innerClass java/util/concurrent/ForkJoinPool$ForkJoinWorkerThreadFactory outerClass java/util/concurrent/ForkJoinPool innerClassName ForkJoinWorkerThreadFactory flags 609 +innerclass innerClass java/util/concurrent/ForkJoinPool$ManagedBlocker outerClass java/util/concurrent/ForkJoinPool innerClassName ManagedBlocker flags 609 +innerclass innerClass java/lang/Thread$UncaughtExceptionHandler outerClass java/lang/Thread innerClassName UncaughtExceptionHandler flags 609 +method name schedule descriptor (Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; flags 1 signature (Ljava/lang/Runnable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture<*>; +method name schedule descriptor (Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; flags 1 signature (Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; +method name scheduleAtFixedRate descriptor (Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; flags 1 signature (Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture<*>; +method name scheduleWithFixedDelay descriptor (Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture; flags 1 signature (Ljava/lang/Runnable;JJLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/ScheduledFuture<*>; +method name submitWithTimeout descriptor (Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;Ljava/util/function/Consumer;)Ljava/util/concurrent/ForkJoinTask; flags 1 signature (Ljava/util/concurrent/Callable;JLjava/util/concurrent/TimeUnit;Ljava/util/function/Consumer<-Ljava/util/concurrent/ForkJoinTask;>;)Ljava/util/concurrent/ForkJoinTask; +method name cancelDelayedTasksOnShutdown descriptor ()V flags 1 +method name getDelayedTaskCount descriptor ()J flags 1 + +class name java/util/concurrent/StructuredTaskScope +header extends java/lang/Object implements java/lang/AutoCloseable nestMembers java/util/concurrent/StructuredTaskScope$TimeoutException,java/util/concurrent/StructuredTaskScope$FailedException,java/util/concurrent/StructuredTaskScope$Configuration,java/util/concurrent/StructuredTaskScope$Joiner,java/util/concurrent/StructuredTaskScope$Subtask,java/util/concurrent/StructuredTaskScope$Subtask$State sealed true permittedSubclasses java/util/concurrent/StructuredTaskScopeImpl flags 601 signature Ljava/lang/Object;Ljava/lang/AutoCloseable; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) +innerclass innerClass java/util/concurrent/StructuredTaskScope$Joiner outerClass java/util/concurrent/StructuredTaskScope innerClassName Joiner flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$TimeoutException outerClass java/util/concurrent/StructuredTaskScope innerClassName TimeoutException flags 19 +innerclass innerClass java/util/concurrent/StructuredTaskScope$FailedException outerClass java/util/concurrent/StructuredTaskScope innerClassName FailedException flags 19 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Configuration outerClass java/util/concurrent/StructuredTaskScope innerClassName Configuration flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask outerClass java/util/concurrent/StructuredTaskScope innerClassName Subtask flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask$State outerClass java/util/concurrent/StructuredTaskScope$Subtask innerClassName State flags 4019 +-method name descriptor (Ljava/lang/String;Ljava/util/concurrent/ThreadFactory;)V +-method name descriptor ()V +-method name ensureOwnerAndJoined descriptor ()V +-method name handleComplete descriptor (Ljava/util/concurrent/StructuredTaskScope$Subtask;)V +-method name fork descriptor (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; +-method name join descriptor ()Ljava/util/concurrent/StructuredTaskScope; +-method name joinUntil descriptor (Ljava/time/Instant;)Ljava/util/concurrent/StructuredTaskScope; +-method name shutdown descriptor ()V +-method name isShutdown descriptor ()Z +-method name close descriptor ()V +-method name toString descriptor ()Ljava/lang/String; +method name open descriptor (Ljava/util/concurrent/StructuredTaskScope$Joiner;Ljava/util/function/Function;)Ljava/util/concurrent/StructuredTaskScope; flags 9 signature (Ljava/util/concurrent/StructuredTaskScope$Joiner<-TT;+TR;>;Ljava/util/function/Function;)Ljava/util/concurrent/StructuredTaskScope; +method name open descriptor (Ljava/util/concurrent/StructuredTaskScope$Joiner;)Ljava/util/concurrent/StructuredTaskScope; flags 9 signature (Ljava/util/concurrent/StructuredTaskScope$Joiner<-TT;+TR;>;)Ljava/util/concurrent/StructuredTaskScope; +method name open descriptor ()Ljava/util/concurrent/StructuredTaskScope; flags 9 signature ()Ljava/util/concurrent/StructuredTaskScope; +method name fork descriptor (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; flags 401 signature (Ljava/util/concurrent/Callable<+TU;>;)Ljava/util/concurrent/StructuredTaskScope$Subtask; +method name fork descriptor (Ljava/lang/Runnable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; flags 401 signature (Ljava/lang/Runnable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; +method name join descriptor ()Ljava/lang/Object; thrownTypes java/lang/InterruptedException flags 401 signature ()TR; +method name isCancelled descriptor ()Z flags 401 +method name close descriptor ()V flags 401 + +class name java/util/concurrent/StructuredTaskScope$Configuration +header extends java/lang/Object nestHost java/util/concurrent/StructuredTaskScope sealed true permittedSubclasses java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl flags 601 classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) +innerclass innerClass java/util/concurrent/StructuredTaskScope$Configuration outerClass java/util/concurrent/StructuredTaskScope innerClassName Configuration flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName ConfigImpl flags 18 +method name withThreadFactory descriptor (Ljava/util/concurrent/ThreadFactory;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 401 +method name withName descriptor (Ljava/lang/String;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 401 +method name withTimeout descriptor (Ljava/time/Duration;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 401 + +class name java/util/concurrent/StructuredTaskScope$FailedException +header extends java/lang/RuntimeException nestHost java/util/concurrent/StructuredTaskScope flags 31 classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) +innerclass innerClass java/util/concurrent/StructuredTaskScope$FailedException outerClass java/util/concurrent/StructuredTaskScope innerClassName FailedException flags 19 + +class name java/util/concurrent/StructuredTaskScope$Joiner +header extends java/lang/Object nestHost java/util/concurrent/StructuredTaskScope flags 601 signature Ljava/lang/Object; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) runtimeAnnotations @Ljava/lang/FunctionalInterface; +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask outerClass java/util/concurrent/StructuredTaskScope innerClassName Subtask flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask$State outerClass java/util/concurrent/StructuredTaskScope$Subtask innerClassName State flags 4019 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Joiner outerClass java/util/concurrent/StructuredTaskScope innerClassName Joiner flags 609 +method name onFork descriptor (Ljava/util/concurrent/StructuredTaskScope$Subtask;)Z flags 1 signature (Ljava/util/concurrent/StructuredTaskScope$Subtask<+TT;>;)Z +method name onComplete descriptor (Ljava/util/concurrent/StructuredTaskScope$Subtask;)Z flags 1 signature (Ljava/util/concurrent/StructuredTaskScope$Subtask<+TT;>;)Z +method name result descriptor ()Ljava/lang/Object; thrownTypes java/lang/Throwable flags 401 signature ()TR; +method name allSuccessfulOrThrow descriptor ()Ljava/util/concurrent/StructuredTaskScope$Joiner; flags 9 signature ()Ljava/util/concurrent/StructuredTaskScope$Joiner;>;>; +method name anySuccessfulResultOrThrow descriptor ()Ljava/util/concurrent/StructuredTaskScope$Joiner; flags 9 signature ()Ljava/util/concurrent/StructuredTaskScope$Joiner; +method name awaitAllSuccessfulOrThrow descriptor ()Ljava/util/concurrent/StructuredTaskScope$Joiner; flags 9 signature ()Ljava/util/concurrent/StructuredTaskScope$Joiner; +method name awaitAll descriptor ()Ljava/util/concurrent/StructuredTaskScope$Joiner; flags 9 signature ()Ljava/util/concurrent/StructuredTaskScope$Joiner; +method name allUntil descriptor (Ljava/util/function/Predicate;)Ljava/util/concurrent/StructuredTaskScope$Joiner; flags 9 signature (Ljava/util/function/Predicate;>;)Ljava/util/concurrent/StructuredTaskScope$Joiner;>;>; + +-class name java/util/concurrent/StructuredTaskScope$ShutdownOnFailure + +-class name java/util/concurrent/StructuredTaskScope$ShutdownOnSuccess + +class name java/util/concurrent/StructuredTaskScope$Subtask +header extends java/lang/Object implements java/util/function/Supplier nestHost java/util/concurrent/StructuredTaskScope sealed true permittedSubclasses java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl flags 601 signature Ljava/lang/Object;Ljava/util/function/Supplier; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask outerClass java/util/concurrent/StructuredTaskScope innerClassName Subtask flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask$State outerClass java/util/concurrent/StructuredTaskScope$Subtask innerClassName State flags 4019 +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName SubtaskImpl flags 18 +-method name task descriptor ()Ljava/util/concurrent/Callable; + +-class name java/util/concurrent/StructuredTaskScope$SubtaskImpl + +class name java/util/concurrent/StructuredTaskScope$TimeoutException +header extends java/lang/RuntimeException nestHost java/util/concurrent/StructuredTaskScope flags 31 classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;STRUCTURED_CONCURRENCY;) +innerclass innerClass java/util/concurrent/StructuredTaskScope$TimeoutException outerClass java/util/concurrent/StructuredTaskScope innerClassName TimeoutException flags 19 + +class name java/util/concurrent/StructuredTaskScopeImpl +header extends java/lang/Object implements java/util/concurrent/StructuredTaskScope nestMembers java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl,java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl flags 30 signature Ljava/lang/Object;Ljava/util/concurrent/StructuredTaskScope; +innerclass innerClass java/util/concurrent/StructuredTaskScope$Joiner outerClass java/util/concurrent/StructuredTaskScope innerClassName Joiner flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName ConfigImpl flags 18 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Configuration outerClass java/util/concurrent/StructuredTaskScope innerClassName Configuration flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName SubtaskImpl flags 18 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask outerClass java/util/concurrent/StructuredTaskScope innerClassName Subtask flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask$State outerClass java/util/concurrent/StructuredTaskScope$Subtask innerClassName State flags 4019 +innerclass innerClass java/util/concurrent/StructuredTaskScope$TimeoutException outerClass java/util/concurrent/StructuredTaskScope innerClassName TimeoutException flags 19 +innerclass innerClass java/util/concurrent/StructuredTaskScope$FailedException outerClass java/util/concurrent/StructuredTaskScope innerClassName FailedException flags 19 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +method name fork descriptor (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; flags 1 signature (Ljava/util/concurrent/Callable<+TU;>;)Ljava/util/concurrent/StructuredTaskScope$Subtask; +method name fork descriptor (Ljava/lang/Runnable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; flags 1 signature (Ljava/lang/Runnable;)Ljava/util/concurrent/StructuredTaskScope$Subtask; +method name join descriptor ()Ljava/lang/Object; thrownTypes java/lang/InterruptedException flags 1 signature ()TR; +method name isCancelled descriptor ()Z flags 1 +method name close descriptor ()V flags 1 +method name toString descriptor ()Ljava/lang/String; flags 1 + +class name java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl +header extends java/lang/Record implements java/util/concurrent/StructuredTaskScope$Configuration nestHost java/util/concurrent/StructuredTaskScopeImpl record true flags 30 +recordcomponent name threadFactory descriptor Ljava/util/concurrent/ThreadFactory; +recordcomponent name name descriptor Ljava/lang/String; +recordcomponent name timeout descriptor Ljava/time/Duration; +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$ConfigImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName ConfigImpl flags 18 +innerclass innerClass java/lang/Thread$Builder outerClass java/lang/Thread innerClassName Builder flags 609 +innerclass innerClass java/lang/Thread$Builder$OfVirtual outerClass java/lang/Thread$Builder innerClassName OfVirtual flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Configuration outerClass java/util/concurrent/StructuredTaskScope innerClassName Configuration flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +method name withThreadFactory descriptor (Ljava/util/concurrent/ThreadFactory;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 1 +method name withName descriptor (Ljava/lang/String;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 1 +method name withTimeout descriptor (Ljava/time/Duration;)Ljava/util/concurrent/StructuredTaskScope$Configuration; flags 1 +method name toString descriptor ()Ljava/lang/String; flags 11 +method name hashCode descriptor ()I flags 11 +method name equals descriptor (Ljava/lang/Object;)Z flags 11 +method name threadFactory descriptor ()Ljava/util/concurrent/ThreadFactory; flags 1 +method name name descriptor ()Ljava/lang/String; flags 1 +method name timeout descriptor ()Ljava/time/Duration; flags 1 + +class name java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl +header extends java/lang/Object implements java/util/concurrent/StructuredTaskScope$Subtask,java/lang/Runnable nestHost java/util/concurrent/StructuredTaskScopeImpl flags 30 signature Ljava/lang/Object;Ljava/util/concurrent/StructuredTaskScope$Subtask;Ljava/lang/Runnable; +innerclass innerClass java/util/concurrent/StructuredTaskScopeImpl$SubtaskImpl outerClass java/util/concurrent/StructuredTaskScopeImpl innerClassName SubtaskImpl flags 18 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask outerClass java/util/concurrent/StructuredTaskScope innerClassName Subtask flags 609 +innerclass innerClass java/util/concurrent/StructuredTaskScope$Subtask$State outerClass java/util/concurrent/StructuredTaskScope$Subtask innerClassName State flags 4019 +method name run descriptor ()V flags 1 +method name state descriptor ()Ljava/util/concurrent/StructuredTaskScope$Subtask$State; flags 1 +method name get descriptor ()Ljava/lang/Object; flags 1 signature ()TT; +method name exception descriptor ()Ljava/lang/Throwable; flags 1 +method name toString descriptor ()Ljava/lang/String; flags 1 + +class name java/util/concurrent/locks/LockSupport +header extends java/lang/Object flags 31 + +class name java/util/concurrent/locks/ReentrantReadWriteLock +header extends java/lang/Object implements java/util/concurrent/locks/ReadWriteLock,java/io/Serializable nestMembers java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock,java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock flags 21 +innerclass innerClass java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock outerClass java/util/concurrent/locks/ReentrantReadWriteLock innerClassName ReadLock flags 9 +innerclass innerClass java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock outerClass java/util/concurrent/locks/ReentrantReadWriteLock innerClassName WriteLock flags 9 +innerclass innerClass java/util/concurrent/locks/AbstractQueuedLongSynchronizer$ConditionObject outerClass java/util/concurrent/locks/AbstractQueuedLongSynchronizer innerClassName ConditionObject flags 1 + +class name java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock +header extends java/lang/Object implements java/util/concurrent/locks/Lock,java/io/Serializable nestHost java/util/concurrent/locks/ReentrantReadWriteLock flags 21 +innerclass innerClass java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock outerClass java/util/concurrent/locks/ReentrantReadWriteLock innerClassName WriteLock flags 9 +innerclass innerClass java/util/concurrent/locks/AbstractQueuedLongSynchronizer$ConditionObject outerClass java/util/concurrent/locks/AbstractQueuedLongSynchronizer innerClassName ConditionObject flags 1 + +class name java/util/zip/Deflater +header extends java/lang/Object implements java/lang/AutoCloseable flags 21 +method name close descriptor ()V flags 1 + +class name java/util/zip/Inflater +header extends java/lang/Object implements java/lang/AutoCloseable flags 21 +method name close descriptor ()V flags 1 + +class name javax/crypto/KDF +header extends java/lang/Object flags 31 +innerclass innerClass java/security/Provider$Service outerClass java/security/Provider innerClassName Service flags 9 + +class name javax/crypto/KDFParameters +header extends java/lang/Object flags 601 + +class name javax/crypto/KDFSpi +header extends java/lang/Object flags 421 + +class name javax/crypto/spec/HKDFParameterSpec +header extends java/lang/Object implements java/security/spec/AlgorithmParameterSpec nestMembers javax/crypto/spec/HKDFParameterSpec$ExtractThenExpand,javax/crypto/spec/HKDFParameterSpec$Expand,javax/crypto/spec/HKDFParameterSpec$Extract,javax/crypto/spec/HKDFParameterSpec$Builder flags 601 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Builder outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Builder flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Expand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Expand flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$ExtractThenExpand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName ExtractThenExpand flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Extract outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Extract flags 19 + +class name javax/crypto/spec/HKDFParameterSpec$Builder +header extends java/lang/Object nestHost javax/crypto/spec/HKDFParameterSpec flags 31 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Builder outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Builder flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Extract outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Extract flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$ExtractThenExpand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName ExtractThenExpand flags 19 + +class name javax/crypto/spec/HKDFParameterSpec$Expand +header extends java/lang/Object implements javax/crypto/spec/HKDFParameterSpec nestHost javax/crypto/spec/HKDFParameterSpec flags 31 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Expand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Expand flags 19 + +class name javax/crypto/spec/HKDFParameterSpec$Extract +header extends java/lang/Object implements javax/crypto/spec/HKDFParameterSpec nestHost javax/crypto/spec/HKDFParameterSpec flags 31 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Extract outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Extract flags 19 + +class name javax/crypto/spec/HKDFParameterSpec$ExtractThenExpand +header extends java/lang/Object implements javax/crypto/spec/HKDFParameterSpec nestHost javax/crypto/spec/HKDFParameterSpec flags 31 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$ExtractThenExpand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName ExtractThenExpand flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Extract outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Extract flags 19 +innerclass innerClass javax/crypto/spec/HKDFParameterSpec$Expand outerClass javax/crypto/spec/HKDFParameterSpec innerClassName Expand flags 19 + +class name javax/net/ssl/SSLPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name javax/security/auth/AuthPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name javax/security/auth/PrivateCredentialPermission +header extends java/security/Permission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl +method name matches descriptor (Ljava/lang/constant/ClassDesc;)Z flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$ConstantDynamicEntryImpl +field name sym descriptor Ljava/lang/constant/DynamicConstantDesc; flags 1 signature Ljava/lang/constant/DynamicConstantDesc<*>; runtimeAnnotations @Ljdk/internal/vm/annotation/Stable; +method name asSymbol descriptor ()Ljava/lang/constant/DynamicConstantDesc; flags 1 signature ()Ljava/lang/constant/DynamicConstantDesc<*>; + +class name jdk/internal/classfile/impl/AbstractPoolEntry$InvokeDynamicEntryImpl +field name sym descriptor Ljava/lang/constant/DynamicCallSiteDesc; flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/Stable; +method name asSymbol descriptor ()Ljava/lang/constant/DynamicCallSiteDesc; flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$MethodHandleEntryImpl +field name sym descriptor Ljava/lang/constant/DirectMethodHandleDesc; flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/Stable; + +class name jdk/internal/classfile/impl/AbstractPoolEntry$MethodTypeEntryImpl +method name matches descriptor (Ljava/lang/constant/MethodTypeDesc;)Z flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$ModuleEntryImpl +method name matches descriptor (Ljava/lang/constant/ModuleDesc;)Z flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$PackageEntryImpl +method name matches descriptor (Ljava/lang/constant/PackageDesc;)Z flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$StringEntryImpl +method name equalsString descriptor (Ljava/lang/String;)Z flags 1 + +class name jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl +method name isFieldType descriptor (Ljava/lang/constant/ClassDesc;)Z flags 1 +method name isMethodType descriptor (Ljava/lang/constant/MethodTypeDesc;)Z flags 1 + +class name jdk/internal/classfile/impl/BlockCodeBuilderImpl +header extends jdk/internal/classfile/impl/NonterminalCodeBuilder implements java/lang/classfile/CodeBuilder$BlockCodeBuilder flags 31 +innerclass innerClass java/lang/classfile/CodeBuilder$BlockCodeBuilder outerClass java/lang/classfile/CodeBuilder innerClassName BlockCodeBuilder flags 609 + +class name jdk/internal/classfile/impl/BoundAttribute$BoundStackMapTableAttribute +method name writeTo descriptor (Ljdk/internal/classfile/impl/BufWriterImpl;)V flags 1 + +class name jdk/internal/classfile/impl/BufWriterImpl +-method name setLabelContext descriptor (Ljdk/internal/classfile/impl/LabelContext;)V +method name setLabelContext descriptor (Ljdk/internal/classfile/impl/LabelContext;Z)V flags 1 +method name labelsMatch descriptor (Ljdk/internal/classfile/impl/LabelContext;)Z flags 1 + +class name jdk/internal/classfile/impl/ChainedClassBuilder +header extends java/lang/Object implements java/lang/classfile/ClassBuilder,java/util/function/Consumer flags 31 signature Ljava/lang/Object;Ljava/lang/classfile/ClassBuilder;Ljava/util/function/Consumer; + +class name jdk/internal/classfile/impl/ChainedFieldBuilder +header extends java/lang/Object implements java/lang/classfile/FieldBuilder flags 31 + +class name jdk/internal/classfile/impl/ChainedMethodBuilder +header extends java/lang/Object implements java/lang/classfile/MethodBuilder flags 31 + +class name jdk/internal/classfile/impl/CodeImpl +header extends jdk/internal/classfile/impl/BoundAttribute$BoundCodeAttribute implements jdk/internal/classfile/impl/LabelContext nestMembers jdk/internal/classfile/impl/CodeImpl$ExceptionHandlerAction flags 31 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundCodeAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundCodeAttribute flags 409 +innerclass innerClass jdk/internal/classfile/impl/CodeImpl$ExceptionHandlerAction outerClass jdk/internal/classfile/impl/CodeImpl innerClassName ExceptionHandlerAction flags 609 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundLineNumberTableAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundLineNumberTableAttribute flags 19 +innerclass innerClass java/lang/classfile/Opcode$Kind outerClass java/lang/classfile/Opcode innerClassName Kind flags 4019 +innerclass innerClass java/lang/classfile/instruction/DiscontinuedInstruction$JsrInstruction outerClass java/lang/classfile/instruction/DiscontinuedInstruction innerClassName JsrInstruction flags 609 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundCharacterRangeTableAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundCharacterRangeTableAttribute flags 19 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTableAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundLocalVariableTableAttribute flags 19 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTypeTableAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundLocalVariableTypeTableAttribute flags 19 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleTypeAnnotationsAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundRuntimeVisibleTypeAnnotationsAttribute flags 19 +innerclass innerClass jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleTypeAnnotationsAttribute outerClass jdk/internal/classfile/impl/BoundAttribute innerClassName BoundRuntimeInvisibleTypeAnnotationsAttribute flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundArgumentConstantInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundArgumentConstantInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundLoadConstantInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundLoadConstantInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundLoadInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundLoadInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundStoreInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundStoreInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundIncrementInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundIncrementInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundBranchInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundBranchInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundTableSwitchInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundTableSwitchInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundLookupSwitchInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundLookupSwitchInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundFieldInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundFieldInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundInvokeInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInterfaceInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundInvokeInterfaceInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeDynamicInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundInvokeDynamicInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundNewObjectInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundNewObjectInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundNewPrimitiveArrayInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundNewPrimitiveArrayInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundNewReferenceArrayInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundNewReferenceArrayInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundTypeCheckInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundTypeCheckInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundRetInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundRetInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundNewMultidimensionalArrayInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundNewMultidimensionalArrayInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$BoundJsrInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName BoundJsrInstruction flags 19 +innerclass innerClass java/lang/classfile/instruction/ConstantInstruction$IntrinsicConstantInstruction outerClass java/lang/classfile/instruction/ConstantInstruction innerClassName IntrinsicConstantInstruction flags 609 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName UnboundLoadInstruction flags 19 +innerclass innerClass jdk/internal/classfile/impl/AbstractInstruction$UnboundStoreInstruction outerClass jdk/internal/classfile/impl/AbstractInstruction innerClassName UnboundStoreInstruction flags 19 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name jdk/internal/classfile/impl/DirectCodeBuilder +method name withMaxs descriptor (Ljava/lang/classfile/CodeBuilder;II)V flags 9 + +class name jdk/internal/classfile/impl/LabelContext +method name canWriteDirect descriptor (Ljdk/internal/classfile/impl/LabelContext;)Z flags 1 + +class name jdk/internal/classfile/impl/NonterminalCodeBuilder +header extends java/lang/Object implements java/lang/classfile/CodeBuilder sealed true permittedSubclasses jdk/internal/classfile/impl/ChainedCodeBuilder,jdk/internal/classfile/impl/BlockCodeBuilderImpl flags 421 + +class name jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout +header extends jdk/internal/foreign/layout/AbstractLayout nestHost jdk/internal/foreign/layout/ValueLayouts sealed true permittedSubclasses jdk/internal/foreign/layout/ValueLayouts$OfBooleanImpl,jdk/internal/foreign/layout/ValueLayouts$OfByteImpl,jdk/internal/foreign/layout/ValueLayouts$OfCharImpl,jdk/internal/foreign/layout/ValueLayouts$OfShortImpl,jdk/internal/foreign/layout/ValueLayouts$OfIntImpl,jdk/internal/foreign/layout/ValueLayouts$OfFloatImpl,jdk/internal/foreign/layout/ValueLayouts$OfLongImpl,jdk/internal/foreign/layout/ValueLayouts$OfDoubleImpl,jdk/internal/foreign/layout/ValueLayouts$OfAddressImpl flags 420 signature ;:Ljava/lang/foreign/ValueLayout;>Ljdk/internal/foreign/layout/AbstractLayout; +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName AbstractValueLayout flags 408 +innerclass innerClass java/lang/foreign/MemoryLayout$PathElement outerClass java/lang/foreign/MemoryLayout innerClassName PathElement flags 609 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfBooleanImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfBooleanImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfByteImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfByteImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfCharImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfCharImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfShortImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfShortImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfIntImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfIntImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfFloatImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfFloatImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfLongImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfLongImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfDoubleImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfDoubleImpl flags 19 +innerclass innerClass jdk/internal/foreign/layout/ValueLayouts$OfAddressImpl outerClass jdk/internal/foreign/layout/ValueLayouts innerClassName OfAddressImpl flags 19 + +class name jdk/internal/lang/stable/StableValueImpl +header extends java/lang/Object implements java/lang/StableValue flags 31 signature Ljava/lang/Object;Ljava/lang/StableValue; +method name trySet descriptor (Ljava/lang/Object;)Z flags 1 signature (TT;)Z runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name setOrThrow descriptor (Ljava/lang/Object;)V flags 1 signature (TT;)V runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name orElseThrow descriptor ()Ljava/lang/Object; flags 1 signature ()TT; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name orElse descriptor (Ljava/lang/Object;)Ljava/lang/Object; flags 1 signature (TT;)TT; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name isSet descriptor ()Z flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name orElseSet descriptor (Ljava/util/function/Supplier;)Ljava/lang/Object; flags 1 signature (Ljava/util/function/Supplier<+TT;>;)TT; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name toString descriptor ()Ljava/lang/String; flags 1 +method name of descriptor ()Ljdk/internal/lang/stable/StableValueImpl; flags 9 signature ()Ljdk/internal/lang/stable/StableValueImpl; +method name wrappedContentsAcquire descriptor ()Ljava/lang/Object; flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; + +class name jdk/internal/vm/vector/VectorSupport +header extends java/lang/Object nestMembers jdk/internal/vm/vector/VectorSupport$VectorMask,jdk/internal/vm/vector/VectorSupport$VectorShuffle,jdk/internal/vm/vector/VectorSupport$Vector,jdk/internal/vm/vector/VectorSupport$VectorPayload,jdk/internal/vm/vector/VectorSupport$VectorSpecies flags 21 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorSpecies outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorSpecies flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorPayload outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorPayload flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorMask outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorMask flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$Vector outerClass jdk/internal/vm/vector/VectorSupport innerClassName Vector flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorShuffle outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorShuffle flags 9 +-method name isNonCapturingLambda descriptor (Ljava/lang/Object;)Z +-method name shuffleIota descriptor (Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorSpecies;IIIILjdk/internal/vm/vector/VectorSupport$ShuffleIotaOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorShuffle; +-method name shuffleToVector descriptor (Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;ILjdk/internal/vm/vector/VectorSupport$ShuffleToVectorOperation;)Ljdk/internal/vm/vector/VectorSupport$Vector; +-method name wrapShuffleIndexes descriptor (Ljava/lang/Class;Ljava/lang/Class;Ljdk/internal/vm/vector/VectorSupport$VectorShuffle;ILjdk/internal/vm/vector/VectorSupport$WrapShuffleIndexesOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorShuffle; +field name VECTOR_OP_MATHLIB_FIRST descriptor I constantValue 101 flags 19 +field name VECTOR_OP_MATHLIB_LAST descriptor I constantValue 118 flags 19 +method name libraryUnaryOp descriptor (JLjava/lang/Class;Ljava/lang/Class;ILjava/lang/String;Ljdk/internal/vm/vector/VectorSupport$Vector;Ljdk/internal/vm/vector/VectorSupport$UnaryOperation;)Ljdk/internal/vm/vector/VectorSupport$Vector; flags 9 signature ;E:Ljava/lang/Object;>(JLjava/lang/Class<+TV;>;Ljava/lang/Class;ILjava/lang/String;TV;Ljdk/internal/vm/vector/VectorSupport$UnaryOperation;)TV; runtimeAnnotations @Ljdk/internal/vm/annotation/IntrinsicCandidate; +method name libraryBinaryOp descriptor (JLjava/lang/Class;Ljava/lang/Class;ILjava/lang/String;Ljdk/internal/vm/vector/VectorSupport$VectorPayload;Ljdk/internal/vm/vector/VectorSupport$VectorPayload;Ljdk/internal/vm/vector/VectorSupport$BinaryOperation;)Ljdk/internal/vm/vector/VectorSupport$VectorPayload; flags 9 signature (JLjava/lang/Class<+TV;>;Ljava/lang/Class;ILjava/lang/String;TV;TV;Ljdk/internal/vm/vector/VectorSupport$BinaryOperation;)TV; runtimeAnnotations @Ljdk/internal/vm/annotation/IntrinsicCandidate; +method name getCPUFeatures descriptor ()Ljava/lang/String; flags 109 +method name loadNativeLibrary descriptor (Ljava/lang/String;)V flags 9 + diff --git a/src/jdk.compiler/share/data/symbols/java.compiler-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.compiler-P.sym.txt new file mode 100644 index 00000000000..2bb9c846013 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.compiler-P.sym.txt @@ -0,0 +1,85 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name javax/lang/model/SourceVersion +field name RELEASE_25 descriptor Ljavax/lang/model/SourceVersion; flags 4019 + +class name javax/lang/model/util/AbstractAnnotationValueVisitor14 +header extends javax/lang/model/util/AbstractAnnotationValueVisitor9 flags 421 signature Ljavax/lang/model/util/AbstractAnnotationValueVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/AbstractAnnotationValueVisitorPreview +header extends javax/lang/model/util/AbstractAnnotationValueVisitor14 flags 421 signature Ljavax/lang/model/util/AbstractAnnotationValueVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/AbstractElementVisitor14 +header extends javax/lang/model/util/AbstractElementVisitor9 flags 421 signature Ljavax/lang/model/util/AbstractElementVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/AbstractElementVisitorPreview +header extends javax/lang/model/util/AbstractElementVisitor14 flags 421 signature Ljavax/lang/model/util/AbstractElementVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/AbstractTypeVisitor14 +header extends javax/lang/model/util/AbstractTypeVisitor9 flags 421 signature Ljavax/lang/model/util/AbstractTypeVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/AbstractTypeVisitorPreview +header extends javax/lang/model/util/AbstractTypeVisitor14 flags 421 signature Ljavax/lang/model/util/AbstractTypeVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/ElementKindVisitor14 +header extends javax/lang/model/util/ElementKindVisitor9 flags 21 signature Ljavax/lang/model/util/ElementKindVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/ElementKindVisitorPreview +header extends javax/lang/model/util/ElementKindVisitor14 flags 21 signature Ljavax/lang/model/util/ElementKindVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/ElementScanner14 +header extends javax/lang/model/util/ElementScanner9 flags 21 signature Ljavax/lang/model/util/ElementScanner9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/ElementScannerPreview +header extends javax/lang/model/util/ElementScanner14 flags 21 signature Ljavax/lang/model/util/ElementScanner14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleAnnotationValueVisitor14 +header extends javax/lang/model/util/SimpleAnnotationValueVisitor9 flags 21 signature Ljavax/lang/model/util/SimpleAnnotationValueVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleAnnotationValueVisitorPreview +header extends javax/lang/model/util/SimpleAnnotationValueVisitor14 flags 21 signature Ljavax/lang/model/util/SimpleAnnotationValueVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleElementVisitor14 +header extends javax/lang/model/util/SimpleElementVisitor9 flags 21 signature Ljavax/lang/model/util/SimpleElementVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleElementVisitorPreview +header extends javax/lang/model/util/SimpleElementVisitor14 flags 21 signature Ljavax/lang/model/util/SimpleElementVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleTypeVisitor14 +header extends javax/lang/model/util/SimpleTypeVisitor9 flags 21 signature Ljavax/lang/model/util/SimpleTypeVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/SimpleTypeVisitorPreview +header extends javax/lang/model/util/SimpleTypeVisitor14 flags 21 signature Ljavax/lang/model/util/SimpleTypeVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/TypeKindVisitor14 +header extends javax/lang/model/util/TypeKindVisitor9 flags 21 signature Ljavax/lang/model/util/TypeKindVisitor9; runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + +class name javax/lang/model/util/TypeKindVisitorPreview +header extends javax/lang/model/util/TypeKindVisitor14 flags 21 signature Ljavax/lang/model/util/TypeKindVisitor14; classAnnotations @Ljdk/internal/javac/PreviewFeature;(feature=eLjdk/internal/javac/PreviewFeature$Feature;LANGUAGE_MODEL;,reflective=Ztrue) runtimeAnnotations @Ljavax/annotation/processing/SupportedSourceVersion;(value=eLjavax/lang/model/SourceVersion;RELEASE_25;) + diff --git a/src/jdk.compiler/share/data/symbols/java.desktop-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.desktop-P.sym.txt new file mode 100644 index 00000000000..23b567b6c05 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.desktop-P.sym.txt @@ -0,0 +1,43 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name java/awt/Color +-method name createContext descriptor (Ljava/awt/image/ColorModel;Ljava/awt/Rectangle;Ljava/awt/geom/Rectangle2D;Ljava/awt/geom/AffineTransform;Ljava/awt/RenderingHints;)Ljava/awt/PaintContext; +method name createContext descriptor (Ljava/awt/image/ColorModel;Ljava/awt/Rectangle;Ljava/awt/geom/Rectangle2D;Ljava/awt/geom/AffineTransform;Ljava/awt/RenderingHints;)Ljava/awt/PaintContext; flags 1 + +class name javax/swing/plaf/basic/BasicScrollBarUI$ScrollListener +header extends java/lang/Object implements java/awt/event/ActionListener nestHost javax/swing/plaf/basic/BasicScrollBarUI flags 21 +innerclass innerClass javax/swing/plaf/basic/BasicScrollBarUI$ScrollListener outerClass javax/swing/plaf/basic/BasicScrollBarUI innerClassName ScrollListener flags 4 +innerclass innerClass javax/swing/plaf/basic/BasicScrollBarUI$TrackListener outerClass javax/swing/plaf/basic/BasicScrollBarUI innerClassName TrackListener flags 4 + +class name javax/swing/plaf/basic/BasicSliderUI +-method name descriptor ()V + +class name javax/swing/plaf/synth/SynthLookAndFeel +-method name load descriptor (Ljava/net/URL;)V + diff --git a/src/jdk.compiler/share/data/symbols/java.logging-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.logging-P.sym.txt new file mode 100644 index 00000000000..dfcaa64bef7 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.logging-P.sym.txt @@ -0,0 +1,44 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name java/util/logging/FileHandler +-method name publish descriptor (Ljava/util/logging/LogRecord;)V +method name publish descriptor (Ljava/util/logging/LogRecord;)V flags 1 + +class name java/util/logging/LoggingPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name java/util/logging/SocketHandler +-method name publish descriptor (Ljava/util/logging/LogRecord;)V +method name publish descriptor (Ljava/util/logging/LogRecord;)V flags 1 + +class name java/util/logging/StreamHandler +-method name publish descriptor (Ljava/util/logging/LogRecord;)V +method name publish descriptor (Ljava/util/logging/LogRecord;)V flags 1 + diff --git a/src/jdk.compiler/share/data/symbols/java.management-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.management-P.sym.txt new file mode 100644 index 00000000000..459df392e02 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.management-P.sym.txt @@ -0,0 +1,57 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name java/lang/management/ManagementPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/management/MBeanPermission +header extends java/security/Permission flags 21 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/management/MBeanServerPermission +header extends java/security/BasicPermission flags 21 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/management/MBeanTrustPermission +header extends java/security/BasicPermission flags 21 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/management/modelmbean/DescriptorSupport +-method name descriptor (Ljava/lang/String;)V +-method name toXMLString descriptor ()Ljava/lang/String; +method name descriptor (Ljava/lang/String;)V thrownTypes javax/management/MBeanException,javax/management/RuntimeOperationsException,javax/management/modelmbean/XMLParseException flags 1 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +method name toXMLString descriptor ()Ljava/lang/String; flags 21 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + +class name javax/management/modelmbean/XMLParseException +header extends java/lang/Exception flags 21 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/management/remote/SubjectDelegationPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + diff --git a/src/jdk.compiler/share/data/symbols/java.net.http-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.net.http-P.sym.txt new file mode 100644 index 00000000000..19ddbb72a38 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.net.http-P.sym.txt @@ -0,0 +1,37 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name java/net/http/HttpResponse +method name connectionLabel descriptor ()Ljava/util/Optional; flags 1 signature ()Ljava/util/Optional; + +class name java/net/http/HttpResponse$BodyHandlers +method name limiting descriptor (Ljava/net/http/HttpResponse$BodyHandler;J)Ljava/net/http/HttpResponse$BodyHandler; flags 9 signature (Ljava/net/http/HttpResponse$BodyHandler;J)Ljava/net/http/HttpResponse$BodyHandler; + +class name java/net/http/HttpResponse$BodySubscribers +method name limiting descriptor (Ljava/net/http/HttpResponse$BodySubscriber;J)Ljava/net/http/HttpResponse$BodySubscriber; flags 9 signature (Ljava/net/http/HttpResponse$BodySubscriber;J)Ljava/net/http/HttpResponse$BodySubscriber; + diff --git a/src/jdk.compiler/share/data/symbols/java.security.jgss-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.security.jgss-P.sym.txt new file mode 100644 index 00000000000..3023fbddc6c --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.security.jgss-P.sym.txt @@ -0,0 +1,36 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name javax/security/auth/kerberos/DelegationPermission +header extends java/security/BasicPermission implements java/io/Serializable flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name javax/security/auth/kerberos/ServicePermission +header extends java/security/Permission implements java/io/Serializable flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + diff --git a/src/jdk.compiler/share/data/symbols/java.xml.crypto-P.sym.txt b/src/jdk.compiler/share/data/symbols/java.xml.crypto-P.sym.txt new file mode 100644 index 00000000000..a6cbe487e88 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/java.xml.crypto-P.sym.txt @@ -0,0 +1,34 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name javax/xml/crypto/dsig/SignatureMethod +field name ECDSA_SHA3_224 descriptor Ljava/lang/String; constantValue http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-224 flags 19 +field name ECDSA_SHA3_256 descriptor Ljava/lang/String; constantValue http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-256 flags 19 +field name ECDSA_SHA3_384 descriptor Ljava/lang/String; constantValue http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-384 flags 19 +field name ECDSA_SHA3_512 descriptor Ljava/lang/String; constantValue http://www.w3.org/2021/04/xmldsig-more#ecdsa-sha3-512 flags 19 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.attach-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.attach-P.sym.txt new file mode 100644 index 00000000000..d947c4babfb --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.attach-P.sym.txt @@ -0,0 +1,32 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name com/sun/tools/attach/AttachPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.compiler-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.compiler-P.sym.txt new file mode 100644 index 00000000000..eaeedda8287 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.compiler-P.sym.txt @@ -0,0 +1,32 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name com/sun/source/tree/ImportTree +-method name isModule descriptor ()Z +method name isModule descriptor ()Z flags 401 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.crypto.cryptoki-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.crypto.cryptoki-P.sym.txt new file mode 100644 index 00000000000..55667354e09 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.crypto.cryptoki-P.sym.txt @@ -0,0 +1,31 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +module name jdk.crypto.cryptoki +header requires name\u0020;java.base\u0020;flags\u0020;8000 provides interface\u0020;java/security/Provider\u0020;impls\u0020;sun/security/pkcs11/SunPKCS11 target macos-aarch64 flags 8000 classAnnotations @Ljdk/internal/javac/ParticipatesInPreview; + diff --git a/src/jdk.compiler/share/data/symbols/jdk.incubator.foreign-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.incubator.foreign-P.sym.txt new file mode 100644 index 00000000000..0f7866c462b --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.incubator.foreign-P.sym.txt @@ -0,0 +1,105 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name jdk/internal/foreign/AbstractMemorySegmentImpl +-method name reinterpret descriptor (JLjava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; +-method name reinterpret descriptor (J)Ljava/lang/foreign/MemorySegment; +-method name reinterpret descriptor (Ljava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; +method name reinterpret descriptor (JLjava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; flags 11 signature (JLjava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; runtimeAnnotations @Ljdk/internal/reflect/CallerSensitive;@Ljdk/internal/vm/annotation/ForceInline; +method name reinterpret descriptor (J)Ljava/lang/foreign/MemorySegment; flags 11 runtimeAnnotations @Ljdk/internal/reflect/CallerSensitive;@Ljdk/internal/vm/annotation/ForceInline; +method name reinterpret descriptor (Ljava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; flags 11 signature (Ljava/lang/foreign/Arena;Ljava/util/function/Consumer;)Ljava/lang/foreign/MemorySegment; runtimeAnnotations @Ljdk/internal/reflect/CallerSensitive;@Ljdk/internal/vm/annotation/ForceInline; + +class name jdk/internal/foreign/LayoutPath +header extends java/lang/Object nestMembers jdk/internal/foreign/LayoutPath$DereferenceElement,jdk/internal/foreign/LayoutPath$SequenceElement,jdk/internal/foreign/LayoutPath$SequenceElementByRange,jdk/internal/foreign/LayoutPath$SequenceElementByIndex,jdk/internal/foreign/LayoutPath$GroupElementByIndex,jdk/internal/foreign/LayoutPath$GroupElementByName flags 21 +innerclass innerClass java/lang/invoke/VarHandle$AccessMode outerClass java/lang/invoke/VarHandle innerClassName AccessMode flags 4019 +innerclass innerClass java/lang/foreign/MemoryLayout$PathElement outerClass java/lang/foreign/MemoryLayout innerClassName PathElement flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$DereferenceElement outerClass jdk/internal/foreign/LayoutPath innerClassName DereferenceElement flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$SequenceElement outerClass jdk/internal/foreign/LayoutPath innerClassName SequenceElement flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$SequenceElementByRange outerClass jdk/internal/foreign/LayoutPath innerClassName SequenceElementByRange flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$SequenceElementByIndex outerClass jdk/internal/foreign/LayoutPath innerClassName SequenceElementByIndex flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$GroupElementByIndex outerClass jdk/internal/foreign/LayoutPath innerClassName GroupElementByIndex flags 19 +innerclass innerClass jdk/internal/foreign/LayoutPath$GroupElementByName outerClass jdk/internal/foreign/LayoutPath innerClassName GroupElementByName flags 19 +field name EMPTY_PATH_ELEMENTS descriptor [Ljava/lang/foreign/MemoryLayout$PathElement; flags 19 + +class name jdk/internal/foreign/abi/LinkerOptions +header extends java/lang/Object nestMembers jdk/internal/foreign/abi/LinkerOptions$Critical,jdk/internal/foreign/abi/LinkerOptions$CaptureCallState,jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg,jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl flags 21 +innerclass innerClass java/lang/foreign/Linker$Option outerClass java/lang/foreign/Linker innerClassName Option flags 609 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName LinkerOptionImpl flags 609 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName FirstVariadicArg flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$CaptureCallState outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName CaptureCallState flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$Critical outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName Critical flags 4019 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +-method name capturedCallState descriptor ()Ljava/util/stream/Stream; +method name capturedCallStateMask descriptor ()I flags 1 + +class name jdk/internal/foreign/abi/LinkerOptions$CaptureCallState +header extends java/lang/Record implements jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl nestHost jdk/internal/foreign/abi/LinkerOptions record true flags 31 +recordcomponent name mask descriptor I +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$CaptureCallState outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName CaptureCallState flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName LinkerOptionImpl flags 609 +-method name descriptor (Ljava/util/Set;)V +-method name toString descriptor ()Ljava/lang/String; +-method name hashCode descriptor ()I +-method name equals descriptor (Ljava/lang/Object;)Z +-method name saved descriptor ()Ljava/util/Set; +method name descriptor (I)V flags 1 methodParameters 0:compact +method name equals descriptor (Ljava/lang/Object;)Z flags 1 +method name hashCode descriptor ()I flags 1 +method name toString descriptor ()Ljava/lang/String; flags 1 +method name mask descriptor ()I flags 1 + +class name jdk/internal/foreign/abi/LinkerOptions$Critical +header extends java/lang/Enum implements jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl nestHost jdk/internal/foreign/abi/LinkerOptions flags 4031 signature Ljava/lang/Enum;Ljdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl; +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$Critical outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName Critical flags 4019 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName LinkerOptionImpl flags 609 +-field name ALLOW_HEAP descriptor Ljdk/internal/foreign/abi/LinkerOptions$Critical; +-field name DONT_ALLOW_HEAP descriptor Ljdk/internal/foreign/abi/LinkerOptions$Critical; +-method name descriptor (Z)V +-method name toString descriptor ()Ljava/lang/String; +-method name hashCode descriptor ()I +-method name equals descriptor (Ljava/lang/Object;)Z +field name ALLOW_HEAP descriptor Ljdk/internal/foreign/abi/LinkerOptions$Critical; flags 4019 +field name DONT_ALLOW_HEAP descriptor Ljdk/internal/foreign/abi/LinkerOptions$Critical; flags 4019 +method name values descriptor ()[Ljdk/internal/foreign/abi/LinkerOptions$Critical; flags 9 +method name valueOf descriptor (Ljava/lang/String;)Ljdk/internal/foreign/abi/LinkerOptions$Critical; flags 9 methodParameters 8000:null + +class name jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg +-method name hashCode descriptor ()I +-method name equals descriptor (Ljava/lang/Object;)Z +method name hashCode descriptor ()I flags 1 +method name equals descriptor (Ljava/lang/Object;)Z flags 1 + +class name jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl +header extends java/lang/Object implements java/lang/foreign/Linker$Option nestHost jdk/internal/foreign/abi/LinkerOptions sealed true permittedSubclasses jdk/internal/foreign/abi/LinkerOptions$CaptureCallState,jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg,jdk/internal/foreign/abi/LinkerOptions$Critical flags 601 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName LinkerOptionImpl flags 609 +innerclass innerClass java/lang/foreign/Linker$Option outerClass java/lang/foreign/Linker innerClassName Option flags 609 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$CaptureCallState outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName CaptureCallState flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName FirstVariadicArg flags 19 +innerclass innerClass jdk/internal/foreign/abi/LinkerOptions$Critical outerClass jdk/internal/foreign/abi/LinkerOptions innerClassName Critical flags 4019 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.incubator.vector-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.incubator.vector-P.sym.txt new file mode 100644 index 00000000000..cae81b02a3d --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.incubator.vector-P.sym.txt @@ -0,0 +1,113 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name jdk/incubator/vector/AbstractVector +header extends jdk/incubator/vector/Vector flags 420 signature Ljdk/incubator/vector/Vector; +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorPayload outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorPayload flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorSpecies outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorSpecies flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Associative outerClass jdk/incubator/vector/VectorOperators innerClassName Associative flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Binary outerClass jdk/incubator/vector/VectorOperators innerClassName Binary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Conversion outerClass jdk/incubator/vector/VectorOperators innerClassName Conversion flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name jdk/incubator/vector/DoubleVector +header extends jdk/incubator/vector/AbstractVector flags 421 signature Ljdk/incubator/vector/AbstractVector; +innerclass innerClass jdk/incubator/vector/VectorOperators$Operator outerClass jdk/incubator/vector/VectorOperators innerClassName Operator flags 609 +innerclass innerClass java/lang/foreign/ValueLayout$OfDouble outerClass java/lang/foreign/ValueLayout innerClassName OfDouble flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorSpecies outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorSpecies flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorPayload outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorPayload flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Unary outerClass jdk/incubator/vector/VectorOperators innerClassName Unary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Comparison outerClass jdk/incubator/vector/VectorOperators innerClassName Comparison flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$Vector outerClass jdk/internal/vm/vector/VectorSupport innerClassName Vector flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorMask outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorMask flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Associative outerClass jdk/incubator/vector/VectorOperators innerClassName Associative flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Binary outerClass jdk/incubator/vector/VectorOperators innerClassName Binary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Ternary outerClass jdk/incubator/vector/VectorOperators innerClassName Ternary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Test outerClass jdk/incubator/vector/VectorOperators innerClassName Test flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorShuffle outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorShuffle flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Conversion outerClass jdk/incubator/vector/VectorOperators innerClassName Conversion flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name jdk/incubator/vector/Float16 +header extends java/lang/Number implements java/lang/Comparable flags 31 signature Ljava/lang/Number;Ljava/lang/Comparable; runtimeAnnotations @Ljdk/internal/ValueBased; +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +-method name valueOf descriptor (F)Ljdk/incubator/vector/Float16; +-method name byteValue descriptor ()B +-method name shortValue descriptor ()S +-method name intValue descriptor ()I +-method name floatValue descriptor ()F +-method name doubleValue descriptor ()D +method name valueOf descriptor (F)Ljdk/incubator/vector/Float16; flags 9 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name byteValue descriptor ()B flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name shortValue descriptor ()S flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name intValue descriptor ()I flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name floatValue descriptor ()F flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name doubleValue descriptor ()D flags 1 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; + +class name jdk/incubator/vector/FloatVector +header extends jdk/incubator/vector/AbstractVector flags 421 signature Ljdk/incubator/vector/AbstractVector; +innerclass innerClass jdk/incubator/vector/VectorOperators$Operator outerClass jdk/incubator/vector/VectorOperators innerClassName Operator flags 609 +innerclass innerClass java/lang/foreign/ValueLayout$OfFloat outerClass java/lang/foreign/ValueLayout innerClassName OfFloat flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorSpecies outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorSpecies flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorPayload outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorPayload flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Unary outerClass jdk/incubator/vector/VectorOperators innerClassName Unary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Comparison outerClass jdk/incubator/vector/VectorOperators innerClassName Comparison flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$Vector outerClass jdk/internal/vm/vector/VectorSupport innerClassName Vector flags 9 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorMask outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorMask flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Associative outerClass jdk/incubator/vector/VectorOperators innerClassName Associative flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Binary outerClass jdk/incubator/vector/VectorOperators innerClassName Binary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Ternary outerClass jdk/incubator/vector/VectorOperators innerClassName Ternary flags 609 +innerclass innerClass jdk/incubator/vector/VectorOperators$Test outerClass jdk/incubator/vector/VectorOperators innerClassName Test flags 609 +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorShuffle outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorShuffle flags 9 +innerclass innerClass jdk/incubator/vector/VectorOperators$Conversion outerClass jdk/incubator/vector/VectorOperators innerClassName Conversion flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + +class name jdk/incubator/vector/VectorShape +-method name forBitSize descriptor (I)Ljdk/incubator/vector/VectorShape; +method name forBitSize descriptor (I)Ljdk/incubator/vector/VectorShape; flags 9 runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; + +class name jdk/incubator/vector/VectorShuffle +header extends jdk/internal/vm/vector/VectorSupport$VectorShuffle flags 421 signature Ljdk/internal/vm/vector/VectorSupport$VectorShuffle; +innerclass innerClass jdk/internal/vm/vector/VectorSupport$VectorShuffle outerClass jdk/internal/vm/vector/VectorSupport innerClassName VectorShuffle flags 9 +innerclass innerClass java/lang/foreign/ValueLayout$OfInt outerClass java/lang/foreign/ValueLayout innerClassName OfInt flags 609 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 +-method name laneSource descriptor (I)I +method name laneSource descriptor (I)I flags 401 +method name fromMemorySegment descriptor (Ljdk/incubator/vector/VectorSpecies;Ljava/lang/foreign/MemorySegment;JLjava/nio/ByteOrder;)Ljdk/incubator/vector/VectorShuffle; flags 19 signature (Ljdk/incubator/vector/VectorSpecies;Ljava/lang/foreign/MemorySegment;JLjava/nio/ByteOrder;)Ljdk/incubator/vector/VectorShuffle; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name intoMemorySegment descriptor (Ljava/lang/foreign/MemorySegment;JLjava/nio/ByteOrder;)V flags 401 + +class name jdk/incubator/vector/VectorSpecies +-method name of descriptor (Ljava/lang/Class;Ljdk/incubator/vector/VectorShape;)Ljdk/incubator/vector/VectorSpecies; +-method name ofLargestShape descriptor (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; +-method name ofPreferred descriptor (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; +-method name elementSize descriptor (Ljava/lang/Class;)I +method name of descriptor (Ljava/lang/Class;Ljdk/incubator/vector/VectorShape;)Ljdk/incubator/vector/VectorSpecies; flags 9 signature (Ljava/lang/Class;Ljdk/incubator/vector/VectorShape;)Ljdk/incubator/vector/VectorSpecies; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name ofLargestShape descriptor (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; flags 9 signature (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name ofPreferred descriptor (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; flags 9 signature (Ljava/lang/Class;)Ljdk/incubator/vector/VectorSpecies; runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; +method name elementSize descriptor (Ljava/lang/Class;)I flags 9 signature (Ljava/lang/Class<*>;)I runtimeAnnotations @Ljdk/internal/vm/annotation/ForceInline; + diff --git a/src/jdk.compiler/share/data/symbols/jdk.jdi-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.jdi-P.sym.txt new file mode 100644 index 00000000000..153c13b1967 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.jdi-P.sym.txt @@ -0,0 +1,32 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name com/sun/jdi/JDIPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.jfr-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.jfr-P.sym.txt new file mode 100644 index 00000000000..8f7fe9f5699 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.jfr-P.sym.txt @@ -0,0 +1,31 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name jdk/jfr/FlightRecorderPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + diff --git a/src/jdk.compiler/share/data/symbols/jdk.jpackage-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.jpackage-P.sym.txt new file mode 100644 index 00000000000..fca76c0b895 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.jpackage-P.sym.txt @@ -0,0 +1,31 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +module name jdk.jpackage +header requires name\u0020;java.base\u0020;flags\u0020;8000,name\u0020;jdk.internal.opt\u0020;flags\u0020;0,name\u0020;jdk.jlink\u0020;flags\u0020;0,name\u0020;java.naming\u0020;flags\u0020;0,name\u0020;java.desktop\u0020;flags\u0020;0 uses jdk/jpackage/internal/Bundler,jdk/jpackage/internal/Bundlers provides interface\u0020;java/util/spi/ToolProvider\u0020;impls\u0020;jdk/jpackage/internal/JPackageToolProvider,interface\u0020;jdk/jpackage/internal/Bundler\u0020;impls\u0020;jdk/jpackage/internal/MacAppBundler\u005C;u002C;jdk/jpackage/internal/MacDmgBundler\u005C;u002C;jdk/jpackage/internal/MacPkgBundler,interface\u0020;jdk/jpackage/internal/Bundlers\u0020;impls\u0020;jdk/jpackage/internal/BasicBundlers target macos-aarch64 moduleMainClass jdk/jpackage/main/Main flags 8000 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.jshell-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.jshell-P.sym.txt new file mode 100644 index 00000000000..116976017d6 --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.jshell-P.sym.txt @@ -0,0 +1,46 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name jdk/jshell/JShellConsole +-method name readLine descriptor ()Ljava/lang/String; + +class name jdk/jshell/Snippet$SubKind +-field name MODULE_IMPORT_SUBKIND descriptor Ljdk/jshell/Snippet$SubKind; +field name MODULE_IMPORT_SUBKIND descriptor Ljdk/jshell/Snippet$SubKind; flags 4019 + +class name jdk/jshell/execution/LocalExecutionControl +header extends jdk/jshell/execution/DirectExecutionControl flags 21 +innerclass innerClass jdk/jshell/spi/ExecutionControl$ClassBytecodes outerClass jdk/jshell/spi/ExecutionControl innerClassName ClassBytecodes flags 19 +innerclass innerClass jdk/jshell/spi/ExecutionControl$StoppedException outerClass jdk/jshell/spi/ExecutionControl innerClassName StoppedException flags 9 +innerclass innerClass jdk/jshell/spi/ExecutionControl$InternalException outerClass jdk/jshell/spi/ExecutionControl innerClassName InternalException flags 9 +innerclass innerClass java/lang/classfile/CodeBuilder$BlockCodeBuilder outerClass java/lang/classfile/CodeBuilder innerClassName BlockCodeBuilder flags 609 +innerclass innerClass jdk/jshell/spi/ExecutionControl$ClassInstallException outerClass jdk/jshell/spi/ExecutionControl innerClassName ClassInstallException flags 9 +innerclass innerClass jdk/jshell/spi/ExecutionControl$NotImplementedException outerClass jdk/jshell/spi/ExecutionControl innerClassName NotImplementedException flags 9 +innerclass innerClass jdk/jshell/spi/ExecutionControl$EngineTerminationException outerClass jdk/jshell/spi/ExecutionControl innerClassName EngineTerminationException flags 9 +innerclass innerClass java/lang/invoke/MethodHandles$Lookup outerClass java/lang/invoke/MethodHandles innerClassName Lookup flags 19 + diff --git a/src/jdk.compiler/share/data/symbols/jdk.net-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.net-P.sym.txt new file mode 100644 index 00000000000..377aa5fb5df --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.net-P.sym.txt @@ -0,0 +1,31 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name jdk/net/NetworkPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + diff --git a/src/jdk.compiler/share/data/symbols/jdk.security.jgss-P.sym.txt b/src/jdk.compiler/share/data/symbols/jdk.security.jgss-P.sym.txt new file mode 100644 index 00000000000..3c4839b8f7a --- /dev/null +++ b/src/jdk.compiler/share/data/symbols/jdk.security.jgss-P.sym.txt @@ -0,0 +1,31 @@ +# +# 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. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# +# ########################################################## +# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ### +# ########################################################## +# +class name com/sun/security/jgss/InquireSecContextPermission +header extends java/security/BasicPermission flags 31 deprecated true runtimeAnnotations @Ljava/lang/Deprecated;(forRemoval=Ztrue,since="25") + diff --git a/src/jdk.compiler/share/data/symbols/symbols b/src/jdk.compiler/share/data/symbols/symbols index f7f2946d582..7793033d22f 100644 --- a/src/jdk.compiler/share/data/symbols/symbols +++ b/src/jdk.compiler/share/data/symbols/symbols @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2024, 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 @@ -29,7 +29,7 @@ #command used to generate this file: #build.tools.symbolgenerator.CreateSymbols build-description-incremental symbols include.list # -generate platforms 8:9:A:B:C:D:E:F:G:H:I:J:K:L:M:N:O +generate platforms 8:9:A:B:C:D:E:F:G:H:I:J:K:L:M:N:O:P platform version 8 files java.activation-8.sym.txt:java.base-8.sym.txt:java.compiler-8.sym.txt:java.corba-8.sym.txt:java.datatransfer-8.sym.txt:java.desktop-8.sym.txt:java.instrument-8.sym.txt:java.logging-8.sym.txt:java.management-8.sym.txt:java.management.rmi-8.sym.txt:java.naming-8.sym.txt:java.prefs-8.sym.txt:java.rmi-8.sym.txt:java.scripting-8.sym.txt:java.security.jgss-8.sym.txt:java.security.sasl-8.sym.txt:java.sql-8.sym.txt:java.sql.rowset-8.sym.txt:java.transaction-8.sym.txt:java.xml-8.sym.txt:java.xml.bind-8.sym.txt:java.xml.crypto-8.sym.txt:java.xml.ws-8.sym.txt:java.xml.ws.annotation-8.sym.txt:jdk.httpserver-8.sym.txt:jdk.management-8.sym.txt:jdk.scripting.nashorn-8.sym.txt:jdk.sctp-8.sym.txt:jdk.security.auth-8.sym.txt:jdk.security.jgss-8.sym.txt platform version 9 base 8 files java.activation-9.sym.txt:java.base-9.sym.txt:java.compiler-9.sym.txt:java.corba-9.sym.txt:java.datatransfer-9.sym.txt:java.desktop-9.sym.txt:java.instrument-9.sym.txt:java.logging-9.sym.txt:java.management-9.sym.txt:java.management.rmi-9.sym.txt:java.naming-9.sym.txt:java.prefs-9.sym.txt:java.rmi-9.sym.txt:java.scripting-9.sym.txt:java.se-9.sym.txt:java.se.ee-9.sym.txt:java.security.jgss-9.sym.txt:java.security.sasl-9.sym.txt:java.smartcardio-9.sym.txt:java.sql-9.sym.txt:java.sql.rowset-9.sym.txt:java.transaction-9.sym.txt:java.xml-9.sym.txt:java.xml.bind-9.sym.txt:java.xml.crypto-9.sym.txt:java.xml.ws-9.sym.txt:java.xml.ws.annotation-9.sym.txt:jdk.accessibility-9.sym.txt:jdk.attach-9.sym.txt:jdk.charsets-9.sym.txt:jdk.compiler-9.sym.txt:jdk.crypto.cryptoki-9.sym.txt:jdk.crypto.ec-9.sym.txt:jdk.dynalink-9.sym.txt:jdk.editpad-9.sym.txt:jdk.hotspot.agent-9.sym.txt:jdk.httpserver-9.sym.txt:jdk.incubator.httpclient-9.sym.txt:jdk.jartool-9.sym.txt:jdk.javadoc-9.sym.txt:jdk.jcmd-9.sym.txt:jdk.jconsole-9.sym.txt:jdk.jdeps-9.sym.txt:jdk.jdi-9.sym.txt:jdk.jdwp.agent-9.sym.txt:jdk.jlink-9.sym.txt:jdk.jshell-9.sym.txt:jdk.jsobject-9.sym.txt:jdk.jstatd-9.sym.txt:jdk.localedata-9.sym.txt:jdk.management-9.sym.txt:jdk.management.agent-9.sym.txt:jdk.naming.dns-9.sym.txt:jdk.naming.rmi-9.sym.txt:jdk.net-9.sym.txt:jdk.pack-9.sym.txt:jdk.policytool-9.sym.txt:jdk.rmic-9.sym.txt:jdk.scripting.nashorn-9.sym.txt:jdk.sctp-9.sym.txt:jdk.security.auth-9.sym.txt:jdk.security.jgss-9.sym.txt:jdk.unsupported-9.sym.txt:jdk.xml.dom-9.sym.txt:jdk.zipfs-9.sym.txt platform version A base 9 files java.activation-A.sym.txt:java.base-A.sym.txt:java.compiler-A.sym.txt:java.corba-A.sym.txt:java.datatransfer-A.sym.txt:java.desktop-A.sym.txt:java.instrument-A.sym.txt:java.logging-A.sym.txt:java.management-A.sym.txt:java.management.rmi-A.sym.txt:java.naming-A.sym.txt:java.prefs-A.sym.txt:java.rmi-A.sym.txt:java.scripting-A.sym.txt:java.se-A.sym.txt:java.se.ee-A.sym.txt:java.security.jgss-A.sym.txt:java.security.sasl-A.sym.txt:java.smartcardio-A.sym.txt:java.sql-A.sym.txt:java.sql.rowset-A.sym.txt:java.transaction-A.sym.txt:java.xml-A.sym.txt:java.xml.bind-A.sym.txt:java.xml.crypto-A.sym.txt:java.xml.ws-A.sym.txt:java.xml.ws.annotation-A.sym.txt:jdk.accessibility-A.sym.txt:jdk.attach-A.sym.txt:jdk.charsets-A.sym.txt:jdk.compiler-A.sym.txt:jdk.crypto.cryptoki-A.sym.txt:jdk.crypto.ec-A.sym.txt:jdk.dynalink-A.sym.txt:jdk.editpad-A.sym.txt:jdk.hotspot.agent-A.sym.txt:jdk.httpserver-A.sym.txt:jdk.incubator.httpclient-A.sym.txt:jdk.jartool-A.sym.txt:jdk.javadoc-A.sym.txt:jdk.jcmd-A.sym.txt:jdk.jconsole-A.sym.txt:jdk.jdeps-A.sym.txt:jdk.jdi-A.sym.txt:jdk.jdwp.agent-A.sym.txt:jdk.jlink-A.sym.txt:jdk.jshell-A.sym.txt:jdk.jsobject-A.sym.txt:jdk.jstatd-A.sym.txt:jdk.localedata-A.sym.txt:jdk.management-A.sym.txt:jdk.management.agent-A.sym.txt:jdk.naming.dns-A.sym.txt:jdk.naming.rmi-A.sym.txt:jdk.net-A.sym.txt:jdk.pack-A.sym.txt:jdk.policytool-A.sym.txt:jdk.rmic-A.sym.txt:jdk.scripting.nashorn-A.sym.txt:jdk.sctp-A.sym.txt:jdk.security.auth-A.sym.txt:jdk.security.jgss-A.sym.txt:jdk.unsupported-A.sym.txt:jdk.xml.dom-A.sym.txt:jdk.zipfs-A.sym.txt @@ -47,3 +47,4 @@ platform version L base K files java.base-L.sym.txt:java.compiler-L.sym.txt:java platform version M base L files java.base-M.sym.txt:java.compiler-M.sym.txt:java.desktop-M.sym.txt:java.xml-M.sym.txt:java.xml.crypto-M.sym.txt:jdk.compiler-M.sym.txt:jdk.crypto.cryptoki-M.sym.txt:jdk.crypto.ec-M.sym.txt:jdk.incubator.foreign-M.sym.txt:jdk.incubator.vector-M.sym.txt:jdk.jartool-M.sym.txt:jdk.jdeps-M.sym.txt:jdk.jfr-M.sym.txt:jdk.jlink-M.sym.txt:jdk.jpackage-M.sym.txt:jdk.jshell-M.sym.txt:jdk.jstatd-M.sym.txt:jdk.unsupported-M.sym.txt platform version N base M files java.base-N.sym.txt:java.compiler-N.sym.txt:java.desktop-N.sym.txt:java.management-N.sym.txt:java.management.rmi-N.sym.txt:jdk.compiler-N.sym.txt:jdk.httpserver-N.sym.txt:jdk.incubator.foreign-N.sym.txt:jdk.javadoc-N.sym.txt:jdk.jshell-N.sym.txt:jdk.localedata-N.sym.txt:jdk.unsupported-N.sym.txt platform version O base N files java.base-O.sym.txt:java.compiler-O.sym.txt:java.datatransfer-O.sym.txt:java.desktop-O.sym.txt:java.instrument-O.sym.txt:java.logging-O.sym.txt:java.management-O.sym.txt:java.management.rmi-O.sym.txt:java.naming-O.sym.txt:java.net.http-O.sym.txt:java.prefs-O.sym.txt:java.rmi-O.sym.txt:java.scripting-O.sym.txt:java.se-O.sym.txt:java.security.jgss-O.sym.txt:java.security.sasl-O.sym.txt:java.smartcardio-O.sym.txt:java.sql-O.sym.txt:java.sql.rowset-O.sym.txt:java.transaction.xa-O.sym.txt:java.xml-O.sym.txt:java.xml.crypto-O.sym.txt:jdk.accessibility-O.sym.txt:jdk.attach-O.sym.txt:jdk.charsets-O.sym.txt:jdk.compiler-O.sym.txt:jdk.crypto.cryptoki-O.sym.txt:jdk.dynalink-O.sym.txt:jdk.editpad-O.sym.txt:jdk.hotspot.agent-O.sym.txt:jdk.httpserver-O.sym.txt:jdk.incubator.foreign-O.sym.txt:jdk.incubator.vector-O.sym.txt:jdk.jartool-O.sym.txt:jdk.javadoc-O.sym.txt:jdk.jcmd-O.sym.txt:jdk.jconsole-O.sym.txt:jdk.jdeps-O.sym.txt:jdk.jdi-O.sym.txt:jdk.jdwp.agent-O.sym.txt:jdk.jfr-O.sym.txt:jdk.jlink-O.sym.txt:jdk.jpackage-O.sym.txt:jdk.jshell-O.sym.txt:jdk.jsobject-O.sym.txt:jdk.jstatd-O.sym.txt:jdk.localedata-O.sym.txt:jdk.management-O.sym.txt:jdk.management.agent-O.sym.txt:jdk.management.jfr-O.sym.txt:jdk.naming.dns-O.sym.txt:jdk.naming.rmi-O.sym.txt:jdk.net-O.sym.txt:jdk.nio.mapmode-O.sym.txt:jdk.sctp-O.sym.txt:jdk.security.auth-O.sym.txt:jdk.security.jgss-O.sym.txt:jdk.unsupported-O.sym.txt:jdk.xml.dom-O.sym.txt:jdk.zipfs-O.sym.txt +platform version P base O files java.base-P.sym.txt:java.compiler-P.sym.txt:java.desktop-P.sym.txt:java.logging-P.sym.txt:java.management-P.sym.txt:java.net.http-P.sym.txt:java.security.jgss-P.sym.txt:java.xml.crypto-P.sym.txt:jdk.attach-P.sym.txt:jdk.compiler-P.sym.txt:jdk.incubator.foreign-P.sym.txt:jdk.incubator.vector-P.sym.txt:jdk.jdi-P.sym.txt:jdk.jfr-P.sym.txt:jdk.jpackage-P.sym.txt:jdk.jshell-P.sym.txt:jdk.net-P.sym.txt:jdk.security.jgss-P.sym.txt diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 3038daf090d..f0e50e58b2b 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -142,6 +142,11 @@ serviceability/sa/TestJmapCoreMetaspace.java 8318754 macosx-aarch64 serviceability/jvmti/stress/StackTrace/NotSuspended/GetStackTraceNotSuspendedStressTest.java 8315980 linux-all,windows-x64 +serviceability/sa/JhsdbThreadInfoTest.java 8344261 generic-all +serviceability/sa/TestJhsdbJstackLock.java 8344261 generic-all +serviceability/sa/sadebugd/DebugdConnectTest.java 8344261 generic-all +serviceability/attach/RemovingUnixDomainSocketTest.java 8344261 generic-all + ############################################################################# # :hotspot_misc diff --git a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java index 5456f560fa1..99c2d27f8d3 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java +++ b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, 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 @@ -58,26 +58,11 @@ public class VMDeprecatedOptions { // { , } // deprecated non-alias flags: {"AllowRedefinitionToAddDeleteMethods", "true"}, - {"LockingMode", "1"}, // deprecated alias flags (see also aliased_jvm_flags): {"CreateMinidumpOnCrash", "false"} } )); - if (Platform.is64bit()) { - deprecated.addAll( - Arrays.asList(new String[][] { - {"UseCompressedClassPointers", "false"}, - }) - ); - } - if (Platform.isLinux()) { - deprecated.addAll( - Arrays.asList(new String[][] { - {"UseOprofile", "false"} - }) - ); - } if (Platform.isX86() || Platform.isX64()) { deprecated.addAll( Arrays.asList(new String[][] { diff --git a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/ClassReader.java b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/ClassReader.java index 7e499412dc2..d84c39112db 100644 --- a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/ClassReader.java +++ b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/ClassReader.java @@ -226,7 +226,7 @@ public class ClassReader { this.b = classFileBuffer; // Check the class' major_version. This field is after the magic and minor_version fields, which // use 4 and 2 bytes respectively. - if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V25) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } diff --git a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java index d6d8e36fc15..da68deacf90 100644 --- a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java +++ b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java @@ -315,6 +315,7 @@ public interface Opcodes { int V23 = 0 << 16 | 67; int V24 = 0 << 16 | 68; int V25 = 0 << 16 | 69; + int V26 = 0 << 16 | 70; /** * Version flag indicating that the class is using 'preview' features. diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index c1303377ddb..8c0e0bccde0 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -834,3 +834,9 @@ java/awt/Checkbox/CheckboxNullLabelTest.java 8340870 windows-all java/awt/dnd/WinMoveFileToShellTest.java 8341665 windows-all java/awt/Menu/MenuVisibilityTest.java 8161110 macosx-all java/awt/Modal/NativeDialogToFrontBackTest.java 7188049 windows-all,linux-all + +############################################################################ + +# jdk_since_checks + +tools/sincechecker/modules/java.base/JavaBaseCheckSince.java 8358627 generic-all diff --git a/test/langtools/tools/javac/api/TestGetSourceVersions.java b/test/langtools/tools/javac/api/TestGetSourceVersions.java index 26892eca0a1..4df60355aad 100644 --- a/test/langtools/tools/javac/api/TestGetSourceVersions.java +++ b/test/langtools/tools/javac/api/TestGetSourceVersions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -24,7 +24,7 @@ /* * @test * @bug 6395981 6458819 7025784 8028543 8028544 8193291 8193292 8193292 8205393 8245585 8245585 8245585 8286034 - * 8296150 8306585 8319414 8330183 8342982 + * 8296150 8306585 8319414 8330183 8342982 8355748 * @summary JavaCompilerTool and Tool must specify version of JLS and JVMS * @author Peter von der Ahé * @modules java.compiler @@ -37,7 +37,7 @@ * RELEASE_8 RELEASE_9 RELEASE_10 RELEASE_11 RELEASE_12 * RELEASE_13 RELEASE_14 RELEASE_15 RELEASE_16 RELEASE_17 * RELEASE_18 RELEASE_19 RELEASE_20 RELEASE_21 RELEASE_22 - * RELEASE_23 RELEASE_24 RELEASE_25 + * RELEASE_23 RELEASE_24 RELEASE_25 RELEASE_26 */ import java.util.EnumSet; diff --git a/test/langtools/tools/javac/classfiles/ClassVersionChecker.java b/test/langtools/tools/javac/classfiles/ClassVersionChecker.java index c12ff311bdb..6afa7ad9e30 100644 --- a/test/langtools/tools/javac/classfiles/ClassVersionChecker.java +++ b/test/langtools/tools/javac/classfiles/ClassVersionChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -24,7 +24,7 @@ /* * @test * @bug 7157626 8001112 8188870 8173382 8193290 8205619 8245586 8257453 8306586 8330184 - * 8342983 + * 8342983 8355751 * @summary Test major version for all legal combinations for -source and -target * @author sgoel * @@ -61,6 +61,7 @@ public class ClassVersionChecker { TWENTY_THREE("23", 67), TWENTY_FOUR("24", 68), TWENTY_FIVE("25", 69), + TWENTY_SIX("26", 70), ; // Reduce code churn when appending new constants private Version(String release, int classFileVer) { diff --git a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java index 081a156d6a0..e181ef8bf63 100644 --- a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java +++ b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -113,7 +113,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { * corresponding platform visitor type. */ - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static abstract class AbstractAnnotationValueVisitor extends AbstractAnnotationValueVisitorPreview { @@ -125,7 +125,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static abstract class AbstractElementVisitor extends AbstractElementVisitorPreview { /** @@ -136,7 +136,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static abstract class AbstractTypeVisitor extends AbstractTypeVisitorPreview { /** @@ -147,7 +147,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class ElementKindVisitor extends ElementKindVisitorPreview { /** @@ -169,7 +169,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class ElementScanner extends ElementScannerPreview { /** @@ -189,7 +189,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class SimpleAnnotationValueVisitor extends SimpleAnnotationValueVisitorPreview { /** @@ -211,7 +211,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class SimpleElementVisitor extends SimpleElementVisitorPreview { /** @@ -233,7 +233,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class SimpleTypeVisitor extends SimpleTypeVisitorPreview { /** @@ -255,7 +255,7 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor { } } - @SupportedSourceVersion(RELEASE_25) + @SupportedSourceVersion(RELEASE_26) @SuppressWarnings("preview") public static class TypeKindVisitor extends TypeKindVisitorPreview { /** diff --git a/test/langtools/tools/javac/options/HelpOutputColumnWidthTest.java b/test/langtools/tools/javac/options/HelpOutputColumnWidthTest.java index ecc8218b5f5..4cef7e1916b 100644 --- a/test/langtools/tools/javac/options/HelpOutputColumnWidthTest.java +++ b/test/langtools/tools/javac/options/HelpOutputColumnWidthTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -47,7 +47,7 @@ import toolbox.Task; public class HelpOutputColumnWidthTest extends TestRunner { - public static final int MAX_COLUMNS = 80; + public static final int MAX_COLUMNS = 84; protected ToolBox tb; diff --git a/test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out b/test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out index 46c55fa62f3..8c455009ae5 100644 --- a/test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out +++ b/test/langtools/tools/javac/preview/classReaderTest/Client.nopreview.out @@ -1,2 +1,2 @@ -- compiler.err.preview.feature.disabled.classfile: Bar.class, 25 +- compiler.err.preview.feature.disabled.classfile: Bar.class, 26 1 error diff --git a/test/langtools/tools/javac/preview/classReaderTest/Client.preview.out b/test/langtools/tools/javac/preview/classReaderTest/Client.preview.out index 539b128efd2..26ff05b085a 100644 --- a/test/langtools/tools/javac/preview/classReaderTest/Client.preview.out +++ b/test/langtools/tools/javac/preview/classReaderTest/Client.preview.out @@ -1,4 +1,4 @@ -- compiler.warn.preview.feature.use.classfile: Bar.class, 25 +- compiler.warn.preview.feature.use.classfile: Bar.class, 26 - compiler.err.warnings.and.werror 1 error 1 warning diff --git a/test/langtools/tools/javac/versions/Versions.java b/test/langtools/tools/javac/versions/Versions.java index 52b0f0d366d..43fcb0353d0 100644 --- a/test/langtools/tools/javac/versions/Versions.java +++ b/test/langtools/tools/javac/versions/Versions.java @@ -26,7 +26,7 @@ * @bug 4981566 5028634 5094412 6304984 7025786 7025789 8001112 8028545 * 8000961 8030610 8028546 8188870 8173382 8173382 8193290 8205619 8028563 * 8245147 8245586 8257453 8286035 8306586 8320806 8306586 8319414 8330183 - * 8342982 8356108 + * 8342982 8355748 8356108 * @summary Check interpretation of -target and -source options * @modules java.compiler * jdk.compiler @@ -73,9 +73,9 @@ public class Versions { public static final Set VALID_SOURCES = Set.of("1.8", "1.9", "1.10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", - "23", "24", "25"); + "23", "24", "25", "26"); - public static final String LATEST_MAJOR_VERSION = "69.0"; + public static final String LATEST_MAJOR_VERSION = "70.0"; static enum SourceTarget { EIGHT(true, "52.0", "8"), @@ -96,6 +96,7 @@ public class Versions { TWENTY_THREE(false,"67.0", "23"), TWENTY_FOUR(false,"68.0", "24"), TWENTY_FIVE(false,"69.0", "25"), + TWENTY_SIX(false, "70.0", "26"), ; // Reduce code churn when appending new constants private final boolean dotOne; @@ -566,4 +567,3 @@ public class Versions { return false; } } - From 62fde687088ce72ef33b94e73babf4bfe1395c17 Mon Sep 17 00:00:00 2001 From: Cesar Soares Lucas Date: Thu, 5 Jun 2025 16:43:29 +0000 Subject: [PATCH 075/143] 8357396: Refactor nmethod::make_not_entrant to use Enum instead of "const char*" Reviewed-by: mhaessig, shade --- src/hotspot/share/c1/c1_Runtime1.cpp | 8 +- src/hotspot/share/ci/ciReplay.cpp | 2 +- src/hotspot/share/code/codeCache.cpp | 2 +- src/hotspot/share/code/nmethod.cpp | 14 ++- src/hotspot/share/code/nmethod.hpp | 85 ++++++++++++++++++- .../share/compiler/compilationPolicy.cpp | 4 +- src/hotspot/share/jvmci/jvmciCompilerToVM.cpp | 9 +- src/hotspot/share/jvmci/jvmciEnv.cpp | 6 +- src/hotspot/share/jvmci/jvmciEnv.hpp | 2 +- src/hotspot/share/jvmci/jvmciRuntime.cpp | 2 +- src/hotspot/share/oops/instanceKlass.cpp | 2 +- src/hotspot/share/oops/method.cpp | 2 +- src/hotspot/share/prims/whitebox.cpp | 2 +- src/hotspot/share/runtime/deoptimization.cpp | 4 +- src/hotspot/share/runtime/javaThread.cpp | 2 +- 15 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 7c41c09d378..e2760689daa 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -818,7 +818,7 @@ JRT_ENTRY(void, Runtime1::deoptimize(JavaThread* current, jint trap_request)) Deoptimization::DeoptReason reason = Deoptimization::trap_request_reason(trap_request); if (action == Deoptimization::Action_make_not_entrant) { - if (nm->make_not_entrant("C1 deoptimize")) { + if (nm->make_not_entrant(nmethod::ChangeReason::C1_deoptimize)) { if (reason == Deoptimization::Reason_tenured) { MethodData* trap_mdo = Deoptimization::get_method_data(current, method, true /*create_if_missing*/); if (trap_mdo != nullptr) { @@ -1110,7 +1110,7 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, C1StubId stub_id )) // safepoint, but if it's still alive then make it not_entrant. nmethod* nm = CodeCache::find_nmethod(caller_frame.pc()); if (nm != nullptr) { - nm->make_not_entrant("C1 code patch"); + nm->make_not_entrant(nmethod::ChangeReason::C1_codepatch); } Deoptimization::deoptimize_frame(current, caller_frame.id()); @@ -1358,7 +1358,7 @@ void Runtime1::patch_code(JavaThread* current, C1StubId stub_id) { // Make sure the nmethod is invalidated, i.e. made not entrant. nmethod* nm = CodeCache::find_nmethod(caller_frame.pc()); if (nm != nullptr) { - nm->make_not_entrant("C1 deoptimize for patching"); + nm->make_not_entrant(nmethod::ChangeReason::C1_deoptimize_for_patching); } } @@ -1486,7 +1486,7 @@ JRT_ENTRY(void, Runtime1::predicate_failed_trap(JavaThread* current)) nmethod* nm = CodeCache::find_nmethod(caller_frame.pc()); assert (nm != nullptr, "no more nmethod?"); - nm->make_not_entrant("C1 predicate failed trap"); + nm->make_not_entrant(nmethod::ChangeReason::C1_predicate_failed_trap); methodHandle m(current, nm->method()); MethodData* mdo = m->method_data(); diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index 5ea4336f35e..f9829e88c4a 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -802,7 +802,7 @@ class CompileReplay : public StackObj { // Make sure the existence of a prior compile doesn't stop this one nmethod* nm = (entry_bci != InvocationEntryBci) ? method->lookup_osr_nmethod_for(entry_bci, comp_level, true) : method->code(); if (nm != nullptr) { - nm->make_not_entrant("CI replay"); + nm->make_not_entrant(nmethod::ChangeReason::CI_replay); } replay_state = this; CompileBroker::compile_method(methodHandle(THREAD, method), entry_bci, comp_level, diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 902d4345622..3a5da1d7cd2 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -1361,7 +1361,7 @@ void CodeCache::make_marked_nmethods_deoptimized() { while(iter.next()) { nmethod* nm = iter.method(); if (nm->is_marked_for_deoptimization() && !nm->has_been_deoptimized() && nm->can_be_deoptimized()) { - nm->make_not_entrant("marked for deoptimization"); + nm->make_not_entrant(nmethod::ChangeReason::marked_for_deoptimization); nm->make_deoptimized(); } } diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 510160a6667..acebaae6ba4 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -1975,14 +1975,12 @@ void nmethod::invalidate_osr_method() { } } -void nmethod::log_state_change(const char* reason) const { - assert(reason != nullptr, "Must provide a reason"); - +void nmethod::log_state_change(ChangeReason change_reason) const { if (LogCompilation) { if (xtty != nullptr) { ttyLocker ttyl; // keep the following output all in one block xtty->begin_elem("make_not_entrant thread='%zu' reason='%s'", - os::current_thread_id(), reason); + os::current_thread_id(), change_reason_to_string(change_reason)); log_identity(xtty); xtty->stamp(); xtty->end_elem(); @@ -1991,7 +1989,7 @@ void nmethod::log_state_change(const char* reason) const { ResourceMark rm; stringStream ss(NEW_RESOURCE_ARRAY(char, 256), 256); - ss.print("made not entrant: %s", reason); + ss.print("made not entrant: %s", change_reason_to_string(change_reason)); CompileTask::print_ul(this, ss.freeze()); if (PrintCompilation) { @@ -2006,9 +2004,7 @@ void nmethod::unlink_from_method() { } // Invalidate code -bool nmethod::make_not_entrant(const char* reason) { - assert(reason != nullptr, "Must provide a reason"); - +bool nmethod::make_not_entrant(ChangeReason change_reason) { // This can be called while the system is already at a safepoint which is ok NoSafepointVerifier nsv; @@ -2077,7 +2073,7 @@ bool nmethod::make_not_entrant(const char* reason) { assert(success, "Transition can't fail"); // Log the transition once - log_state_change(reason); + log_state_change(change_reason); // Remove nmethod from method. unlink_from_method(); diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 2ce6e5cd361..7453bdfa0ef 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -471,6 +471,85 @@ class nmethod : public CodeBlob { void oops_do_set_strong_done(nmethod* old_head); public: + enum class ChangeReason : u1 { + C1_codepatch, + C1_deoptimize, + C1_deoptimize_for_patching, + C1_predicate_failed_trap, + CI_replay, + JVMCI_invalidate_nmethod, + JVMCI_invalidate_nmethod_mirror, + JVMCI_materialize_virtual_object, + JVMCI_new_installation, + JVMCI_register_method, + JVMCI_replacing_with_new_code, + JVMCI_reprofile, + marked_for_deoptimization, + missing_exception_handler, + not_used, + OSR_invalidation_back_branch, + OSR_invalidation_for_compiling_with_C1, + OSR_invalidation_of_lower_level, + set_native_function, + uncommon_trap, + whitebox_deoptimization, + zombie, + }; + + + static const char* change_reason_to_string(ChangeReason change_reason) { + switch (change_reason) { + case ChangeReason::C1_codepatch: + return "C1 code patch"; + case ChangeReason::C1_deoptimize: + return "C1 deoptimized"; + case ChangeReason::C1_deoptimize_for_patching: + return "C1 deoptimize for patching"; + case ChangeReason::C1_predicate_failed_trap: + return "C1 predicate failed trap"; + case ChangeReason::CI_replay: + return "CI replay"; + case ChangeReason::JVMCI_invalidate_nmethod: + return "JVMCI invalidate nmethod"; + case ChangeReason::JVMCI_invalidate_nmethod_mirror: + return "JVMCI invalidate nmethod mirror"; + case ChangeReason::JVMCI_materialize_virtual_object: + return "JVMCI materialize virtual object"; + case ChangeReason::JVMCI_new_installation: + return "JVMCI new installation"; + case ChangeReason::JVMCI_register_method: + return "JVMCI register method"; + case ChangeReason::JVMCI_replacing_with_new_code: + return "JVMCI replacing with new code"; + case ChangeReason::JVMCI_reprofile: + return "JVMCI reprofile"; + case ChangeReason::marked_for_deoptimization: + return "marked for deoptimization"; + case ChangeReason::missing_exception_handler: + return "missing exception handler"; + case ChangeReason::not_used: + return "not used"; + case ChangeReason::OSR_invalidation_back_branch: + return "OSR invalidation back branch"; + case ChangeReason::OSR_invalidation_for_compiling_with_C1: + return "OSR invalidation for compiling with C1"; + case ChangeReason::OSR_invalidation_of_lower_level: + return "OSR invalidation of lower level"; + case ChangeReason::set_native_function: + return "set native function"; + case ChangeReason::uncommon_trap: + return "uncommon trap"; + case ChangeReason::whitebox_deoptimization: + return "whitebox deoptimization"; + case ChangeReason::zombie: + return "zombie"; + default: { + assert(false, "Unhandled reason"); + return "Unknown"; + } + } + } + // create nmethod with entry_bci static nmethod* new_nmethod(const methodHandle& method, int compile_id, @@ -633,8 +712,8 @@ public: // alive. It is used when an uncommon trap happens. Returns true // if this thread changed the state of the nmethod or false if // another thread performed the transition. - bool make_not_entrant(const char* reason); - bool make_not_used() { return make_not_entrant("not used"); } + bool make_not_entrant(ChangeReason change_reason); + bool make_not_used() { return make_not_entrant(ChangeReason::not_used); } bool is_marked_for_deoptimization() const { return deoptimization_status() != not_marked; } bool has_been_deoptimized() const { return deoptimization_status() == deoptimize_done; } @@ -947,7 +1026,7 @@ public: // Logging void log_identity(xmlStream* log) const; void log_new_nmethod() const; - void log_state_change(const char* reason) const; + void log_state_change(ChangeReason change_reason) const; // Prints block-level comments, including nmethod specific block labels: void print_nmethod_labels(outputStream* stream, address block_begin, bool print_section_labels=true) const; diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 39c90281c79..bab437eaade 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -924,7 +924,7 @@ void CompilationPolicy::compile(const methodHandle& mh, int bci, CompLevel level nmethod* osr_nm = mh->lookup_osr_nmethod_for(bci, CompLevel_simple, false); if (osr_nm != nullptr && osr_nm->comp_level() > CompLevel_simple) { // Invalidate the existing OSR nmethod so that a compile at CompLevel_simple is permitted. - osr_nm->make_not_entrant("OSR invalidation for compiling with C1"); + osr_nm->make_not_entrant(nmethod::ChangeReason::OSR_invalidation_for_compiling_with_C1); } compile(mh, bci, CompLevel_simple, THREAD); } @@ -1516,7 +1516,7 @@ void CompilationPolicy::method_back_branch_event(const methodHandle& mh, const m int osr_bci = nm->is_osr_method() ? nm->osr_entry_bci() : InvocationEntryBci; print_event(MAKE_NOT_ENTRANT, mh(), mh(), osr_bci, level); } - nm->make_not_entrant("OSR invalidation, back branch"); + nm->make_not_entrant(nmethod::ChangeReason::OSR_invalidation_back_branch); } } // Fix up next_level if necessary to avoid deopts diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index d45036ced94..001a40f74bc 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -27,6 +27,7 @@ #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" +#include "code/nmethod.hpp" #include "code/scopeDesc.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerEvent.hpp" @@ -1206,7 +1207,7 @@ C2V_VMENTRY_0(jint, installCode0, (JNIEnv *env, jobject, assert(JVMCIENV->isa_HotSpotNmethod(installed_code_handle), "wrong type"); // Clear the link to an old nmethod first JVMCIObject nmethod_mirror = installed_code_handle; - JVMCIENV->invalidate_nmethod_mirror(nmethod_mirror, true, JVMCI_CHECK_0); + JVMCIENV->invalidate_nmethod_mirror(nmethod_mirror, true, nmethod::ChangeReason::JVMCI_replacing_with_new_code, JVMCI_CHECK_0); } else { assert(JVMCIENV->isa_InstalledCode(installed_code_handle), "wrong type"); } @@ -1382,7 +1383,7 @@ C2V_VMENTRY(void, reprofile, (JNIEnv* env, jobject, ARGUMENT_PAIR(method))) nmethod* code = method->code(); if (code != nullptr) { - code->make_not_entrant("JVMCI reprofile"); + code->make_not_entrant(nmethod::ChangeReason::JVMCI_reprofile); } MethodData* method_data = method->method_data(); @@ -1397,7 +1398,7 @@ C2V_END C2V_VMENTRY(void, invalidateHotSpotNmethod, (JNIEnv* env, jobject, jobject hs_nmethod, jboolean deoptimize)) JVMCIObject nmethod_mirror = JVMCIENV->wrap(hs_nmethod); - JVMCIENV->invalidate_nmethod_mirror(nmethod_mirror, deoptimize, JVMCI_CHECK); + JVMCIENV->invalidate_nmethod_mirror(nmethod_mirror, deoptimize, nmethod::ChangeReason::JVMCI_invalidate_nmethod, JVMCI_CHECK); C2V_END C2V_VMENTRY_NULL(jlongArray, collectCounters, (JNIEnv* env, jobject)) @@ -1822,7 +1823,7 @@ C2V_VMENTRY(void, materializeVirtualObjects, (JNIEnv* env, jobject, jobject _hs_ if (!fst.current()->is_compiled_frame()) { JVMCI_THROW_MSG(IllegalStateException, "compiled stack frame expected"); } - fst.current()->cb()->as_nmethod()->make_not_entrant("JVMCI materialize virtual objects"); + fst.current()->cb()->as_nmethod()->make_not_entrant(nmethod::ChangeReason::JVMCI_materialize_virtual_object); } Deoptimization::deoptimize(thread, *fst.current(), Deoptimization::Reason_none); // look for the frame again as it has been updated by deopt (pc, deopt state...) diff --git a/src/hotspot/share/jvmci/jvmciEnv.cpp b/src/hotspot/share/jvmci/jvmciEnv.cpp index b8474552256..8c9facf8489 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.cpp +++ b/src/hotspot/share/jvmci/jvmciEnv.cpp @@ -1750,7 +1750,7 @@ void JVMCIEnv::initialize_installed_code(JVMCIObject installed_code, CodeBlob* c } -void JVMCIEnv::invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimize, JVMCI_TRAPS) { +void JVMCIEnv::invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimize, nmethod::ChangeReason change_reason, JVMCI_TRAPS) { if (mirror.is_null()) { JVMCI_THROW(NullPointerException); } @@ -1773,7 +1773,7 @@ void JVMCIEnv::invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimize, JV if (!deoptimize) { // Prevent future executions of the nmethod but let current executions complete. - nm->make_not_entrant("JVMCI invalidate nmethod mirror"); + nm->make_not_entrant(change_reason); // Do not clear the address field here as the Java code may still // want to later call this method with deoptimize == true. That requires @@ -1782,7 +1782,7 @@ void JVMCIEnv::invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimize, JV // Deoptimize the nmethod immediately. DeoptimizationScope deopt_scope; deopt_scope.mark(nm); - nm->make_not_entrant("JVMCI invalidate nmethod mirror"); + nm->make_not_entrant(change_reason); nm->make_deoptimized(); deopt_scope.deoptimize_marked(); diff --git a/src/hotspot/share/jvmci/jvmciEnv.hpp b/src/hotspot/share/jvmci/jvmciEnv.hpp index 69f6647b0d6..b7b7c8f6771 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.hpp +++ b/src/hotspot/share/jvmci/jvmciEnv.hpp @@ -462,7 +462,7 @@ public: // field of `mirror` to prevent it from being called. // If `deoptimize` is true, the nmethod is immediately deoptimized. // The HotSpotNmethod.address field is zero upon returning. - void invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimze, JVMCI_TRAPS); + void invalidate_nmethod_mirror(JVMCIObject mirror, bool deoptimze, nmethod::ChangeReason change_reason, JVMCI_TRAPS); void initialize_installed_code(JVMCIObject installed_code, CodeBlob* cb, JVMCI_TRAPS); diff --git a/src/hotspot/share/jvmci/jvmciRuntime.cpp b/src/hotspot/share/jvmci/jvmciRuntime.cpp index bee5f4ea445..1f10e132eff 100644 --- a/src/hotspot/share/jvmci/jvmciRuntime.cpp +++ b/src/hotspot/share/jvmci/jvmciRuntime.cpp @@ -2184,7 +2184,7 @@ JVMCI::CodeInstallResult JVMCIRuntime::register_method(JVMCIEnv* JVMCIENV, tty->print_cr("Replacing method %s", method_name); } if (old != nullptr) { - old->make_not_entrant("JVMCI register method"); + old->make_not_entrant(nmethod::ChangeReason::JVMCI_register_method); } LogTarget(Info, nmethod, install) lt; diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index b3d896b6863..32d7943e1e8 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -3492,7 +3492,7 @@ void InstanceKlass::add_osr_nmethod(nmethod* n) { for (int l = CompLevel_limited_profile; l < n->comp_level(); l++) { nmethod *inv = lookup_osr_nmethod(n->method(), n->osr_entry_bci(), l, true); if (inv != nullptr && inv->is_in_use()) { - inv->make_not_entrant("OSR invalidation of lower levels"); + inv->make_not_entrant(nmethod::ChangeReason::OSR_invalidation_of_lower_level); } } } diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index d13458dfa93..1a36fce23aa 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -1028,7 +1028,7 @@ void Method::set_native_function(address function, bool post_event_flag) { // If so, we have to make it not_entrant. nmethod* nm = code(); // Put it into local variable to guard against concurrent updates if (nm != nullptr) { - nm->make_not_entrant("set native function"); + nm->make_not_entrant(nmethod::ChangeReason::set_native_function); } } diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index f14c5efc65d..cf3b48c11b5 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -794,7 +794,7 @@ class VM_WhiteBoxDeoptimizeFrames : public VM_WhiteBoxOperation { if (_make_not_entrant) { nmethod* nm = CodeCache::find_nmethod(f->pc()); assert(nm != nullptr, "did not find nmethod"); - nm->make_not_entrant("Whitebox deoptimization"); + nm->make_not_entrant(nmethod::ChangeReason::whitebox_deoptimization); } ++_result; } diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index 4042bb01c58..a0d9dd00339 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -1826,7 +1826,7 @@ void Deoptimization::deoptimize(JavaThread* thread, frame fr, DeoptReason reason #if INCLUDE_JVMCI address Deoptimization::deoptimize_for_missing_exception_handler(nmethod* nm) { // there is no exception handler for this pc => deoptimize - nm->make_not_entrant("missing exception handler"); + nm->make_not_entrant(nmethod::ChangeReason::missing_exception_handler); // Use Deoptimization::deoptimize for all of its side-effects: // gathering traps statistics, logging... @@ -2455,7 +2455,7 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* current, jint tr // Recompile if (make_not_entrant) { - if (!nm->make_not_entrant("uncommon trap")) { + if (!nm->make_not_entrant(nmethod::ChangeReason::uncommon_trap)) { return; // the call did not change nmethod's state } diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 57f93f87d47..f3ad0e0a325 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -1337,7 +1337,7 @@ void JavaThread::make_zombies() { // it is a Java nmethod nmethod* nm = CodeCache::find_nmethod(fst.current()->pc()); assert(nm != nullptr, "did not find nmethod"); - nm->make_not_entrant("zombie"); + nm->make_not_entrant(nmethod::ChangeReason::zombie); } } } From fe3be498b83e70a9f4739ddad6642c3aa04a97d3 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Thu, 5 Jun 2025 17:30:01 +0000 Subject: [PATCH 076/143] 8357141: Update to use jtreg 7.5.2 Reviewed-by: erikj, ihse, iris --- make/autoconf/lib-tests.m4 | 2 +- make/conf/github-actions.conf | 2 +- make/conf/jib-profiles.js | 4 ++-- test/docs/TEST.ROOT | 2 +- test/hotspot/jtreg/TEST.ROOT | 2 +- test/jaxp/TEST.ROOT | 2 +- test/jdk/TEST.ROOT | 2 +- test/langtools/TEST.ROOT | 2 +- test/lib-test/TEST.ROOT | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index d2a4fcbb191..9eb5ee5a046 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=7.5.1 +JTREG_MINIMUM_VERSION=7.5.2 GTEST_MINIMUM_VERSION=1.14.0 ################################################################################ diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index 27845ffbd7a..d2b6cd23128 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -26,7 +26,7 @@ # Versions and download locations for dependencies used by GitHub Actions (GHA) GTEST_VERSION=1.14.0 -JTREG_VERSION=7.5.1+1 +JTREG_VERSION=7.5.2+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk24/1f9ff9062db4449d8ca828c504ffae90/36/GPL/openjdk-24_linux-x64_bin.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 9bedc77cc28..91876878046 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1174,9 +1174,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "7.5.1", + version: "7.5.2", build_number: "1", - file: "bundles/jtreg-7.5.1+1.zip", + file: "bundles/jtreg-7.5.2+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), diff --git a/test/docs/TEST.ROOT b/test/docs/TEST.ROOT index 9be915dd4ff..5ca9b1f144f 100644 --- a/test/docs/TEST.ROOT +++ b/test/docs/TEST.ROOT @@ -38,7 +38,7 @@ groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Use new module options useNewOptions=true diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 59dd48e5af7..8f4ef153907 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -100,7 +100,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../../ notation to reach them diff --git a/test/jaxp/TEST.ROOT b/test/jaxp/TEST.ROOT index 7a457d87b69..6398c399f0a 100644 --- a/test/jaxp/TEST.ROOT +++ b/test/jaxp/TEST.ROOT @@ -23,7 +23,7 @@ modules=java.xml groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 30e2f7cab41..97efc340019 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -119,7 +119,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index 9b1c5368e66..e3bd3b74bcf 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -15,7 +15,7 @@ keys=intermittent randomness needs-src needs-src-jdk_javadoc groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Use new module options useNewOptions=true diff --git a/test/lib-test/TEST.ROOT b/test/lib-test/TEST.ROOT index 21b6fba41f2..162e6e15ec2 100644 --- a/test/lib-test/TEST.ROOT +++ b/test/lib-test/TEST.ROOT @@ -29,7 +29,7 @@ keys=randomness # Minimum jtreg version -requiredVersion=7.5.1+1 +requiredVersion=7.5.2+1 # Allow querying of various System properties in @requires clauses requires.extraPropDefns = ../jtreg-ext/requires/VMProps.java From 15178aa298e43be3e27121343432f25884db4e5d Mon Sep 17 00:00:00 2001 From: Andrey Turbanov Date: Thu, 5 Jun 2025 20:19:53 +0000 Subject: [PATCH 077/143] 8357688: Remove unnecessary List.get before remove in PopupFactory Reviewed-by: azvegint, kizune, serb --- .../classes/javax/swing/PopupFactory.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/PopupFactory.java b/src/java.desktop/share/classes/javax/swing/PopupFactory.java index 9789e95e7ff..682cdbd005b 100644 --- a/src/java.desktop/share/classes/javax/swing/PopupFactory.java +++ b/src/java.desktop/share/classes/javax/swing/PopupFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -433,10 +433,8 @@ public class PopupFactory { } else { return null; } - if (cache.size() > 0) { - HeavyWeightPopup r = cache.get(0); - cache.remove(0); - return r; + if (!cache.isEmpty()) { + return cache.removeFirst(); } return null; } @@ -776,10 +774,8 @@ public class PopupFactory { private static LightWeightPopup getRecycledLightWeightPopup() { synchronized (LightWeightPopup.class) { List lightPopupCache = getLightWeightPopupCache(); - if (lightPopupCache.size() > 0) { - LightWeightPopup r = lightPopupCache.get(0); - lightPopupCache.remove(0); - return r; + if (!lightPopupCache.isEmpty()) { + return lightPopupCache.removeFirst(); } return null; } @@ -934,10 +930,8 @@ public class PopupFactory { private static MediumWeightPopup getRecycledMediumWeightPopup() { synchronized (MediumWeightPopup.class) { List mediumPopupCache = getMediumWeightPopupCache(); - if (mediumPopupCache.size() > 0) { - MediumWeightPopup r = mediumPopupCache.get(0); - mediumPopupCache.remove(0); - return r; + if (!mediumPopupCache.isEmpty()) { + return mediumPopupCache.removeFirst(); } return null; } From c793de989facdb532021e1d5ddd01eb0e089b8e6 Mon Sep 17 00:00:00 2001 From: Archie Cobbs Date: Thu, 5 Jun 2025 21:57:33 +0000 Subject: [PATCH 078/143] 8350212: Track source end positions of declarations that support @SuppressWarnings Co-authored-by: Jan Lahoda Reviewed-by: mcimadamore --- .../com/sun/tools/javac/comp/Lower.java | 4 +- .../classes/com/sun/tools/javac/jvm/Gen.java | 8 +- .../sun/tools/javac/parser/JavacParser.java | 196 +++++++----------- .../com/sun/tools/javac/tree/EndPosTable.java | 31 ++- .../com/sun/tools/javac/tree/JCTree.java | 8 +- .../com/sun/tools/javac/tree/TreeInfo.java | 27 +-- .../share/classes/jdk/jshell/ReplParser.java | 3 +- .../MissingLNTEntryForFinalizerTest.java | 4 +- .../javac/parser/DeclarationEndPositions.java | 119 +++++++++++ .../tools/javac/parser/JavacParserTest.java | 9 +- .../javac/parser/extend/TrialParser.java | 3 +- 11 files changed, 252 insertions(+), 160 deletions(-) create mode 100644 test/langtools/tools/javac/parser/DeclarationEndPositions.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index a544b3948e5..54226155cf8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -3697,7 +3697,7 @@ public class Lower extends TreeTranslator { vardefinit).setType(tree.var.type); indexDef.sym = tree.var.sym; JCBlock body = make.Block(0, List.of(indexDef, tree.body)); - body.endpos = TreeInfo.endPos(tree.body); + body.bracePos = TreeInfo.endPos(tree.body); result = translate(make. ForLoop(List.of(init), cond, @@ -4158,7 +4158,7 @@ public class Lower extends TreeTranslator { stmtList.append(switch2); JCBlock res = make.Block(0L, stmtList.toList()); - res.endpos = TreeInfo.endPos(tree); + res.bracePos = TreeInfo.endPos(tree); return res; } else { JCSwitchExpression switch2 = make.SwitchExpression(make.Ident(dollar_tmp), lb.toList()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 725c7f8063b..14c5420c7c0 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -500,7 +500,7 @@ public class Gen extends JCTree.Visitor { c.members().enter(clinit); List clinitStats = clinitCode.toList(); JCBlock block = make.at(clinitStats.head.pos()).Block(0, clinitStats); - block.endpos = TreeInfo.endPos(clinitStats.last()); + block.bracePos = TreeInfo.endPos(clinitStats.last()); methodDefs.append(make.MethodDef(clinit, block)); if (!clinitTAs.isEmpty()) @@ -553,8 +553,8 @@ public class Gen extends JCTree.Visitor { // Find the super() invocation and append the given initializer code. TreeInfo.mapSuperCalls(md.body, supercall -> make.Block(0, initCode.prepend(supercall))); - if (md.body.endpos == Position.NOPOS) - md.body.endpos = TreeInfo.endPos(md.body.stats.last()); + if (md.body.bracePos == Position.NOPOS) + md.body.bracePos = TreeInfo.endPos(md.body.stats.last()); md.sym.appendUniqueTypeAttributes(initTAs); } @@ -1121,7 +1121,7 @@ public class Gen extends JCTree.Visitor { genStats(tree.stats, localEnv); // End the scope of all block-local variables in variable info. if (!env.tree.hasTag(METHODDEF)) { - code.statBegin(tree.endpos); + code.statBegin(tree.bracePos); code.endScopes(limit); code.pendingStatPos = Position.NOPOS; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 1383baa57b8..7ebcd0c844b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -231,8 +231,8 @@ public class JavacParser implements Parser { protected AbstractEndPosTable newEndPosTable(boolean keepEndPositions) { return keepEndPositions - ? new SimpleEndPosTable(this) - : new EmptyEndPosTable(this); + ? new SimpleEndPosTable() + : new MinimalEndPosTable(); } protected DocCommentTable newDocCommentTable(boolean keepDocComments, ParserFactory fac) { @@ -633,12 +633,14 @@ public class JavacParser implements Parser { * * @param tree The tree to be used as index in the hashtable * @param dc The doc comment to associate with the tree, or null. + * @return {@code tree} */ - protected void attach(JCTree tree, Comment dc) { + protected T attach(T tree, Comment dc) { if (keepDocComments && dc != null) { docComments.putComment(tree, dc); } reportDanglingComments(tree, dc); + return tree; } /** Reports all dangling comments associated with the @@ -702,16 +704,35 @@ public class JavacParser implements Parser { endPosTable.setErrorEndPos(errPos); } - protected void storeEnd(JCTree tree, int endpos) { - endPosTable.storeEnd(tree, endpos); + /** + * Store ending position for a tree, the value of which is the greater of + * last error position in {@link #endPosTable} and the given ending position. + * @param tree tree node + * @param endpos the ending position to associate with {@code tree} + * @return {@code tree} + */ + protected T storeEnd(T tree, int endpos) { + return endPosTable.storeEnd(tree, endpos); } - protected T to(T t) { - return endPosTable.to(t); + /** + * Store current token's ending position for a tree, the value of which + * will be the greater of last error position in {@link #endPosTable} + * and the ending position of the current token. + * @param tree tree node + */ + protected T to(T tree) { + return storeEnd(tree, token.endPos); } - protected T toP(T t) { - return endPosTable.toP(t); + /** + * Store current token's ending position for a tree, the value of which + * will be the greater of last error position in {@link #endPosTable} + * and the ending position of the previous token. + * @param tree tree node + */ + protected T toP(T tree) { + return storeEnd(tree, S.prevToken().endPos); } /** Get the start position for a tree node. The start position is @@ -1741,7 +1762,7 @@ public class JavacParser implements Parser { case RBRACE: case EOF: JCSwitchExpression e = to(F.at(switchPos).SwitchExpression(selector, cases.toList())); - e.endpos = token.pos; + e.bracePos = token.pos; accept(RBRACE); return e; default: @@ -2819,9 +2840,9 @@ public class JavacParser implements Parser { syntaxError(token.pos, Errors.Orphaned(token.kind)); switchBlockStatementGroups(); } - // the Block node has a field "endpos" for first char of last token, which is + // the Block node has a field "bracePos" for first char of last token, which is // usually but not necessarily the last char of the last token. - t.endpos = token.pos; + t.bracePos = token.pos; accept(RBRACE); return toP(t); } @@ -3142,7 +3163,7 @@ public class JavacParser implements Parser { accept(LBRACE); List cases = switchBlockStatementGroups(); JCSwitch t = to(F.at(pos).Switch(selector, cases)); - t.endpos = token.endPos; + t.bracePos = token.endPos; accept(RBRACE); return t; } @@ -3658,9 +3679,7 @@ public class JavacParser implements Parser { } else { throw new AssertionError("Unhandled annotation kind: " + kind); } - - storeEnd(ann, S.prevToken().endPos); - return ann; + return toP(ann); } List annotationFieldValuesOpt() { @@ -3835,9 +3854,8 @@ public class JavacParser implements Parser { } } JCVariableDecl result = toP(F.at(pos).VarDef(mods, name, type, init, declaredUsingVar)); - attach(result, dc); result.startPos = startPos; - return result; + return attach(result, dc); } Name restrictedTypeName(JCExpression e, boolean shouldWarn) { @@ -4131,7 +4149,7 @@ public class JavacParser implements Parser { firstTypeDecl = false; } } - List topLevelDefs = isImplicitClass ? constructImplicitClass(defs.toList()) : defs.toList(); + List topLevelDefs = isImplicitClass ? constructImplicitClass(defs.toList(), S.prevToken().endPos) : defs.toList(); JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs); if (!consumedToplevelDoc) attach(toplevel, firstToken.docComment()); @@ -4141,13 +4159,12 @@ public class JavacParser implements Parser { toplevel.docComments = docComments; if (keepLineMap) toplevel.lineMap = S.getLineMap(); - this.endPosTable.setParser(null); // remove reference to parser toplevel.endPositions = this.endPosTable; return toplevel; } // Restructure top level to be an implicitly declared class. - private List constructImplicitClass(List origDefs) { + private List constructImplicitClass(List origDefs, int endPos) { ListBuffer topDefs = new ListBuffer<>(); ListBuffer defs = new ListBuffer<>(); @@ -4177,6 +4194,7 @@ public class JavacParser implements Parser { JCClassDecl implicit = F.at(primaryPos).ClassDef( implicitMods, name, List.nil(), null, List.nil(), List.nil(), defs.toList()); + storeEnd(implicit, endPos); topDefs.append(implicit); return topDefs.toList(); } @@ -4192,11 +4210,12 @@ public class JavacParser implements Parser { accept(LBRACE); directives = moduleDirectiveList(); accept(RBRACE); + int endPos = S.prevToken().endPos; accept(EOF); - JCModuleDecl result = toP(F.at(pos).ModuleDef(mods, kind, name, directives)); - attach(result, dc); - return result; + JCModuleDecl result = F.at(pos).ModuleDef(mods, kind, name, directives); + storeEnd(result, endPos); + return attach(result, dc); } List moduleDirectiveList() { @@ -4386,8 +4405,7 @@ public class JavacParser implements Parser { List defs = classInterfaceOrRecordBody(name, false, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, extending, implementing, permitting, defs)); - attach(result, dc); - return result; + return attach(result, dc); } protected JCClassDecl recordDeclaration(JCModifiers mods, Comment dc) { @@ -4434,8 +4452,7 @@ public class JavacParser implements Parser { defs = defs.prepend(field); } JCClassDecl result = toP(F.at(pos).ClassDef(mods, name, typarams, null, implementing, defs)); - attach(result, dc); - return result; + return attach(result, dc); } Name typeName() { @@ -4474,8 +4491,7 @@ public class JavacParser implements Parser { defs = classInterfaceOrRecordBody(name, true, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, null, extending, permitting, defs)); - attach(result, dc); - return result; + return attach(result, dc); } List permitsClause(JCModifiers mods, String classOrInterface) { @@ -4522,8 +4538,7 @@ public class JavacParser implements Parser { JCClassDecl result = toP(F.at(pos). ClassDef(mods, name, List.nil(), null, implementing, defs)); - attach(result, dc); - return result; + return attach(result, dc); } /** EnumBody = "{" { EnumeratorDeclarationList } [","] @@ -4664,8 +4679,7 @@ public class JavacParser implements Parser { storeEnd(create, S.prevToken().endPos); ident = F.at(identPos).Ident(enumName); JCTree result = toP(F.at(pos).VarDef(mods, name, ident, create)); - attach(result, dc); - return result; + return attach(result, dc); } /** TypeList = Type {"," Type} @@ -5097,8 +5111,7 @@ public class JavacParser implements Parser { toP(F.at(pos).MethodDef(mods, name, type, typarams, receiverParam, params, thrown, body, defaultValue)); - attach(result, dc); - return result; + return attach(result, dc); } finally { this.receiverParam = prevReceiverParam; } @@ -5395,8 +5408,7 @@ public class JavacParser implements Parser { return mostInnerTypeToReturn; } else { mostInnerArrayType.elemtype = mostInnerTypeToReturn; - storeEnd(type, origEndPos); - return type; + return storeEnd(type, origEndPos); } } @@ -5622,121 +5634,69 @@ public class JavacParser implements Parser { } } - /* - * a functional source tree and end position mappings + /** + * A straightforward {@link EndPosTable} implementation. */ protected static class SimpleEndPosTable extends AbstractEndPosTable { - private final IntHashTable endPosMap; + private final IntHashTable endPosMap = new IntHashTable(); - SimpleEndPosTable(JavacParser parser) { - super(parser); - endPosMap = new IntHashTable(); - } - - public void storeEnd(JCTree tree, int endpos) { - endPosMap.put(tree, errorEndPos > endpos ? errorEndPos : endpos); - } - - protected T to(T t) { - storeEnd(t, parser.token.endPos); - return t; - } - - protected T toP(T t) { - storeEnd(t, parser.S.prevToken().endPos); - return t; + @Override + public T storeEnd(T tree, int endpos) { + endPosMap.put(tree, Math.max(endpos, errorEndPos)); + return tree; } + @Override public int getEndPos(JCTree tree) { int value = endPosMap.get(tree); // As long as Position.NOPOS==-1, this just returns value. return (value == -1) ? Position.NOPOS : value; } + @Override public int replaceTree(JCTree oldTree, JCTree newTree) { int pos = endPosMap.remove(oldTree); - if (pos != -1) { + if (pos != -1 && newTree != null) { storeEnd(newTree, pos); - return pos; } - return Position.NOPOS; + return pos; } } - /* - * a default skeletal implementation without any mapping overhead. + /** + * A minimal implementation that only stores what's required. */ - protected static class EmptyEndPosTable extends AbstractEndPosTable { + protected static class MinimalEndPosTable extends SimpleEndPosTable { - EmptyEndPosTable(JavacParser parser) { - super(parser); + @Override + public T storeEnd(T tree, int endpos) { + switch (tree.getTag()) { + case MODULEDEF: + case PACKAGEDEF: + case CLASSDEF: + case METHODDEF: + case VARDEF: + break; + default: + return tree; + } + return super.storeEnd(tree, endpos); } - - public void storeEnd(JCTree tree, int endpos) { /* empty */ } - - protected T to(T t) { - return t; - } - - protected T toP(T t) { - return t; - } - - public int getEndPos(JCTree tree) { - return Position.NOPOS; - } - - public int replaceTree(JCTree oldTree, JCTree newTree) { - return Position.NOPOS; - } - } protected abstract static class AbstractEndPosTable implements EndPosTable { - /** - * The current parser. - */ - protected JavacParser parser; /** * Store the last error position. */ public int errorEndPos = Position.NOPOS; - public AbstractEndPosTable(JavacParser parser) { - this.parser = parser; - } - - /** - * Store current token's ending position for a tree, the value of which - * will be the greater of last error position and the ending position of - * the current token. - * @param t The tree. - */ - protected abstract T to(T t); - - /** - * Store current token's ending position for a tree, the value of which - * will be the greater of last error position and the ending position of - * the previous token. - * @param t The tree. - */ - protected abstract T toP(T t); - - /** - * Set the error position during the parsing phases, the value of which - * will be set only if it is greater than the last stored error position. - * @param errPos The error position - */ + @Override public void setErrorEndPos(int errPos) { if (errPos > errorEndPos) { errorEndPos = errPos; } } - - public void setParser(JavacParser parser) { - this.parser = parser; - } } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/EndPosTable.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/EndPosTable.java index d68ba838ba1..83fe402c0a7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/EndPosTable.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/EndPosTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -25,8 +25,21 @@ package com.sun.tools.javac.tree; +import com.sun.tools.javac.util.Position; + /** * Specifies the methods to access a mappings of syntax trees to end positions. + * + *

+ * Implementations must store end positions for at least these node types: + *

    + *
  • {@link JCTree.JCModuleDecl} + *
  • {@link JCTree.JCPackageDecl} + *
  • {@link JCTree.JCClassDecl} + *
  • {@link JCTree.JCMethodDecl} + *
  • {@link JCTree.JCVariableDecl} + *
+ * *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own * risk. This code and its internal interfaces are subject to change @@ -40,23 +53,31 @@ public interface EndPosTable { * @param tree JCTree * @return position of the source tree or Positions.NOPOS for non-existent mapping */ - public int getEndPos(JCTree tree); + int getEndPos(JCTree tree); /** * Store ending position for a tree, the value of which is the greater of * last error position and the given ending position. * @param tree The tree. * @param endpos The ending position to associate with the tree. + * @return the {@code tree} */ - public abstract void storeEnd(JCTree tree, int endpos); + T storeEnd(T tree, int endpos); + + /** + * Set the error position during the parsing phases, the value of which + * will be set only if it is greater than the last stored error position. + * @param errPos The error position + */ + void setErrorEndPos(int errPos); /** * Give an old tree and a new tree, the old tree will be replaced with * the new tree, the position of the new tree will be that of the old * tree. * @param oldtree a JCTree to be replaced - * @param newtree a JCTree to be replaced with + * @param newtree a JCTree to be replaced with, or null to just remove {@code oldtree} * @return position of the old tree or Positions.NOPOS for non-existent mapping */ - public int replaceTree(JCTree oldtree, JCTree newtree); + int replaceTree(JCTree oldtree, JCTree newtree); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index de86b7e2c57..debc10c9ff8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -1121,7 +1121,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { /** statements */ public List stats; /** Position of closing brace, optional. */ - public int endpos = Position.NOPOS; + public int bracePos = Position.NOPOS; /** If this block contains record pattern, it is necessary to catch * exceptions from the deconstructors and wrap them. * The {@code patternMatchingCatch} keeps the list of the deconstructor @@ -1330,7 +1330,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public JCExpression selector; public List cases; /** Position of closing brace, optional. */ - public int endpos = Position.NOPOS; + public int bracePos = Position.NOPOS; public boolean hasUnconditionalPattern; public boolean isExhaustive; public boolean patternSwitch; @@ -1430,7 +1430,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { public JCExpression selector; public List cases; /** Position of closing brace, optional. */ - public int endpos = Position.NOPOS; + public int bracePos = Position.NOPOS; public boolean hasUnconditionalPattern; public boolean isExhaustive; public boolean patternSwitch; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java index 5e3b043fb11..ae946c5b6d6 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -500,12 +500,12 @@ public class TreeInfo { return tree.pos; } - /** The end position of given tree, if it is a block with - * defined endpos. + /** The closing brace position of given tree, if it is a block with + * defined bracePos. */ public static int endPos(JCTree tree) { - if (tree.hasTag(BLOCK) && ((JCBlock) tree).endpos != Position.NOPOS) - return ((JCBlock) tree).endpos; + if (tree.hasTag(BLOCK) && ((JCBlock) tree).bracePos != Position.NOPOS) + return ((JCBlock) tree).bracePos; else if (tree.hasTag(SYNCHRONIZED)) return endPos(((JCSynchronized) tree).body); else if (tree.hasTag(TRY)) { @@ -513,11 +513,11 @@ public class TreeInfo { return endPos((t.finalizer != null) ? t.finalizer : (t.catchers.nonEmpty() ? t.catchers.last().body : t.body)); } else if (tree.hasTag(SWITCH) && - ((JCSwitch) tree).endpos != Position.NOPOS) { - return ((JCSwitch) tree).endpos; + ((JCSwitch) tree).bracePos != Position.NOPOS) { + return ((JCSwitch) tree).bracePos; } else if (tree.hasTag(SWITCH_EXPRESSION) && - ((JCSwitchExpression) tree).endpos != Position.NOPOS) { - return ((JCSwitchExpression) tree).endpos; + ((JCSwitchExpression) tree).bracePos != Position.NOPOS) { + return ((JCSwitchExpression) tree).bracePos; } else return tree.pos; } @@ -646,11 +646,6 @@ public class TreeInfo { if (tree == null) return Position.NOPOS; - if (endPosTable == null) { - // fall back on limited info in the tree - return endPos(tree); - } - int mapPos = endPosTable.getEndPos(tree); if (mapPos != Position.NOPOS) return mapPos; @@ -731,8 +726,8 @@ public class TreeInfo { /** A DiagnosticPosition with the preferred position set to the - * end position of given tree, if it is a block with - * defined endpos. + * closing brace position of given tree, if it is a block with + * defined closing brace position. */ public static DiagnosticPosition diagEndPos(final JCTree tree) { final int endPos = TreeInfo.endPos(tree); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java b/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java index 2968bc3e173..0219fa0eaf8 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -141,7 +141,6 @@ class ReplParser extends JavacParser { storeEnd(toplevel, S.prevToken().endPos); } toplevel.lineMap = S.getLineMap(); - this.endPosTable.setParser(null); // remove reference to parser toplevel.endPositions = this.endPosTable; return toplevel; } diff --git a/test/langtools/tools/javac/T8180660/MissingLNTEntryForFinalizerTest.java b/test/langtools/tools/javac/T8180660/MissingLNTEntryForFinalizerTest.java index ff3f2cf3200..95a9ef2dd53 100644 --- a/test/langtools/tools/javac/T8180660/MissingLNTEntryForFinalizerTest.java +++ b/test/langtools/tools/javac/T8180660/MissingLNTEntryForFinalizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -151,7 +151,7 @@ public class MissingLNTEntryForFinalizerTest { com.sun.tools.javac.code.Type result = super.attribStat(tree, env); if (tree.hasTag(TRY)) { JCTry tryTree = (JCTry)tree; - lineNumber = env.toplevel.lineMap.getLineNumber(tryTree.finalizer.endpos); + lineNumber = env.toplevel.lineMap.getLineNumber(tryTree.finalizer.bracePos); } return result; } diff --git a/test/langtools/tools/javac/parser/DeclarationEndPositions.java b/test/langtools/tools/javac/parser/DeclarationEndPositions.java new file mode 100644 index 00000000000..473d6bc2712 --- /dev/null +++ b/test/langtools/tools/javac/parser/DeclarationEndPositions.java @@ -0,0 +1,119 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8350212 + * @summary Verify ending source positions are calculated for declarations supporting SuppressWarnings + * @modules jdk.compiler/com.sun.tools.javac.tree + * @run main DeclarationEndPositions + */ + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.TreeInfo; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +public class DeclarationEndPositions { + + public static void checkEndPosition(Class nodeType, String input, String marker) throws IOException { + + // Create source + var source = new SimpleJavaFileObject(URI.create("file://T.java"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return input; + } + }; + + // Parse source + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, List.of(), List.of(), List.of(source)); + Iterable units = ((JavacTask)task).parse(); + + // Find node and check end position + JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit)units.iterator().next(); + unit.accept(new TreeScanner() { + @Override + public Void scan(Tree node, Void aVoid) { + if (nodeType.isInstance(node)) { + JCTree tree = (JCTree)node; + int actual = TreeInfo.getEndPos(tree, unit.endPositions); + int expected = marker.indexOf('^') + 1; + if (actual != expected) { + throw new AssertionError(String.format( + "wrong end pos %d != %d for \"%s\" @ %d", actual, expected, input, tree.pos)); + } + } + return super.scan(node, aVoid); + } + }, null); + } + + public static void main(String... args) throws Exception { + + // JCModuleDecl + checkEndPosition(JCModuleDecl.class, + "/* comment */ module fred { /* comment */ } /* comment */", + " ^ "); + + // JCPackageDecl + checkEndPosition(JCPackageDecl.class, + "/* comment */ package fred; /* comment */", + " ^ "); + + // JCClassDecl + checkEndPosition(JCClassDecl.class, + "/* comment */ class Fred { /* comment */ } /* comment */", + " ^ "); + + // JCMethodDecl + checkEndPosition(JCMethodDecl.class, + "/* comment */ class Fred { void m() { /* comment */ } } /* comment */", + " ^ "); + + // JCVariableDecl + checkEndPosition(JCVariableDecl.class, + "/* comment */ class Fred { int x; } /* comment */", + " ^ "); + checkEndPosition(JCVariableDecl.class, + "/* comment */ class Fred { int x = 123; } /* comment */", + " ^ "); + checkEndPosition(JCVariableDecl.class, + "/* comment */ class A { try {} catch (Error err) {} } /* comment */", + " ^ "); + } +} diff --git a/test/langtools/tools/javac/parser/JavacParserTest.java b/test/langtools/tools/javac/parser/JavacParserTest.java index 6a2850fc8dd..9c6ad617132 100644 --- a/test/langtools/tools/javac/parser/JavacParserTest.java +++ b/test/langtools/tools/javac/parser/JavacParserTest.java @@ -2298,10 +2298,9 @@ public class JavacParserTest extends TestCase { @Test //JDK-8310326 void testUnnamedClassPositions() throws IOException { - String code = """ - void main() { - } - """; + // 0 1 2 + // 012345678901234567890 + String code = "void main() { }"; DiagnosticCollector coll = new DiagnosticCollector<>(); JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, coll, null, @@ -2313,7 +2312,7 @@ public class JavacParserTest extends TestCase { @Override public Void visitClass(ClassTree node, Void p) { assertEquals("Wrong start position", 0, sp.getStartPosition(cut, node)); - assertEquals("Wrong end position", -1, sp.getEndPosition(cut, node)); + assertEquals("Wrong end position", 15, sp.getEndPosition(cut, node)); assertEquals("Wrong modifiers start position", -1, sp.getStartPosition(cut, node.getModifiers())); assertEquals("Wrong modifiers end position", -1, sp.getEndPosition(cut, node.getModifiers())); return super.visitClass(node, p); diff --git a/test/langtools/tools/javac/parser/extend/TrialParser.java b/test/langtools/tools/javac/parser/extend/TrialParser.java index 539676a90fb..c9a858fbc47 100644 --- a/test/langtools/tools/javac/parser/extend/TrialParser.java +++ b/test/langtools/tools/javac/parser/extend/TrialParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -139,7 +139,6 @@ class TrialParser extends JavacParser { storeEnd(toplevel, S.prevToken().endPos); } toplevel.lineMap = S.getLineMap(); - this.endPosTable.setParser(null); // remove reference to parser toplevel.endPositions = this.endPosTable; return toplevel; } From 78158f30aee51e14ab203b0127aeb883c010319c Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Thu, 5 Jun 2025 22:13:24 +0000 Subject: [PATCH 079/143] 8358099: PEM spec updates Reviewed-by: mullan --- .../classes/java/security/PEMDecoder.java | 40 ++++++------- .../classes/java/security/PEMEncoder.java | 7 ++- .../classes/java/security/PEMRecord.java | 56 +++++++------------ .../sun/security/provider/X509Factory.java | 2 +- .../share/classes/sun/security/util/Pem.java | 2 +- .../jdk/java/security/PEM/PEMDecoderTest.java | 8 +-- 6 files changed, 48 insertions(+), 67 deletions(-) diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java index eeb6decdf40..7279e75cc98 100644 --- a/src/java.base/share/classes/java/security/PEMDecoder.java +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -81,24 +81,24 @@ import java.util.Objects; * {@link PEMRecord}. * *

The {@linkplain #decode(String, Class)} and - * {@linkplain #decode(InputStream, Class)} methods take a Class parameter + * {@linkplain #decode(InputStream, Class)} methods take a class parameter * which determines the type of {@code DEREncodable} that is returned. These * methods are useful when extracting or changing the return class. * For example, if the PEM contains both public and private keys, the - * Class parameter can specify which to return. Use + * class parameter can specify which to return. Use * {@code PrivateKey.class} to return only the private key. - * If the Class parameter is set to {@code X509EncodedKeySpec.class}, the + * If the class parameter is set to {@code X509EncodedKeySpec.class}, the * public key will be returned in that format. Any type of PEM data can be * decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}. - * If the Class parameter doesn't match the PEM content, an - * {@code IllegalArgumentException} will be thrown. + * If the class parameter doesn't match the PEM content, a + * {@linkplain ClassCastException} will be thrown. * *

A new {@code PEMDecoder} instance is created when configured * with {@linkplain #withFactory(Provider)} and/or * {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)} * configures the decoder to use only {@linkplain KeyFactory} and * {@linkplain CertificateFactory} instances from the given {@code Provider}. - * {@link#withDecryption(char[])} configures the decoder to decrypt all + * {@linkplain #withDecryption(char[])} configures the decoder to decrypt all * encrypted private key PEM data using the given password. * Configuring an instance for decryption does not prevent decoding with * unencrypted PEM. Any encrypted PEM that fails decryption @@ -117,15 +117,15 @@ import java.util.Objects; *

Here is an example of a {@code PEMDecoder} configured with decryption * and a factory provider: * {@snippet lang = java: - * PEMDecoder pe = PEMDecoder.of().withDecryption(password). + * PEMDecoder pd = PEMDecoder.of().withDecryption(password). * withFactory(provider); - * byte[] pemData = pe.decode(privKey); + * byte[] pemData = pd.decode(privKey); * } * * @implNote An implementation may support other PEM types and - * {@code DEREncodables}. This implementation additionally supports PEM types: - * {@code X509 CERTIFICATE}, {@code X.509 CERTIFICATE}, {@code CRL}, - * and {@code RSA PRIVATE KEY}. + * {@code DEREncodable} objects. This implementation additionally supports + * the following PEM types: {@code X509 CERTIFICATE}, + * {@code X.509 CERTIFICATE}, {@code CRL}, and {@code RSA PRIVATE KEY}. * * @see PEMEncoder * @see PEMRecord @@ -179,13 +179,13 @@ public final class PEMDecoder { return switch (pem.type()) { case Pem.PUBLIC_KEY -> { X509EncodedKeySpec spec = - new X509EncodedKeySpec(decoder.decode(pem.pem())); + new X509EncodedKeySpec(decoder.decode(pem.content())); yield getKeyFactory( KeyUtil.getAlgorithm(spec.getEncoded())). generatePublic(spec); } case Pem.PRIVATE_KEY -> { - PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.pem())); + PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.content())); String algo = p8key.getAlgorithm(); KeyFactory kf = getKeyFactory(algo); DEREncodable d = kf.generatePrivate( @@ -216,27 +216,27 @@ public final class PEMDecoder { case Pem.ENCRYPTED_PRIVATE_KEY -> { if (password == null) { yield new EncryptedPrivateKeyInfo(decoder.decode( - pem.pem())); + pem.content())); } - yield new EncryptedPrivateKeyInfo(decoder.decode(pem.pem())). + yield new EncryptedPrivateKeyInfo(decoder.decode(pem.content())). getKey(password.getPassword()); } case Pem.CERTIFICATE, Pem.X509_CERTIFICATE, Pem.X_509_CERTIFICATE -> { CertificateFactory cf = getCertFactory("X509"); yield (X509Certificate) cf.generateCertificate( - new ByteArrayInputStream(decoder.decode(pem.pem()))); + new ByteArrayInputStream(decoder.decode(pem.content()))); } case Pem.X509_CRL, Pem.CRL -> { CertificateFactory cf = getCertFactory("X509"); yield (X509CRL) cf.generateCRL( - new ByteArrayInputStream(decoder.decode(pem.pem()))); + new ByteArrayInputStream(decoder.decode(pem.content()))); } case Pem.RSA_PRIVATE_KEY -> { KeyFactory kf = getKeyFactory("RSA"); yield kf.generatePrivate( RSAPrivateCrtKeyImpl.getKeySpec(decoder.decode( - pem.pem()))); + pem.content()))); } default -> pem; }; @@ -271,7 +271,6 @@ public final class PEMDecoder { */ public DEREncodable decode(String str) { Objects.requireNonNull(str); - DEREncodable de; try { return decode(new ByteArrayInputStream( str.getBytes(StandardCharsets.UTF_8))); @@ -483,9 +482,6 @@ public final class PEMDecoder { * from the specified {@link Provider} to produce cryptographic objects. * Any errors using the {@code Provider} will occur during decoding. * - *

If {@code provider} is {@code null}, a new instance is returned with - * the default provider configuration. - * * @param provider the factory provider * @return a new PEMEncoder instance configured to the {@code Provider}. * @throws NullPointerException if {@code provider} is null diff --git a/src/java.base/share/classes/java/security/PEMEncoder.java b/src/java.base/share/classes/java/security/PEMEncoder.java index d5465083330..95fcffe967b 100644 --- a/src/java.base/share/classes/java/security/PEMEncoder.java +++ b/src/java.base/share/classes/java/security/PEMEncoder.java @@ -71,7 +71,7 @@ import java.util.concurrent.locks.ReentrantLock; * OneAsymmetricKey structure using the "PRIVATE KEY" type. * *

When encoding a {@link PEMRecord}, the API surrounds the - * {@linkplain PEMRecord#pem()} with the PEM header and footer + * {@linkplain PEMRecord#content()} with the PEM header and footer * from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is * not included in the encoding. {@code PEMRecord} will not perform * validity checks on the data. @@ -108,7 +108,8 @@ import java.util.concurrent.locks.ReentrantLock; * byte[] pemData = pe.encode(privKey); * } * - * @implNote An implementation may support other PEM types and DEREncodables. + * @implNote An implementation may support other PEM types and + * {@code DEREncodable} objects. * * * @see PEMDecoder @@ -287,7 +288,7 @@ public final class PEMEncoder { } // If `keySpec` is non-null, then `key` hasn't been established. - // Setting a `key' prevents repeated key generations operations. + // Setting a `key` prevents repeated key generation operations. // withEncryption() is a configuration method and cannot throw an // exception; therefore generation is delayed. if (keySpec != null) { diff --git a/src/java.base/share/classes/java/security/PEMRecord.java b/src/java.base/share/classes/java/security/PEMRecord.java index dfe951a0963..2ce567f9fde 100644 --- a/src/java.base/share/classes/java/security/PEMRecord.java +++ b/src/java.base/share/classes/java/security/PEMRecord.java @@ -29,7 +29,6 @@ import jdk.internal.javac.PreviewFeature; import sun.security.util.Pem; -import java.util.Base64; import java.util.Objects; /** @@ -39,20 +38,20 @@ import java.util.Objects; * cryptographic object is not desired or the type has no * {@code DEREncodable}. * - *

{@code type} and {@code pem} may not be {@code null}. + *

{@code type} and {@code content} may not be {@code null}. * {@code leadingData} may be null if no non-PEM data preceded PEM header * during decoding. {@code leadingData} may be useful for reading metadata * that accompanies PEM data. * *

No validation is performed during instantiation to ensure that - * {@code type} conforms to {@code RFC 7468}, that {@code pem} is valid Base64, - * or that {@code pem} matches the {@code type}. {@code leadingData} is not - * defensively copied and does not return a clone when - * {@linkplain #leadingData()} is called. + * {@code type} conforms to {@code RFC 7468}, that {@code content} is valid + * Base64, or that {@code content} matches the {@code type}. + * {@code leadingData} is not defensively copied and does not return a + * clone when {@linkplain #leadingData()} is called. * * @param type the type identifier in the PEM header without PEM syntax labels. * For a public key, {@code type} would be "PUBLIC KEY". - * @param pem any data between the PEM header and footer. + * @param content the Base64-encoded data, excluding the PEM header and footer * @param leadingData any non-PEM data preceding the PEM header when decoding. * * @spec https://www.rfc-editor.org/info/rfc7468 @@ -64,25 +63,25 @@ import java.util.Objects; * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) -public record PEMRecord(String type, String pem, byte[] leadingData) +public record PEMRecord(String type, String content, byte[] leadingData) implements DEREncodable { /** * Creates a {@code PEMRecord} instance with the given parameters. * * @param type the type identifier - * @param pem the Base64-encoded data encapsulated by the PEM header and - * footer. + * @param content the Base64-encoded data, excluding the PEM header and + * footer * @param leadingData any non-PEM data read during the decoding process * before the PEM header. This value maybe {@code null}. - * @throws IllegalArgumentException if the {@code type} is incorrectly + * @throws IllegalArgumentException if {@code type} is incorrectly * formatted. - * @throws NullPointerException if {@code type} and/or {@code pem} are + * @throws NullPointerException if {@code type} and/or {@code content} are * {@code null}. */ - public PEMRecord(String type, String pem, byte[] leadingData) { + public PEMRecord { Objects.requireNonNull(type, "\"type\" cannot be null."); - Objects.requireNonNull(pem, "\"pem\" cannot be null."); + Objects.requireNonNull(content, "\"content\" cannot be null."); // With no validity checking on `type`, the constructor accept anything // including lowercase. The onus is on the caller. @@ -92,37 +91,22 @@ public record PEMRecord(String type, String pem, byte[] leadingData) "Only the PEM type identifier is allowed"); } - this.type = type; - this.pem = pem; - this.leadingData = leadingData; } /** * Creates a {@code PEMRecord} instance with a given {@code type} and - * {@code pem} data in String form. {@code leadingData} is set to null. + * {@code content} data in String form. {@code leadingData} is set to null. * * @param type the PEM type identifier - * @param pem the Base64-encoded data encapsulated by the PEM header and - * footer. - * @throws IllegalArgumentException if the {@code type} is incorrectly + * @param content the Base64-encoded data, excluding the PEM header and + * footer + * @throws IllegalArgumentException if {@code type} is incorrectly * formatted. - * @throws NullPointerException if {@code type} and/or {@code pem} are + * @throws NullPointerException if {@code type} and/or {@code content} are * {@code null}. */ - public PEMRecord(String type, String pem) { - this(type, pem, null); - } - - /** - * Returns the binary encoding from the Base64 data contained in - * {@code pem}. - * - * @throws IllegalArgumentException if {@code pem} cannot be decoded. - * @return a new array of the binary encoding each time this - * method is called. - */ - public byte[] getEncoded() { - return Base64.getMimeDecoder().decode(pem); + public PEMRecord(String type, String content) { + this(type, content, null); } /** diff --git a/src/java.base/share/classes/sun/security/provider/X509Factory.java b/src/java.base/share/classes/sun/security/provider/X509Factory.java index ec7a1ac998a..1a8ace55fc8 100644 --- a/src/java.base/share/classes/sun/security/provider/X509Factory.java +++ b/src/java.base/share/classes/sun/security/provider/X509Factory.java @@ -565,7 +565,7 @@ public class X509Factory extends CertificateFactorySpi { } catch (EOFException e) { return null; } - return Base64.getDecoder().decode(rec.pem()); + return Base64.getDecoder().decode(rec.content()); } catch (IllegalArgumentException e) { throw new IOException(e); } diff --git a/src/java.base/share/classes/sun/security/util/Pem.java b/src/java.base/share/classes/sun/security/util/Pem.java index c6e56381771..492017eca29 100644 --- a/src/java.base/share/classes/sun/security/util/Pem.java +++ b/src/java.base/share/classes/sun/security/util/Pem.java @@ -343,7 +343,7 @@ public class Pem { * @return PEM in a string */ public static String pemEncoded(PEMRecord pem) { - String p = pem.pem().replaceAll("(.{64})", "$1\r\n"); + String p = pem.content().replaceAll("(.{64})", "$1\r\n"); return pemEncoded(pem.type(), p); } } diff --git a/test/jdk/java/security/PEM/PEMDecoderTest.java b/test/jdk/java/security/PEM/PEMDecoderTest.java index 6486bfc83c7..2ee9d1a69b3 100644 --- a/test/jdk/java/security/PEM/PEMDecoderTest.java +++ b/test/jdk/java/security/PEM/PEMDecoderTest.java @@ -107,8 +107,8 @@ public class PEMDecoderTest { System.err.println("received: " + new String(rec.leadingData())); throw new AssertionError("ecCSRWithData preData wrong"); } - if (rec.pem().lastIndexOf("F") > rec.pem().length() - 5) { - System.err.println("received: " + rec.pem()); + if (rec.content().lastIndexOf("F") > rec.content().length() - 5) { + System.err.println("received: " + rec.content()); throw new AssertionError("ecCSRWithData: " + "End of PEM data has an unexpected character"); } @@ -235,10 +235,10 @@ public class PEMDecoderTest { PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class); String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), ""); try { - PEMData.checkResults(expected, r.pem()); + PEMData.checkResults(expected, r.content()); } catch (AssertionError e) { System.err.println("expected:\n" + expected); - System.err.println("received:\n" + r.pem()); + System.err.println("received:\n" + r.content()); throw e; } From 029e3bf8f582f7399b80c592421b2fd72737e264 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 6 Jun 2025 02:07:51 +0000 Subject: [PATCH 080/143] 8349914: ZipFile::entries and ZipFile::getInputStream not consistent with each other when there are duplicate entries Co-authored-by: Lance Andersen Reviewed-by: lancea --- .../share/classes/java/util/zip/ZipEntry.java | 7 +- .../share/classes/java/util/zip/ZipFile.java | 18 +- .../zip/ZipFile/DupEntriesGetInputStream.java | 216 ++++++++++++++++++ 3 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 test/jdk/java/util/zip/ZipFile/DupEntriesGetInputStream.java diff --git a/src/java.base/share/classes/java/util/zip/ZipEntry.java b/src/java.base/share/classes/java/util/zip/ZipEntry.java index 836dda141b7..bf0bf55ff98 100644 --- a/src/java.base/share/classes/java/util/zip/ZipEntry.java +++ b/src/java.base/share/classes/java/util/zip/ZipEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -60,6 +60,10 @@ public class ZipEntry implements ZipConstants, Cloneable { byte[] extra; // optional extra field data for entry String comment; // optional comment string for entry int externalFileAttributes = -1; // File type, setuid, setgid, sticky, POSIX permissions + + // entry's LOC offset as noted in the entry's central header, -1 implies undetermined + long locOffset = -1; + /** * Compression method for uncompressed entries. */ @@ -138,6 +142,7 @@ public class ZipEntry implements ZipConstants, Cloneable { extra = e.extra; comment = e.comment; externalFileAttributes = e.externalFileAttributes; + locOffset = e.locOffset; } /** diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 17382181876..7fa507980c2 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -341,7 +341,7 @@ public class ZipFile implements ZipConstants, Closeable { if (pos == -1) { return null; } - in = new ZipFileInputStream(zsrc.cen, pos); + in = new ZipFileInputStream(zsrc.cen, pos, entry.locOffset); switch (CENHOW(zsrc.cen, pos)) { case STORED: synchronized (istreams) { @@ -653,6 +653,7 @@ public class ZipFile implements ZipConstants, Closeable { // read all bits in this field, including sym link attributes e.externalFileAttributes = CENATX_PERMS(cen, pos) & 0xFFFF; } + e.locOffset = CENOFF(cen, pos); int nlen = CENNAM(cen, pos); int elen = CENEXT(cen, pos); @@ -847,10 +848,21 @@ public class ZipFile implements ZipConstants, Closeable { protected long rem; // number of remaining bytes within entry protected long size; // uncompressed size of this entry - ZipFileInputStream(byte[] cen, int cenpos) { + /** + * @param cen the ZIP's CEN + * @param cenpos the entry's offset within the CEN + * @param locOffset the entry's LOC offset in the ZIP stream. If -1 is passed + * then the LOC offset for the entry will be read from the + * entry's central header + */ + ZipFileInputStream(byte[] cen, int cenpos, long locOffset) { rem = CENSIZ(cen, cenpos); size = CENLEN(cen, cenpos); - pos = CENOFF(cen, cenpos); + if (locOffset == -1) { + pos = CENOFF(cen, cenpos); + } else { + pos = locOffset; + } // ZIP64 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || pos == ZIP64_MAGICVAL) { diff --git a/test/jdk/java/util/zip/ZipFile/DupEntriesGetInputStream.java b/test/jdk/java/util/zip/ZipFile/DupEntriesGetInputStream.java new file mode 100644 index 00000000000..cbab0b2d910 --- /dev/null +++ b/test/jdk/java/util/zip/ZipFile/DupEntriesGetInputStream.java @@ -0,0 +1,216 @@ +/* + * 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. + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HexFormat; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @bug 8349914 + * @summary Validate ZipFile::getFileInputStream returns the correct data when + * there are multiple entries with the same filename + * @run junit DupEntriesGetInputStream + */ +class DupEntriesGetInputStream { + + // created through a call to createNormalZIP() + private static final String NORMAL_ZIP_CONTENT_HEX = """ + 504b03041400080808009195c35a00000000000000000000000006000000456e747279310bc9c8 + 2c560022d7bc92a24a054300504b07089bc0e55b0f0000000f000000504b030414000808080091 + 95c35a00000000000000000000000006000000456e747279320bc9c82c560022d7bc92a24a85bc + d2dca4d4228590f27c00504b0708ebda8deb1800000018000000504b03041400080808009195c3 + 5a00000000000000000000000006000000456e747279330bc9c82c560022d7bc92a24a05650563 + 00504b0708d1eafe7d1100000011000000504b010214001400080808009195c35a9bc0e55b0f00 + 00000f000000060000000000000000000000000000000000456e74727931504b01021400140008 + 0808009195c35aebda8deb1800000018000000060000000000000000000000000043000000456e + 74727932504b010214001400080808009195c35ad1eafe7d110000001100000006000000000000 + 000000000000008f000000456e74727933504b050600000000030003009c000000d40000000000 + """; + + // intentionally unused but left here to allow for constructing newer/updated + // NORMAL_ZIP_CONTENT_HEX, when necessary + private static String createNormalZIP() throws IOException { + final ByteArrayOutputStream zipContent = new ByteArrayOutputStream(); + try (ZipOutputStream zos = new ZipOutputStream(zipContent)) { + zos.putNextEntry(new ZipEntry(ENTRY1_NAME)); + zos.write(ENTRY1.getBytes(US_ASCII)); + zos.putNextEntry(new ZipEntry(ENTRY2_NAME)); + zos.write(ENTRY2.getBytes(US_ASCII)); + zos.putNextEntry(new ZipEntry(ENTRY3_NAME)); + zos.write(ENTRY3.getBytes(US_ASCII)); + } + return HexFormat.of().formatHex(zipContent.toByteArray()); + } + + // Entry Names and their data to be added to the ZIP File + private static final String ENTRY1_NAME = "Entry1"; + private static final String ENTRY1 = "This is Entry 1"; + + private static final String ENTRY2_NAME = "Entry2"; + private static final String ENTRY2 = "This is Entry number Two"; + + private static final String ENTRY3_NAME = "Entry3"; + private static final String ENTRY3 = "This is Entry # 3"; + + // ZIP entry and its expected data + record ZIP_ENTRY(String entryName, String data) { + } + + private static final ZIP_ENTRY[] ZIP_ENTRIES = new ZIP_ENTRY[]{ + new ZIP_ENTRY(ENTRY1_NAME, ENTRY1), + new ZIP_ENTRY(ENTRY2_NAME, ENTRY2), + new ZIP_ENTRY(ENTRY1_NAME, ENTRY3) + }; + + private static Path dupEntriesZipFile; + + // 008F LOCAL HEADER #3 04034B50 + // ... + // 00A1 Compressed Length 00000000 + // 00A5 Uncompressed Length 00000000 + // 00A9 Filename Length 0006 + // ... + // 00AD Filename 'Entry3' + private static final int ENTRY3_FILENAME_LOC_OFFSET = 0x00AD; + + // 013C CENTRAL HEADER #3 02014B50 + // ... + // 0150 Compressed Length 00000011 + // 0154 Uncompressed Length 00000011 + // 0158 Filename Length 0006 + // ... + // 016A Filename 'Entry3' + private static final int ENTRY3_FILENAME_CEN_OFFSET = 0x016A; + + @BeforeAll + static void createDupEntriesZIP() throws Exception { + final byte[] originalZIPContent = HexFormat.of().parseHex( + NORMAL_ZIP_CONTENT_HEX.replace("\n", "")); + final ByteBuffer buf = ByteBuffer.wrap(originalZIPContent).order(LITTLE_ENDIAN); + // replace the file name literal "Entry3" with the literal "Entry1", both in the + // LOC header and the CEN of Entry3 + final int locEntry3LastCharOffset = ENTRY3_FILENAME_LOC_OFFSET + ENTRY3_NAME.length() - 1; + buf.put(locEntry3LastCharOffset, (byte) 49); // 49 represents the character "1" + final int cenEntry3LastCharOffset = ENTRY3_FILENAME_CEN_OFFSET + ENTRY3_NAME.length() - 1; + buf.put(cenEntry3LastCharOffset, (byte) 49); // 49 represents the character "1" + buf.rewind(); + // write out the manipulated ZIP content, containing duplicate entries, into a file + // so that it can be read using ZipFile + dupEntriesZipFile = Files.createTempFile(Path.of("."), "8349914-", ".zip"); + Files.write(dupEntriesZipFile, buf.array()); + System.out.println("created ZIP file with duplicate entries at " + dupEntriesZipFile); + } + + /* + * Validate that the correct ZipEntry data is returned when a List is used + * to access entries returned from ZipFile::entries + * + * @throws IOException if an error occurs + */ + @Test + public void testUsingListEntries() throws IOException { + System.out.println("Processing entries via Collections.list()"); + try (ZipFile zf = new ZipFile(dupEntriesZipFile.toFile())) { + var entryNumber = 0; + for (var e : Collections.list(zf.entries())) { + verifyEntry(entryNumber++, zf, e); + } + } + } + + /* + * Validate that the correct ZipEntry data is returned when a ZipEntryIterator + * is used to access entries returned from ZipFile::entries + * + * @throws IOException if an error occurs + */ + @Test + public void testUsingZipEntryIterator() throws IOException { + System.out.println("Processing entries via a ZipEntryIterator"); + try (ZipFile zf = new ZipFile(dupEntriesZipFile.toFile())) { + Enumeration entries = zf.entries(); + var entryNumber = 0; + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + verifyEntry(entryNumber++, zf, entry); + } + } + } + + /* + * Validate that the correct ZipEntry data is returned when a EntrySpliterator + * is used to access entries returned from ZipFile::stream + * + * @throws IOException if an error occurs + */ + @Test + public void testUsingEntrySpliterator() throws IOException { + System.out.println("Processing entries via a EntrySpliterator"); + AtomicInteger eNumber = new AtomicInteger(0); + try (ZipFile zf = new ZipFile(dupEntriesZipFile.toFile())) { + zf.stream().forEach(e -> { + try { + verifyEntry(eNumber.getAndIncrement(), zf, e); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } + } + + /* + * Verify the ZipEntry returned matches what is expected + * + * @param entryNumber offset into ZIP_ENTRIES containing the expected value + * to be returned + * @param zf ZipFile containing the entry + * @param e ZipEntry to validate + * @throws IOException + */ + private static void verifyEntry(int entryNumber, ZipFile zf, ZipEntry e) throws IOException { + System.out.println("Validating Entry: " + entryNumber); + assertEquals(ZIP_ENTRIES[entryNumber].entryName(), e.getName()); + try (var in = zf.getInputStream(e)) { + var entryData = new String(in.readAllBytes(), StandardCharsets.UTF_8); + assertEquals(ZIP_ENTRIES[entryNumber].data(), entryData); + } + } +} From 28acca609bbb8ade0af88b536c8c88b7fa43849a Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 6 Jun 2025 03:50:06 +0000 Subject: [PATCH 081/143] 8358653: [s390] Clean up comments regarding frame manager Reviewed-by: mdoerr --- src/hotspot/cpu/s390/frame_s390.hpp | 2 +- src/hotspot/cpu/s390/register_s390.hpp | 6 +++--- src/hotspot/cpu/s390/runtime_s390.cpp | 2 +- src/hotspot/cpu/s390/sharedRuntime_s390.cpp | 2 +- src/hotspot/cpu/s390/stubGenerator_s390.cpp | 16 ++++++++-------- .../s390/templateInterpreterGenerator_s390.cpp | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index 85e957fe992..ad754706367 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -410,7 +410,7 @@ // C2I adapter frames: // - // STACK (interpreted called from compiled, on entry to frame manager): + // STACK (interpreted called from compiled, on entry to template interpreter): // // [TOP_C2I_FRAME] // [JIT_FRAME] diff --git a/src/hotspot/cpu/s390/register_s390.hpp b/src/hotspot/cpu/s390/register_s390.hpp index e7fdaa58d1a..a33145db02c 100644 --- a/src/hotspot/cpu/s390/register_s390.hpp +++ b/src/hotspot/cpu/s390/register_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -414,7 +414,7 @@ constexpr FloatRegister Z_FARG2 = Z_F2; constexpr FloatRegister Z_FARG3 = Z_F4; constexpr FloatRegister Z_FARG4 = Z_F6; -// Register declarations to be used in frame manager assembly code. +// Register declarations to be used in template interpreter assembly code. // Use only non-volatile registers in order to keep values across C-calls. // Register to cache the integer value on top of the operand stack. @@ -439,7 +439,7 @@ constexpr Register Z_bcp = Z_R13; // Bytecode which is dispatched (short lived!). constexpr Register Z_bytecode = Z_R14; -// Temporary registers to be used within frame manager. We can use +// Temporary registers to be used within template interpreter. We can use // the nonvolatile ones because the call stub has saved them. // Use only non-volatile registers in order to keep values across C-calls. constexpr Register Z_tmp_1 = Z_R10; diff --git a/src/hotspot/cpu/s390/runtime_s390.cpp b/src/hotspot/cpu/s390/runtime_s390.cpp index 8f96ff55ccb..99a33716b8b 100644 --- a/src/hotspot/cpu/s390/runtime_s390.cpp +++ b/src/hotspot/cpu/s390/runtime_s390.cpp @@ -118,7 +118,7 @@ ExceptionBlob* OptoRuntime::generate_exception_blob() { __ z_lgr(Z_SP, saved_sp); // [Z_RET] isn't null was possible in hotspot5 but not in sapjvm6. - // C2I adapter extensions are now removed by a resize in the frame manager + // C2I adapter extensions are now removed by a resize in the template interpreter // (unwind_initial_activation_pending_exception). #ifdef ASSERT __ z_ltgr(handle_exception, handle_exception); diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index bd5bbf4c7e5..cb1f12504fd 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -2139,7 +2139,7 @@ static address gen_c2i_adapter(MacroAssembler *masm, Register value = Z_R12; // Remember the senderSP so we can pop the interpreter arguments off of the stack. - // In addition, frame manager expects initial_caller_sp in Z_R10. + // In addition, template interpreter expects initial_caller_sp in Z_R10. __ z_lgr(sender_SP, Z_SP); // This should always fit in 14 bit immediate. diff --git a/src/hotspot/cpu/s390/stubGenerator_s390.cpp b/src/hotspot/cpu/s390/stubGenerator_s390.cpp index 1e9136cdca9..d3f6540a3ea 100644 --- a/src/hotspot/cpu/s390/stubGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/stubGenerator_s390.cpp @@ -115,7 +115,7 @@ class StubGenerator: public StubCodeGenerator { // [SP+176] - thread : Thread* // address generate_call_stub(address& return_address) { - // Set up a new C frame, copy Java arguments, call frame manager + // Set up a new C frame, copy Java arguments, call template interpreter // or native_entry, and process result. StubGenStubId stub_id = StubGenStubId::call_stub_id; @@ -272,10 +272,10 @@ class StubGenerator: public StubCodeGenerator { BLOCK_COMMENT("call {"); { - // Call frame manager or native entry. + // Call template interpreter or native entry. // - // Register state on entry to frame manager / native entry: + // Register state on entry to template interpreter / native entry: // // Z_ARG1 = r_top_of_arguments_addr - intptr_t *sender tos (prepushed) // Lesp = (SP) + copied_arguments_offset - 8 @@ -290,7 +290,7 @@ class StubGenerator: public StubCodeGenerator { __ z_lgr(Z_esp, r_top_of_arguments_addr); // - // Stack on entry to frame manager / native entry: + // Stack on entry to template interpreter / native entry: // // F0 [TOP_IJAVA_FRAME_ABI] // [outgoing Java arguments] @@ -300,7 +300,7 @@ class StubGenerator: public StubCodeGenerator { // // Do a light-weight C-call here, r_new_arg_entry holds the address - // of the interpreter entry point (frame manager or native entry) + // of the interpreter entry point (template interpreter or native entry) // and save runtime-value of return_pc in return_address // (call by reference argument). return_address = __ call_stub(r_new_arg_entry); @@ -309,11 +309,11 @@ class StubGenerator: public StubCodeGenerator { { BLOCK_COMMENT("restore registers {"); - // Returned from frame manager or native entry. + // Returned from template interpreter or native entry. // Now pop frame, process result, and return to caller. // - // Stack on exit from frame manager / native entry: + // Stack on exit from template interpreter / native entry: // // F0 [ABI] // ... @@ -330,7 +330,7 @@ class StubGenerator: public StubCodeGenerator { __ pop_frame(); // Reload some volatile registers which we've spilled before the call - // to frame manager / native entry. + // to template interpreter / native entry. // Access all locals via frame pointer, because we know nothing about // the topmost frame's size. __ z_lg(r_arg_result_addr, result_address_offset, r_entryframe_fp); diff --git a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp index e387d60b594..e03d891496a 100644 --- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp @@ -1217,7 +1217,7 @@ void TemplateInterpreterGenerator::generate_fixed_frame(bool native_call) { // Various method entries -// Math function, frame manager must set up an interpreter state, etc. +// Math function, template interpreter must set up an interpreter state, etc. address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::MethodKind kind) { // Decide what to do: Use same platform specific instructions and runtime calls as compilers. From e918a59b1dacf273620aee334517bebfb1fb1a0f Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 6 Jun 2025 06:26:09 +0000 Subject: [PATCH 082/143] 8357821: Revert incorrectly named JavaLangAccess::unchecked* methods Reviewed-by: pminborg --- .../share/classes/java/io/DataInputStream.java | 4 ++-- .../classes/java/io/ObjectInputStream.java | 4 ++-- .../share/classes/java/lang/System.java | 6 +++--- .../share/classes/java/util/zip/ZipCoder.java | 2 +- .../jdk/internal/access/JavaLangAccess.java | 18 +++++++++--------- .../classfile/impl/AbstractPoolEntry.java | 4 ++-- .../share/classes/sun/nio/cs/CESU_8.java | 2 +- .../share/classes/sun/nio/cs/DoubleByte.java | 2 +- .../share/classes/sun/nio/cs/ISO_8859_1.java | 2 +- .../share/classes/sun/nio/cs/SingleByte.java | 2 +- .../share/classes/sun/nio/cs/US_ASCII.java | 2 +- .../share/classes/sun/nio/cs/UTF_8.java | 2 +- 12 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/java/io/DataInputStream.java b/src/java.base/share/classes/java/io/DataInputStream.java index fa6fecf83da..daf75b1318f 100644 --- a/src/java.base/share/classes/java/io/DataInputStream.java +++ b/src/java.base/share/classes/java/io/DataInputStream.java @@ -595,7 +595,7 @@ loop: while (true) { int chararr_count=0; in.readFully(bytearr, 0, utflen); - int ascii = JLA.uncheckedCountPositives(bytearr, 0, utflen); + int ascii = JLA.countPositives(bytearr, 0, utflen); if (ascii == utflen) { String str; if (trusted) { @@ -621,7 +621,7 @@ loop: while (true) { } if (ascii != 0) { - JLA.uncheckedInflateBytesToChars(bytearr, 0, chararr, 0, ascii); + JLA.inflateBytesToChars(bytearr, 0, chararr, 0, ascii); count += ascii; chararr_count += ascii; } diff --git a/src/java.base/share/classes/java/io/ObjectInputStream.java b/src/java.base/share/classes/java/io/ObjectInputStream.java index b9593fa2c2f..daed5f3cce5 100644 --- a/src/java.base/share/classes/java/io/ObjectInputStream.java +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java @@ -3536,7 +3536,7 @@ public class ObjectInputStream if (utflen > 0 && utflen < Integer.MAX_VALUE) { // Scan for leading ASCII chars int avail = end - pos; - int ascii = JLA.uncheckedCountPositives(buf, pos, Math.min(avail, (int)utflen)); + int ascii = JLA.countPositives(buf, pos, Math.min(avail, (int)utflen)); if (ascii == utflen) { // Complete match, consume the buf[pos ... pos + ascii] range and return. // Modified UTF-8 and ISO-8859-1 are both ASCII-compatible encodings bytes @@ -3549,7 +3549,7 @@ public class ObjectInputStream // Avoid allocating a StringBuilder if there's enough data in buf and // cbuf is large enough if (avail >= utflen && utflen <= CHAR_BUF_SIZE) { - JLA.uncheckedInflateBytesToChars(buf, pos, cbuf, 0, ascii); + JLA.inflateBytesToChars(buf, pos, cbuf, 0, ascii); pos += ascii; int cbufPos = readUTFSpan(ascii, utflen - ascii); return new String(cbuf, 0, cbufPos); diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index bcdbc39e665..0175558d313 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2118,7 +2118,7 @@ public final class System { return ModuleLayer.layers(loader); } - public int uncheckedCountPositives(byte[] bytes, int offset, int length) { + public int countPositives(byte[] bytes, int offset, int length) { return StringCoding.countPositives(bytes, offset, length); } public int countNonZeroAscii(String s) { @@ -2145,11 +2145,11 @@ public final class System { return String.getBytesUTF8NoRepl(s); } - public void uncheckedInflateBytesToChars(byte[] src, int srcOff, char[] dst, int dstOff, int len) { + public void inflateBytesToChars(byte[] src, int srcOff, char[] dst, int dstOff, int len) { StringLatin1.inflate(src, srcOff, dst, dstOff, len); } - public int uncheckedDecodeASCII(byte[] src, int srcOff, char[] dst, int dstOff, int len) { + public int decodeASCII(byte[] src, int srcOff, char[] dst, int dstOff, int len) { return String.decodeASCII(src, srcOff, dst, dstOff, len); } diff --git a/src/java.base/share/classes/java/util/zip/ZipCoder.java b/src/java.base/share/classes/java/util/zip/ZipCoder.java index bbf1c10112a..0c3282e3518 100644 --- a/src/java.base/share/classes/java/util/zip/ZipCoder.java +++ b/src/java.base/share/classes/java/util/zip/ZipCoder.java @@ -266,7 +266,7 @@ class ZipCoder { return 0; } int end = off + len; - int asciiLen = JLA.uncheckedCountPositives(a, off, len); + int asciiLen = JLA.countPositives(a, off, len); if (asciiLen != len) { // Non-ASCII, fall back to decoding a String // We avoid using decoder() here since the UTF8ZipCoder is diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 5109e7b2a5b..e8343274cac 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -302,10 +302,10 @@ public interface JavaLangAccess { /** * Count the number of leading positive bytes in the range. - *

- * WARNING: This method does not perform any bound checks. + * + * @implSpec Implementations of this method must perform bounds checks. */ - int uncheckedCountPositives(byte[] ba, int off, int len); + int countPositives(byte[] ba, int off, int len); /** * Count the number of leading non-zero ascii chars in the String. @@ -390,20 +390,20 @@ public interface JavaLangAccess { /** * Inflated copy from {@code byte[]} to {@code char[]}, as defined by * {@code StringLatin1.inflate}. - *

- * WARNING: This method does not perform any bound checks. + * + * @implSpec Implementations of this method must perform bounds checks. */ - void uncheckedInflateBytesToChars(byte[] src, int srcOff, char[] dst, int dstOff, int len); + void inflateBytesToChars(byte[] src, int srcOff, char[] dst, int dstOff, int len); /** * Decodes ASCII from the source byte array into the destination * char array. - *

- * WARNING: This method does not perform any bound checks. + * + * @implSpec Implementations of this method must perform bounds checks. * * @return the number of bytes successfully decoded, at most len */ - int uncheckedDecodeASCII(byte[] src, int srcOff, char[] dst, int dstOff, int len); + int decodeASCII(byte[] src, int srcOff, char[] dst, int dstOff, int len); /** * Returns the initial `System.in` to determine if it is replaced diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java index ab7f0847bbd..962a2057585 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -218,7 +218,7 @@ public abstract sealed class AbstractPoolEntry { * two-times-three-byte format instead. */ private void inflate() { - int singleBytes = JLA.uncheckedCountPositives(rawBytes, offset, rawLen); + int singleBytes = JLA.countPositives(rawBytes, offset, rawLen); int hash = ArraysSupport.hashCodeOfUnsigned(rawBytes, offset, singleBytes, 0); if (singleBytes == rawLen) { this.contentHash = hash; @@ -233,7 +233,7 @@ public abstract sealed class AbstractPoolEntry { char[] chararr = new char[rawLen]; int chararr_count = singleBytes; // Inflate prefix of bytes to characters - JLA.uncheckedInflateBytesToChars(rawBytes, offset, chararr, 0, singleBytes); + JLA.inflateBytesToChars(rawBytes, offset, chararr, 0, singleBytes); int px = offset + singleBytes; int utfend = offset + rawLen; diff --git a/src/java.base/share/classes/sun/nio/cs/CESU_8.java b/src/java.base/share/classes/sun/nio/cs/CESU_8.java index a6e233873a8..a9a25e151ad 100644 --- a/src/java.base/share/classes/sun/nio/cs/CESU_8.java +++ b/src/java.base/share/classes/sun/nio/cs/CESU_8.java @@ -196,7 +196,7 @@ class CESU_8 extends Unicode int dp = doff + dst.position(); int dl = doff + dst.limit(); - int n = JLA.uncheckedDecodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.decodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; diff --git a/src/java.base/share/classes/sun/nio/cs/DoubleByte.java b/src/java.base/share/classes/sun/nio/cs/DoubleByte.java index c4c9856a60b..4738f51515b 100644 --- a/src/java.base/share/classes/sun/nio/cs/DoubleByte.java +++ b/src/java.base/share/classes/sun/nio/cs/DoubleByte.java @@ -168,7 +168,7 @@ public class DoubleByte { try { if (isASCIICompatible) { - int n = JLA.uncheckedDecodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); + int n = JLA.decodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); dp += n; sp += n; } diff --git a/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java b/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java index 241171d8deb..39215bfa93d 100644 --- a/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java +++ b/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java @@ -87,7 +87,7 @@ public class ISO_8859_1 int dl = doff + dst.limit(); int decodeLen = Math.min(sl - sp, dl - dp); - JLA.uncheckedInflateBytesToChars(sa, sp, da, dp, decodeLen); + JLA.inflateBytesToChars(sa, sp, da, dp, decodeLen); sp += decodeLen; dp += decodeLen; src.position(sp - soff); diff --git a/src/java.base/share/classes/sun/nio/cs/SingleByte.java b/src/java.base/share/classes/sun/nio/cs/SingleByte.java index fe66f3216a4..d802cc85aa8 100644 --- a/src/java.base/share/classes/sun/nio/cs/SingleByte.java +++ b/src/java.base/share/classes/sun/nio/cs/SingleByte.java @@ -95,7 +95,7 @@ public class SingleByte } if (isASCIICompatible) { - int n = JLA.uncheckedDecodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); + int n = JLA.decodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); sp += n; dp += n; } diff --git a/src/java.base/share/classes/sun/nio/cs/US_ASCII.java b/src/java.base/share/classes/sun/nio/cs/US_ASCII.java index 627b1433d61..bb84ab1bd4b 100644 --- a/src/java.base/share/classes/sun/nio/cs/US_ASCII.java +++ b/src/java.base/share/classes/sun/nio/cs/US_ASCII.java @@ -83,7 +83,7 @@ public class US_ASCII int dl = doff + dst.limit(); // ASCII only loop - int n = JLA.uncheckedDecodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.decodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; src.position(sp - soff); diff --git a/src/java.base/share/classes/sun/nio/cs/UTF_8.java b/src/java.base/share/classes/sun/nio/cs/UTF_8.java index 89a199db99b..54e479f838a 100644 --- a/src/java.base/share/classes/sun/nio/cs/UTF_8.java +++ b/src/java.base/share/classes/sun/nio/cs/UTF_8.java @@ -227,7 +227,7 @@ public final class UTF_8 extends Unicode { int dp = doff + dst.position(); int dl = doff + dst.limit(); - int n = JLA.uncheckedDecodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.decodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; From bb2611ad43a2feaebc2246fdbac2179a40115d67 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 6 Jun 2025 06:53:10 +0000 Subject: [PATCH 083/143] 8357993: Use "stdin.encoding" for reading System.in with InputStreamReader/Scanner [hotspot] Reviewed-by: cjplummer, sspitsyn --- .../share/classes/sun/jvm/hotspot/CLHSDB.java | 7 ++++--- .../classes/jdk/jfr/internal/jfc/model/UserInterface.java | 6 ++++-- .../jvmti/AttachOnDemand/attach010/attach010Agent00.java | 5 +++-- .../jtreg/vmTestbase/nsk/share/jpda/BindServer.java | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/CLHSDB.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/CLHSDB.java index 3ed34ef1ae9..565d9e83329 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/CLHSDB.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/CLHSDB.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -28,6 +28,7 @@ import sun.jvm.hotspot.*; import sun.jvm.hotspot.debugger.*; import java.io.*; +import java.nio.charset.Charset; import java.util.*; public class CLHSDB { @@ -106,8 +107,8 @@ public class CLHSDB { }; - BufferedReader in = - new BufferedReader(new InputStreamReader(System.in)); + Charset charset = Charset.forName(System.getProperty("stdin.encoding"), Charset.defaultCharset()); + BufferedReader in = new BufferedReader(new InputStreamReader(System.in, charset)); CommandProcessor cp = new CommandProcessor(di, in, System.out, System.err); cp.run(true); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java index 7d9fc2478ba..f397fc4ec7d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -27,6 +27,7 @@ package jdk.jfr.internal.jfc.model; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; public final class UserInterface { @@ -40,7 +41,8 @@ public final class UserInterface { public String readLine() throws AbortException { try { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + Charset charset = Charset.forName(System.getProperty("stdin.encoding"), Charset.defaultCharset()); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in, charset)); String line = br.readLine(); if (line == null || line.equalsIgnoreCase("Q")) { println(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/AttachOnDemand/attach010/attach010Agent00.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/AttachOnDemand/attach010/attach010Agent00.java index 877c1e3862e..1e0abf4e11e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/AttachOnDemand/attach010/attach010Agent00.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/AttachOnDemand/attach010/attach010Agent00.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 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 @@ -60,7 +60,8 @@ public class attach010Agent00 extends AbstractJarAgent { FileInputStream newInputStream = new FileInputStream(inStreamFileName); System.setIn(newInputStream); - BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader(System.in)); + BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader( + System.in, System.getProperty("stdin.encoding"))); int readValue = Integer.parseInt(inputStreamReader.readLine()); if (readValue != valueToWrite) { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/BindServer.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/BindServer.java index 2ed334e0386..58a7ca2a703 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/BindServer.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/BindServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -173,7 +173,7 @@ public final class BindServer { } BufferedReader stdIn = new BufferedReader( - new InputStreamReader(System.in)); + new InputStreamReader(System.in, System.getProperty("stdin.encoding"))); try (ListeningThread listeningThread = new ListeningThread(this)) { listeningThread.bind(); listeningThread.start(); From d1b788005bdf11f1426baa8e811c121a956482c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Fri, 6 Jun 2025 08:16:15 +0000 Subject: [PATCH 084/143] 8357951: Remove the IdealLoopTree* loop parameter from PhaseIdealLoop::loop_iv_phi Reviewed-by: thartmann, mhaessig --- src/hotspot/share/opto/loopnode.cpp | 12 ++++++------ src/hotspot/share/opto/loopnode.hpp | 4 ++-- src/hotspot/share/opto/loopopts.cpp | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 195d48b37c6..3130df54681 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -458,7 +458,7 @@ Node* PhaseIdealLoop::loop_iv_incr(Node* incr, Node* x, IdealLoopTree* loop, Nod return incr; } -Node* PhaseIdealLoop::loop_iv_stride(Node* incr, IdealLoopTree* loop, Node*& xphi) { +Node* PhaseIdealLoop::loop_iv_stride(Node* incr, Node*& xphi) { assert(incr->Opcode() == Op_AddI || incr->Opcode() == Op_AddL, "caller resp."); // Get merge point xphi = incr->in(1); @@ -474,7 +474,7 @@ Node* PhaseIdealLoop::loop_iv_stride(Node* incr, IdealLoopTree* loop, Node*& xph return stride; } -PhiNode* PhaseIdealLoop::loop_iv_phi(Node* xphi, Node* phi_incr, Node* x, IdealLoopTree* loop) { +PhiNode* PhaseIdealLoop::loop_iv_phi(Node* xphi, Node* phi_incr, Node* x) { if (!xphi->is_Phi()) { return nullptr; // Too much math on the trip counter } @@ -1481,11 +1481,11 @@ void PhaseIdealLoop::check_counted_loop_shape(IdealLoopTree* loop, Node* x, Basi assert(incr != nullptr && incr->Opcode() == Op_Add(bt), "no incr"); Node* xphi = nullptr; - Node* stride = loop_iv_stride(incr, loop, xphi); + Node* stride = loop_iv_stride(incr, xphi); assert(stride != nullptr, "no stride"); - PhiNode* phi = loop_iv_phi(xphi, phi_incr, x, loop); + PhiNode* phi = loop_iv_phi(xphi, phi_incr, x); assert(phi != nullptr && phi->in(LoopNode::LoopBackControl) == incr, "No phi"); @@ -1650,7 +1650,7 @@ bool PhaseIdealLoop::is_counted_loop(Node* x, IdealLoopTree*&loop, BasicType iv_ assert(incr->Opcode() == Op_Add(iv_bt), "wrong increment code"); Node* xphi = nullptr; - Node* stride = loop_iv_stride(incr, loop, xphi); + Node* stride = loop_iv_stride(incr, xphi); if (stride == nullptr) { return false; @@ -1664,7 +1664,7 @@ bool PhaseIdealLoop::is_counted_loop(Node* x, IdealLoopTree*&loop, BasicType iv_ jlong stride_con = stride->get_integer_as_long(iv_bt); assert(stride_con != 0, "missed some peephole opt"); - PhiNode* phi = loop_iv_phi(xphi, phi_incr, x, loop); + PhiNode* phi = loop_iv_phi(xphi, phi_incr, x); if (phi == nullptr || (trunc1 == nullptr && phi->in(LoopNode::LoopBackControl) != incr) || diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 0f87ed6763d..7670f6c620e 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1280,8 +1280,8 @@ public: Node* loop_exit_control(Node* x, IdealLoopTree* loop); Node* loop_exit_test(Node* back_control, IdealLoopTree* loop, Node*& incr, Node*& limit, BoolTest::mask& bt, float& cl_prob); Node* loop_iv_incr(Node* incr, Node* x, IdealLoopTree* loop, Node*& phi_incr); - Node* loop_iv_stride(Node* incr, IdealLoopTree* loop, Node*& xphi); - PhiNode* loop_iv_phi(Node* xphi, Node* phi_incr, Node* x, IdealLoopTree* loop); + Node* loop_iv_stride(Node* incr, Node*& xphi); + PhiNode* loop_iv_phi(Node* xphi, Node* phi_incr, Node* x); bool is_counted_loop(Node* x, IdealLoopTree*&loop, BasicType iv_bt); diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 070749cfe1f..912d8452d21 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -4271,13 +4271,13 @@ bool PhaseIdealLoop::duplicate_loop_backedge(IdealLoopTree *loop, Node_List &old } assert(in->Opcode() == Op_AddI, "wrong increment code"); Node* xphi = nullptr; - Node* stride = loop_iv_stride(in, loop, xphi); + Node* stride = loop_iv_stride(in, xphi); if (stride == nullptr) { continue; } - PhiNode* phi = loop_iv_phi(xphi, nullptr, head, loop); + PhiNode* phi = loop_iv_phi(xphi, nullptr, head); if (phi == nullptr || (trunc1 == nullptr && phi->in(LoopNode::LoopBackControl) != incr) || (trunc1 != nullptr && phi->in(LoopNode::LoopBackControl) != trunc1)) { From 65fda5c02aeb1832bc88dc83ee8465cd8ad89179 Mon Sep 17 00:00:00 2001 From: Harald Eilertsen Date: Fri, 6 Jun 2025 08:16:37 +0000 Subject: [PATCH 085/143] 8358593: Add ucontext accessors for *BSD on Aarch64 Co-authored-by: Greg Lewis Co-authored-by: Kurt Miller Reviewed-by: aph --- .../os_cpu/bsd_aarch64/os_bsd_aarch64.cpp | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp index ae352641516..ad32ee150e8 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp +++ b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp @@ -81,14 +81,12 @@ #endif #define SPELL_REG_SP "sp" -#define SPELL_REG_FP "fp" #ifdef __APPLE__ // see darwin-xnu/osfmk/mach/arm/_structs.h // 10.5 UNIX03 member name prefixes #define DU3_PREFIX(s, m) __ ## s.__ ## m -#endif #define context_x uc_mcontext->DU3_PREFIX(ss,x) #define context_fp uc_mcontext->DU3_PREFIX(ss,fp) @@ -97,6 +95,31 @@ #define context_pc uc_mcontext->DU3_PREFIX(ss,pc) #define context_cpsr uc_mcontext->DU3_PREFIX(ss,cpsr) #define context_esr uc_mcontext->DU3_PREFIX(es,esr) +#endif + +#ifdef __FreeBSD__ +# define context_x uc_mcontext.mc_gpregs.gp_x +# define context_fp context_x[REG_FP] +# define context_lr uc_mcontext.mc_gpregs.gp_lr +# define context_sp uc_mcontext.mc_gpregs.gp_sp +# define context_pc uc_mcontext.mc_gpregs.gp_elr +#endif + +#ifdef __NetBSD__ +# define context_x uc_mcontext.__gregs +# define context_fp uc_mcontext.__gregs[_REG_FP] +# define context_lr uc_mcontext.__gregs[_REG_LR] +# define context_sp uc_mcontext.__gregs[_REG_SP] +# define context_pc uc_mcontext.__gregs[_REG_ELR] +#endif + +#ifdef __OpenBSD__ +# define context_x sc_x +# define context_fp sc_x[REG_FP] +# define context_lr sc_lr +# define context_sp sc_sp +# define context_pc sc_elr +#endif #define REG_BCP context_x[22] @@ -497,9 +520,11 @@ int os::extra_bang_size_in_bytes() { return 0; } +#ifdef __APPLE__ void os::current_thread_enable_wx(WXMode mode) { pthread_jit_write_protect_np(mode == WXExec); } +#endif static inline void atomic_copy64(const volatile void *src, volatile void *dst) { *(jlong *) dst = *(const jlong *) src; From b2e7cda6a0bd21fa3c4ffe2a67da4953f1ca3f1f Mon Sep 17 00:00:00 2001 From: Fernando Guallini Date: Fri, 6 Jun 2025 09:53:25 +0000 Subject: [PATCH 086/143] 8358171: Additional code coverage for PEM API Reviewed-by: ascarpino --- test/jdk/java/security/PEM/PEMData.java | 221 +++++++++++++++--- .../jdk/java/security/PEM/PEMDecoderTest.java | 80 ++++++- .../jdk/java/security/PEM/PEMEncoderTest.java | 77 +++++- .../java/security/PEM/PEMMultiThreadTest.java | 97 ++++++++ .../security/PEM/java.security-anotherAlgo | 1 + .../java/security/PEM/java.security-emptyAlgo | 1 + .../EncryptedPrivateKeyInfo/EncryptKey.java | 45 ++++ .../EncryptedPrivateKeyInfo/GetKey.java | 33 ++- .../sun/security/pkcs/pkcs8/PKCS8Test.java | 15 +- .../jdk/test/lib/security/SecurityUtils.java | 6 +- 10 files changed, 533 insertions(+), 43 deletions(-) create mode 100644 test/jdk/java/security/PEM/PEMMultiThreadTest.java create mode 100644 test/jdk/java/security/PEM/java.security-anotherAlgo create mode 100644 test/jdk/java/security/PEM/java.security-emptyAlgo diff --git a/test/jdk/java/security/PEM/PEMData.java b/test/jdk/java/security/PEM/PEMData.java index e1f32cdbb7c..2c8c60fcccc 100644 --- a/test/jdk/java/security/PEM/PEMData.java +++ b/test/jdk/java/security/PEM/PEMData.java @@ -27,6 +27,7 @@ import javax.crypto.EncryptedPrivateKeyInfo; import java.security.DEREncodable; import java.security.KeyPair; import java.security.PEMRecord; +import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.security.interfaces.*; import java.util.ArrayList; @@ -45,7 +46,7 @@ class PEMData { KwDdi3cNwu7YYD/QtJ+9+AEBdoqhRANCAASL+REY4vvAI9M3gonaml5K3lRgHq5w +OO4oO0VNduC44gUN1nrk7/wdNSpL+xXNEX52Dsff+2RD/fop224ANvB -----END PRIVATE KEY----- - """, KeyPair.class); + """, KeyPair.class, "SunEC"); public static final Entry rsapriv = new Entry("rsapriv", """ @@ -65,7 +66,68 @@ class PEMData { 6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob WqcWcoJqfdLEyBT+ -----END PRIVATE KEY----- - """, RSAPrivateKey.class); + """, RSAPrivateKey.class, "SunRsaSign"); + + public static final Entry rsaCrtCoefZeroPriv = new Entry("rsaCrtCoefZeroPriv", + """ + -----BEGIN RSA PRIVATE KEY----- + MIIEIwIBAAKCAQEAuZAPiPMlA5R0oOIbxbq+gOmBRcvptIT+0pmG6rZ6H//r7A/Z + MRwen0iO2GuhlyUgOk9Fja/TMBrNX99boVDEZtL4oxRTJibdLNjfQqPeFhs3NNvv + CJJEGD91Dq/fGbWv1TMZcEywqqYSdERDEA7yluw87I7YZc9kXVBwRw5AedvoXOL/ + z5yPjK8W7FTCLHSVKiD/X3P3ZX9TmFjTIbRH15Do5sRxsPdrZczYjWdXFXNQEUuF + sVFGHFbB/AJiZlGYqMU+hEIErE35sHrKpZYkj9YovYQe0SBJyuROPl8wmz0Cd69s + rhg142Qf23RPhclBuCNAQQOkefeCppg4IFFh7QIDAQABAoIBADlKHlm4Q7m2uElB + dbSWwqEXNnefjJBUrT3E84/8dXjysNppTDNqzJN9uchcdn+tESWfeshTO97yr2yF + j4se3fwm72ed60vwnMFvVYKECBmIHoO90S8yxT49PT0jFDyiSN6IT7bJnpOZAUKP + HqtTCheJaQfZ1DqejIx4vKlbX5GfShwPQV0Q7KeKnfxhryhAbM5Y5UT8grQGBQU7 + aQUZuasQV10APVRaz39VU8/hzBc081LR3O0tjnZcrMAQ7ANsP9Gu3My04cnQ5WBo + P8uCCaSPSkrzCvjd9YYkdnwXMbVCfALOa+MxBddMi4IQG0qI28Bpw6dkKziPxage + KcAQnAsCgYEA0/CwzUlyrG6f+FF3uAvPVn/jEhWatJb7Me7lkhO3suV92brb1pQo + 1W1jHdx0OqMuCVfIJ4bXkTwKuvLQGcWJzNWUVh4Tij/ZAV0Ssw2cKbBSCQGNIFsx + Ux0V9tDSYJsEdk+Y5grloDNJokYYCCpF5bz/6QYmX+t3yzjyVSvcNeMCgYEA4COU + ezUXKLSWD+jJZXpS5yopB7oXAg7mmonlc1DRuaIxspwBrbXPLQ/neN8Sefpz9Nxn + 4ksPxGbLnJh0wGCnxSu0Qfn4eNVSsulag4IQmbCO2UBsdXNCJB5KlDpRlVhvvviH + Zpz2Dkdx+itLf1EV841MCtPAHZJwrs6i6JntEe8CgYEAgJYdjs+rJXcQ04YKDr4L + k72PtR8qd7rKuObqnhAcegvGqV03mB7YD3WIl0tzsUfj3INHysOC8njtQbOkEp7J + Fl/W2dDxpgVK0gr4F26AesKhYxlv2Fu7t2OEOfVETpx+vpFYgOnHm8TCPhQs7HdJ + ZTOgSG8UxUmFquToEkjEGGUCgYB6AMP8wKxHeuzH4iVl+EySCa/lxdRqSWQasH7V + 4yMVkYTNvP9o57LKy4Jql7n97Wca3LIrSkJd3LpuFcpPQQ1xVNW8p+0pEK0AN+cN + +ElC7wkCln+y+rcA5AAiaRApY8cHw04oe72vjhIrY0+oEKILPVkr95D2R9TQQigI + xmh1vwIBAA== + -----END RSA PRIVATE KEY----- + """, RSAPrivateKey.class, "SunRsaSign"); + + public static final Entry rsapsspriv = new Entry("rsapsspriv", + """ + -----BEGIN PRIVATE KEY----- + MIIEugIBADALBgkqhkiG9w0BAQoEggSmMIIEogIBAAKCAQEAn3qFQtvj9JVqVPRh + mMMRyT17GiUY+NWOwUHx5bHqfhlHJoCllctSU/YXzrH4Da1w7sSeaMmAKYMW4X5k + rn9hnKOhgHnm2nkZBaVNQeyrseuDnfwWtLXjnj8rEKpgf9UPRUeXGRSoAb1qpwBf + epFtLSKZrzswZY2u2UEUGJERABi6Qp+cIZ8uXzBkIsMgrhb50xsdysZ9+qq95z0i + N1vh/V+Yi2fYpSYVDE8aMWdpvs0oWGvoLQjRgElJx/SknndAfLD42HPYZyyXevyJ + RgUf+NP0V7c+UtE7m7pgMs1hhxHSmUNdfH9GnOSg9m3+L3WqhaNNWB4aKMqFyhlM + EsAuawIDAQABAoIBAAMJ9yXIgeYEaN54j67fsg7nJIQ228HLc/5xGWvFQaYofidu + K87twm0GKKWlf4gR24U5QEQtPst2YNu9fav+PRJv4yEgcYqNOdxWg4veN4Fa7wr2 + JZ/zmARJHjLMRFgl67YSwKmCMBdkZXa24PA5et6cJQM7+gFIELe5b+lt7j3VsxQ7 + JpTJyp3I73lXcJrzcb/OCTxobFPLtkVSgKUNwae26xlNqXX4fQfLp99LHGnkmG3k + Wlzjs6dUi4fl4yLAJYMxEwxQbSbmY66ZKnM4SkT/YHx67gyJw2CMRp4FQDg94Sor + 0IDDKiSMGzcjuCuUl27/qTuv+iMgCqNB7CSPXtECgYEAvqN8ZuZXeUKz4tn6wxJk + k1utCl3pSM5PLMF4J43uvX49HF1i3svXrwxzJqug6kPXvB7cuB6U2DFIM3lh2jNE + u2w0U/5zVz2yEI9EaRjnOXePLsSWjOiC+5MGTafJWy5vZ8+zaWL9tjtUH5hsg1cB + ZMlXtWrI+AmAUAv6FFDZaHECgYEA1igXzRMGgXAT+YX0wdZQcRMy9jD5WIYIiCex + 6/WRE2LDM855ZBwTU5gyqZC2MWC3yn1ASDraxKdH1m3872Q0NVJfsOaLvBk1HuPk + dlSHRKO2GeO9m5/YzrZm9jpGs0IH6DKOah/t0aCd2CFxt6qef2wOUmXTCK6tyCXN + EiUmEpsCgYAMue85E1Ftl+VYVILn+Ndb+ve/RGupX5RrgXLa+R+h6MZ9mUJbazI3 + zlX1k+mHGgZR2aGUbP40vH18ajL9FQUWme+YV9ktTsIPVvETLwVokbGuRpNiTrdH + whXeoz/O5Xesb3Ijq+cR/j3sagl8bxd5ufMv+jP2UvQM4+/K4WbSEQKBgGuZeVvw + UzR1u5ODWpaJt6EYpGJN+PohXegLCbokh9/Vn35IH3XNJWi677mCnAfzMGTsyX+B + Eqn74nw6hvtAvXqNCMc5DrxTbf03Q3KwxcYW+0fGxV2L0sMJonHUlfE7G/3uaN+p + azQIH0aYhypg74HWKNv9jSqvmWEWnRKg16BBAoGAGLAqCRCP8wxqRVZZjhUJ+5JN + b6PrykDxCKhlA6JqZKuCYzvXhLABq/7miNDg0CmRREl+yKXlkEoFl4g9LtP7wfjX + n4us/WNmh+GPZYxlCJSNRTjgk7pm5TjVH5YWURDEnjIHZ7yxbAFlvNUhI1mF5g97 + KVcB4fjBitP1h8P+MoY= + -----END PRIVATE KEY----- + """, RSAPrivateKey.class, "SunRsaSign"); public static final Entry rsaprivbc = new Entry("rsaprivbc", """ @@ -85,14 +147,14 @@ class PEMData { 6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob WqcWcoJqfdLEyBT+ -----END PRIVATE KEY----- - """, RSAPrivateKey.class); + """, RSAPrivateKey.class, "SunRsaSign"); public static final Entry ec25519priv = new Entry("ed25519priv", """ -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I -----END PRIVATE KEY----- - """, EdECPrivateKey.class); + """, EdECPrivateKey.class, "SunEC"); public static final Entry rsapub = new Entry("rsapub", """ @@ -102,7 +164,20 @@ class PEMData { MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h XCYEHZS1cqd8wokFPwIDAQAB -----END PUBLIC KEY----- - """, RSAPublicKey.class); + """, RSAPublicKey.class, "SunRsaSign"); + + public static final Entry rsapsspub = new Entry("rsapsspub", + """ + -----BEGIN PUBLIC KEY----- + MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAJ96hULb4/SValT0YZjDEck9 + exolGPjVjsFB8eWx6n4ZRyaApZXLUlP2F86x+A2tcO7EnmjJgCmDFuF+ZK5/YZyj + oYB55tp5GQWlTUHsq7Hrg538FrS1454/KxCqYH/VD0VHlxkUqAG9aqcAX3qRbS0i + ma87MGWNrtlBFBiREQAYukKfnCGfLl8wZCLDIK4W+dMbHcrGffqqvec9Ijdb4f1f + mItn2KUmFQxPGjFnab7NKFhr6C0I0YBJScf0pJ53QHyw+Nhz2Gcsl3r8iUYFH/jT + 9Fe3PlLRO5u6YDLNYYcR0plDXXx/RpzkoPZt/i91qoWjTVgeGijKhcoZTBLALmsC + AwEAAQ== + -----END PUBLIC KEY----- + """, RSAPublicKey.class, "SunRsaSign"); public static final Entry rsapubbc = new Entry("rsapubbc", """ @@ -112,14 +187,14 @@ class PEMData { MtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarbD6D4yRY1hWHluiuOtzhxuueCuf9h XCYEHZS1cqd8wokFPwIDAQAB -----END PUBLIC KEY----- - """, RSAPublicKey.class); + """, RSAPublicKey.class, "SunRsaSign"); public static final Entry ecsecp256pub = new Entry("ecsecp256pub", """ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u cPjjuKDtFTXbguOIFDdZ65O/8HTUqS/sVzRF+dg7H3/tkQ/36KdtuADbwQ== -----END PUBLIC KEY----- - """, ECPublicKey.class); + """, ECPublicKey.class, "SunEC"); // EC key with explicit parameters -- Not currently supported by SunEC public static final String pubec_explicit = """ @@ -152,7 +227,7 @@ class PEMData { P4c4mySRy5N3plFQUp3pIB7wqshi1t6hkdg7gRGjMtJpIPIXynEqRy2mIw2GrKTtu3dqrW+ndarb D6D4yRY1hWHluiuOtzhxuueCuf9hXCYEHZS1cqd8wokFPwIDAQAB -----END PRIVATE KEY----- - """, KeyPair.class); + """, KeyPair.class, "SunRsaSign"); public static final Entry oasrfc8410 = new Entry("oasrfc8410", """ @@ -161,7 +236,24 @@ class PEMData { oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB Z9w7lshQhqowtrbLDFw4rXAxZuE= -----END PRIVATE KEY----- - """, KeyPair.class); + """, KeyPair.class, "SunEC"); + + public static final Entry oasxdh = new Entry("oasxdh", + """ + -----BEGIN PRIVATE KEY----- + MFECAQEwBQYDK2VuBCIEIIrMS7w5YxuBTyPFiaFvp6ILiGET7wY9ybk7Qqhe3hSq + gSEAB7ODPxRePrPnJMaj3f47blVx6c5bfxcllQzLp4bW5x4= + -----END PRIVATE KEY----- + """, KeyPair.class, "SunEC"); + + public static final Entry oasec = new Entry("oasec", + """ + -----BEGIN PRIVATE KEY----- + MIGFAgEBMBMGByqGSM49AgEGCCqGSM49AwEHBCcwJQIBAQQgkGEVbZE1yAiO11Ya + eepcrBQL+HpVE4fy0V6jbpJcmkiBQgAERCqYYmN9uNT9Z1O2Z2VC3Zag9eUAhz7G + p8DqC21VrIgpqVQ4BrcWsieNg9fSd4N2hgfMpk9PCQwJQ8ifCMiBVQ== + -----END PRIVATE KEY----- + """, KeyPair.class, "SunEC"); public static final Entry rsaOpenSSL = new Entry("rsaOpenSSL", """ @@ -192,7 +284,7 @@ class PEMData { EcgIOtkvoTrJ9Cquvuj+O7/d2yNoH0SZQ4IYJKq47/Z4kKhwXzJnBCCCBKgkjfub RTQSNnSEgTaBD29l7FrhNRHX9lIKFZ23caCTBS6o3q3+KgPbq7ao -----END RSA PRIVATE KEY----- - """, RSAPrivateKey.class); + """, RSAPrivateKey.class, "SunRsaSign"); static final Entry ed25519ep8 = new Entry("ed25519ep8", """ @@ -202,11 +294,11 @@ class PEMData { vdMyi46+Dw7cOjwEQLtx5ME0NOOo7vlCGm3H/4j+Tf5UXrMb1UrkPjqc8OiLbC0n IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo= -----END ENCRYPTED PRIVATE KEY----- - """, EdECPrivateKey.class, "fish".toCharArray()); + """, EdECPrivateKey.class, "SunEC", "fish".toCharArray()); // This is not meant to be decrypted and to stay as an EKPI static final Entry ed25519ekpi = new Entry("ed25519ekpi", - ed25519ep8.pem(), EncryptedPrivateKeyInfo.class, null); + ed25519ep8.pem(), EncryptedPrivateKeyInfo.class, "SunEC", null); static final Entry rsaCert = new Entry("rsaCert", """ @@ -237,7 +329,7 @@ class PEMData { 8gOYV33zkPhziWJt4uFMFIi7N2DLEk5UVZv1KTLZlfPl55DRs7j/Sb4vKHpB17AO meVknxVvifDVY0TIz57t28Accsk6ClBCxNPluPU/8YLGAZJYsdDXjGcndQ13s5G7 -----END CERTIFICATE----- - """, X509Certificate.class); + """, X509Certificate.class, "SUN"); static final Entry ecCert = new Entry("ecCert", """ @@ -249,7 +341,68 @@ class PEMData { lU3G9QAwCgYIKoZIzj0EAwIDSAAwRQIgMwYld7aBzkcRt9mn27YOed5+n0xN1y8Q VEcFjLI/tBYCIQDU3szDZ/PK2mUZwtgQxLqHdh+f1JY0UwQS6M8QUvoDHw== -----END CERTIFICATE----- - """, X509Certificate.class); + """, X509Certificate.class, "SUN"); + + private static final Entry rsaCrl = new Entry("rsaCrl", + """ + -----BEGIN X509 CRL----- + MIIBRTCBrwIBATANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJVUzEaMBgGA1UE + ChMRVGVzdCBDZXJ0aWZpY2F0ZXMxITAfBgNVBAMTGEJhc2ljIEhUVFAgVVJJIFBl + ZXIgMSBDQRcNMDUwNjAzMjE0NTQ3WhcNMTUwNjAxMjE0NTQ3WqAvMC0wHwYDVR0j + BBgwFoAUa+bxcvx1zVdUhvIEd9hcfbmFdw4wCgYDVR0UBAMCAQEwDQYJKoZIhvcN + AQEFBQADgYEAZ+21yt1pJn2FU6vBwpFtAKVeBCCCqJVFiRxT84XbUw0BpLrCFvlk + FOo6tC95aoV7vPGwOEyUNbKJJOCzLliIwV1PPfgZQV20xohSIPISHdUjmlyttglv + AuEvltGnbP7ENxw18HxvM20XmHz+akuFu6npI6MkBjfoxvlq1bcdTrI= + -----END X509 CRL----- + """, X509CRL.class, "SUN"); + + // Few random manipulated Base64 characters in PEM content + private static final Entry invalidDer = new Entry("invalidDER", """ + -----BEGIN PRIVATE KEY----- + MIICeAIBADANBhkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOtjMnCzPy4jCeZb + OdOvmvU3jl7+cvPFgL5MfqDCM5a8yI0yImg/hzibJJHLk3emUVBSnekgHvCqyGLW + 3qGR2DuBEaMy0mkg8hfKcSpHLaYjDYaspO27d2qtb6d1qtsPoPjJFjWFYeW6K463 + OHG654K5/2FcJgQdlLVyp3zCiQU/AgMBAAECgYEAwNkDkTv5rlX8nWLuLJV5kh/T + H9a93SRZxw8qy5Bv7bZ7ZNfHP7uUkHbi7iPojKWRhwo43692SdzR0dCSk7LGgN9q + CYvndsYR6gifVGBi0WF+St4+NdtcQ3VlNdsojy2BdIx0oC+r7i3bn+zc968O/kI+ + EgdgrMcjjFqyx6tMHpECQQD8TYPKGHyN7Jdy28llCoUX/sL/yZ2vIi5mnDAFE5ae + KZQSkNAXG+8i9Qbs/Wdd5S3oZDqu+6DBn9gib80pYY05AkEA7tY59Oy8ka7nBlGP + g6Wo1usF2bKqk8vjko9ioZQay7f86aB10QFcAjCr+cCUm16Lc9DwzWl02nNggRZa + Jz8eNwJBAO+1zfLjFOPb14F/JHdlaVKE8EwKCFDuztsapd0M4Vtf8Zk6ERsDpU63 + Ml9T2zOwnM9g+whpdjDAZ59ATdJ1JrECQQDReJQ2SxeL0lGPCiOLu9RcQp7L81aF + 79G1bgp8WlAyEjlAkloiqEWRKiz7DDuKFR7Lwhognng9S+n87aS+PS57AkBh75t8 + 6onPAs4hkm+63dfzCojvEkALevO8J3OVX7YS5q9J1r75wDn60Ob0Zh+iiorpx8Ob + WqcWcoJqfdLEyBT+ + -----END PRIVATE KEY----- + """, DEREncodable.class, null); + + private static final Entry invalidPEM = new Entry("invalidPEM", """ + -----BEGIN INVALID PEM----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBVS52ZSKZ0oES7twD2 + GGwRIVu3uHlGIwlu0xzFe7sgIPntca2bHfYMhgGxrlCm0q+hZANiAAQNWgwWfLX8 + 8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM + """, DEREncodable.class, null); + + private static final Entry invalidHeader = new Entry("invalidHeader", """ + ---BEGIN PRIVATE KEY--- + MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I + -----END PRIVATE KEY----- + """, DEREncodable.class, null); + + private static final Entry invalidFooter = new Entry("invalidFooter", """ + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I + ---END PRIVATE KEY--- + """, DEREncodable.class, null); + + private static final Entry incorrectFooter = new Entry("incorrectFooter", """ + -----BEGIN PRIVATE KEY----- + MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBVS52ZSKZ0oES7twD2 + GGwRIVu3uHlGIwlu0xzFe7sgIPntca2bHfYMhgGxrlCm0q+hZANiAAQNWgwWfLX8 + 8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM + 8fTqPkQl6RyWEDHeXqJK8zTBHMeBq9nLfDPSbzQgLDyC64Orn0D8exM= + -----END PUBLIC KEY----- + """, DEREncodable.class, null); // EC cert with explicit parameters -- Not currently supported by SunEC static final String ecCertEX = """ @@ -280,7 +433,7 @@ class PEMData { 8pYVjvwbfvDF9f+Oa9w6JjrfpWwFAUI6b1OPgrNUh+yXtUXnQNXnfUcIu0Os53bM 8fTqPkQl6RyWEDHeXqJK8zTBHMeBq9nLfDPSbzQgLDyC64Orn0D8exM= -----END PRIVATE KEY----- - """, KeyPair.class); + """, KeyPair.class, "SunEC"); public static final Entry ecCSR = new Entry("ecCSR", """ @@ -297,7 +450,7 @@ class PEMData { MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW -----END CERTIFICATE REQUEST----- - """, PEMRecord.class); + """, PEMRecord.class, "SunEC"); public static final String preData = "TEXT BLAH TEXT BLAH" + System.lineSeparator(); @@ -318,39 +471,41 @@ class PEMData { MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW -----END CERTIFICATE REQUEST----- - """ + postData, PEMRecord.class); + """ + postData, PEMRecord.class, "SunEC"); final static Pattern CR = Pattern.compile("\r"); final static Pattern LF = Pattern.compile("\n"); final static Pattern LSDEFAULT = Pattern.compile(System.lineSeparator()); - public record Entry(String name, String pem, Class clazz, char[] password, + public record Entry(String name, String pem, Class clazz, String provider, char[] password, byte[] der) { - public Entry(String name, String pem, Class clazz, char[] password, + public Entry(String name, String pem, Class clazz, String provider, char[] password, byte[] der) { this.name = name; this.pem = pem; this.clazz = clazz; + this.provider = provider; this.password = password; - if (pem != null && pem.length() > 0) { + if (pem != null && pem.length() > 0 && + !name.contains("incorrect") && !name.contains("invalid")) { String[] pemtext = pem.split("-----"); this.der = Base64.getMimeDecoder().decode(pemtext[2]); } else { this.der = null; } } - Entry(String name, String pem, Class clazz, char[] password) { - this(name, pem, clazz, password, null); + Entry(String name, String pem, Class clazz, String provider, char[] password) { + this(name, pem, clazz, provider, password, null); } - Entry(String name, String pem, Class clazz) { - this(name, pem, clazz, null, null); + Entry(String name, String pem, Class clazz, String provider) { + this(name, pem, clazz, provider, null, null); } public Entry newClass(String name, Class c) { - return new Entry(name, pem, c, password); + return new Entry(name, pem, c, provider, password); } public Entry newClass(Class c) { @@ -360,20 +515,20 @@ class PEMData { Entry makeCRLF(String name) { return new Entry(name, Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r\n"), - clazz, password()); + clazz, provider, password()); } Entry makeCR(String name) { return new Entry(name, Pattern.compile(System.lineSeparator()).matcher(pem).replaceAll("\r"), - clazz, password()); + clazz, provider, password()); } Entry makeNoCRLF(String name) { return new Entry(name, LF.matcher(CR.matcher(pem).replaceAll("")). replaceAll(""), - clazz, password()); + clazz, provider, password()); } } @@ -401,10 +556,12 @@ class PEMData { static { pubList.add(rsapub); + pubList.add(rsapsspub); pubList.add(rsapubbc); pubList.add(ecsecp256pub.makeCR("ecsecp256pub-r")); pubList.add(ecsecp256pub.makeCRLF("ecsecp256pub-rn")); privList.add(rsapriv); + privList.add(rsapsspriv); privList.add(rsaprivbc); privList.add(ecsecp256); privList.add(ecsecp384); @@ -413,9 +570,12 @@ class PEMData { privList.add(rsaOpenSSL); oasList.add(oasrfc8410); oasList.add(oasbcpem); + oasList.add(oasec); + oasList.add(oasxdh); certList.add(rsaCert); certList.add(ecCert); + certList.add(rsaCrl); entryList.addAll(pubList); entryList.addAll(privList); @@ -429,6 +589,11 @@ class PEMData { failureEntryList.add(new Entry("emptyPEM", "", DEREncodable.class, null)); failureEntryList.add(new Entry("nullPEM", null, DEREncodable.class, null)); + failureEntryList.add(incorrectFooter); + failureEntryList.add(invalidPEM); + failureEntryList.add(invalidDer); + failureEntryList.add(invalidHeader); + failureEntryList.add(invalidFooter); } static void checkResults(PEMData.Entry entry, String result) { diff --git a/test/jdk/java/security/PEM/PEMDecoderTest.java b/test/jdk/java/security/PEM/PEMDecoderTest.java index 2ee9d1a69b3..90d67af2f8d 100644 --- a/test/jdk/java/security/PEM/PEMDecoderTest.java +++ b/test/jdk/java/security/PEM/PEMDecoderTest.java @@ -26,6 +26,7 @@ /* * @test * @bug 8298420 + * @library /test/lib * @modules java.base/sun.security.pkcs * java.base/sun.security.util * @summary Testing basic PEM API decoding @@ -37,23 +38,27 @@ import java.io.*; import java.lang.Class; import java.nio.charset.StandardCharsets; import java.security.*; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.security.interfaces.*; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; +import java.security.spec.*; import java.util.*; import java.util.Arrays; +import jdk.test.lib.Asserts; +import sun.security.pkcs.PKCS8Key; import sun.security.util.Pem; public class PEMDecoderTest { static HexFormat hex = HexFormat.of(); - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { System.out.println("Decoder test:"); - PEMData.entryList.forEach(PEMDecoderTest::test); + PEMData.entryList.forEach(entry -> test(entry, false)); + System.out.println("Decoder test withFactory:"); + PEMData.entryList.forEach(entry -> test(entry, true)); System.out.println("Decoder test returning DEREncodable class:"); PEMData.entryList.forEach(entry -> test(entry, DEREncodable.class)); System.out.println("Decoder test with encrypted PEM:"); @@ -95,7 +100,11 @@ public class PEMDecoderTest { System.out.println("Check a Signature/Verify op is successful:"); PEMData.privList.forEach(PEMDecoderTest::testSignature); - PEMData.oasList.forEach(PEMDecoderTest::testSignature); + PEMData.oasList.stream().filter(e -> !e.name().endsWith("xdh")) + .forEach(PEMDecoderTest::testSignature); + + System.out.println("Checking if decode() returns a PKCS8Key and can generate a pub"); + PEMData.oasList.forEach(PEMDecoderTest::testPKCS8Key); System.out.println("Checking if ecCSR:"); test(PEMData.ecCSR); @@ -182,6 +191,10 @@ public class PEMDecoderTest { } catch (Exception e) { throw new AssertionError("error getting key", e); } + testCertTypeConverter(PEMData.ecCert); + + System.out.println("Decoder test testCoefZero:"); + testCoefZero(PEMData.rsaCrtCoefZeroPriv); } static void testInputStream() throws IOException { @@ -231,6 +244,24 @@ public class PEMDecoderTest { throw new AssertionError("Failed"); } + // test that X509 CERTIFICATE is converted to CERTIFICATE in PEM + static void testCertTypeConverter(PEMData.Entry entry) throws CertificateEncodingException { + String certPem = entry.pem().replace("CERTIFICATE", "X509 CERTIFICATE"); + Asserts.assertEqualsByteArray(entry.der(), + PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded()); + + certPem = entry.pem().replace("CERTIFICATE", "X.509 CERTIFICATE"); + Asserts.assertEqualsByteArray(entry.der(), + PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded()); + } + + // test that when the crtCoeff is zero, the key is decoded but only the modulus and private + // exponent are used resulting in a different der + static void testCoefZero(PEMData.Entry entry) { + RSAPrivateKey decoded = PEMDecoder.of().decode(entry.pem(), RSAPrivateKey.class); + Asserts.assertNotEqualsByteArray(decoded.getEncoded(), entry.der()); + } + static void testPEMRecord(PEMData.Entry entry) { PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class); String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), ""); @@ -333,13 +364,26 @@ public class PEMDecoderTest { // Change the Entry to use the given class as the expected class returned static DEREncodable test(PEMData.Entry entry, Class c) { - return test(entry.newClass(c)); + return test(entry.newClass(c), false); } // Run test with a given Entry static DEREncodable test(PEMData.Entry entry) { + return test(entry, false); + } + + // Run test with a given Entry + static DEREncodable test(PEMData.Entry entry, boolean withFactory) { + System.out.printf("Testing %s %s%n", entry.name(), entry.provider()); try { - DEREncodable r = test(entry.pem(), entry.clazz(), PEMDecoder.of()); + PEMDecoder pemDecoder; + if (withFactory) { + Provider provider = Security.getProvider(entry.provider()); + pemDecoder = PEMDecoder.of().withFactory(provider); + } else { + pemDecoder = PEMDecoder.of(); + } + DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder); System.out.println("PASS (" + entry.name() + ")"); return r; } catch (Exception | AssertionError e) { @@ -412,6 +456,19 @@ public class PEMDecoderTest { } } + private static void testPKCS8Key(PEMData.Entry entry) { + try { + PKCS8Key key = PEMDecoder.of().decode(entry.pem(), PKCS8Key.class); + PKCS8EncodedKeySpec spec = + new PKCS8EncodedKeySpec(key.getEncoded()); + + KeyFactory kf = KeyFactory.getInstance(key.getAlgorithm()); + kf.generatePublic(spec); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + static void testClass(PEMData.Entry entry, Class clazz) throws IOException { var pk = PEMDecoder.of().decode(entry.pem(), clazz); } @@ -472,9 +529,15 @@ public class PEMDecoderTest { "should not be null"); } + AlgorithmParameterSpec spec = null; String algorithm = switch(privateKey.getAlgorithm()) { case "EC" -> "SHA256withECDSA"; case "EdDSA" -> "EdDSA"; + case "RSASSA-PSS" -> { + spec = new PSSParameterSpec( + "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); + yield "RSASSA-PSS"; + } case null -> { System.out.println("Algorithm is null " + entry.name()); @@ -487,6 +550,9 @@ public class PEMDecoderTest { try { if (d instanceof PrivateKey) { s = Signature.getInstance(algorithm); + if (spec != null) { + s.setParameter(spec); + } s.initSign(privateKey); s.update(data); s.sign(); diff --git a/test/jdk/java/security/PEM/PEMEncoderTest.java b/test/jdk/java/security/PEM/PEMEncoderTest.java index c8b19c313a5..3d1948ba2fe 100644 --- a/test/jdk/java/security/PEM/PEMEncoderTest.java +++ b/test/jdk/java/security/PEM/PEMEncoderTest.java @@ -26,9 +26,15 @@ /* * @test * @bug 8298420 + * @library /test/lib * @summary Testing basic PEM API encoding * @enablePreview * @modules java.base/sun.security.util + * @run main PEMEncoderTest PBEWithHmacSHA256AndAES_128 + * @run main/othervm -Djava.security.properties=${test.src}/java.security-anotherAlgo + * PEMEncoderTest PBEWithHmacSHA512AndAES_256 + * @run main/othervm -Djava.security.properties=${test.src}/java.security-emptyAlgo + * PEMEncoderTest PBEWithHmacSHA256AndAES_128 */ import sun.security.util.Pem; @@ -39,13 +45,22 @@ import javax.crypto.spec.PBEParameterSpec; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.InvalidParameterSpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.*; +import jdk.test.lib.security.SecurityUtils; + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertThrows; + public class PEMEncoderTest { static Map keymap; + static String pkcs8DefaultAlgExpect; public static void main(String[] args) throws Exception { + pkcs8DefaultAlgExpect = args[0]; PEMEncoder encoder = PEMEncoder.of(); // These entries are removed @@ -63,7 +78,12 @@ public class PEMEncoderTest { System.out.println("New instance re-encode testToString:"); keymap.keySet().stream().forEach(key -> testToString(key, PEMEncoder.of())); - + System.out.println("Same instance Encoder testEncodedKeySpec:"); + testEncodedKeySpec(encoder); + System.out.println("New instance Encoder testEncodedKeySpec:"); + testEncodedKeySpec(PEMEncoder.of()); + System.out.println("Same instance Encoder testEmptyKey:"); + testEmptyAndNullKey(encoder); keymap = generateObjKeyMap(PEMData.encryptedList); System.out.println("Same instance Encoder match test:"); keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder)); @@ -86,6 +106,13 @@ public class PEMEncoderTest { PEMRecord pemRecord = d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class); PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString()); + + // test PemRecord is encapsulated with PEM header and footer on encoding + String[] pemLines = PEMData.ed25519ep8.pem().split("\n"); + String[] pemNoHeaderFooter = Arrays.copyOfRange(pemLines, 1, pemLines.length - 1); + PEMRecord pemR = new PEMRecord("ENCRYPTED PRIVATE KEY", String.join("\n", + pemNoHeaderFooter)); + PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pemR)); } static Map generateObjKeyMap(List list) { @@ -145,10 +172,12 @@ public class PEMEncoderTest { static void testEncrypted(String key, PEMEncoder encoder) { PEMData.Entry entry = PEMData.getEntry(key); try { - encoder.withEncryption( + String pem = encoder.withEncryption( (entry.password() != null ? entry.password() : "fish".toCharArray())) .encodeToString(keymap.get(key)); + + verifyEncriptionAlg(pem); } catch (RuntimeException e) { throw new AssertionError("Encrypted encoder failed with " + entry.name(), e); @@ -157,6 +186,11 @@ public class PEMEncoderTest { System.out.println("PASS: " + entry.name()); } + private static void verifyEncriptionAlg(String pem) { + var epki = PEMDecoder.of().decode(pem, EncryptedPrivateKeyInfo.class); + assertEquals(epki.getAlgName(), pkcs8DefaultAlgExpect); + } + /* Test cannot verify PEM was the same as known PEM because we have no public access to the AlgoritmID.params and PBES2Parameters. @@ -195,5 +229,42 @@ public class PEMEncoderTest { PEMData.checkResults(entry, result); System.out.println("PASS: " + entry.name()); } -} + static void testEncodedKeySpec(PEMEncoder encoder) throws NoSuchAlgorithmException { + KeyPair kp = getKeyPair(); + encoder.encodeToString(new X509EncodedKeySpec(kp.getPublic().getEncoded())); + encoder.encodeToString(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded())); + System.out.println("PASS: testEncodedKeySpec"); + } + private static void testEmptyAndNullKey(PEMEncoder encoder) throws NoSuchAlgorithmException { + KeyPair kp = getKeyPair(); + assertThrows(IllegalArgumentException.class, () -> encoder.encode( + new KeyPair(kp.getPublic(), new EmptyKey()))); + assertThrows(IllegalArgumentException.class, () -> encoder.encode( + new KeyPair(kp.getPublic(), null))); + + assertThrows(IllegalArgumentException.class, () -> encoder.encode( + new KeyPair(new EmptyKey(), kp.getPrivate()))); + assertThrows(IllegalArgumentException.class, () -> encoder.encode( + new KeyPair(null, kp.getPrivate()))); + System.out.println("PASS: testEmptyKey"); + } + + private static KeyPair getKeyPair() throws NoSuchAlgorithmException { + Provider provider = Security.getProvider("SunRsaSign"); + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", provider); + kpg.initialize(SecurityUtils.getTestKeySize("RSA")); + return kpg.generateKeyPair(); + } + + private static class EmptyKey implements PublicKey, PrivateKey { + @Override + public String getAlgorithm() { return "Test"; } + + @Override + public String getFormat() { return "Test"; } + + @Override + public byte[] getEncoded() { return new byte[0]; } + } +} diff --git a/test/jdk/java/security/PEM/PEMMultiThreadTest.java b/test/jdk/java/security/PEM/PEMMultiThreadTest.java new file mode 100644 index 00000000000..f345a3c129d --- /dev/null +++ b/test/jdk/java/security/PEM/PEMMultiThreadTest.java @@ -0,0 +1,97 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/* + * @test + * @bug 8298420 + * @library /test/lib + * @summary Testing PEM API is thread safe + * @enablePreview + * @modules java.base/sun.security.util + */ + +import java.security.*; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import jdk.test.lib.security.SecurityUtils; + +public class PEMMultiThreadTest { + static final int THREAD_COUNT = 5; + static final int KEYS_COUNT = 50; + + public static void main(String[] args) throws Exception { + PEMEncoder encoder = PEMEncoder.of(); + try (ExecutorService ex = Executors.newFixedThreadPool(THREAD_COUNT)) { + Map keys = new HashMap<>(); + Map encoded = Collections.synchronizedMap(new HashMap<>()); + Map decoded = Collections.synchronizedMap(new HashMap<>()); + final CountDownLatch encodingComplete = new CountDownLatch(KEYS_COUNT); + final CountDownLatch decodingComplete = new CountDownLatch(KEYS_COUNT); + + // Generate keys and encode them in parallel + for (int i = 0; i < KEYS_COUNT; i++) { + final int finalI = i; + KeyPair kp = getKeyPair(); + keys.put(finalI, kp.getPublic()); + + ex.submit(() -> { + encoded.put(finalI, encoder.encodeToString(kp.getPublic())); + encodingComplete.countDown(); + }); + } + encodingComplete.await(); + + // Decode keys in parallel + PEMDecoder decoder = PEMDecoder.of(); + for (Map.Entry entry : encoded.entrySet()) { + ex.submit(() -> { + decoded.put(entry.getKey(), decoder.decode(entry.getValue(), PublicKey.class) + .toString()); + decodingComplete.countDown(); + }); + } + decodingComplete.await(); + + // verify all keys were properly encoded and decoded comparing with the original key map + for (Map.Entry kp : keys.entrySet()) { + if (!decoded.get(kp.getKey()).equals(kp.getValue().toString())) { + throw new RuntimeException("a key was not properly encoded and decoded: " + decoded); + } + } + } + + System.out.println("PASS: testThreadSafety"); + } + + private static KeyPair getKeyPair() throws NoSuchAlgorithmException { + String alg = "EC"; + KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg); + kpg.initialize(SecurityUtils.getTestKeySize(alg)); + return kpg.generateKeyPair(); + } +} diff --git a/test/jdk/java/security/PEM/java.security-anotherAlgo b/test/jdk/java/security/PEM/java.security-anotherAlgo new file mode 100644 index 00000000000..c084f465fa2 --- /dev/null +++ b/test/jdk/java/security/PEM/java.security-anotherAlgo @@ -0,0 +1 @@ +jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA512AndAES_256 diff --git a/test/jdk/java/security/PEM/java.security-emptyAlgo b/test/jdk/java/security/PEM/java.security-emptyAlgo new file mode 100644 index 00000000000..deb2ab7aa45 --- /dev/null +++ b/test/jdk/java/security/PEM/java.security-emptyAlgo @@ -0,0 +1 @@ +jdk.epkcs8.defaultAlgorithm= diff --git a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java index d1fccd9730a..3fe8cfcfbfa 100644 --- a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java +++ b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java @@ -25,11 +25,15 @@ /** * @test + * @library /test/lib + * @modules java.base/sun.security.util * @bug 8298420 * @summary Testing encryptKey * @enablePreview */ +import sun.security.util.Pem; + import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.SecretKey; import javax.crypto.spec.PBEParameterSpec; @@ -37,8 +41,13 @@ import javax.crypto.spec.SecretKeySpec; import java.security.AlgorithmParameters; import java.security.PEMDecoder; import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; import java.util.Arrays; +import static jdk.test.lib.Asserts.assertEquals; + public class EncryptKey { private static final String encEdECKey = @@ -56,6 +65,8 @@ public class EncryptKey { passwdText.getBytes(), "PBE"); public static void main(String[] args) throws Exception { + Provider p = Security.getProvider(System.getProperty("test.provider.name", "SunJCE")); + EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey, EncryptedPrivateKeyInfo.class); PrivateKey priKey = PEMDecoder.of().withDecryption(password). @@ -71,6 +82,19 @@ public class EncryptKey { " with expected."); } + // Test encryptKey(PrivateKey, char[], String, ...) with provider + e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p); + if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { + throw new AssertionError("encryptKey() didn't match" + + " with expected."); + } + + // Test encryptKey(PrivateKey, char[], String, ...) with provider and null algorithm + e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, null, null, + p); + assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO); + // Test encryptKey(PrivateKey, Key, String, ...) e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), ap.getParameterSpec(PBEParameterSpec.class),null, null); @@ -78,5 +102,26 @@ public class EncryptKey { throw new AssertionError("encryptKey() didn't match" + " with expected."); } + + // Test encryptKey(PrivateKey, Key, String, ...) with provider and null random + e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p, null); + if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { + throw new AssertionError("encryptKey() didn't match" + + " with expected."); + } + + // Test encryptKey(PrivateKey, Key, String, ...) with provider and SecureRandom + e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p, new SecureRandom()); + if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { + throw new AssertionError("encryptKey() didn't match" + + " with expected."); + } + + // Test encryptKey(PrivateKey, Key, String, ...) with provider and null algorithm + e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, null, null, + p, new SecureRandom()); + assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO); } } diff --git a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java index 7c8951b3417..b1917ffa84d 100644 --- a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java +++ b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java @@ -35,6 +35,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.PEMDecoder; import java.security.PrivateKey; +import java.security.Provider; +import java.security.Security; import java.util.Arrays; public class GetKey { @@ -48,12 +50,29 @@ public class GetKey { IycFtI70ciPjgwDSjtCcPxR8fSxJPrm2yOJsRVo= -----END ENCRYPTED PRIVATE KEY----- """; + private static final String encDHECKey = + """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIIBvDBmBgkqhkiG9w0BBQ0wWTA4BgkqhkiG9w0BBQwwKwQUN8pkErJx7aqH0fJF + BcOadPKiuRoCAhAAAgEQMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBAT1Vwd + gU4rTd6zy7lKr4wmBIIBUMe+2+O0AG6t4CMSHcDVceRg2jvbs5PmPjW4Ka5mDich + hVEsjSpJLbUyJdbji6UaiUpuWgvYSMLZ10pfhOFw/ssXwCw+JrlXUqDpQGLaW8ZR + zSL3CoozTI2Y6EBdWt53KbySwtZMoTpW/W3vPi98bJXtR635msf6gYXmSUP7DyoJ + 79dxz3pRYsnOuBe0yZ2wTq9iMgTMudzLJAFX2qyi+3KOb1g5Va9DYAqJmzCYOd74 + +I+0gGNFtSc1vGQYr3cAfcKT8AZ1RHE4IkpnpgFD5HsZ8f4hy0yK8juk9NE9Gzuy + B929LBXk6V3L0MKzIABS3QvAlhWETM6XtGBDugzAgsooo9lEHLwYRldvOlL+QYyE + CtqDmXOrgEMWvxWGEFCTKYhKkqMKjU3y3GiozEEdb9j2okW1s30yHQjIoj0OR4nB + D8GeP0QnY73NfbOw7z81TA== + -----END ENCRYPTED PRIVATE KEY----- + """; private static final String passwdText = "fish"; private static final char[] password = passwdText.toCharArray(); private static final SecretKey key = new SecretKeySpec( passwdText.getBytes(), "PBE"); public static void main(String[] args) throws Exception { + Provider p = Security.getProvider(System.getProperty("test.provider.name", "SunJCE")); + EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey, EncryptedPrivateKeyInfo.class); PrivateKey priKey = PEMDecoder.of().withDecryption(password). @@ -66,11 +85,23 @@ public class GetKey { + "match with expected."); } - // Test getKey(key, provider) + // Test getKey(key, provider) provider null if (!Arrays.equals(priKey.getEncoded(), ekpi.getKey(key, null).getEncoded())) { throw new AssertionError("getKey(key, provider) " + "didn't match with expected."); } + + // Test getKey(key, provider) with provider + EncryptedPrivateKeyInfo ekpiDH = PEMDecoder.of().decode(encDHECKey, + EncryptedPrivateKeyInfo.class); + PrivateKey priKeyDH = PEMDecoder.of().withDecryption(password). + decode(encDHECKey, PrivateKey.class); + + if (!Arrays.equals(priKeyDH.getEncoded(), + ekpiDH.getKey(key, p).getEncoded())) { + throw new AssertionError("getKey(key, provider) " + + "didn't match with expected."); + } } } diff --git a/test/jdk/sun/security/pkcs/pkcs8/PKCS8Test.java b/test/jdk/sun/security/pkcs/pkcs8/PKCS8Test.java index 49cca69971f..9bb5502ad9f 100644 --- a/test/jdk/sun/security/pkcs/pkcs8/PKCS8Test.java +++ b/test/jdk/sun/security/pkcs/pkcs8/PKCS8Test.java @@ -31,10 +31,13 @@ * java.base/sun.security.provider * java.base/sun.security.x509 * @run main PKCS8Test + * @run main/othervm -Dtest.provider.name=SunJCE PKCS8Test */ import java.math.BigInteger; import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.Security; import java.util.Arrays; import java.util.HexFormat; @@ -45,6 +48,7 @@ import sun.security.provider.DSAPrivateKey; public class PKCS8Test { + static Provider provider; static final String FORMAT = "PKCS#8"; static final String EXPECTED_ALG_ID_CHRS = "DSA, \n" + "\tp: 02\n\tq: 03\n\tg: 04\n"; @@ -59,7 +63,7 @@ public class PKCS8Test { "0403020101"); // PrivateKey OCTET int x = 1 public static void main(String[] args) throws Exception { - + provider = Security.getProvider(System.getProperty("test.provider.name")); byte[] encodedKey = new DSAPrivateKey( BigInteger.valueOf(1), BigInteger.valueOf(2), @@ -73,7 +77,8 @@ public class PKCS8Test { .toString(encodedKey)); } - PKCS8Key decodedKey = (PKCS8Key)PKCS8Key.parseKey(encodedKey); + PKCS8Key decodedKey = provider == null ? (PKCS8Key)PKCS8Key.parseKey(encodedKey) : + (PKCS8Key)PKCS8Key.parseKey(encodedKey, provider); assert(ALGORITHM.equalsIgnoreCase(decodedKey.getAlgorithm())); assert(FORMAT.equalsIgnoreCase(decodedKey.getFormat())); @@ -126,7 +131,11 @@ public class PKCS8Test { original[1] = (byte) (length - 2); // the length field inside DER original[4] = (byte) newVersion; // the version inside DER try { - PKCS8Key.parseKey(original); + if (provider == null) { + PKCS8Key.parseKey(original); + } else { + PKCS8Key.parseKey(original, provider); + } } catch (InvalidKeyException e) { throw new RuntimeException(e); } diff --git a/test/lib/jdk/test/lib/security/SecurityUtils.java b/test/lib/jdk/test/lib/security/SecurityUtils.java index 7509488225e..be6ff1cc0e3 100644 --- a/test/lib/jdk/test/lib/security/SecurityUtils.java +++ b/test/lib/jdk/test/lib/security/SecurityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -45,6 +45,8 @@ public final class SecurityUtils { private enum KeySize{ RSA(2048), DSA(2048), + Ed25519(256), + EC(256), DH(2048); private final int keySize; @@ -145,6 +147,8 @@ public final class SecurityUtils { return switch (algo) { case "RSA" -> KeySize.RSA.keySize; case "DSA" -> KeySize.DSA.keySize; + case "Ed25519" -> KeySize.Ed25519.keySize; + case "EC" -> KeySize.EC.keySize; case "DH", "DiffieHellman" -> KeySize.DH.keySize; default -> throw new RuntimeException("Test key size not defined for " + algo); }; From 9658cecde34a6e9cd39656d21a4ae8bc42da5956 Mon Sep 17 00:00:00 2001 From: Hamlin Li Date: Fri, 6 Jun 2025 13:59:17 +0000 Subject: [PATCH 087/143] 8358685: [TEST] AOTLoggingTag.java failed with missing log message Reviewed-by: iklam, shade --- .../runtime/cds/appcds/aotCache/AOTLoggingTag.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java index 5499063ebbd..896df7ca496 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java @@ -34,7 +34,6 @@ * @run driver AOTLoggingTag */ -import java.io.File; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; @@ -59,7 +58,7 @@ public class AOTLoggingTag { "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "train"); - out.shouldContain("[info][aot] Writing binary AOTConfiguration file:"); + out.shouldContain("[aot] Writing binary AOTConfiguration file:"); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -71,7 +70,7 @@ public class AOTLoggingTag { "-Xlog:aot", "-cp", appJar); out = CDSTestUtils.executeAndLog(pb, "asm"); - out.shouldContain("[info][aot] Opened AOT configuration file hello.aotconfig"); + out.shouldContain("[aot] Opened AOT configuration file hello.aotconfig"); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -81,7 +80,7 @@ public class AOTLoggingTag { "-Xlog:aot", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); - out.shouldContain("[info][aot] Opened AOT cache hello.aot"); + out.shouldContain("[aot] Opened AOT cache hello.aot"); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -92,7 +91,7 @@ public class AOTLoggingTag { "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); out.shouldNotContain("No tag set matches selection: aot+heap"); - out.shouldContain("[info][aot,heap] resolve subgraph java.lang.Integer$IntegerCache"); + out.shouldContain("[aot,heap] resolve subgraph java.lang.Integer$IntegerCache"); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -102,7 +101,7 @@ public class AOTLoggingTag { "-XX:AOTMode=on", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); - out.shouldContain("[error][aot] An error has occurred while processing the AOT cache. Run with -Xlog:aot for details."); + out.shouldContain("[aot] An error has occurred while processing the AOT cache. Run with -Xlog:aot for details."); out.shouldNotHaveExitValue(0); } From 8adb052b46f90e8a0605cfc5ddc667acb7c61952 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Fri, 6 Jun 2025 14:11:27 +0000 Subject: [PATCH 088/143] 8341778: Some javac tests ignore the result of JavacTask::call Reviewed-by: shade --- .../gc/g1/unloading/GenClassPoolJar.java | 4 ++- test/langtools/tools/javac/T6358024.java | 4 ++- test/langtools/tools/javac/T6358166.java | 4 ++- test/langtools/tools/javac/T6361619.java | 7 +++-- test/langtools/tools/javac/T6395974.java | 4 ++- test/langtools/tools/javac/T6397286.java | 1 + .../tools/javac/T6458823/T6458823.java | 4 ++- .../TryWithResources/TwrAvoidNullCheck.java | 4 ++- .../TryWithResources/TwrSimpleClose.java | 4 ++- .../tools/javac/api/6406133/T6406133.java | 4 ++- .../tools/javac/api/6410643/T6410643.java | 1 + .../tools/javac/api/6412656/T6412656.java | 4 ++- .../tools/javac/api/6423003/T6423003.java | 3 +- .../tools/javac/api/6731573/T6731573.java | 4 ++- .../tools/javac/api/7086261/T7086261.java | 4 ++- .../tools/javac/api/8007344/Test.java | 19 +++++++++--- test/langtools/tools/javac/api/DiagSpans.java | 30 ++++++++++++------- test/langtools/tools/javac/api/T6357331.java | 4 ++- .../tools/javac/api/TestTreePath.java | 4 ++- .../api/taskListeners/EventsBalancedTest.java | 4 ++- .../ImproveFatalErrorHandling.java | 4 ++- test/langtools/tools/javac/lib/DPrinter.java | 4 ++- .../tools/javac/modules/QueryBeforeEnter.java | 4 ++- .../patterns/SOEDeeplyNestedBlocksTest.java | 6 ++-- .../tools/javac/positions/TreeEndPosTest.java | 4 ++- .../javac/processing/6348499/T6348499.java | 4 ++- .../javac/processing/6414633/T6414633.java | 4 ++- .../javac/processing/6430209/T6430209.java | 4 ++- .../tools/javac/processing/T6439826.java | 4 ++- .../tools/javac/processing/T8142931.java | 4 ++- .../processing/model/LocalInAnonymous.java | 12 ++++++-- .../options/TestNoteOnImplicitProcessing.java | 8 +++-- .../processing/rounds/CompleteOnClosed.java | 4 ++- 33 files changed, 135 insertions(+), 48 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/gc/g1/unloading/GenClassPoolJar.java b/test/hotspot/jtreg/vmTestbase/gc/g1/unloading/GenClassPoolJar.java index fb3fb4d0e8e..cea00fc2efc 100644 --- a/test/hotspot/jtreg/vmTestbase/gc/g1/unloading/GenClassPoolJar.java +++ b/test/hotspot/jtreg/vmTestbase/gc/g1/unloading/GenClassPoolJar.java @@ -187,7 +187,9 @@ public class GenClassPoolJar { StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); Iterable fileObjects = sjfm.getJavaFileObjects(files); JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, optionList, null, fileObjects); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } sjfm.close(); } diff --git a/test/langtools/tools/javac/T6358024.java b/test/langtools/tools/javac/T6358024.java index 86bc37afda9..7388ce55cef 100644 --- a/test/langtools/tools/javac/T6358024.java +++ b/test/langtools/tools/javac/T6358024.java @@ -87,7 +87,9 @@ public class T6358024 extends AbstractProcessor { Arrays.asList(f)); MyTaskListener tl = new MyTaskListener(); task.setTaskListener(tl); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (tl.started != expect) throw new AssertionError("Unexpected number of TaskListener events; " + "expected " + expect + ", found " + tl.started); diff --git a/test/langtools/tools/javac/T6358166.java b/test/langtools/tools/javac/T6358166.java index e1aa2e00bbf..d3d4b2ae162 100644 --- a/test/langtools/tools/javac/T6358166.java +++ b/test/langtools/tools/javac/T6358166.java @@ -75,7 +75,9 @@ public class T6358166 extends AbstractProcessor { JavacTool tool = JavacTool.create(); JavacTaskImpl task = (JavacTaskImpl) tool.getTask(null, fm, null, allArgs, null, List.of(f), context); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } JavaCompiler c = JavaCompiler.instance(context); if (c.errorCount() != 0) diff --git a/test/langtools/tools/javac/T6361619.java b/test/langtools/tools/javac/T6361619.java index 67450129e3e..1f299a8f4c3 100644 --- a/test/langtools/tools/javac/T6361619.java +++ b/test/langtools/tools/javac/T6361619.java @@ -50,7 +50,8 @@ public class T6361619 extends AbstractProcessor { final PrintWriter out = new PrintWriter(System.err, true); - Iterable flags = Arrays.asList("-processorpath", testClassDir, + Iterable flags = Arrays.asList("--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "-processorpath", testClassDir, "-processor", self, "-d", "."); DiagnosticListener dl = new DiagnosticListener() { @@ -69,7 +70,9 @@ public class T6361619 extends AbstractProcessor { task.setTaskListener(tl); // should complete, without exceptions - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } } } diff --git a/test/langtools/tools/javac/T6395974.java b/test/langtools/tools/javac/T6395974.java index e8d2b716735..623a9563695 100644 --- a/test/langtools/tools/javac/T6395974.java +++ b/test/langtools/tools/javac/T6395974.java @@ -63,7 +63,9 @@ public class T6395974 { MyTaskListener tl = new MyTaskListener(); task.setTaskListener(tl); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } } } diff --git a/test/langtools/tools/javac/T6397286.java b/test/langtools/tools/javac/T6397286.java index 5153a61aa77..8e8ef882aa2 100644 --- a/test/langtools/tools/javac/T6397286.java +++ b/test/langtools/tools/javac/T6397286.java @@ -56,6 +56,7 @@ public class T6397286 { }); try { + // no need to check the result of JavacTask::call, reevaluate if the test is modified task.call(); throw new AssertionError("no exception thrown"); } catch (RuntimeException e) { diff --git a/test/langtools/tools/javac/T6458823/T6458823.java b/test/langtools/tools/javac/T6458823/T6458823.java index 91736229e5a..cd23066edc8 100644 --- a/test/langtools/tools/javac/T6458823/T6458823.java +++ b/test/langtools/tools/javac/T6458823/T6458823.java @@ -66,7 +66,9 @@ public class T6458823 { files.add(new File(T6458823.class.getResource("TestClass.java").toURI())); final CompilationTask task = compiler.getTask(null, fm, diagColl, options, null, fm.getJavaFileObjectsFromFiles(files)); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } int diagCount = 0; for (Diagnostic diag : diagColl.getDiagnostics()) { if (diag.getKind() != Diagnostic.Kind.WARNING) { diff --git a/test/langtools/tools/javac/TryWithResources/TwrAvoidNullCheck.java b/test/langtools/tools/javac/TryWithResources/TwrAvoidNullCheck.java index f095b92b571..045558374c7 100644 --- a/test/langtools/tools/javac/TryWithResources/TwrAvoidNullCheck.java +++ b/test/langtools/tools/javac/TryWithResources/TwrAvoidNullCheck.java @@ -76,7 +76,9 @@ public class TwrAvoidNullCheck { DumpLower.preRegister(ctx); Iterable files = Arrays.asList(new ToolBox.JavaSource(code)); JavacTask task = JavacTool.create().getTask(null, null, null, null, null, files, ctx); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } boolean hasNullCheck = ((DumpLower) DumpLower.instance(ctx)).hasNullCheck; diff --git a/test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java b/test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java index af3f4ea19ef..11691a43040 100644 --- a/test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java +++ b/test/langtools/tools/javac/TryWithResources/TwrSimpleClose.java @@ -89,7 +89,9 @@ public class TwrSimpleClose { JFMImpl fm = new JFMImpl(sfm)) { Iterable files = Arrays.asList(new ToolBox.JavaSource(code)); JavacTask task = (JavacTask) compiler.getTask(null, fm, null, null, null, files); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (fm.classBytes.size() != 1) { throw new AssertionError(); diff --git a/test/langtools/tools/javac/api/6406133/T6406133.java b/test/langtools/tools/javac/api/6406133/T6406133.java index 70e2fbe5fe6..24ddba1e602 100644 --- a/test/langtools/tools/javac/api/6406133/T6406133.java +++ b/test/langtools/tools/javac/api/6406133/T6406133.java @@ -90,7 +90,9 @@ public class T6406133 extends ToolTester { task = tool.getTask(pw, fm, listener, null, null, compilationUnits); task.setProcessors(Arrays.asList(processor)); task.setLocale(locale); //6443132 - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } if (!processor.locale.equals(locale)) throw new AssertionError("Error in diagnostic localization during annotation processing"); String res = useListener ? listener.result : pw.toString(); diff --git a/test/langtools/tools/javac/api/6410643/T6410643.java b/test/langtools/tools/javac/api/6410643/T6410643.java index a551f802d62..d8f5c798249 100644 --- a/test/langtools/tools/javac/api/6410643/T6410643.java +++ b/test/langtools/tools/javac/api/6410643/T6410643.java @@ -53,6 +53,7 @@ public class T6410643 extends ToolTester { void test(String... args) { task = tool.getTask(null, null, null, null, null, null); try { + // no need to check the result of JavacTask::call, reevaluate if the test is modified task.call(); throw new AssertionError("Error expected"); } catch (IllegalStateException e) { diff --git a/test/langtools/tools/javac/api/6412656/T6412656.java b/test/langtools/tools/javac/api/6412656/T6412656.java index b7976e036fd..cfea162ae98 100644 --- a/test/langtools/tools/javac/api/6412656/T6412656.java +++ b/test/langtools/tools/javac/api/6412656/T6412656.java @@ -47,7 +47,9 @@ public class T6412656 extends ToolTester { task = tool.getTask(null, fm, null, null, Collections.singleton(T6412656.class.getName()), null); task.setProcessors(Collections.singleton(new MyProc(this))); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (count == 0) throw new AssertionError("Annotation processor not run"); System.out.println("OK"); diff --git a/test/langtools/tools/javac/api/6423003/T6423003.java b/test/langtools/tools/javac/api/6423003/T6423003.java index 893f2dd6344..58d055e9685 100644 --- a/test/langtools/tools/javac/api/6423003/T6423003.java +++ b/test/langtools/tools/javac/api/6423003/T6423003.java @@ -41,11 +41,12 @@ public class T6423003 extends ToolTester { void test(String... args) { task = tool.getTask(null, fm, null, Arrays.asList("-Xlint:all"), null, null); try { + // no need to check the result of JavacTask::call, reevaluate if the test is modified task.call(); + throw new AssertionError("Expected IllegalStateException not thrown"); } catch (IllegalStateException ex) { return; } - throw new AssertionError("Expected IllegalStateException not thrown"); } public static void main(String... args) throws IOException { try (T6423003 t = new T6423003()) { diff --git a/test/langtools/tools/javac/api/6731573/T6731573.java b/test/langtools/tools/javac/api/6731573/T6731573.java index 0d7ceb8fd2f..892b8e63ffb 100644 --- a/test/langtools/tools/javac/api/6731573/T6731573.java +++ b/test/langtools/tools/javac/api/6731573/T6731573.java @@ -93,7 +93,9 @@ public class T6731573 extends ToolTester { if (sourceLine.optValue != null) options.add(sourceLine.optValue); task = tool.getTask(pw, fm, null, options, null, compilationUnits); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } checkErrorLine(pw.toString(), diagType.shouldDisplaySource(sourceLine), options); diff --git a/test/langtools/tools/javac/api/7086261/T7086261.java b/test/langtools/tools/javac/api/7086261/T7086261.java index 1bc2d1a2dc8..391e71c94a7 100644 --- a/test/langtools/tools/javac/api/7086261/T7086261.java +++ b/test/langtools/tools/javac/api/7086261/T7086261.java @@ -71,7 +71,9 @@ public class T7086261 { try (JavaFileManager jfm = javac.getStandardFileManager(null, null, null)) { JavaCompiler.CompilationTask task = javac.getTask(null, jfm, new DiagnosticChecker(), null, null, Arrays.asList(new ErroneousSource())); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } } } diff --git a/test/langtools/tools/javac/api/8007344/Test.java b/test/langtools/tools/javac/api/8007344/Test.java index c99b77a101f..d230fb6086b 100644 --- a/test/langtools/tools/javac/api/8007344/Test.java +++ b/test/langtools/tools/javac/api/8007344/Test.java @@ -38,6 +38,7 @@ import java.io.File; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; import java.util.Set; import javax.annotation.processing.RoundEnvironment; @@ -75,6 +76,12 @@ public class Test { } } + static final List OPTIONS = List.of( + "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); + PrintWriter out; int errors; @@ -102,10 +109,12 @@ public class Test { Iterable files, PrintWriter out, int expectedDocComments) { out.println("Test annotation processor"); - JavacTask task = javac.getTask(out, fm, null, null, null, files); + JavacTask task = javac.getTask(out, fm, null, OPTIONS, null, files); AnnoProc ap = new AnnoProc(DocTrees.instance(task)); task.setProcessors(Arrays.asList(ap)); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } ap.checker.checkDocComments(expectedDocComments); } @@ -113,10 +122,12 @@ public class Test { Iterable files, PrintWriter out, int expectedDocComments) { out.println("Test task listener"); - JavacTask task = javac.getTask(out, fm, null, null, null, files); + JavacTask task = javac.getTask(out, fm, null, OPTIONS, null, files); TaskListnr tl = new TaskListnr(DocTrees.instance(task)); task.addTaskListener(tl); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } tl.checker.checkDocComments(expectedDocComments); } diff --git a/test/langtools/tools/javac/api/DiagSpans.java b/test/langtools/tools/javac/api/DiagSpans.java index e4cdc6a5f07..527fe1aad62 100644 --- a/test/langtools/tools/javac/api/DiagSpans.java +++ b/test/langtools/tools/javac/api/DiagSpans.java @@ -72,7 +72,8 @@ public class DiagSpans extends TestRunner { } """, '/', - '^'); + '^', + false); } @Test @@ -87,7 +88,8 @@ public class DiagSpans extends TestRunner { } """, '/', - '^'); + '^', + false); } @Test @@ -102,7 +104,8 @@ public class DiagSpans extends TestRunner { } """, '/', - '^'); + '^', + false); } @Test @@ -118,7 +121,8 @@ public class DiagSpans extends TestRunner { } """, '/', - '^'); + '^', + false); } @Test @@ -134,7 +138,8 @@ public class DiagSpans extends TestRunner { } """, '/', - '^'); + '^', + false); } @Test @@ -158,7 +163,8 @@ public class DiagSpans extends TestRunner { class Sub2 extends Base2 {} """, '/', - '^'); + '^', + true); } @Test @@ -175,7 +181,8 @@ public class DiagSpans extends TestRunner { class Sub1 extends Base1 {} """, '/', - '^'); + '^', + false); } @Test @@ -192,10 +199,11 @@ public class DiagSpans extends TestRunner { class Sub1 extends Base1 {} """, '/', - '^'); + '^', + false); } - private void runDiagSpanTest(String code, char spanMarker, char prefMarker) throws Exception { + private void runDiagSpanTest(String code, char spanMarker, char prefMarker, boolean succCompilationExpected) throws Exception { var realCode = new StringBuilder(); var expectedError = new ArrayList(); int startPos = -1; @@ -238,7 +246,9 @@ public class DiagSpans extends TestRunner { }; var sourceFiles = List.of(new JFOImpl(realCode.toString())); var task = compiler.getTask(null, null, dl, null, null, sourceFiles); - task.call(); + if (task.call() != succCompilationExpected) { + throw new AssertionError("unexpected compilation result"); + } if (!Objects.equals(expectedError, actualErrors)) { throw new AssertionError("Expected error spans not found, expected: " + expectedError + ", actual: " + actualErrors); diff --git a/test/langtools/tools/javac/api/T6357331.java b/test/langtools/tools/javac/api/T6357331.java index 5253ebededd..0bc4ff98a53 100644 --- a/test/langtools/tools/javac/api/T6357331.java +++ b/test/langtools/tools/javac/api/T6357331.java @@ -53,7 +53,9 @@ public class T6357331 public void finished(TaskEvent e) { } }); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } // now the compilation is over, we expect IllegalStateException (not NPE) try { diff --git a/test/langtools/tools/javac/api/TestTreePath.java b/test/langtools/tools/javac/api/TestTreePath.java index b3887619947..7a016f42545 100644 --- a/test/langtools/tools/javac/api/TestTreePath.java +++ b/test/langtools/tools/javac/api/TestTreePath.java @@ -116,7 +116,9 @@ public class TestTreePath extends AbstractProcessor { null, null, null, Arrays.asList("-processor", this.getClass().getName()), null, tests); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } } } diff --git a/test/langtools/tools/javac/api/taskListeners/EventsBalancedTest.java b/test/langtools/tools/javac/api/taskListeners/EventsBalancedTest.java index bd01d192b6e..759ec8d66ac 100644 --- a/test/langtools/tools/javac/api/taskListeners/EventsBalancedTest.java +++ b/test/langtools/tools/javac/api/taskListeners/EventsBalancedTest.java @@ -79,7 +79,9 @@ public class EventsBalancedTest { task.setTaskListener(listener); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } for (Entry e : listener.kind2Count.entrySet()) { if (e.getValue() != null && e.getValue() != 0) { diff --git a/test/langtools/tools/javac/fatalErrors/ImproveFatalErrorHandling.java b/test/langtools/tools/javac/fatalErrors/ImproveFatalErrorHandling.java index de55cd8d5f7..278d8863b4a 100644 --- a/test/langtools/tools/javac/fatalErrors/ImproveFatalErrorHandling.java +++ b/test/langtools/tools/javac/fatalErrors/ImproveFatalErrorHandling.java @@ -76,7 +76,9 @@ public class ImproveFatalErrorHandling extends TestRunner { .getSystemJavaCompiler() .getTask(null, null, null, null, null, files); Context context = task.getContext(); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } JavaCompiler compiler = context.get(compilerKey); compiler.closeables = com.sun.tools.javac.util.List.of( new CloseException1(), new CloseException2(), diff --git a/test/langtools/tools/javac/lib/DPrinter.java b/test/langtools/tools/javac/lib/DPrinter.java index 6fa2f17dc67..5092e0698c2 100644 --- a/test/langtools/tools/javac/lib/DPrinter.java +++ b/test/langtools/tools/javac/lib/DPrinter.java @@ -1602,7 +1602,9 @@ public class DPrinter { } }); - task.call(); + if (!task.call()) { + throw new AssertionError("compilation failed at DPrinter.Main::run"); + } } TaskEvent.Kind getKind(String s) { diff --git a/test/langtools/tools/javac/modules/QueryBeforeEnter.java b/test/langtools/tools/javac/modules/QueryBeforeEnter.java index 226c6aed8e3..293b6302eec 100644 --- a/test/langtools/tools/javac/modules/QueryBeforeEnter.java +++ b/test/langtools/tools/javac/modules/QueryBeforeEnter.java @@ -359,7 +359,9 @@ public class QueryBeforeEnter extends ModuleTestBase { "-Xplugin:test"), null, fm.getJavaFileObjects(testSource)); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } } Main.compile(new String[] {"--processor-path", processorPath, diff --git a/test/langtools/tools/javac/patterns/SOEDeeplyNestedBlocksTest.java b/test/langtools/tools/javac/patterns/SOEDeeplyNestedBlocksTest.java index 4e738cce028..8f64b4cff37 100644 --- a/test/langtools/tools/javac/patterns/SOEDeeplyNestedBlocksTest.java +++ b/test/langtools/tools/javac/patterns/SOEDeeplyNestedBlocksTest.java @@ -34,7 +34,7 @@ import javax.tools.*; public class SOEDeeplyNestedBlocksTest { - static final int NESTING_DEPTH = 1000; + static final int NESTING_DEPTH = 500; public static void main(String... args) { var lines = new ArrayList(); @@ -48,7 +48,9 @@ public class SOEDeeplyNestedBlocksTest { var source = SimpleJavaFileObject.forSource(URI.create("mem://Test.java"), String.join("\n", lines)); var compiler = ToolProvider.getSystemJavaCompiler(); var task = compiler.getTask(null, null, noErrors, null, null, List.of(source)); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } } static DiagnosticListener noErrors = d -> { diff --git a/test/langtools/tools/javac/positions/TreeEndPosTest.java b/test/langtools/tools/javac/positions/TreeEndPosTest.java index 990053a7d4e..dc87409f4b2 100644 --- a/test/langtools/tools/javac/positions/TreeEndPosTest.java +++ b/test/langtools/tools/javac/positions/TreeEndPosTest.java @@ -174,7 +174,9 @@ public class TreeEndPosTest { compiler.getTask(writer, javaFileManager, dc, options, null, sources); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } for (Diagnostic diagnostic : (List) dc.getDiagnostics()) { long actualStart = diagnostic.getStartPosition(); long actualEnd = diagnostic.getEndPosition(); diff --git a/test/langtools/tools/javac/processing/6348499/T6348499.java b/test/langtools/tools/javac/processing/6348499/T6348499.java index f8f8c30bdd7..266caa1e14f 100644 --- a/test/langtools/tools/javac/processing/6348499/T6348499.java +++ b/test/langtools/tools/javac/processing/6348499/T6348499.java @@ -61,7 +61,9 @@ public class T6348499 { "-processorpath", testClassPath); StringWriter out = new StringWriter(); JavacTask task = tool.getTask(out, fm, dl, opts, null, files); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } String s = out.toString(); System.err.print(s); // Expect the following 1 multi-line diagnostic, and no output to log diff --git a/test/langtools/tools/javac/processing/6414633/T6414633.java b/test/langtools/tools/javac/processing/6414633/T6414633.java index d38d1acdd82..5ee7b5e646d 100644 --- a/test/langtools/tools/javac/processing/6414633/T6414633.java +++ b/test/langtools/tools/javac/processing/6414633/T6414633.java @@ -60,7 +60,9 @@ public class T6414633 { String[] opts = { "-proc:only", "-processor", A.class.getName() }; JavacTask task = tool.getTask(null, fm, dl, Arrays.asList(opts), null, files); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } // two annotations on the same element -- expect 2 diags from the processor if (dl.diags != 2) diff --git a/test/langtools/tools/javac/processing/6430209/T6430209.java b/test/langtools/tools/javac/processing/6430209/T6430209.java index 2f79b36a3e1..1371b7c09d6 100644 --- a/test/langtools/tools/javac/processing/6430209/T6430209.java +++ b/test/langtools/tools/javac/processing/6430209/T6430209.java @@ -66,7 +66,9 @@ public class T6430209 { "-processorpath", testClassPath); StringWriter out = new StringWriter(); JavacTask task = tool.getTask(out, fm, null, opts, null, files); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } String s = out.toString(); System.err.print(s); s = s.replace(System.getProperty("line.separator"), "\n"); diff --git a/test/langtools/tools/javac/processing/T6439826.java b/test/langtools/tools/javac/processing/T6439826.java index 846d77c3a7e..fed293d4e5e 100644 --- a/test/langtools/tools/javac/processing/T6439826.java +++ b/test/langtools/tools/javac/processing/T6439826.java @@ -55,7 +55,9 @@ public class T6439826 extends AbstractProcessor { "-processorpath", testClasses); StringWriter out = new StringWriter(); JavacTask task = tool.getTask(out, fm, dl, opts, null, files); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } String s = out.toString(); System.err.print(s); // Expect the following 2 diagnostics, and no output to log diff --git a/test/langtools/tools/javac/processing/T8142931.java b/test/langtools/tools/javac/processing/T8142931.java index 5abeac83687..8ade5e19767 100644 --- a/test/langtools/tools/javac/processing/T8142931.java +++ b/test/langtools/tools/javac/processing/T8142931.java @@ -62,7 +62,9 @@ public class T8142931 extends AbstractProcessor { "-processorpath", testClasses); StringWriter out = new StringWriter(); JavacTask task = (JavacTask)tool.getTask(out, fm, dl, opts, null, files); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } String s = out.toString(); System.err.print(s); System.err.println(dl.count + " diagnostics; " + s.length() + " characters"); diff --git a/test/langtools/tools/javac/processing/model/LocalInAnonymous.java b/test/langtools/tools/javac/processing/model/LocalInAnonymous.java index 6cbbe512784..616062bb542 100644 --- a/test/langtools/tools/javac/processing/model/LocalInAnonymous.java +++ b/test/langtools/tools/javac/processing/model/LocalInAnonymous.java @@ -81,7 +81,9 @@ public class LocalInAnonymous { List options = Arrays.asList("-d", classes.toString()); StringWriter out = new StringWriter(); JavacTask task = (JavacTask) compiler.getTask(out, null, noErrors, options, null, files); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (!out.toString().isEmpty()) { throw new AssertionError("Unexpected output: " + out); } @@ -103,7 +105,9 @@ public class LocalInAnonymous { } } }); - task2.call(); + if (!task2.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (!out.toString().isEmpty()) { throw new AssertionError("Unexpected output: " + out); } @@ -112,7 +116,9 @@ public class LocalInAnonymous { "-processorpath", System.getProperty("test.classes"), "-processor", Processor.class.getName()); JavacTask task3 = (JavacTask) compiler.getTask(out, null, noErrors, options, null, files); - task3.call(); + if (!task3.call()) { + throw new AssertionError("test failed due to a compilation error"); + } if (!out.toString().isEmpty()) { throw new AssertionError("Unexpected output: " + out); } diff --git a/test/langtools/tools/javac/processing/options/TestNoteOnImplicitProcessing.java b/test/langtools/tools/javac/processing/options/TestNoteOnImplicitProcessing.java index 73089a9a3cf..181c29eeb56 100644 --- a/test/langtools/tools/javac/processing/options/TestNoteOnImplicitProcessing.java +++ b/test/langtools/tools/javac/processing/options/TestNoteOnImplicitProcessing.java @@ -323,7 +323,9 @@ public class TestNoteOnImplicitProcessing extends TestRunner { List options = List.of("-classpath", jarFile.toString(), "-XDrawDiagnostics"); CompilationTask task = provider.getTask(compilerOut, null, null, options, null, inputFile); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } verifyMessages(out, compilerOut, false, false); } @@ -335,7 +337,9 @@ public class TestNoteOnImplicitProcessing extends TestRunner { (Processor) processorClass.getDeclaredConstructor().newInstance(); task.setProcessors(List.of(processor)); - task.call(); + if (!task.call()) { + throw new AssertionError("test failed due to a compilation error"); + } verifyMessages(out, compilerOut, false, true); } diff --git a/test/langtools/tools/javac/processing/rounds/CompleteOnClosed.java b/test/langtools/tools/javac/processing/rounds/CompleteOnClosed.java index 1ebc6b2acec..f724d605e51 100644 --- a/test/langtools/tools/javac/processing/rounds/CompleteOnClosed.java +++ b/test/langtools/tools/javac/processing/rounds/CompleteOnClosed.java @@ -56,7 +56,9 @@ public class CompleteOnClosed extends JavacTestingAbstractProcessor { Iterable files = Arrays.asList(new ToolBox.JavaSource(source)); Iterable options = Arrays.asList("-processor", "CompleteOnClosed"); CompilationTask task = compiler.getTask(null, null, collector, options, null, files); - task.call(); + if (task.call()) { + throw new AssertionError("test compilation was expected to fail"); + } for (Diagnostic d : collector.getDiagnostics()) { System.out.println(d.toString()); } From 026975a1aa290613934ae421bbc56326627bad8d Mon Sep 17 00:00:00 2001 From: Alexandre Iline Date: Fri, 6 Jun 2025 15:05:43 +0000 Subject: [PATCH 089/143] 8358721: Update JCov for class file version 70 Reviewed-by: iris, alanb, erikj --- make/conf/jib-profiles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 91876878046..d4877604a90 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1192,8 +1192,8 @@ var getJibProfilesDependencies = function (input, common) { server: "jpg", product: "jcov", version: "3.0", - build_number: "1", - file: "bundles/jcov-3.0+1.zip", + build_number: "3", + file: "bundles/jcov-3.0+3.zip", environment_name: "JCOV_HOME", }, From d024f58e61ec27f6c13fde5dadb95c31875815d6 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Fri, 6 Jun 2025 20:07:43 +0000 Subject: [PATCH 090/143] 8358809: Improve link to stdin.encoding from java.lang.IO Reviewed-by: naoto --- src/java.base/share/classes/java/lang/IO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/IO.java b/src/java.base/share/classes/java/lang/IO.java index 1f88de7e238..4630d8a3be2 100644 --- a/src/java.base/share/classes/java/lang/IO.java +++ b/src/java.base/share/classes/java/lang/IO.java @@ -38,7 +38,7 @@ import java.nio.charset.StandardCharsets; *

* The {@link #readln()} and {@link #readln(String)} methods decode bytes read from * {@code System.in} into characters. The charset used for decoding is specified by the - * {@link System#getProperties stdin.encoding} property. If this property is not present, + * {@link System##stdin.encoding stdin.encoding} property. If this property is not present, * or if the charset it names cannot be loaded, then UTF-8 is used instead. Decoding * always replaces malformed and unmappable byte sequences with the charset's default * replacement string. From d7352559195b9e052c3eb24d773c0d6c10dc23ad Mon Sep 17 00:00:00 2001 From: Rajan Halade Date: Fri, 6 Jun 2025 21:35:21 +0000 Subject: [PATCH 091/143] 8345414: Google CAInterop test failures Reviewed-by: weijun Backport-of: 8e9ba788ae04a9a617a393709bf2c51a0c157206 --- .../certification/CAInterop.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index 2bfd3ea7603..5612ad4a8a2 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -635,20 +635,20 @@ public class CAInterop { "https://revoked.sfig2.catest.starfieldtech.com"); case "globalsigneccrootcar4" -> - new CATestURLs("https://good.gsr4.demo.pki.goog", - "https://revoked.gsr4.demo.pki.goog"); + new CATestURLs("https://good.gsr4.demosite.pki.goog", + "https://revoked.gsr4.demosite.pki.goog"); case "gtsrootcar1" -> - new CATestURLs("https://good.gtsr1.demo.pki.goog", - "https://revoked.gtsr1.demo.pki.goog"); + new CATestURLs("https://good.gtsr1.demosite.pki.goog", + "https://revoked.gtsr1.demosite.pki.goog"); case "gtsrootcar2" -> - new CATestURLs("https://good.gtsr2.demo.pki.goog", - "https://revoked.gtsr2.demo.pki.goog"); + new CATestURLs("https://good.gtsr2.demosite.pki.goog", + "https://revoked.gtsr2.demosite.pki.goog"); case "gtsrootecccar3" -> - new CATestURLs("https://good.gtsr3.demo.pki.goog", - "https://revoked.gtsr3.demo.pki.goog"); + new CATestURLs("https://good.gtsr3.demosite.pki.goog", + "https://revoked.gtsr3.demosite.pki.goog"); case "gtsrootecccar4" -> - new CATestURLs("https://good.gtsr4.demo.pki.goog", - "https://revoked.gtsr4.demo.pki.goog"); + new CATestURLs("https://good.gtsr4.demosite.pki.goog", + "https://revoked.gtsr4.demosite.pki.goog"); case "microsoftecc2017" -> new CATestURLs("https://acteccroot2017.pki.microsoft.com", From e94ad551c6d31b91ec066f92f9bbdb956f54e887 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Sat, 7 Jun 2025 20:34:34 +0000 Subject: [PATCH 092/143] 8342639: Global operator new in adlc has wrong exception spec Reviewed-by: kvn, mdoerr --- src/hotspot/share/adlc/main.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hotspot/share/adlc/main.cpp b/src/hotspot/share/adlc/main.cpp index 16fd4ddcf93..4e8a96617e8 100644 --- a/src/hotspot/share/adlc/main.cpp +++ b/src/hotspot/share/adlc/main.cpp @@ -481,7 +481,3 @@ int get_legal_text(FileBuff &fbuf, char **legal_text) *legal_text = legal_start; return (int) (legal_end - legal_start); } - -void *operator new( size_t size, int, const char *, int ) throw() { - return ::operator new( size ); -} From 6c616c71ec9a8ee6e0203921deef20d09db39698 Mon Sep 17 00:00:00 2001 From: Daniel Skantz Date: Mon, 9 Jun 2025 06:11:05 +0000 Subject: [PATCH 093/143] 8357822: C2: Multiple string optimization tests are no longer testing string concatenation optimizations Reviewed-by: rcastanedalo, epeter --- test/hotspot/jtreg/compiler/c2/Test7046096.java | 14 +++++++++++++- .../hotspot/jtreg/compiler/c2/Test7179138_2.java | 13 +++++++++++++ .../Implicit01/cs_disabled/TestDescription.java | 16 +++++++++++++++- .../Implicit01/cs_enabled/TestDescription.java | 16 +++++++++++++++- .../Merge01/cs_disabled/TestDescription.java | 16 +++++++++++++++- .../Merge01/cs_enabled/TestDescription.java | 16 +++++++++++++++- 6 files changed, 86 insertions(+), 5 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/Test7046096.java b/test/hotspot/jtreg/compiler/c2/Test7046096.java index 46eb935e2d0..fb73bba4373 100644 --- a/test/hotspot/jtreg/compiler/c2/Test7046096.java +++ b/test/hotspot/jtreg/compiler/c2/Test7046096.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -30,6 +30,18 @@ * compiler.c2.Test7046096 */ + +/* + * @test id=stringConcatInline + * @bug 7046096 8357822 + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * + * @compile -XDstringConcat=inline Test7046096.java + * @run main/othervm -Xbatch compiler.c2.Test7046096 + */ + + package compiler.c2; public class Test7046096 { diff --git a/test/hotspot/jtreg/compiler/c2/Test7179138_2.java b/test/hotspot/jtreg/compiler/c2/Test7179138_2.java index 4e89f6aced0..5f96ea942ef 100644 --- a/test/hotspot/jtreg/compiler/c2/Test7179138_2.java +++ b/test/hotspot/jtreg/compiler/c2/Test7179138_2.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Skip Balk. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -32,6 +33,18 @@ * @author Skip Balk */ + +/* + * @test id=stringConcatInline + * @bug 7179138 + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * + * @compile -XDstringConcat=inline Test7179138_2.java + * @run main/othervm -Xbatch -XX:-TieredCompilation compiler.c2.Test7179138_2 + */ + + package compiler.c2; public class Test7179138_2 { diff --git a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_disabled/TestDescription.java b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_disabled/TestDescription.java index 2eddeafa2c9..13bc3b4d640 100644 --- a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_disabled/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_disabled/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -33,3 +33,17 @@ * @run main/othervm -XX:-CompactStrings vm.compiler.optimizations.stringconcat.implicit.Implicit01 */ + +/* + * @test id=stringConcatInline + * + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * VM Testbase keywords: [jit, quick] + * + * @library /vmTestbase + * /test/lib + * @compile -XDstringConcat=inline ../../Implicit01.java + * @run main/othervm -XX:-CompactStrings vm.compiler.optimizations.stringconcat.implicit.Implicit01 + */ + diff --git a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_enabled/TestDescription.java b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_enabled/TestDescription.java index 7e7295f5108..87e8bdcbcc7 100644 --- a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_enabled/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Implicit01/cs_enabled/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -33,3 +33,17 @@ * @run main/othervm -XX:+CompactStrings vm.compiler.optimizations.stringconcat.implicit.Implicit01 */ + +/* + * @test id=stringConcatInline + * + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * VM Testbase keywords: [jit, quick] + * + * @library /vmTestbase + * /test/lib + * @compile -XDstringConcat=inline ../../Implicit01.java + * @run main/othervm -XX:+CompactStrings vm.compiler.optimizations.stringconcat.implicit.Implicit01 + */ + diff --git a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_disabled/TestDescription.java b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_disabled/TestDescription.java index c56a554b1da..cbbe4d97ee4 100644 --- a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_disabled/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_disabled/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -33,3 +33,17 @@ * @run main/othervm -XX:-CompactStrings vm.compiler.optimizations.stringconcat.implicit.Merge01 */ + +/* + * @test id=stringConcatInline + * + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * VM Testbase keywords: [jit, quick] + * + * @library /vmTestbase + * /test/lib + * @compile -XDstringConcat=inline ../../Merge01.java + * @run main/othervm -XX:-CompactStrings vm.compiler.optimizations.stringconcat.implicit.Merge01 + */ + diff --git a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_enabled/TestDescription.java b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_enabled/TestDescription.java index 3c082fefd25..c6fc8d67de6 100644 --- a/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_enabled/TestDescription.java +++ b/test/hotspot/jtreg/vmTestbase/vm/compiler/optimizations/stringconcat/implicit/Merge01/cs_enabled/TestDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -33,3 +33,17 @@ * @run main/othervm -XX:+CompactStrings vm.compiler.optimizations.stringconcat.implicit.Merge01 */ + +/* + * @test id=stringConcatInline + * + * @summary The same test with an updated compile directive that produces + * StringBuilder-backed string concatenations. + * VM Testbase keywords: [jit, quick] + * + * @library /vmTestbase + * /test/lib + * @compile -XDstringConcat=inline ../../Merge01.java + * @run main/othervm -XX:+CompactStrings vm.compiler.optimizations.stringconcat.implicit.Merge01 + */ + From 91f12600d2b188ca98c5c575a34b85f5835399a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Mon, 9 Jun 2025 06:23:17 +0000 Subject: [PATCH 094/143] 8345067: C2: enable implicit null checks for ZGC reads Reviewed-by: aboldtch, kvn, epeter --- src/hotspot/cpu/aarch64/aarch64.ad | 4 + src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad | 13 +- .../cpu/aarch64/macroAssembler_aarch64.hpp | 19 +- src/hotspot/cpu/ppc/gc/z/z_ppc.ad | 2 + src/hotspot/cpu/ppc/ppc.ad | 4 + src/hotspot/cpu/riscv/gc/z/z_riscv.ad | 1 + src/hotspot/cpu/riscv/riscv.ad | 4 + src/hotspot/cpu/x86/gc/z/z_x86_64.ad | 4 + src/hotspot/cpu/x86/x86_64.ad | 4 + src/hotspot/share/adlc/output_h.cpp | 2 + src/hotspot/share/opto/block.hpp | 8 + src/hotspot/share/opto/lcm.cpp | 103 ++++---- src/hotspot/share/opto/machnode.hpp | 7 + src/hotspot/share/opto/output.cpp | 6 +- .../gcbarriers/TestImplicitNullChecks.java | 231 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 5 + 16 files changed, 363 insertions(+), 54 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index f367362b4d8..75baef153f8 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -3921,6 +3921,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must // compute_padding() function must be // provided for the instruction +// Whether this node is expanded during code emission into a sequence of +// instructions and the first instruction can perform an implicit null check. +ins_attrib ins_is_late_expanded_null_check_candidate(false); + //----------OPERANDS----------------------------------------------------------- // Operand definitions must precede instruction definitions for correct parsing // in the ADLC because operands constitute user defined types which are used in diff --git a/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad b/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad index 47abaae3d5b..78dc5d56bbd 100644 --- a/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad +++ b/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad @@ -106,6 +106,13 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr) match(Set dst (LoadP mem)); predicate(UseZGC && !needs_acquiring_load(n) && n->as_Load()->barrier_data() != 0); effect(TEMP dst, KILL cr); + // The main load is a candidate to implement implicit null checks, as long as + // legitimize_address() does not require a preceding lea instruction to + // materialize the memory operand. The absence of a preceding lea instruction + // is guaranteed for immLoffset8 memory operands, because these do not lead to + // out-of-range offsets (see definition of immLoffset8). Fortunately, + // immLoffset8 memory operands are the most common ones in practice. + ins_is_late_expanded_null_check_candidate(opnd_array(1)->opcode() == INDOFFL8); ins_cost(4 * INSN_COST); @@ -117,7 +124,11 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr) // Fix up any out-of-range offsets. assert_different_registers(rscratch2, as_Register($mem$$base)); assert_different_registers(rscratch2, $dst$$Register); - ref_addr = __ legitimize_address(ref_addr, 8, rscratch2); + int size = 8; + assert(!this->is_late_expanded_null_check_candidate() || + !MacroAssembler::legitimize_address_requires_lea(ref_addr, size), + "an instruction that can be used for implicit null checking should emit the candidate memory access first"); + ref_addr = __ legitimize_address(ref_addr, size, rscratch2); } __ ldr($dst$$Register, ref_addr); z_load_barrier(masm, this, ref_addr, $dst$$Register, rscratch1); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index d77bc92875f..f5f0f630c0c 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -129,16 +129,21 @@ class MacroAssembler: public Assembler { a.lea(this, r); } + // Whether materializing the given address for a LDR/STR requires an + // additional lea instruction. + static bool legitimize_address_requires_lea(const Address &a, int size) { + return a.getMode() == Address::base_plus_offset && + !Address::offset_ok_for_immed(a.offset(), exact_log2(size)); + } + /* Sometimes we get misaligned loads and stores, usually from Unsafe accesses, and these can exceed the offset range. */ Address legitimize_address(const Address &a, int size, Register scratch) { - if (a.getMode() == Address::base_plus_offset) { - if (! Address::offset_ok_for_immed(a.offset(), exact_log2(size))) { - block_comment("legitimize_address {"); - lea(scratch, a); - block_comment("} legitimize_address"); - return Address(scratch); - } + if (legitimize_address_requires_lea(a, size)) { + block_comment("legitimize_address {"); + lea(scratch, a); + block_comment("} legitimize_address"); + return Address(scratch); } return a; } diff --git a/src/hotspot/cpu/ppc/gc/z/z_ppc.ad b/src/hotspot/cpu/ppc/gc/z/z_ppc.ad index 65fb206ad7e..0872932ccb2 100644 --- a/src/hotspot/cpu/ppc/gc/z/z_ppc.ad +++ b/src/hotspot/cpu/ppc/gc/z/z_ppc.ad @@ -141,6 +141,7 @@ instruct zLoadP(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0) %{ match(Set dst (LoadP mem)); effect(TEMP_DEF dst, KILL cr0); + ins_is_late_expanded_null_check_candidate(true); ins_cost(MEMORY_REF_COST); predicate((UseZGC && n->as_Load()->barrier_data() != 0) @@ -160,6 +161,7 @@ instruct zLoadP_acq(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0) %{ match(Set dst (LoadP mem)); effect(TEMP_DEF dst, KILL cr0); + ins_is_late_expanded_null_check_candidate(true); ins_cost(3 * MEMORY_REF_COST); // Predicate on instruction order is implicitly present due to the predicate of the cheaper zLoadP operation diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 128e566d0f3..d8e00cfef89 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -4036,6 +4036,10 @@ ins_attrib ins_field_cbuf_insts_offset(-1); ins_attrib ins_field_load_ic_hi_node(0); ins_attrib ins_field_load_ic_node(0); +// Whether this node is expanded during code emission into a sequence of +// instructions and the first instruction can perform an implicit null check. +ins_attrib ins_is_late_expanded_null_check_candidate(false); + //----------OPERANDS----------------------------------------------------------- // Operand definitions must precede instruction definitions for correct // parsing in the ADLC because operands constitute user defined types diff --git a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad index fd9a1d43afc..e3847019bb3 100644 --- a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad @@ -96,6 +96,7 @@ instruct zLoadP(iRegPNoSp dst, memory mem, iRegPNoSp tmp, rFlagsReg cr) match(Set dst (LoadP mem)); predicate(UseZGC && n->as_Load()->barrier_data() != 0); effect(TEMP dst, TEMP tmp, KILL cr); + ins_is_late_expanded_null_check_candidate(true); ins_cost(4 * DEFAULT_COST); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index e838ee184fb..0d44acc803f 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2619,6 +2619,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must // compute_padding() function must be // provided for the instruction +// Whether this node is expanded during code emission into a sequence of +// instructions and the first instruction can perform an implicit null check. +ins_attrib ins_is_late_expanded_null_check_candidate(false); + //----------OPERANDS----------------------------------------------------------- // Operand definitions must precede instruction definitions for correct parsing // in the ADLC because operands constitute user defined types which are used in diff --git a/src/hotspot/cpu/x86/gc/z/z_x86_64.ad b/src/hotspot/cpu/x86/gc/z/z_x86_64.ad index 045aa5d5381..e66b4b0da7a 100644 --- a/src/hotspot/cpu/x86/gc/z/z_x86_64.ad +++ b/src/hotspot/cpu/x86/gc/z/z_x86_64.ad @@ -118,6 +118,10 @@ instruct zLoadP(rRegP dst, memory mem, rFlagsReg cr) predicate(UseZGC && n->as_Load()->barrier_data() != 0); match(Set dst (LoadP mem)); effect(TEMP dst, KILL cr); + // The main load is a candidate to implement implicit null checks. The + // barrier's slow path includes an identical reload, which does not need to be + // registered in the exception table because it is dominated by the main one. + ins_is_late_expanded_null_check_candidate(true); ins_cost(125); diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index 22490ba7bb3..d17bdf5e2c9 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -2055,6 +2055,10 @@ ins_attrib ins_alignment(1); // Required alignment attribute (must // compute_padding() function must be // provided for the instruction +// Whether this node is expanded during code emission into a sequence of +// instructions and the first instruction can perform an implicit null check. +ins_attrib ins_is_late_expanded_null_check_candidate(false); + //----------OPERANDS----------------------------------------------------------- // Operand definitions must precede instruction definitions for correct parsing // in the ADLC because operands constitute user defined types which are used in diff --git a/src/hotspot/share/adlc/output_h.cpp b/src/hotspot/share/adlc/output_h.cpp index b62bc43791f..cbcc00efa3b 100644 --- a/src/hotspot/share/adlc/output_h.cpp +++ b/src/hotspot/share/adlc/output_h.cpp @@ -1626,6 +1626,8 @@ void ArchDesc::declareClasses(FILE *fp) { while (attr != nullptr) { if (strcmp (attr->_ident, "ins_is_TrapBasedCheckNode") == 0) { fprintf(fp, " virtual bool is_TrapBasedCheckNode() const { return %s; }\n", attr->_val); + } else if (strcmp (attr->_ident, "ins_is_late_expanded_null_check_candidate") == 0) { + fprintf(fp, " virtual bool is_late_expanded_null_check_candidate() const { return %s; }\n", attr->_val); } else if (strcmp (attr->_ident, "ins_cost") != 0 && strncmp(attr->_ident, "ins_field_", 10) != 0 && // Must match function in node.hpp: return type bool, no prefix "ins_". diff --git a/src/hotspot/share/opto/block.hpp b/src/hotspot/share/opto/block.hpp index c5a3a589407..e6a98c28e77 100644 --- a/src/hotspot/share/opto/block.hpp +++ b/src/hotspot/share/opto/block.hpp @@ -464,6 +464,14 @@ class PhaseCFG : public Phase { Node* catch_cleanup_find_cloned_def(Block* use_blk, Node* def, Block* def_blk, int n_clone_idx); void catch_cleanup_inter_block(Node *use, Block *use_blk, Node *def, Block *def_blk, int n_clone_idx); + // Ensure that n happens at b or above, i.e. at a block that dominates b. + // We expect n to be an orphan node without further inputs. + void ensure_node_is_at_block_or_above(Node* n, Block* b); + + // Move node n from its current placement into the end of block b. + // Move also outgoing Mach projections. + void move_node_and_its_projections_to_block(Node* n, Block* b); + // Detect implicit-null-check opportunities. Basically, find null checks // with suitable memory ops nearby. Use the memory op to do the null check. // I can generate a memory op if there is not one nearby. diff --git a/src/hotspot/share/opto/lcm.cpp b/src/hotspot/share/opto/lcm.cpp index 4df3bbab731..0fe246a7679 100644 --- a/src/hotspot/share/opto/lcm.cpp +++ b/src/hotspot/share/opto/lcm.cpp @@ -76,6 +76,36 @@ static bool needs_explicit_null_check_for_read(Node *val) { return true; } +void PhaseCFG::move_node_and_its_projections_to_block(Node* n, Block* b) { + assert(!is_CFG(n), "cannot move CFG node"); + Block* old = get_block_for_node(n); + old->find_remove(n); + b->add_inst(n); + map_node_to_block(n, b); + // Check for Mach projections that also need to be moved. + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* out = n->fast_out(i); + if (!out->is_MachProj()) { + continue; + } + assert(!n->is_MachProj(), "nested projections are not allowed"); + move_node_and_its_projections_to_block(out, b); + } +} + +void PhaseCFG::ensure_node_is_at_block_or_above(Node* n, Block* b) { + assert(!is_CFG(n), "cannot move CFG node"); + Block* current = get_block_for_node(n); + if (current->dominates(b)) { + return; // n is already placed above b, do nothing. + } + // We only expect nodes without further inputs, like MachTemp or load Base. + assert(n->req() == 0 || (n->req() == 1 && n->in(0) == (Node*)C->root()), + "need for recursive hoisting not expected"); + assert(b->dominates(current), "precondition: can only move n to b if b dominates n"); + move_node_and_its_projections_to_block(n, b); +} + //------------------------------implicit_null_check---------------------------- // Detect implicit-null-check opportunities. Basically, find null checks // with suitable memory ops nearby. Use the memory op to do the null check. @@ -160,12 +190,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo Node *m = val->out(i); if( !m->is_Mach() ) continue; MachNode *mach = m->as_Mach(); - if (mach->barrier_data() != 0) { + if (mach->barrier_data() != 0 && + !mach->is_late_expanded_null_check_candidate()) { // Using memory accesses with barriers to perform implicit null checks is - // not supported. These operations might expand into multiple assembly - // instructions during code emission, including new memory accesses (e.g. - // in G1's pre-barrier), which would invalidate the implicit null - // exception table. + // only supported if these are explicit marked as emitting a candidate + // memory access instruction at their initial address. If not marked as + // such, barrier-tagged operations might expand into one or several memory + // access instructions located at arbitrary offsets from the initial + // address, which would invalidate the implicit null exception table. continue; } was_store = false; @@ -321,6 +353,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo // Ignore DecodeN val which could be hoisted to where needed. if( is_decoden ) continue; } + if (mach->in(j)->is_MachTemp()) { + assert(mach->in(j)->outcnt() == 1, "MachTemp nodes should not be shared"); + // Ignore MachTemp inputs, they can be safely hoisted with the candidate. + // MachTemp nodes have no inputs themselves and are only used to reserve + // a scratch register for the implementation of the node (e.g. in + // late-expanded GC barriers). + continue; + } // Block of memory-op input Block *inb = get_block_for_node(mach->in(j)); Block *b = block; // Start from nul check @@ -388,38 +428,24 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo // Hoist it up to the end of the test block together with its inputs if they exist. for (uint i = 2; i < val->req(); i++) { // DecodeN has 2 regular inputs + optional MachTemp or load Base inputs. - Node *temp = val->in(i); - Block *tempb = get_block_for_node(temp); - if (!tempb->dominates(block)) { - assert(block->dominates(tempb), "sanity check: temp node placement"); - // We only expect nodes without further inputs, like MachTemp or load Base. - assert(temp->req() == 0 || (temp->req() == 1 && temp->in(0) == (Node*)C->root()), - "need for recursive hoisting not expected"); - tempb->find_remove(temp); - block->add_inst(temp); - map_node_to_block(temp, block); - } - } - valb->find_remove(val); - block->add_inst(val); - map_node_to_block(val, block); - // DecodeN on x86 may kill flags. Check for flag-killing projections - // that also need to be hoisted. - for (DUIterator_Fast jmax, j = val->fast_outs(jmax); j < jmax; j++) { - Node* n = val->fast_out(j); - if( n->is_MachProj() ) { - get_block_for_node(n)->find_remove(n); - block->add_inst(n); - map_node_to_block(n, block); - } + // Inputs of val may already be early enough, but if not move them together with val. + ensure_node_is_at_block_or_above(val->in(i), block); } + move_node_and_its_projections_to_block(val, block); } } + + // Move any MachTemp inputs to the end of the test block. + for (uint i = 0; i < best->req(); i++) { + Node* n = best->in(i); + if (n == nullptr || !n->is_MachTemp()) { + continue; + } + ensure_node_is_at_block_or_above(n, block); + } + // Hoist the memory candidate up to the end of the test block. - Block *old_block = get_block_for_node(best); - old_block->find_remove(best); - block->add_inst(best); - map_node_to_block(best, block); + move_node_and_its_projections_to_block(best, block); // Move the control dependence if it is pinned to not-null block. // Don't change it in other cases: null or dominating control. @@ -429,17 +455,6 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo best->set_req(0, proj->in(0)->in(0)); } - // Check for flag-killing projections that also need to be hoisted - // Should be DU safe because no edge updates. - for (DUIterator_Fast jmax, j = best->fast_outs(jmax); j < jmax; j++) { - Node* n = best->fast_out(j); - if( n->is_MachProj() ) { - get_block_for_node(n)->find_remove(n); - block->add_inst(n); - map_node_to_block(n, block); - } - } - // proj==Op_True --> ne test; proj==Op_False --> eq test. // One of two graph shapes got matched: // (IfTrue (If (Bool NE (CmpP ptr null)))) diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index 5fd262d54e1..3594806b91e 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -386,6 +386,13 @@ public: // Returns true if this node is a check that can be implemented with a trap. virtual bool is_TrapBasedCheckNode() const { return false; } + + // Whether this node is expanded during code emission into a sequence of + // instructions and the first instruction can perform an implicit null check. + virtual bool is_late_expanded_null_check_candidate() const { + return false; + } + void set_removed() { add_flag(Flag_is_removed_by_peephole); } bool get_removed() { return (flags() & Flag_is_removed_by_peephole) != 0; } diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index 930c355fa62..f0a7b742998 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -2015,8 +2015,10 @@ void PhaseOutput::FillExceptionTables(uint cnt, uint *call_returns, uint *inct_s // Handle implicit null exception table updates if (n->is_MachNullCheck()) { - assert(n->in(1)->as_Mach()->barrier_data() == 0, - "Implicit null checks on memory accesses with barriers are not yet supported"); + MachNode* access = n->in(1)->as_Mach(); + assert(access->barrier_data() == 0 || + access->is_late_expanded_null_check_candidate(), + "Implicit null checks on memory accesses with barriers are only supported on nodes explicitly marked as null-check candidates"); uint block_num = block->non_connector_successor(0)->_pre_order; _inc_table.append(inct_starts[inct_cnt++], blk_labels[block_num].loc_pos()); continue; diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java new file mode 100644 index 00000000000..a77a51312de --- /dev/null +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java @@ -0,0 +1,231 @@ +/* + * 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 compiler.gcbarriers; + +import compiler.lib.ir_framework.*; +import java.lang.invoke.VarHandle; +import java.lang.invoke.MethodHandles; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import jdk.test.lib.Asserts; + +/** + * @test + * @summary Test that implicit null checks are generated as expected for + different GC memory accesses. + * @library /test/lib / + * @run driver compiler.gcbarriers.TestImplicitNullChecks + */ + + +public class TestImplicitNullChecks { + + static class Outer { + Object f; + } + + static class OuterWithVolatileField { + volatile Object f; + } + + static final VarHandle fVarHandle; + static { + MethodHandles.Lookup l = MethodHandles.lookup(); + try { + fVarHandle = l.findVarHandle(Outer.class, "f", Object.class); + } catch (Exception e) { + throw new Error(e); + } + } + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:CompileCommand=inline,java.lang.ref.*::*", + "-XX:-TieredCompilation"); + } + + @Test + @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + counts = {IRNode.NULL_CHECK, "1"}, + phase = CompilePhase.FINAL_CODE) + static Object testLoad(Outer o) { + return o.f; + } + + @Test + // On aarch64, volatile loads always use indirect memory operands, which + // leads to a pattern that cannot be exploited by the current C2 analysis. + // On PPC64, volatile loads are preceded by membar_volatile instructions, + // which also inhibits the current C2 analysis. + @IR(applyIfPlatformAnd = {"aarch64", "false", "ppc", "false"}, + applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + counts = {IRNode.NULL_CHECK, "1"}, + phase = CompilePhase.FINAL_CODE) + static Object testLoadVolatile(OuterWithVolatileField o) { + return o.f; + } + + @Run(test = {"testLoad", + "testLoadVolatile"}, + mode = RunMode.STANDALONE) + static void runLoadTests() { + { + Outer o = new Outer(); + // Trigger compilation with implicit null check. + for (int i = 0; i < 10_000; i++) { + testLoad(o); + } + // Trigger null pointer exception. + o = null; + boolean nullPointerException = false; + try { + testLoad(o); + } catch (NullPointerException e) { nullPointerException = true; } + Asserts.assertTrue(nullPointerException); + } + { + OuterWithVolatileField o = new OuterWithVolatileField(); + // Trigger compilation with implicit null check. + for (int i = 0; i < 10_000; i++) { + testLoadVolatile(o); + } + // Trigger null pointer exception. + o = null; + boolean nullPointerException = false; + try { + testLoadVolatile(o); + } catch (NullPointerException e) { nullPointerException = true; } + Asserts.assertTrue(nullPointerException); + } + } + + @Test + // G1 and ZGC stores cannot be currently used to implement implicit null + // checks, because they expand into multiple memory access instructions that + // are not necessarily located at the initial instruction start address. + @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + failOn = IRNode.NULL_CHECK, + phase = CompilePhase.FINAL_CODE) + static void testStore(Outer o, Object o1) { + o.f = o1; + } + + @Run(test = {"testStore"}) + static void runStoreTests() { + { + Outer o = new Outer(); + Object o1 = new Object(); + testStore(o, o1); + } + } + + @Test + // G1 and ZGC compare-and-exchange operations cannot be currently used to + // implement implicit null checks, because they expand into multiple memory + // access instructions that are not necessarily located at the initial + // instruction start address. The same holds for testCompareAndSwap and + // testGetAndSet below. + @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + failOn = IRNode.NULL_CHECK, + phase = CompilePhase.FINAL_CODE) + static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { + return fVarHandle.compareAndExchange(o, oldVal, newVal); + } + + @Test + @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + failOn = IRNode.NULL_CHECK, + phase = CompilePhase.FINAL_CODE) + static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { + return fVarHandle.compareAndSet(o, oldVal, newVal); + } + + @Test + @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + failOn = IRNode.NULL_CHECK, + phase = CompilePhase.FINAL_CODE) + static Object testGetAndSet(Outer o, Object newVal) { + return fVarHandle.getAndSet(o, newVal); + } + + @Run(test = {"testCompareAndExchange", + "testCompareAndSwap", + "testGetAndSet"}) + static void runAtomicTests() { + { + Outer o = new Outer(); + Object oldVal = new Object(); + Object newVal = new Object(); + testCompareAndExchange(o, oldVal, newVal); + } + { + Outer o = new Outer(); + Object oldVal = new Object(); + Object newVal = new Object(); + testCompareAndSwap(o, oldVal, newVal); + } + { + Outer o = new Outer(); + Object oldVal = new Object(); + Object newVal = new Object(); + testGetAndSet(o, newVal); + } + } + + @Test + // G1 reference loads use indirect memory operands, which leads to a pattern + // that cannot be exploited by the current C2 analysis. The same holds for + // testLoadWeakReference. + @IR(applyIf = {"UseZGC", "true"}, + counts = {IRNode.NULL_CHECK, "1"}, + phase = CompilePhase.FINAL_CODE) + static Object testLoadSoftReference(SoftReference ref) { + return ref.get(); + } + + @Test + @IR(applyIf = {"UseZGC", "true"}, + counts = {IRNode.NULL_CHECK, "1"}, + phase = CompilePhase.FINAL_CODE) + static Object testLoadWeakReference(WeakReference ref) { + return ref.get(); + } + + @Run(test = {"testLoadSoftReference", + "testLoadWeakReference"}) + static void runReferenceTests() { + { + Object o1 = new Object(); + SoftReference sref = new SoftReference(o1); + Object o2 = testLoadSoftReference(sref); + } + { + Object o1 = new Object(); + WeakReference wref = new WeakReference(o1); + Object o2 = testLoadWeakReference(wref); + } + } + +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 96b62a2b12a..9388e1a8892 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1499,6 +1499,11 @@ public class IRNode { trapNodes(NULL_ASSERT_TRAP, "null_assert"); } + public static final String NULL_CHECK = PREFIX + "NULL_CHECK" + POSTFIX; + static { + machOnlyNameRegex(NULL_CHECK, "NullCheck"); + } + public static final String NULL_CHECK_TRAP = PREFIX + "NULL_CHECK_TRAP" + POSTFIX; static { trapNodes(NULL_CHECK_TRAP, "null_check"); From 52338c94f610611a9e89a6ccbe6f2c6cd768b50a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 9 Jun 2025 07:00:51 +0000 Subject: [PATCH 095/143] 8358520: Improve lazy computation in BreakIteratorResourceBundle and related classes Reviewed-by: naoto, jlu --- .../classes/java/util/ResourceBundle.java | 33 ++++--- .../BreakIteratorResourceBundle.java | 25 +++-- .../resources/OpenListResourceBundle.java | 94 ++++++++----------- 3 files changed, 65 insertions(+), 87 deletions(-) diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index fb88a38903a..b3807ac770a 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -54,6 +54,7 @@ import java.net.URL; import java.net.URLConnection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; import java.util.jar.JarEntry; import java.util.spi.ResourceBundleControlProvider; import java.util.spi.ResourceBundleProvider; @@ -487,7 +488,20 @@ public abstract class ResourceBundle { /** * A Set of the keys contained only in this ResourceBundle. */ - private volatile Set keySet; + private final Supplier> keySet = StableValue.supplier( + new Supplier<>() { public Set get() { return keySet0(); }}); + + private Set keySet0() { + final Set keys = new HashSet<>(); + final Enumeration enumKeys = getKeys(); + while (enumKeys.hasMoreElements()) { + final String key = enumKeys.nextElement(); + if (handleGetObject(key) != null) { + keys.add(key); + } + } + return keys; + } /** * Sole constructor. (For invocation by subclass constructors, typically @@ -2298,22 +2312,7 @@ public abstract class ResourceBundle { * @since 1.6 */ protected Set handleKeySet() { - if (keySet == null) { - synchronized (this) { - if (keySet == null) { - Set keys = new HashSet<>(); - Enumeration enumKeys = getKeys(); - while (enumKeys.hasMoreElements()) { - String key = enumKeys.nextElement(); - if (handleGetObject(key) != null) { - keys.add(key); - } - } - keySet = keys; - } - } - } - return keySet; + return keySet.get(); } diff --git a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java index 3a55e7ccb8c..d7c575962f6 100644 --- a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Supplier; /** * BreakIteratorResourceBundle is an abstract class for loading BreakIterator @@ -49,7 +50,15 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle { // those keys must be added to NON_DATA_KEYS. private static final Set NON_DATA_KEYS = Set.of("BreakIteratorClasses"); - private volatile Set keys; + private final Supplier> keys = StableValue.supplier( + new Supplier<>() { public Set get() { return keys0(); }}); + + private Set keys0() { + final ResourceBundle info = getBreakIteratorInfo(); + final Set k = info.keySet(); + k.removeAll(NON_DATA_KEYS); + return k; + } /** * Returns an instance of the corresponding {@code BreakIteratorInfo} (basename). @@ -84,16 +93,6 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle { @Override protected Set handleKeySet() { - if (keys == null) { - ResourceBundle info = getBreakIteratorInfo(); - Set k = info.keySet(); - k.removeAll(NON_DATA_KEYS); - synchronized (this) { - if (keys == null) { - keys = k; - } - } - } - return keys; + return keys.get(); } } diff --git a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java index d375930dde0..0c16f508018 100644 --- a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -44,8 +44,11 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Supplier; + import sun.util.ResourceBundleEnumeration; /** @@ -69,12 +72,9 @@ public abstract class OpenListResourceBundle extends ResourceBundle { // Implements java.util.ResourceBundle.handleGetObject; inherits javadoc specification. @Override protected Object handleGetObject(String key) { - if (key == null) { - throw new NullPointerException(); - } - - loadLookupTablesIfNecessary(); - return lookup.get(key); // this class ignores locales + Objects.requireNonNull(key); + return lookup.get() + .get(key); // this class ignores locales } /** @@ -93,26 +93,13 @@ public abstract class OpenListResourceBundle extends ResourceBundle { */ @Override protected Set handleKeySet() { - loadLookupTablesIfNecessary(); - return lookup.keySet(); + return lookup.get() + .keySet(); } @Override public Set keySet() { - if (keyset != null) { - return keyset; - } - Set ks = createSet(); - ks.addAll(handleKeySet()); - if (parent != null) { - ks.addAll(parent.keySet()); - } - synchronized (this) { - if (keyset == null) { - keyset = ks; - } - } - return keyset; + return keyset.get(); } /** @@ -120,38 +107,6 @@ public abstract class OpenListResourceBundle extends ResourceBundle { */ protected abstract Object[][] getContents(); - /** - * Load lookup tables if they haven't been loaded already. - */ - void loadLookupTablesIfNecessary() { - if (lookup == null) { - loadLookup(); - } - } - - /** - * We lazily load the lookup hashtable. This function does the - * loading. - */ - private void loadLookup() { - Object[][] contents = getContents(); - Map temp = createMap(contents.length); - for (int i = 0; i < contents.length; ++i) { - // key must be non-null String, value must be non-null - String key = (String) contents[i][0]; - Object value = contents[i][1]; - if (key == null || value == null) { - throw new NullPointerException(); - } - temp.put(key, value); - } - synchronized (this) { - if (lookup == null) { - lookup = temp; - } - } - } - /** * Lets subclasses provide specialized Map implementations. * Default uses HashMap. @@ -164,6 +119,31 @@ public abstract class OpenListResourceBundle extends ResourceBundle { return new HashSet<>(); } - private volatile Map lookup; - private volatile Set keyset; + private final Supplier> lookup = StableValue.supplier( + new Supplier<>() { public Map get() { return lookup0(); }}); + + private Map lookup0() { + final Object[][] contents = getContents(); + final Map temp = createMap(contents.length); + for (Object[] content : contents) { + // key must be non-null String, value must be non-null + final String key = Objects.requireNonNull((String) content[0]); + final Object value = Objects.requireNonNull(content[1]); + temp.put(key, value); + } + return temp; + } + + private final Supplier> keyset = StableValue.supplier( + new Supplier<>() { public Set get() { return keyset0(); }}); + + private Set keyset0() { + final Set ks = createSet(); + ks.addAll(handleKeySet()); + if (parent != null) { + ks.addAll(parent.keySet()); + } + return ks; + } + } From 1c72b350e417bd210dc6b66eba5afe0e3bc6306c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 9 Jun 2025 09:03:12 +0000 Subject: [PATCH 096/143] 8357053: ZGC: Improved utility for ZPageAge Co-authored-by: Axel Boldt-Christmas Reviewed-by: sjohanss, stefank --- src/hotspot/share/gc/z/zAllocator.cpp | 3 +- src/hotspot/share/gc/z/zAllocator.hpp | 4 +- src/hotspot/share/gc/z/zAllocator.inline.hpp | 5 +- src/hotspot/share/gc/z/zGeneration.cpp | 10 ++-- src/hotspot/share/gc/z/zPageAge.hpp | 18 ++++++- src/hotspot/share/gc/z/zPageAge.inline.hpp | 52 +++++++++++++++++++ src/hotspot/share/gc/z/zRelocate.cpp | 12 ++--- .../share/gc/z/zRelocationSetSelector.cpp | 17 +++--- .../share/gc/z/zRelocationSetSelector.hpp | 10 ++-- .../gc/z/zRelocationSetSelector.inline.hpp | 24 ++++----- src/hotspot/share/gc/z/zStat.cpp | 18 +++---- src/hotspot/share/gc/z/z_globals.hpp | 2 +- src/hotspot/share/utilities/enumIterator.hpp | 27 ++++++++-- test/hotspot/gtest/gc/z/test_zPageAge.cpp | 47 +++++++++++++++++ 14 files changed, 191 insertions(+), 58 deletions(-) create mode 100644 src/hotspot/share/gc/z/zPageAge.inline.hpp create mode 100644 test/hotspot/gtest/gc/z/test_zPageAge.cpp diff --git a/src/hotspot/share/gc/z/zAllocator.cpp b/src/hotspot/share/gc/z/zAllocator.cpp index 22f3b6ba112..dd1ab3b712a 100644 --- a/src/hotspot/share/gc/z/zAllocator.cpp +++ b/src/hotspot/share/gc/z/zAllocator.cpp @@ -23,6 +23,7 @@ #include "gc/z/zAllocator.hpp" #include "gc/z/zObjectAllocator.hpp" +#include "gc/z/zPageAge.inline.hpp" ZAllocatorEden* ZAllocator::_eden; ZAllocatorForRelocation* ZAllocator::_relocation[ZAllocator::_relocation_allocators]; @@ -47,7 +48,7 @@ ZPageAge ZAllocatorForRelocation::install() { for (uint i = 0; i < ZAllocator::_relocation_allocators; ++i) { if (_relocation[i] == nullptr) { _relocation[i] = this; - return static_cast(i + 1); + return to_zpageage(i + 1); } } diff --git a/src/hotspot/share/gc/z/zAllocator.hpp b/src/hotspot/share/gc/z/zAllocator.hpp index 45a70888f9d..ec4c0ec1c9c 100644 --- a/src/hotspot/share/gc/z/zAllocator.hpp +++ b/src/hotspot/share/gc/z/zAllocator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -35,7 +35,7 @@ class ZPage; class ZAllocator { public: - static constexpr uint _relocation_allocators = static_cast(ZPageAge::old); + static constexpr uint _relocation_allocators = ZPageAgeCount - 1; protected: ZObjectAllocator _object_allocator; diff --git a/src/hotspot/share/gc/z/zAllocator.inline.hpp b/src/hotspot/share/gc/z/zAllocator.inline.hpp index ba558a1b0f3..ec64676dbf0 100644 --- a/src/hotspot/share/gc/z/zAllocator.inline.hpp +++ b/src/hotspot/share/gc/z/zAllocator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -28,13 +28,14 @@ #include "gc/z/zAddress.inline.hpp" #include "gc/z/zHeap.hpp" +#include "gc/z/zPageAge.inline.hpp" inline ZAllocatorEden* ZAllocator::eden() { return _eden; } inline ZAllocatorForRelocation* ZAllocator::relocation(ZPageAge page_age) { - return _relocation[static_cast(page_age) - 1]; + return _relocation[untype(page_age - 1)]; } inline ZAllocatorForRelocation* ZAllocator::old() { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 772c5a0a98b..534f0195c90 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -41,6 +41,7 @@ #include "gc/z/zHeap.inline.hpp" #include "gc/z/zJNICritical.hpp" #include "gc/z/zMark.inline.hpp" +#include "gc/z/zPageAge.inline.hpp" #include "gc/z/zPageAllocator.hpp" #include "gc/z/zRelocationSet.inline.hpp" #include "gc/z/zRelocationSetSelector.inline.hpp" @@ -699,11 +700,10 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st uint last_populated_age = 0; size_t last_populated_live = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { const size_t young_live = stats.small(age).live() + stats.medium(age).live() + stats.large(age).live(); if (young_live > 0) { - last_populated_age = i; + last_populated_age = untype(age); last_populated_live = young_live; if (young_live_last > 0) { young_life_expectancy_sum += double(young_live) / double(young_live_last); @@ -842,8 +842,8 @@ void ZGenerationYoung::mark_start() { // Retire allocating pages ZAllocator::eden()->retire_pages(); - for (ZPageAge i = ZPageAge::survivor1; i <= ZPageAge::survivor14; i = static_cast(static_cast(i) + 1)) { - ZAllocator::relocation(i)->retire_pages(); + for (ZPageAge age : ZPageAgeRangeSurvivor) { + ZAllocator::relocation(age)->retire_pages(); } // Reset allocated/reclaimed/used statistics diff --git a/src/hotspot/share/gc/z/zPageAge.hpp b/src/hotspot/share/gc/z/zPageAge.hpp index b7b1688b830..3209790c008 100644 --- a/src/hotspot/share/gc/z/zPageAge.hpp +++ b/src/hotspot/share/gc/z/zPageAge.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -24,6 +24,7 @@ #ifndef SHARE_GC_Z_ZPAGEAGE_HPP #define SHARE_GC_Z_ZPAGEAGE_HPP +#include "utilities/enumIterator.hpp" #include "utilities/globalDefinitions.hpp" enum class ZPageAge : uint8_t { @@ -45,6 +46,19 @@ enum class ZPageAge : uint8_t { old }; -constexpr uint ZPageAgeMax = static_cast(ZPageAge::old); +constexpr uint ZPageAgeCount = static_cast(ZPageAge::old) + 1; +constexpr ZPageAge ZPageAgeLastPlusOne = static_cast(ZPageAgeCount); + +ENUMERATOR_RANGE(ZPageAge, + ZPageAge::eden, + ZPageAge::old); + +using ZPageAgeRange = EnumRange; + +constexpr ZPageAgeRange ZPageAgeRangeEden = ZPageAgeRange::create(); +constexpr ZPageAgeRange ZPageAgeRangeYoung = ZPageAgeRange::create(); +constexpr ZPageAgeRange ZPageAgeRangeSurvivor = ZPageAgeRange::create(); +constexpr ZPageAgeRange ZPageAgeRangeRelocation = ZPageAgeRange::create(); +constexpr ZPageAgeRange ZPageAgeRangeOld = ZPageAgeRange::create(); #endif // SHARE_GC_Z_ZPAGEAGE_HPP diff --git a/src/hotspot/share/gc/z/zPageAge.inline.hpp b/src/hotspot/share/gc/z/zPageAge.inline.hpp new file mode 100644 index 00000000000..0944caa69a6 --- /dev/null +++ b/src/hotspot/share/gc/z/zPageAge.inline.hpp @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#ifndef SHARE_GC_Z_ZPAGEAGE_INLINE_HPP +#define SHARE_GC_Z_ZPAGEAGE_INLINE_HPP + +#include "gc/z/zPageAge.hpp" + +#include "utilities/checkedCast.hpp" + +#include + +inline uint untype(ZPageAge age) { + return static_cast(age); +} + +inline ZPageAge to_zpageage(uint age) { + assert(age < ZPageAgeCount, "Invalid age"); + return static_cast(age); +} + +inline ZPageAge operator+(ZPageAge age, size_t size) { + const auto size_value = checked_cast>(size); + return to_zpageage(untype(age) + size_value); +} + +inline ZPageAge operator-(ZPageAge age, size_t size) { + const auto size_value = checked_cast>(size); + return to_zpageage(untype(age) - size_value); +} + +#endif // SHARE_GC_Z_ZPAGEAGE_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index df2dd8a01cb..b45d8b70e72 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -488,11 +488,11 @@ public: } ZPage* shared(ZPageAge age) { - return _shared[static_cast(age) - 1]; + return _shared[untype(age - 1)]; } void set_shared(ZPageAge age, ZPage* page) { - _shared[static_cast(age) - 1] = page; + _shared[untype(age - 1)] = page; } ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* target) { @@ -570,11 +570,11 @@ private: ZPage* target(ZPageAge age) { - return _target[static_cast(age) - 1]; + return _target[untype(age - 1)]; } void set_target(ZPageAge age, ZPage* page) { - _target[static_cast(age) - 1] = page; + _target[untype(age - 1)] = page; } size_t object_alignment() const { @@ -1232,12 +1232,12 @@ ZPageAge ZRelocate::compute_to_age(ZPageAge from_age) { return ZPageAge::old; } - const uint age = static_cast(from_age); + const uint age = untype(from_age); if (age >= ZGeneration::young()->tenuring_threshold()) { return ZPageAge::old; } - return static_cast(age + 1); + return to_zpageage(age + 1); } class ZFlipAgePagesTask : public ZTask { diff --git a/src/hotspot/share/gc/z/zRelocationSetSelector.cpp b/src/hotspot/share/gc/z/zRelocationSetSelector.cpp index e9ef66a19dc..aac4fd51271 100644 --- a/src/hotspot/share/gc/z/zRelocationSetSelector.cpp +++ b/src/hotspot/share/gc/z/zRelocationSetSelector.cpp @@ -25,6 +25,7 @@ #include "gc/z/zArray.inline.hpp" #include "gc/z/zForwarding.inline.hpp" #include "gc/z/zPage.inline.hpp" +#include "gc/z/zPageAge.inline.hpp" #include "gc/z/zRelocationSetSelector.inline.hpp" #include "jfr/jfrEvents.hpp" #include "logging/log.hpp" @@ -117,8 +118,8 @@ void ZRelocationSetSelectorGroup::select_inner() { const int npages = _live_pages.length(); int selected_from = 0; int selected_to = 0; - size_t npages_selected[ZPageAgeMax + 1] = { 0 }; - size_t selected_live_bytes[ZPageAgeMax + 1] = { 0 }; + size_t npages_selected[ZPageAgeCount] = { 0 }; + size_t selected_live_bytes[ZPageAgeCount] = { 0 }; size_t selected_forwarding_entries = 0; size_t from_live_bytes = 0; @@ -149,8 +150,8 @@ void ZRelocationSetSelectorGroup::select_inner() { if (diff_reclaimable > _fragmentation_limit) { selected_from = from; selected_to = to; - selected_live_bytes[static_cast(page->age())] += page_live_bytes; - npages_selected[static_cast(page->age())] += 1; + selected_live_bytes[untype(page->age())] += page_live_bytes; + npages_selected[untype(page->age())] += 1; selected_forwarding_entries = from_forwarding_entries; } @@ -172,7 +173,7 @@ void ZRelocationSetSelectorGroup::select_inner() { _forwarding_entries = selected_forwarding_entries; // Update statistics - for (uint i = 0; i <= ZPageAgeMax; ++i) { + for (uint i = 0; i < ZPageAgeCount; ++i) { _stats[i]._relocate = selected_live_bytes[i]; _stats[i]._npages_selected = npages_selected[i]; } @@ -200,7 +201,7 @@ void ZRelocationSetSelectorGroup::select() { } ZRelocationSetSelectorGroupStats s{}; - for (uint i = 0; i <= ZPageAgeMax; ++i) { + for (uint i = 0; i < ZPageAgeCount; ++i) { s._npages_candidates += _stats[i].npages_candidates(); s._total += _stats[i].total(); s._empty += _stats[i].empty(); @@ -239,8 +240,8 @@ void ZRelocationSetSelector::select() { ZRelocationSetSelectorStats ZRelocationSetSelector::stats() const { ZRelocationSetSelectorStats stats; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { + const uint i = untype(age); stats._small[i] = _small.stats(age); stats._medium[i] = _medium.stats(age); stats._large[i] = _large.stats(age); diff --git a/src/hotspot/share/gc/z/zRelocationSetSelector.hpp b/src/hotspot/share/gc/z/zRelocationSetSelector.hpp index b6ec2347153..21772405f25 100644 --- a/src/hotspot/share/gc/z/zRelocationSetSelector.hpp +++ b/src/hotspot/share/gc/z/zRelocationSetSelector.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -62,9 +62,9 @@ class ZRelocationSetSelectorStats { friend class ZRelocationSetSelector; private: - ZRelocationSetSelectorGroupStats _small[ZPageAgeMax + 1]; - ZRelocationSetSelectorGroupStats _medium[ZPageAgeMax + 1]; - ZRelocationSetSelectorGroupStats _large[ZPageAgeMax + 1]; + ZRelocationSetSelectorGroupStats _small[ZPageAgeCount]; + ZRelocationSetSelectorGroupStats _medium[ZPageAgeCount]; + ZRelocationSetSelectorGroupStats _large[ZPageAgeCount]; size_t _has_relocatable_pages; @@ -90,7 +90,7 @@ private: ZArray _live_pages; ZArray _not_selected_pages; size_t _forwarding_entries; - ZRelocationSetSelectorGroupStats _stats[ZPageAgeMax + 1]; + ZRelocationSetSelectorGroupStats _stats[ZPageAgeCount]; bool is_disabled(); bool is_selectable(); diff --git a/src/hotspot/share/gc/z/zRelocationSetSelector.inline.hpp b/src/hotspot/share/gc/z/zRelocationSetSelector.inline.hpp index 7cd0a005aac..7740e0764a6 100644 --- a/src/hotspot/share/gc/z/zRelocationSetSelector.inline.hpp +++ b/src/hotspot/share/gc/z/zRelocationSetSelector.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -29,6 +29,7 @@ #include "gc/z/zArray.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zPage.inline.hpp" +#include "gc/z/zPageAge.inline.hpp" #include "utilities/powerOfTwo.hpp" inline size_t ZRelocationSetSelectorGroupStats::npages_candidates() const { @@ -60,15 +61,15 @@ inline bool ZRelocationSetSelectorStats::has_relocatable_pages() const { } inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::small(ZPageAge age) const { - return _small[static_cast(age)]; + return _small[untype(age)]; } inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::medium(ZPageAge age) const { - return _medium[static_cast(age)]; + return _medium[untype(age)]; } inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::large(ZPageAge age) const { - return _large[static_cast(age)]; + return _large[untype(age)]; } inline bool ZRelocationSetSelectorGroup::pre_filter_page(const ZPage* page, size_t live_bytes) const { @@ -113,7 +114,7 @@ inline void ZRelocationSetSelectorGroup::register_live_page(ZPage* page) { } const size_t size = page->size(); - const uint age = static_cast(page->age()); + const uint age = untype(page->age()); _stats[age]._npages_candidates++; _stats[age]._total += size; _stats[age]._live += live; @@ -122,7 +123,7 @@ inline void ZRelocationSetSelectorGroup::register_live_page(ZPage* page) { inline void ZRelocationSetSelectorGroup::register_empty_page(ZPage* page) { const size_t size = page->size(); - const uint age = static_cast(page->age()); + const uint age = untype(page->age()); _stats[age]._npages_candidates++; _stats[age]._total += size; _stats[age]._empty += size; @@ -141,7 +142,7 @@ inline size_t ZRelocationSetSelectorGroup::forwarding_entries() const { } inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorGroup::stats(ZPageAge age) const { - return _stats[static_cast(age)]; + return _stats[untype(age)]; } inline void ZRelocationSetSelector::register_live_page(ZPage* page) { @@ -188,8 +189,7 @@ inline void ZRelocationSetSelector::clear_empty_pages() { inline size_t ZRelocationSetSelector::total() const { size_t sum = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { sum += _small.stats(age).total() + _medium.stats(age).total() + _large.stats(age).total(); } return sum; @@ -197,8 +197,7 @@ inline size_t ZRelocationSetSelector::total() const { inline size_t ZRelocationSetSelector::empty() const { size_t sum = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { sum += _small.stats(age).empty() + _medium.stats(age).empty() + _large.stats(age).empty(); } return sum; @@ -206,8 +205,7 @@ inline size_t ZRelocationSetSelector::empty() const { inline size_t ZRelocationSetSelector::relocate() const { size_t sum = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { sum += _small.stats(age).relocate() + _medium.stats(age).relocate() + _large.stats(age).relocate(); } return sum; diff --git a/src/hotspot/share/gc/z/zStat.cpp b/src/hotspot/share/gc/z/zStat.cpp index ec751cbb693..bfde904c1e7 100644 --- a/src/hotspot/share/gc/z/zStat.cpp +++ b/src/hotspot/share/gc/z/zStat.cpp @@ -30,6 +30,7 @@ #include "gc/z/zGeneration.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zNMethodTable.hpp" +#include "gc/z/zPageAge.inline.hpp" #include "gc/z/zPageAllocator.inline.hpp" #include "gc/z/zRelocationSetSelector.inline.hpp" #include "gc/z/zStat.hpp" @@ -1499,9 +1500,7 @@ void ZStatRelocation::print_page_summary() { summary.relocate += stats.relocate(); }; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); - + for (ZPageAge age : ZPageAgeRange()) { account_page_size(small_summary, _selector_stats.small(age)); account_page_size(medium_summary, _selector_stats.medium(age)); account_page_size(large_summary, _selector_stats.large(age)); @@ -1557,13 +1556,13 @@ void ZStatRelocation::print_age_table() { .center("Large") .end()); - size_t live[ZPageAgeMax + 1] = {}; - size_t total[ZPageAgeMax + 1] = {}; + size_t live[ZPageAgeCount] = {}; + size_t total[ZPageAgeCount] = {}; uint oldest_none_empty_age = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { + uint i = untype(age); auto summarize_pages = [&](const ZRelocationSetSelectorGroupStats& stats) { live[i] += stats.live(); total[i] += stats.total(); @@ -1579,7 +1578,7 @@ void ZStatRelocation::print_age_table() { } for (uint i = 0; i <= oldest_none_empty_age; ++i) { - ZPageAge age = static_cast(i); + ZPageAge age = to_zpageage(i); FormatBuffer<> age_str(""); if (age == ZPageAge::eden) { @@ -1791,8 +1790,7 @@ void ZStatHeap::at_select_relocation_set(const ZRelocationSetSelectorStats& stat ZLocker locker(&_stat_lock); size_t live = 0; - for (uint i = 0; i <= ZPageAgeMax; ++i) { - const ZPageAge age = static_cast(i); + for (ZPageAge age : ZPageAgeRange()) { live += stats.small(age).live() + stats.medium(age).live() + stats.large(age).live(); } _at_mark_end.live = live; diff --git a/src/hotspot/share/gc/z/z_globals.hpp b/src/hotspot/share/gc/z/z_globals.hpp index e1f525e5372..d818cf9fc3f 100644 --- a/src/hotspot/share/gc/z/z_globals.hpp +++ b/src/hotspot/share/gc/z/z_globals.hpp @@ -113,7 +113,7 @@ \ product(int, ZTenuringThreshold, -1, DIAGNOSTIC, \ "Young generation tenuring threshold, -1 for dynamic computation")\ - range(-1, static_cast(ZPageAgeMax)) \ + range(-1, static_cast(ZPageAgeCount) - 1) \ \ develop(bool, ZVerifyOops, false, \ "Verify accessed oops") \ diff --git a/src/hotspot/share/utilities/enumIterator.hpp b/src/hotspot/share/utilities/enumIterator.hpp index 6fa7f071f4a..42cc405fcd7 100644 --- a/src/hotspot/share/utilities/enumIterator.hpp +++ b/src/hotspot/share/utilities/enumIterator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -25,13 +25,14 @@ #ifndef SHARE_UTILITIES_ENUMITERATOR_HPP #define SHARE_UTILITIES_ENUMITERATOR_HPP -#include -#include #include "memory/allStatic.hpp" #include "metaprogramming/enableIf.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "utilities/debug.hpp" +#include +#include + // Iteration support for enums. // // E is enum type, U is underlying type of E. @@ -147,6 +148,12 @@ public: assert(value <= end, "out of range"); } + template + static constexpr void assert_in_range() { + static_assert(_start <= static_cast(Value), "out of range"); + static_assert(static_cast(Value) <= _end, "out of range"); + } + // Convert an enumerator value to the corresponding underlying type. static constexpr Underlying underlying_value(T value) { return static_cast(value); @@ -229,6 +236,12 @@ class EnumRange { assert(size() > 0, "empty range"); } + struct ConstExprConstructTag {}; + + constexpr EnumRange(T start, T end, ConstExprConstructTag) : + _start(Traits::underlying_value(start)), + _end(Traits::underlying_value(end)) {} + public: using EnumType = T; using Iterator = EnumIterator; @@ -252,6 +265,14 @@ public: assert(start <= end, "invalid range"); } + template + static constexpr EnumRange create() { + Traits::template assert_in_range(); + Traits::template assert_in_range(); + static_assert(Start <= End, "invalid range"); + return EnumRange(Start, End, ConstExprConstructTag{}); + } + // Return an iterator for the start of the range. constexpr Iterator begin() const { return Iterator(Traits::enumerator(_start)); diff --git a/test/hotspot/gtest/gc/z/test_zPageAge.cpp b/test/hotspot/gtest/gc/z/test_zPageAge.cpp new file mode 100644 index 00000000000..1c93a380db7 --- /dev/null +++ b/test/hotspot/gtest/gc/z/test_zPageAge.cpp @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#include "gc/z/zPageAge.hpp" +#include "unittest.hpp" + +TEST(ZPageAgeRangeTest, test) { + ZPageAgeRange rangeEden = ZPageAgeRangeEden; + EXPECT_EQ(rangeEden.first(), ZPageAge::eden); + EXPECT_EQ(rangeEden.last(), ZPageAge::eden); + + ZPageAgeRange rangeYoung = ZPageAgeRangeYoung; + EXPECT_EQ(rangeYoung.first(), ZPageAge::eden); + EXPECT_EQ(rangeYoung.last(), ZPageAge::survivor14); + + ZPageAgeRange rangeSurvivor = ZPageAgeRangeSurvivor; + EXPECT_EQ(rangeSurvivor.first(), ZPageAge::survivor1); + EXPECT_EQ(rangeSurvivor.last(), ZPageAge::survivor14); + + ZPageAgeRange rangeRelocation = ZPageAgeRangeRelocation; + EXPECT_EQ(rangeRelocation.first(), ZPageAge::survivor1); + EXPECT_EQ(rangeRelocation.last(), ZPageAge::old); + + ZPageAgeRange rangeOld = ZPageAgeRangeOld; + EXPECT_EQ(rangeOld.first(), ZPageAge::old); + EXPECT_EQ(rangeOld.last(), ZPageAge::old); +} From 2103dc15cb662fd8795b1b51d9cb61c389bed7a0 Mon Sep 17 00:00:00 2001 From: Alexander Zvegintsev Date: Mon, 9 Jun 2025 13:35:01 +0000 Subject: [PATCH 097/143] 8358452: JNI exception pending in Java_sun_awt_screencast_ScreencastHelper_remoteDesktopKeyImpl of screencast_pipewire.c:1214 (ID: 51119) Reviewed-by: psadhukhan, serb, aivanov, avu --- .../unix/native/libawt_xawt/awt/screencast_pipewire.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c index ec12445c87b..ea921d3f636 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c @@ -1016,6 +1016,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl const gchar *token = jtoken ? (*env)->GetStringUTFChars(env, jtoken, NULL) : NULL; + JNU_CHECK_EXCEPTION_RETURN(env, RESULT_ERROR); isGtkMainThread = gtk->g_main_context_is_owner(gtk->g_main_context_default()); DEBUG_SCREENCAST( @@ -1121,7 +1122,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMou const gchar *token = jtoken ? (*env)->GetStringUTFChars(env, jtoken, NULL) : NULL; - + JNU_CHECK_EXCEPTION_RETURN(env, RESULT_ERROR); DEBUG_SCREENCAST("moving mouse to\n\t%d %d\n\twith token |%s|\n", jx, jy, token); @@ -1151,6 +1152,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMou const gchar *token = jtoken ? (*env)->GetStringUTFChars(env, jtoken, NULL) : NULL; + JNU_CHECK_EXCEPTION_RETURN(env, RESULT_ERROR); gboolean result = initPortal(token, NULL, 0); DEBUG_SCREENCAST("init result %b, mouse pressing %d\n", result, buttons) @@ -1178,6 +1180,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopMou const gchar *token = jtoken ? (*env)->GetStringUTFChars(env, jtoken, NULL) : NULL; + JNU_CHECK_EXCEPTION_RETURN(env, RESULT_ERROR); gboolean result = initPortal(token, NULL, 0); DEBUG_SCREENCAST("init result %b, mouse wheel %d\n", result, jWheelAmt) @@ -1206,13 +1209,14 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_remoteDesktopKey int key = awt_getX11KeySym(jkey); AWT_UNLOCK(); - if (key == NoSymbol) { + if (key == NoSymbol || (*env)->ExceptionCheck(env)) { return RESULT_ERROR; } const gchar *token = jtoken ? (*env)->GetStringUTFChars(env, jtoken, NULL) : NULL; + JNU_CHECK_EXCEPTION_RETURN(env, RESULT_ERROR); gboolean result = initPortal(token, NULL, 0); DEBUG_SCREENCAST("init result %b, key %d -> %d isPress %b\n", result, jkey, key, isPress) From eb8ee8bdc7c170910abc9aa18de1e22677160358 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 9 Jun 2025 16:01:18 +0000 Subject: [PATCH 098/143] 8358731: Remove jdk.internal.access.JavaAWTAccess.java Reviewed-by: dfuchs, serb --- .../jdk/internal/access/JavaAWTAccess.java | 36 -- .../jdk/internal/access/SharedSecrets.java | 11 - .../share/classes/sun/awt/AppContext.java | 63 --- .../logging/LogManagerAppContextDeadlock.java | 381 ------------- .../java/util/logging/TestGetLoggerNPE.java | 99 ---- .../util/logging/TestUILoggerContext.java | 520 ------------------ 6 files changed, 1110 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/access/JavaAWTAccess.java delete mode 100644 test/jdk/java/util/logging/LogManagerAppContextDeadlock.java delete mode 100644 test/jdk/java/util/logging/TestGetLoggerNPE.java delete mode 100644 test/jdk/java/util/logging/TestUILoggerContext.java diff --git a/src/java.base/share/classes/jdk/internal/access/JavaAWTAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaAWTAccess.java deleted file mode 100644 index a4c170ca918..00000000000 --- a/src/java.base/share/classes/jdk/internal/access/JavaAWTAccess.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2011, 2018, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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.internal.access; - -public interface JavaAWTAccess { - - // Returns the AppContext used for applet logging isolation, or null if - // no isolation is required. - // If there's no applet, or if the caller is a stand alone application, - // or running in the main app context, returns null. - // Otherwise, returns the AppContext of the calling applet. - public Object getAppletContext(); -} diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index bc955a76abb..5c6212d0bf6 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -59,7 +59,6 @@ import javax.security.auth.x500.X500Principal; */ public class SharedSecrets { - private static JavaAWTAccess javaAWTAccess; private static JavaAWTFontAccess javaAWTFontAccess; private static JavaBeansAccess javaBeansAccess; private static JavaLangAccess javaLangAccess; @@ -321,16 +320,6 @@ public class SharedSecrets { javaUtilZipFileAccess = access; } - public static void setJavaAWTAccess(JavaAWTAccess jaa) { - javaAWTAccess = jaa; - } - - public static JavaAWTAccess getJavaAWTAccess() { - // this may return null in which case calling code needs to - // provision for. - return javaAWTAccess; - } - public static void setJavaAWTFontAccess(JavaAWTFontAccess jafa) { javaAWTFontAccess = jafa; } diff --git a/src/java.desktop/share/classes/sun/awt/AppContext.java b/src/java.desktop/share/classes/sun/awt/AppContext.java index a35d89f6a0c..3b93f1d2755 100644 --- a/src/java.desktop/share/classes/sun/awt/AppContext.java +++ b/src/java.desktop/share/classes/sun/awt/AppContext.java @@ -42,8 +42,6 @@ import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeListener; import java.lang.ref.SoftReference; -import jdk.internal.access.JavaAWTAccess; -import jdk.internal.access.SharedSecrets; import sun.util.logging.PlatformLogger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -746,67 +744,6 @@ public final class AppContext { return changeSupport.getPropertyChangeListeners(propertyName); } - // Set up JavaAWTAccess in SharedSecrets - static { - SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() { - private boolean hasRootThreadGroup(final AppContext ecx) { - return ecx.threadGroup.getParent() == null; - } - - /** - * Returns the AppContext used for applet logging isolation, or null if - * the default global context can be used. - * If there's no applet, or if the caller is a stand alone application, - * or running in the main app context, returns null. - * Otherwise, returns the AppContext of the calling applet. - * @return null if the global default context can be used, - * an AppContext otherwise. - **/ - public Object getAppletContext() { - // There's no AppContext: return null. - // No need to call getAppContext() if numAppContext == 0: - // it means that no AppContext has been created yet, and - // we don't want to trigger the creation of a main app - // context since we don't need it. - if (numAppContexts.get() == 0) return null; - - AppContext ecx = null; - - // Not sure we really need to re-check numAppContexts here. - // If all applets have gone away then we could have a - // numAppContexts coming back to 0. So we recheck - // it here because we don't want to trigger the - // creation of a main AppContext in that case. - // This is probably not 100% MT-safe but should reduce - // the window of opportunity in which that issue could - // happen. - if (numAppContexts.get() > 0) { - // Defaults to thread group caching. - // This is probably not required as we only really need - // isolation in a deployed applet environment, in which - // case ecx will not be null when we reach here - // However it helps emulate the deployed environment, - // in tests for instance. - ecx = ecx != null ? ecx : getAppContext(); - } - - // getAppletContext() may be called when initializing the main - // app context - in which case mainAppContext will still be - // null. To work around this issue we simply use - // AppContext.threadGroup.getParent() == null instead, since - // mainAppContext is the only AppContext which should have - // the root TG as its thread group. - // See: JDK-8023258 - final boolean isMainAppContext = ecx == null - || mainAppContext == ecx - || mainAppContext == null && hasRootThreadGroup(ecx); - - return isMainAppContext ? null : ecx; - } - - }); - } - public static T getSoftReferenceValue(Object key, Supplier supplier) { diff --git a/test/jdk/java/util/logging/LogManagerAppContextDeadlock.java b/test/jdk/java/util/logging/LogManagerAppContextDeadlock.java deleted file mode 100644 index f39b46b43d1..00000000000 --- a/test/jdk/java/util/logging/LogManagerAppContextDeadlock.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (c) 2013, 2022, 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. - */ - -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.util.Enumeration; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import jdk.internal.access.JavaAWTAccess; -import jdk.internal.access.SharedSecrets; - -/** - * @test - * @bug 8065991 - * @summary check that when LogManager is initialized, a deadlock similar - * to that described in 8065709 will not occur. - * @modules java.base/jdk.internal.access - * java.logging - * java.management - * @run main/othervm LogManagerAppContextDeadlock UNSECURE - * @run main/othervm LogManagerAppContextDeadlock SECURE - * - * @author danielfuchs - */ -public class LogManagerAppContextDeadlock { - - public static final Semaphore sem = new Semaphore(0); - public static final Semaphore sem2 = new Semaphore(0); - public static final Semaphore sem3 = new Semaphore(-2); - public static volatile boolean goOn = true; - public static volatile Exception thrown; - - // Emulate EventQueue - static class FakeEventQueue { - static final Logger logger = Logger.getLogger("foo"); - } - - // Emulate AppContext - static class FakeAppContext { - - static final AtomicInteger numAppContexts = new AtomicInteger(0); - static final class FakeAppContextLock {} - static final FakeAppContextLock lock = new FakeAppContextLock(); - static volatile FakeAppContext appContext; - - final FakeEventQueue queue; - FakeAppContext() { - appContext = this; - numAppContexts.incrementAndGet(); - // release sem2 to let Thread t2 call Logger.getLogger(). - sem2.release(); - try { - // Wait until we JavaAWTAccess is called by LogManager. - // Thread 2 will call Logger.getLogger() which will - // trigger a call to JavaAWTAccess - which will release - // sem, thus ensuring that Thread #2 is where we want it. - sem.acquire(); - System.out.println("Sem acquired: Thread #2 has called JavaAWTAccess"); - } catch(InterruptedException x) { - Thread.interrupted(); - } - queue = new FakeEventQueue(); - } - - static FakeAppContext getAppContext() { - synchronized (lock) { - if (numAppContexts.get() == 0) { - return new FakeAppContext(); - } - return appContext; - } - } - - static { - SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() { - @Override - public Object getAppletContext() { - if (numAppContexts.get() == 0) return null; - // We are in JavaAWTAccess, we can release sem and let - // FakeAppContext constructor proceeed. - System.out.println("Releasing Sem"); - sem.release(); - return getAppContext(); - } - - }); - } - - } - - - // Test with or without a security manager - public static enum TestCase { - UNSECURE, SECURE; - public void run() throws Exception { - System.out.println("Running test case: " + name()); - Configure.setUp(this); - test(this); - } - } - - public static void test(TestCase test) throws Exception { - Thread t1 = new Thread() { - @Override - public void run() { - sem3.release(); - System.out.println("FakeAppContext.getAppContext()"); - FakeAppContext.getAppContext(); - System.out.println("Done: FakeAppContext.getAppContext()"); - } - }; - t1.setDaemon(true); - t1.start(); - Thread t2 = new Thread() { - public Object logger; - public void run() { - sem3.release(); - try { - // Wait until Thread1 is in FakeAppContext constructor - sem2.acquire(); - System.out.println("Sem2 acquired: Thread #1 will be waiting to acquire Sem"); - } catch (InterruptedException ie) { - Thread.interrupted(); - } - System.out.println("Logger.getLogger(name).info(name)"); - // stick the logger in an instance variable to prevent it - // from being garbage collected before the main thread - // calls LogManager.getLogger() below. - logger = Logger.getLogger(test.name());//.info(name); - System.out.println("Done: Logger.getLogger(name).info(name)"); - } - }; - t2.setDaemon(true); - t2.start(); - System.out.println("Should exit now..."); - Thread detector = new DeadlockDetector(); - detector.start(); - - // Wait for the 3 threads to start - sem3.acquire(); - - // Now wait for t1 & t2 to finish, or for a deadlock to be detected. - while (goOn && (t1.isAlive() || t2.isAlive())) { - if (t2.isAlive()) t2.join(1000); - if (test == TestCase.UNSECURE && System.getSecurityManager() == null) { - // if there's no security manager, AppContext.getAppContext() is - // not called - so Thread t2 will not end up calling - // sem.release(). In that case we must release the semaphore here - // so that t1 can proceed. - if (LogManager.getLogManager().getLogger(TestCase.UNSECURE.name()) != null) { - // means Thread t2 has created the logger - sem.release(); - } - } - if (t1.isAlive()) t1.join(1000); - } - if (thrown != null) { - throw thrown; - } - } - - // Thrown by the deadlock detector - static final class DeadlockException extends RuntimeException { - public DeadlockException(String message) { - super(message); - } - @Override - public void printStackTrace() { - } - } - - public static void main(String[] args) throws Exception { - - if (args.length == 0) { - args = new String[] { "SECURE" }; - } - - // If we don't initialize LogManager here, there will be - // a deadlock. - // See - // for more details. - Logger.getLogger("main").info("starting..."); - try { - TestCase.valueOf(args[0]).run(); - System.out.println("Test "+args[0]+" Passed"); - } catch(Throwable t) { - System.err.println("Test " + args[0] +" failed: " + t); - t.printStackTrace(); - } - } - - // Called by the deadlock detector when a deadlock is found. - static void fail(Exception x) { - x.printStackTrace(); - if (thrown == null) { - thrown = x; - } - goOn = false; - } - - // A thread that detect deadlocks. - static final class DeadlockDetector extends Thread { - - public DeadlockDetector() { - this.setDaemon(true); - } - - @Override - public void run() { - sem3.release(); - Configure.doPrivileged(this::loop); - } - public void loop() { - while(goOn) { - try { - long[] ids = ManagementFactory.getThreadMXBean().findDeadlockedThreads(); - ids = ids == null ? new long[0] : ids; - if (ids.length == 1) { - throw new RuntimeException("Found 1 deadlocked thread: "+ids[0]); - } else if (ids.length > 0) { - ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, Integer.MAX_VALUE); - System.err.println("Found "+ids.length+" deadlocked threads: "); - for (ThreadInfo inf : infos) { - System.err.println(inf); - } - throw new DeadlockException("Found "+ids.length+" deadlocked threads"); - } - Thread.sleep(100); - } catch(InterruptedException | RuntimeException x) { - fail(x); - } - } - } - - } - - // A helper class to configure the security manager for the test, - // and bypass it when needed. - static class Configure { - static Policy policy = null; - static final ThreadLocal allowAll = new ThreadLocal() { - @Override - protected AtomicBoolean initialValue() { - return new AtomicBoolean(false); - } - }; - static void setUp(TestCase test) { - switch (test) { - case SECURE: - if (policy == null && System.getSecurityManager() != null) { - throw new IllegalStateException("SecurityManager already set"); - } else if (policy == null) { - policy = new SimplePolicy(TestCase.SECURE, allowAll); - Policy.setPolicy(policy); - System.setSecurityManager(new SecurityManager()); - } - if (System.getSecurityManager() == null) { - throw new IllegalStateException("No SecurityManager."); - } - if (policy == null) { - throw new IllegalStateException("policy not configured"); - } - break; - case UNSECURE: - if (System.getSecurityManager() != null) { - throw new IllegalStateException("SecurityManager already set"); - } - break; - default: - new InternalError("No such testcase: " + test); - } - } - static void doPrivileged(Runnable run) { - allowAll.get().set(true); - try { - run.run(); - } finally { - allowAll.get().set(false); - } - } - } - - // A Helper class to build a set of permissions. - static final class PermissionsBuilder { - final Permissions perms; - public PermissionsBuilder() { - this(new Permissions()); - } - public PermissionsBuilder(Permissions perms) { - this.perms = perms; - } - public PermissionsBuilder add(Permission p) { - perms.add(p); - return this; - } - public PermissionsBuilder addAll(PermissionCollection col) { - if (col != null) { - for (Enumeration e = col.elements(); e.hasMoreElements(); ) { - perms.add(e.nextElement()); - } - } - return this; - } - public Permissions toPermissions() { - final PermissionsBuilder builder = new PermissionsBuilder(); - builder.addAll(perms); - return builder.perms; - } - } - - // Policy for the test... - public static class SimplePolicy extends Policy { - - static final Policy DEFAULT_POLICY = Policy.getPolicy(); - - final Permissions permissions; - final Permissions allPermissions; - final ThreadLocal allowAll; // actually: this should be in a thread locale - public SimplePolicy(TestCase test, ThreadLocal allowAll) { - this.allowAll = allowAll; - // we don't actually need any permission to create our - // FileHandlers because we're passing invalid parameters - // which will make the creation fail... - permissions = new Permissions(); - permissions.add(new RuntimePermission("accessClassInPackage.jdk.internal.access")); - - // these are used for configuring the test itself... - allPermissions = new Permissions(); - allPermissions.add(new java.security.AllPermission()); - - } - - @Override - public boolean implies(ProtectionDomain domain, Permission permission) { - if (allowAll.get().get()) return allPermissions.implies(permission); - return permissions.implies(permission) || DEFAULT_POLICY.implies(domain, permission); - } - - @Override - public PermissionCollection getPermissions(CodeSource codesource) { - return new PermissionsBuilder().addAll(allowAll.get().get() - ? allPermissions : permissions).toPermissions(); - } - - @Override - public PermissionCollection getPermissions(ProtectionDomain domain) { - return new PermissionsBuilder().addAll(allowAll.get().get() - ? allPermissions : permissions).toPermissions(); - } - } - -} diff --git a/test/jdk/java/util/logging/TestGetLoggerNPE.java b/test/jdk/java/util/logging/TestGetLoggerNPE.java deleted file mode 100644 index 02c57b75067..00000000000 --- a/test/jdk/java/util/logging/TestGetLoggerNPE.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2013, 2024, 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. - */ -import java.io.PrintStream; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import jdk.internal.access.JavaAWTAccess; -import jdk.internal.access.SharedSecrets; - -/* - * @test - * @bug 8025512 - * - * @summary NPE with logging while launching webstart - * - * @modules java.base/jdk.internal.access - * java.logging - * @build TestGetLoggerNPE - * @run main/othervm TestGetLoggerNPE getLogger - * @run main/othervm TestGetLoggerNPE getLogManager - */ -public class TestGetLoggerNPE { - static volatile Throwable thrown = null; - public static void main(String[] args) throws Exception { - final String testCase = args.length == 0 ? "getLogger" : args[0]; - final JavaAWTAccessStub access = new JavaAWTAccessStub(); - SharedSecrets.setJavaAWTAccess(access); - final ThreadGroup tg = new ThreadGroup("TestGroup"); - Thread t = new Thread(tg, "test") { - public void run() { - try { - access.setContext(Context.ONE); - final PrintStream out = System.out; - System.setOut(null); - try { - if ("getLogger".equals(testCase)) { - Logger.getLogger("sun.plugin"); - } else { - LogManager.getLogManager(); - } - } finally { - System.setOut(out); - } - - System.out.println(Logger.global); - } catch (Throwable x) { - x.printStackTrace(); - thrown = x; - } - } - }; - t.start(); - t.join(); - if (thrown == null) { - System.out.println("PASSED: " + testCase); - } else { - System.err.println("FAILED: " + testCase); - throw new Error("Test failed: " + testCase + " - " + thrown, thrown); - } - - } - - static enum Context { ONE, TWO }; - - static final class JavaAWTAccessStub implements JavaAWTAccess { - private static final InheritableThreadLocal context = new InheritableThreadLocal<>(); - - - public void setContext(Context context) { - JavaAWTAccessStub.context.set(context); - } - - @Override - public Object getAppletContext() { - return context.get(); - } - - } - -} diff --git a/test/jdk/java/util/logging/TestUILoggerContext.java b/test/jdk/java/util/logging/TestUILoggerContext.java deleted file mode 100644 index d5e170a1100..00000000000 --- a/test/jdk/java/util/logging/TestUILoggerContext.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright (c) 2013, 2024, 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. - */ - -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import jdk.internal.access.JavaAWTAccess; -import jdk.internal.access.SharedSecrets; - -/* - * @test - * @bug 8017174 8010727 8019945 - * @summary NPE when using Logger.getAnonymousLogger or - * LogManager.getLogManager().getLogger - * - * @modules java.base/jdk.internal.access - * java.logging - * @run main/othervm TestUILoggerContext LoadingUIContext - * @run main/othervm TestUILoggerContext LoadingMain - * @run main/othervm TestUILoggerContext One - * @run main/othervm TestUILoggerContext Two - * @run main/othervm TestUILoggerContext Three - * @run main/othervm TestUILoggerContext Four - * @run main/othervm TestUILoggerContext Five - * @run main/othervm TestUILoggerContext Six - * @run main/othervm TestUILoggerContext Seven - * @run main/othervm TestUILoggerContext - */ - -// NOTE: We run in other VM in order to cause LogManager class to be loaded anew. -public class TestUILoggerContext { - - // The bridge class initializes the logging system. - // It stubs the UI context in order to simulate context changes. - // - public static class Bridge { - - private static class JavaAWTAccessStub implements JavaAWTAccess { - boolean active = true; - - private static class TestExc { - private final Map map = new HashMap<>(); - void put(Object key, Object v) { map.put(key, v); } - Object get(Object key) { return map.get(key); } - void remove(Object o) { map.remove(o); } - public static TestExc exc(Object o) { - return TestExc.class.cast(o); - } - } - - TestExc exc; - - @Override - public Object getAppletContext() { return active ? exc : null; } - } - - static final JavaAWTAccessStub javaAwtAccess = new JavaAWTAccessStub(); - public static void init() { - SharedSecrets.setJavaAWTAccess(javaAwtAccess); - } - - public static void changeContext() { - System.out.println("... Switching to a new UI context ..."); - javaAwtAccess.active = true; - javaAwtAccess.exc = new JavaAWTAccessStub.TestExc(); - } - - public static void desactivate() { - System.out.println("... Running with no UI context ..."); - javaAwtAccess.exc = null; - javaAwtAccess.active = false; - } - - public static class CustomAnonymousLogger extends Logger { - public CustomAnonymousLogger() { - this(""); - } - public CustomAnonymousLogger(String name) { - super(null, null); - System.out.println( " LogManager: " +LogManager.getLogManager()); - System.out.println( " getLogger: " +LogManager.getLogManager().getLogger(name)); - setParent(LogManager.getLogManager().getLogger(name)); - } - } - - public static class CustomLogger extends Logger { - CustomLogger(String name) { - super(name, null); - } - } - } - - public static enum TestCase { - LoadingUIContext, LoadingMain, One, Two, Three, Four, Five, Six, Seven; - public void test() { - switch(this) { - // When run - each of these two tests must be - // run before any other tests and before each other. - case LoadingUIContext: testLoadingUIContext(); break; - case LoadingMain: testLoadingMain(); break; - case One: testOne(); break; - case Two: testTwo(); break; - case Three: testThree(); break; - case Four: testFour(); break; - case Five: testFive(); break; - case Six: testSix(); break; - case Seven: testSeven(); break; - } - } - public String describe() { - switch(this) { - case LoadingUIContext: - return "Test that when the LogManager class is" - + " loaded with UI context first," - + "\n all LoggerContexts are correctly initialized"; - case LoadingMain: - return "Test that when the LogManager class is" - + " loaded in the main thread first," - + "\n all LoggerContexts are correctly initialized"; - case One: - return "Test that Logger.getAnonymousLogger()" - + " and new CustomAnonymousLogger() don't throw NPE"; - case Two: - return "Test that Logger.getLogger(\"\")" - + " does not return null nor throws NPE"; - case Three: - return "Test that LogManager.getLogManager().getLogger(\"\")" - + " does not return null nor throws NPE"; - case Four: - return "Test that Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)" - + " does not return null,\n and that" - + " new CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME)" - + " does not throw NPE"; - case Five: - return "Test that LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME)" - + "\n does not return null nor throws NPE"; - case Six: - return "Test that manager.getLogger(Logger.GLOBAL_LOGGER_NAME)" - + " returns null\n when manager is not the default" - + " LogManager instance.\n" - + "Test adding a new logger named \"global\" in that" - + " non default instance."; - case Seven: return "Test that manager.getLogger(\"\")" - + " returns null\n when manager is not the default" - + " LogManager instance.\n" - + "Test adding a new logger named \"\" in that" - + " non default instance."; - default: return "Undefined"; - } - } - }; - - /** - * @param args the command line arguments - */ - public static void main(String[] args) { - Bridge.init(); - EnumSet tests = EnumSet.noneOf(TestCase.class); - for (String arg : args) { - tests.add(TestCase.valueOf(arg)); - } - if (args.length == 0) { - tests = EnumSet.complementOf(EnumSet.of(TestCase.LoadingMain)); - } - final EnumSet loadingTests = - EnumSet.of(TestCase.LoadingUIContext, TestCase.LoadingMain); - int testrun = 0; - for (TestCase test : tests) { - if (loadingTests.contains(test)) { - if (testrun > 0) { - throw new UnsupportedOperationException("Test case " - + test + " must be executed first!"); - } - } - System.out.println("Testing "+ test+": "); - System.out.println(test.describe()); - try { - test.test(); - } catch (Exception x) { - throw new Error(String.valueOf(test) - + "failed: "+x+"\n "+"FAILED: "+test.describe()+"\n", x); - } finally { - testrun++; - } - Bridge.changeContext(); - System.out.println("PASSED: "+ test); - } - } - - public static void testLoadingUIContext() { - Bridge.changeContext(); - - Logger bar = new Bridge.CustomLogger("com.foo.Bar"); - LogManager.getLogManager().addLogger(bar); - assertNotNull(bar.getParent()); - testParent(bar); - testParent(LogManager.getLogManager().getLogger("global")); - testParent(LogManager.getLogManager().getLogger(bar.getName())); - - Bridge.desactivate(); - - Logger foo = new Bridge.CustomLogger("com.foo.Foo"); - boolean b = LogManager.getLogManager().addLogger(foo); - assertEquals(Boolean.TRUE, Boolean.valueOf(b)); - assertNotNull(foo.getParent()); - testParent(foo); - testParent(LogManager.getLogManager().getLogger("global")); - testParent(LogManager.getLogManager().getLogger(foo.getName())); - } - - public static void testLoadingMain() { - Bridge.desactivate(); - - Logger bar = new Bridge.CustomLogger("com.foo.Bar"); - LogManager.getLogManager().addLogger(bar); - assertNotNull(bar.getParent()); - testParent(bar); - testParent(LogManager.getLogManager().getLogger("global")); - testParent(LogManager.getLogManager().getLogger(bar.getName())); - - Bridge.changeContext(); - - Logger foo = new Bridge.CustomLogger("com.foo.Foo"); - boolean b = LogManager.getLogManager().addLogger(foo); - assertEquals(Boolean.TRUE, Boolean.valueOf(b)); - assertNotNull(foo.getParent()); - testParent(foo); - testParent(LogManager.getLogManager().getLogger("global")); - testParent(LogManager.getLogManager().getLogger(foo.getName())); - - } - - public static void testOne() { - for (int i=0; i<3 ; i++) { - Logger logger1 = Logger.getAnonymousLogger(); - Logger logger1b = Logger.getAnonymousLogger(); - Bridge.changeContext(); - Logger logger2 = Logger.getAnonymousLogger(); - Logger logger2b = Logger.getAnonymousLogger(); - Bridge.changeContext(); - Logger logger3 = new Bridge.CustomAnonymousLogger(); - Logger logger3b = new Bridge.CustomAnonymousLogger(); - Bridge.changeContext(); - Logger logger4 = new Bridge.CustomAnonymousLogger(); - Logger logger4b = new Bridge.CustomAnonymousLogger(); - } - } - - - public static void testTwo() { - for (int i=0; i<3 ; i++) { - Logger logger1 = Logger.getLogger(""); - Logger logger1b = Logger.getLogger(""); - assertNotNull(logger1); - assertNotNull(logger1b); - assertEquals(logger1, logger1b); - Bridge.changeContext(); - Logger logger2 = Logger.getLogger(""); - Logger logger2b = Logger.getLogger(""); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, logger2b); - assertEquals(logger1, logger2); - } - } - - public static void testThree() { - for (int i=0; i<3 ; i++) { - Logger logger1 = LogManager.getLogManager().getLogger(""); - Logger logger1b = LogManager.getLogManager().getLogger(""); - assertNotNull(logger1); - assertNotNull(logger1b); - assertEquals(logger1, logger1b); - Bridge.changeContext(); - Logger logger2 = LogManager.getLogManager().getLogger(""); - Logger logger2b = LogManager.getLogManager().getLogger(""); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, logger2b); - assertEquals(logger1, logger2); - } - } - - public static void testFour() { - for (int i=0; i<3 ; i++) { - Logger logger1 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger1b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger1); - assertNotNull(logger1b); - assertEquals(logger1, logger1b); - Bridge.changeContext(); - - Logger logger2 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger2b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, logger2b); - - assertEquals(logger1, logger2); - - Bridge.changeContext(); - Logger logger3 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger3b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); - Bridge.changeContext(); - Logger logger4 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger4b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); - } - } - - public static void testFive() { - for (int i=0; i<3 ; i++) { - Logger logger1 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger1b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger1); - assertNotNull(logger1b); - assertEquals(logger1, logger1b); - - Bridge.changeContext(); - - Logger logger2 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger2b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, logger2b); - - assertEquals(logger1, logger2); - } - } - - /** - * This test is designed to test the behavior of additional LogManager instances. - * It must be noted that if the security manager is off, then calling - * Bridge.changeContext() has actually no effect. - * off. - **/ - public static void testSix() { - for (int i=0; i<3 ; i++) { - Bridge.desactivate(); - LogManager manager = new LogManager() {}; - Logger logger1 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger1b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNull(logger1); - assertNull(logger1b); - Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global); - Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, global); - assertEquals(logger2b, global); - assertNull(manager.getLogger("")); - assertNull(manager.getLogger("")); - - for (int j = 0; j<3; j++) { - Bridge.changeContext(); - - // this is not a supported configuration: - // We are in an UI context with several log managers. - // We however need to check our assumptions... - - // UI context => root logger and global logger should also be null. - - Logger expected = global; - Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertEquals(expected, logger3); - assertEquals(expected, logger3b); - Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global2); - Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger4); - assertNotNull(logger4b); - expected = global; - assertEquals(logger4, expected); - assertEquals(logger4b, expected); - - Logger logger5 = manager.getLogger(""); - Logger logger5b = manager.getLogger(""); - Logger expectedRoot = null; - assertEquals(logger5, expectedRoot); - assertEquals(logger5b, expectedRoot); - } - - } - } - - /** - * This test is designed to test the behavior of additional LogManager instances. - * It must be noted that if the security manager is off, then calling - * Bridge.changeContext() has actually no effect. - **/ - public static void testSeven() { - for (int i=0; i<3 ; i++) { - Bridge.desactivate(); - LogManager manager = new LogManager() {}; - Logger logger1 = manager.getLogger(""); - Logger logger1b = manager.getLogger(""); - assertNull(logger1); - assertNull(logger1b); - Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global); - Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - assertNotNull(logger2); - assertNotNull(logger2b); - assertEquals(logger2, global); - assertEquals(logger2b, global); - Logger logger3 = manager.getLogger(""); - Logger logger3b = manager.getLogger(""); - assertNull(logger3); - assertNull(logger3b); - Logger root = new Bridge.CustomLogger(""); - manager.addLogger(root); - Logger logger4 = manager.getLogger(""); - Logger logger4b = manager.getLogger(""); - assertNotNull(logger4); - assertNotNull(logger4b); - assertEquals(logger4, root); - assertEquals(logger4b, root); - - for (int j = 0 ; j < 3 ; j++) { - Bridge.changeContext(); - - // this is not a supported configuration: - // We are in an UI context with several log managers. - // We however need to check our assumptions... - - // UI context => root logger and global logger should also be null. - - Logger logger5 = manager.getLogger(""); - Logger logger5b = manager.getLogger(""); - Logger expectedRoot = root; - assertEquals(logger5, expectedRoot); - assertEquals(logger5b, expectedRoot); - - assertEquals(global, manager.getLogger(Logger.GLOBAL_LOGGER_NAME)); - - Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); - manager.addLogger(global2); - Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); - Logger expectedGlobal = global; - - assertNotNull(logger6); - assertNotNull(logger6b); - assertEquals(logger6, expectedGlobal); - assertEquals(logger6b, expectedGlobal); - assertEquals(root, manager.getLogger("")); - - Logger root2 = new Bridge.CustomLogger(""); - manager.addLogger(root2); - expectedRoot = root; - Logger logger7 = manager.getLogger(""); - Logger logger7b = manager.getLogger(""); - assertNotNull(logger7); - assertNotNull(logger7b); - assertEquals(logger7, expectedRoot); - assertEquals(logger7b, expectedRoot); - } - } - } - - public static void testParent(Logger logger) { - Logger l = logger; - while (l.getParent() != null) { - l = l.getParent(); - } - assertEquals("", l.getName()); - } - - public static class TestError extends RuntimeException { - public TestError(String msg) { - super(msg); - } - } - - public static void assertNotNull(Object obj) { - if (obj == null) throw new NullPointerException(); - } - - public static void assertNull(Object obj) { - if (obj != null) throw new TestError("Null expected, got "+obj); - } - - public static void assertEquals(Object o1, Object o2) { - if (o1 != o2) { - throw new TestError(o1 + " != " + o2); - } - } - - public static void assertNotEquals(Object o1, Object o2) { - if (o1 == o2) { - throw new TestError(o1 + " == " + o2); - } - } -} From cae1fd3385e0635beeac34a2287627e556155783 Mon Sep 17 00:00:00 2001 From: Jiangli Zhou Date: Mon, 9 Jun 2025 16:08:18 +0000 Subject: [PATCH 099/143] 8357632: CDS test failures on static JDK Reviewed-by: ccheung, dholmes --- src/hotspot/share/cds/cdsConfig.cpp | 22 ++++++++++++++----- test/hotspot/jtreg/ProblemList-StaticJdk.txt | 9 -------- .../runtime/cds/NonJVMVariantLocation.java | 1 + 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index b3c13b63aff..b93238eca88 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -110,12 +110,24 @@ const char* CDSConfig::default_archive_path() { // before CDSConfig::ergo_initialize() is called. assert(_cds_ergo_initialize_started, "sanity"); if (_default_archive_path == nullptr) { - char jvm_path[JVM_MAXPATHLEN]; - os::jvm_path(jvm_path, sizeof(jvm_path)); - char *end = strrchr(jvm_path, *os::file_separator()); - if (end != nullptr) *end = '\0'; stringStream tmp; - tmp.print("%s%sclasses", jvm_path, os::file_separator()); + if (is_vm_statically_linked()) { + // It's easier to form the path using JAVA_HOME as os::jvm_path + // gives the path to the launcher executable on static JDK. + const char* subdir = WINDOWS_ONLY("bin") NOT_WINDOWS("lib"); + tmp.print("%s%s%s%s%s%sclasses", + Arguments::get_java_home(), os::file_separator(), + subdir, os::file_separator(), + Abstract_VM_Version::vm_variant(), os::file_separator()); + } else { + // Assume .jsa is in the same directory where libjvm resides on + // non-static JDK. + char jvm_path[JVM_MAXPATHLEN]; + os::jvm_path(jvm_path, sizeof(jvm_path)); + char *end = strrchr(jvm_path, *os::file_separator()); + if (end != nullptr) *end = '\0'; + tmp.print("%s%sclasses", jvm_path, os::file_separator()); + } #ifdef _LP64 if (!UseCompressedOops) { tmp.print_raw("_nocoops"); diff --git a/test/hotspot/jtreg/ProblemList-StaticJdk.txt b/test/hotspot/jtreg/ProblemList-StaticJdk.txt index c872a97b301..cb727e470f3 100644 --- a/test/hotspot/jtreg/ProblemList-StaticJdk.txt +++ b/test/hotspot/jtreg/ProblemList-StaticJdk.txt @@ -42,12 +42,3 @@ gtest/MetaspaceGtests.java#no-ccs 8356201 generic-all gtest/NMTGtests.java#nmt-detail 8356201 generic-all gtest/NMTGtests.java#nmt-off 8356201 generic-all gtest/NMTGtests.java#nmt-summary 8356201 generic-all - -# Need CDS archive -runtime/cds/NonJVMVariantLocation.java 8357632 generic-all -runtime/cds/TestCDSVMCrash.java 8357632 generic-all -runtime/cds/TestDefaultArchiveLoading.java#coops_coh 8357632 generic-all -runtime/cds/TestDefaultArchiveLoading.java#coops_nocoh 8357632 generic-all -runtime/cds/TestDefaultArchiveLoading.java#nocoops_coh 8357632 generic-all -runtime/cds/TestDefaultArchiveLoading.java#nocoops_nocoh 8357632 generic-all -serviceability/jvmti/RedefineClasses/RedefineSharedClass.java 8357632 generic-all diff --git a/test/hotspot/jtreg/runtime/cds/NonJVMVariantLocation.java b/test/hotspot/jtreg/runtime/cds/NonJVMVariantLocation.java index 81e931e450e..24772d620ad 100644 --- a/test/hotspot/jtreg/runtime/cds/NonJVMVariantLocation.java +++ b/test/hotspot/jtreg/runtime/cds/NonJVMVariantLocation.java @@ -26,6 +26,7 @@ * @test * @summary Test that CDS archive can be loaded if the archive is in a non-JVM variant directory. * @bug 8353504 + * @requires !jdk.static * @requires vm.cds * @requires vm.flagless * @requires vm.flavor == "server" From a377773fa76b46ac98533c61bc1410485390115e Mon Sep 17 00:00:00 2001 From: kieran-farrell Date: Mon, 9 Jun 2025 17:39:39 +0000 Subject: [PATCH 100/143] 8358617: java/net/HttpURLConnection/HttpURLConnectionExpectContinueTest.java fails with 403 due to system proxies Reviewed-by: dfuchs --- .../HttpURLConnection/HttpURLConnectionExpectContinueTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/net/HttpURLConnection/HttpURLConnectionExpectContinueTest.java b/test/jdk/java/net/HttpURLConnection/HttpURLConnectionExpectContinueTest.java index c9b8bc85b70..e701e234d83 100644 --- a/test/jdk/java/net/HttpURLConnection/HttpURLConnectionExpectContinueTest.java +++ b/test/jdk/java/net/HttpURLConnection/HttpURLConnectionExpectContinueTest.java @@ -429,7 +429,7 @@ public class HttpURLConnectionExpectContinueTest { .port(control.serverSocket.getLocalPort()) .toURL(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); connection.setDoOutput(true); connection.setReadTimeout(5000); connection.setUseCaches(false); From 156187accc1c3e2a897ced011727a5c8d8e1b0cf Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Mon, 9 Jun 2025 17:58:49 +0000 Subject: [PATCH 101/143] 8356978: Convert unicode sequences in Java source code to UTF-8 Co-authored-by: Alexey Ivanov Reviewed-by: naoto, prr, joehw --- src/demo/share/jfc/Stylepad/HelloWorld.java | 14 +++++--------- .../sun/text/resources/JavaTimeSupplementary.java | 14 +++++++------- .../sun/text/resources/ext/FormatData_ja.java | 2 +- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/demo/share/jfc/Stylepad/HelloWorld.java b/src/demo/share/jfc/Stylepad/HelloWorld.java index c4e7a8aee40..59ea1b45335 100644 --- a/src/demo/share/jfc/Stylepad/HelloWorld.java +++ b/src/demo/share/jfc/Stylepad/HelloWorld.java @@ -187,22 +187,18 @@ public class HelloWorld { new Run("none", "Hello from Cupertino") }), new Paragraph("title", new Run[] { - new Run("none", "\u53F0\u5317\u554F\u5019\u60A8\u0021") + new Run("none", "台北問候您!") }), new Paragraph("title", new Run[] { - new Run("none", "\u0391\u03B8\u03B7\u03BD\u03B1\u03B9\u0020" // Greek - + "\u03B1\u03C3\u03C0\u03B1\u03B6\u03BF\u03BD" - + "\u03C4\u03B1\u03B9\u0020\u03C5\u03BC\u03B1" - + "\u03C2\u0021") + new Run("none", "Αθηναι ασπαζονται υμας!") // Greek }), new Paragraph("title", new Run[] { - new Run("none", "\u6771\u4eac\u304b\u3089\u4eca\u65e5\u306f") + new Run("none", "東京から今日は") }), new Paragraph("title", new Run[] { - new Run("none", "\u05e9\u05dc\u05d5\u05dd \u05de\u05d9\u05e8\u05d5" - + "\u05e9\u05dc\u05d9\u05dd") + new Run("none", "שלום מירושלים") }), new Paragraph("title", new Run[] { - new Run("none", "\u0633\u0644\u0627\u0645") + new Run("none", "سلام") }), }; } diff --git a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java b/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java index 7b041591c6a..cbcb724d258 100644 --- a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java +++ b/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java @@ -232,8 +232,8 @@ public class JavaTimeSupplementary extends OpenListResourceBundle { "Sha.", "Ram.", "Shaw.", - "Dhu\u02bbl-Q.", - "Dhu\u02bbl-H.", + "Dhuʻl-Q.", + "Dhuʻl-H.", "", } }, @@ -241,16 +241,16 @@ public class JavaTimeSupplementary extends OpenListResourceBundle { new String[] { "Muharram", "Safar", - "Rabi\u02bb I", - "Rabi\u02bb II", + "Rabiʻ I", + "Rabiʻ II", "Jumada I", "Jumada II", "Rajab", - "Sha\u02bbban", + "Shaʻban", "Ramadan", "Shawwal", - "Dhu\u02bbl-Qi\u02bbdah", - "Dhu\u02bbl-Hijjah", + "Dhuʻl-Qiʻdah", + "Dhuʻl-Hijjah", "", } }, diff --git a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java index fe210146db8..d9a3ebafe14 100644 --- a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java +++ b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java @@ -87,7 +87,7 @@ public class FormatData_ja extends ParallelListResourceBundle { return new Object[][] { { "japanese.FirstYear", new String[] { // first year name - "\u5143", // "Gan"-nen + "元", // "Gan"-nen } }, }; From eb256deb8021d5b243ef782eb9e2622472909e97 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Mon, 9 Jun 2025 18:33:00 +0000 Subject: [PATCH 102/143] 8358326: Use oopFactory array allocation Reviewed-by: fparain, stefank --- src/hotspot/share/jvmci/jvmciEnv.cpp | 3 +-- src/hotspot/share/memory/oopFactory.cpp | 20 ++++++++-------- src/hotspot/share/oops/objArrayKlass.cpp | 8 +++---- src/hotspot/share/oops/objArrayKlass.hpp | 9 ++++--- src/hotspot/share/oops/typeArrayKlass.cpp | 6 ++--- src/hotspot/share/oops/typeArrayKlass.hpp | 9 ++++--- src/hotspot/share/prims/jni.cpp | 8 ++++--- src/hotspot/share/prims/vectorSupport.cpp | 5 ++-- src/hotspot/share/runtime/deoptimization.cpp | 4 ++-- src/hotspot/share/runtime/reflection.cpp | 25 +++++++++++--------- 10 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/hotspot/share/jvmci/jvmciEnv.cpp b/src/hotspot/share/jvmci/jvmciEnv.cpp index 8c9facf8489..851ead247f1 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.cpp +++ b/src/hotspot/share/jvmci/jvmciEnv.cpp @@ -1463,8 +1463,7 @@ JVMCIPrimitiveArray JVMCIEnv::new_byteArray(int length, JVMCI_TRAPS) { JVMCIObjectArray JVMCIEnv::new_byte_array_array(int length, JVMCI_TRAPS) { JavaThread* THREAD = JavaThread::current(); // For exception macros. if (is_hotspot()) { - Klass* byteArrayArrayKlass = TypeArrayKlass::cast(Universe::byteArrayKlass())->array_klass(CHECK_(JVMCIObject())); - objArrayOop result = ObjArrayKlass::cast(byteArrayArrayKlass) ->allocate(length, CHECK_(JVMCIObject())); + objArrayOop result = oopFactory::new_objArray(Universe::byteArrayKlass(), length, CHECK_(JVMCIObject())); return wrap(result); } else { JNIAccessMark jni(this, THREAD); diff --git a/src/hotspot/share/memory/oopFactory.cpp b/src/hotspot/share/memory/oopFactory.cpp index 949b4fe134b..b2a468d3a23 100644 --- a/src/hotspot/share/memory/oopFactory.cpp +++ b/src/hotspot/share/memory/oopFactory.cpp @@ -40,41 +40,41 @@ #include "utilities/utf8.hpp" typeArrayOop oopFactory::new_boolArray(int length, TRAPS) { - return Universe::boolArrayKlass()->allocate(length, THREAD); + return Universe::boolArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_charArray(int length, TRAPS) { - return Universe::charArrayKlass()->allocate(length, THREAD); + return Universe::charArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_floatArray(int length, TRAPS) { - return Universe::floatArrayKlass()->allocate(length, THREAD); + return Universe::floatArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_doubleArray(int length, TRAPS) { - return Universe::doubleArrayKlass()->allocate(length, THREAD); + return Universe::doubleArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_byteArray(int length, TRAPS) { - return Universe::byteArrayKlass()->allocate(length, THREAD); + return Universe::byteArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_shortArray(int length, TRAPS) { - return Universe::shortArrayKlass()->allocate(length, THREAD); + return Universe::shortArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_intArray(int length, TRAPS) { - return Universe::intArrayKlass()->allocate(length, THREAD); + return Universe::intArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_longArray(int length, TRAPS) { - return Universe::longArrayKlass()->allocate(length, THREAD); + return Universe::longArrayKlass()->allocate_instance(length, THREAD); } // create java.lang.Object[] objArrayOop oopFactory::new_objectArray(int length, TRAPS) { assert(Universe::objectArrayKlass() != nullptr, "Too early?"); - return Universe::objectArrayKlass()->allocate(length, THREAD); + return Universe::objectArrayKlass()->allocate_instance(length, THREAD); } typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) { @@ -88,7 +88,7 @@ typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) { typeArrayOop oopFactory::new_typeArray(BasicType type, int length, TRAPS) { TypeArrayKlass* klass = Universe::typeArrayKlass(type); - return klass->allocate(length, THREAD); + return klass->allocate_instance(length, THREAD); } // Create a Java array that points to Symbol. diff --git a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp index 7af6b963052..e1fcc25f150 100644 --- a/src/hotspot/share/oops/objArrayKlass.cpp +++ b/src/hotspot/share/oops/objArrayKlass.cpp @@ -44,7 +44,7 @@ #include "runtime/mutexLocker.hpp" #include "utilities/macros.hpp" -ObjArrayKlass* ObjArrayKlass::allocate(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS) { +ObjArrayKlass* ObjArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS) { assert(ObjArrayKlass::header_size() <= InstanceKlass::header_size(), "array klasses must be same size as InstanceKlass"); @@ -100,7 +100,7 @@ ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_da } // Initialize instance variables - ObjArrayKlass* oak = ObjArrayKlass::allocate(loader_data, n, element_klass, name, CHECK_NULL); + ObjArrayKlass* oak = ObjArrayKlass::allocate_klass(loader_data, n, element_klass, name, CHECK_NULL); ModuleEntry* module = oak->module(); assert(module != nullptr, "No module entry for array"); @@ -149,7 +149,7 @@ size_t ObjArrayKlass::oop_size(oop obj) const { return objArrayOop(obj)->object_size(); } -objArrayOop ObjArrayKlass::allocate(int length, TRAPS) { +objArrayOop ObjArrayKlass::allocate_instance(int length, TRAPS) { check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); size_t size = objArrayOopDesc::object_size(length); return (objArrayOop)Universe::heap()->array_allocate(this, size, length, @@ -160,7 +160,7 @@ oop ObjArrayKlass::multi_allocate(int rank, jint* sizes, TRAPS) { int length = *sizes; ArrayKlass* ld_klass = lower_dimension(); // If length < 0 allocate will throw an exception. - objArrayOop array = allocate(length, CHECK_NULL); + objArrayOop array = allocate_instance(length, CHECK_NULL); objArrayHandle h_array (THREAD, array); if (rank > 1) { if (length != 0) { diff --git a/src/hotspot/share/oops/objArrayKlass.hpp b/src/hotspot/share/oops/objArrayKlass.hpp index c44c8d28f62..11fe4f2a521 100644 --- a/src/hotspot/share/oops/objArrayKlass.hpp +++ b/src/hotspot/share/oops/objArrayKlass.hpp @@ -33,8 +33,10 @@ class ClassLoaderData; // ObjArrayKlass is the klass for objArrays class ObjArrayKlass : public ArrayKlass { - friend class VMStructs; + friend class Deoptimization; friend class JVMCIVMStructs; + friend class oopFactory; + friend class VMStructs; public: static const KlassKind Kind = ObjArrayKlassKind; @@ -47,7 +49,9 @@ class ObjArrayKlass : public ArrayKlass { // Constructor ObjArrayKlass(int n, Klass* element_klass, Symbol* name); - static ObjArrayKlass* allocate(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS); + static ObjArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS); + + objArrayOop allocate_instance(int length, TRAPS); public: // For dummy objects ObjArrayKlass() {} @@ -78,7 +82,6 @@ class ObjArrayKlass : public ArrayKlass { static ObjArrayKlass* allocate_objArray_klass(ClassLoaderData* loader_data, int n, Klass* element_klass, TRAPS); - objArrayOop allocate(int length, TRAPS); oop multi_allocate(int rank, jint* sizes, TRAPS); // Copying diff --git a/src/hotspot/share/oops/typeArrayKlass.cpp b/src/hotspot/share/oops/typeArrayKlass.cpp index 39385bb0184..bdf37c7db49 100644 --- a/src/hotspot/share/oops/typeArrayKlass.cpp +++ b/src/hotspot/share/oops/typeArrayKlass.cpp @@ -50,7 +50,7 @@ TypeArrayKlass* TypeArrayKlass::create_klass(BasicType type, ClassLoaderData* null_loader_data = ClassLoaderData::the_null_class_loader_data(); - TypeArrayKlass* ak = TypeArrayKlass::allocate(null_loader_data, type, sym, CHECK_NULL); + TypeArrayKlass* ak = TypeArrayKlass::allocate_klass(null_loader_data, type, sym, CHECK_NULL); // Call complete_create_array_klass after all instance variables have been initialized. complete_create_array_klass(ak, ak->super(), ModuleEntryTable::javabase_moduleEntry(), CHECK_NULL); @@ -65,7 +65,7 @@ TypeArrayKlass* TypeArrayKlass::create_klass(BasicType type, return ak; } -TypeArrayKlass* TypeArrayKlass::allocate(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS) { +TypeArrayKlass* TypeArrayKlass::allocate_klass(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS) { assert(TypeArrayKlass::header_size() <= InstanceKlass::header_size(), "array klasses must be same size as InstanceKlass"); @@ -101,7 +101,7 @@ oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) { // For typeArrays this is only called for the last dimension assert(rank == 1, "just checking"); int length = *last_size; - return allocate(length, THREAD); + return allocate_instance(length, THREAD); } diff --git a/src/hotspot/share/oops/typeArrayKlass.hpp b/src/hotspot/share/oops/typeArrayKlass.hpp index fa4e301e3e4..22600702fe2 100644 --- a/src/hotspot/share/oops/typeArrayKlass.hpp +++ b/src/hotspot/share/oops/typeArrayKlass.hpp @@ -33,6 +33,8 @@ class ClassLoaderData; // It contains the type and size of the elements class TypeArrayKlass : public ArrayKlass { + friend class Deoptimization; + friend class oopFactory; friend class VMStructs; public: @@ -43,7 +45,10 @@ class TypeArrayKlass : public ArrayKlass { // Constructor TypeArrayKlass(BasicType type, Symbol* name); - static TypeArrayKlass* allocate(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS); + static TypeArrayKlass* allocate_klass(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS); + + typeArrayOop allocate_common(int length, bool do_zero, TRAPS); + typeArrayOop allocate_instance(int length, TRAPS) { return allocate_common(length, true, THREAD); } public: TypeArrayKlass() {} // For dummy objects. @@ -66,8 +71,6 @@ class TypeArrayKlass : public ArrayKlass { size_t oop_size(oop obj) const; // Allocation - typeArrayOop allocate_common(int length, bool do_zero, TRAPS); - typeArrayOop allocate(int length, TRAPS) { return allocate_common(length, true, THREAD); } oop multi_allocate(int rank, jint* sizes, TRAPS); oop protection_domain() const { return nullptr; } diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 228244bbd05..5a43baeb8d8 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -2285,9 +2285,11 @@ JNI_ENTRY(jobjectArray, jni_NewObjectArray(JNIEnv *env, jsize length, jclass ele jobjectArray ret = nullptr; DT_RETURN_MARK(NewObjectArray, jobjectArray, (const jobjectArray&)ret); Klass* ek = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(elementClass)); - Klass* ak = ek->array_klass(CHECK_NULL); - ObjArrayKlass::cast(ak)->initialize(CHECK_NULL); - objArrayOop result = ObjArrayKlass::cast(ak)->allocate(length, CHECK_NULL); + + // Make sure bottom_klass is initialized. + ek->initialize(CHECK_NULL); + objArrayOop result = oopFactory::new_objArray(ek, length, CHECK_NULL); + oop initial_value = JNIHandles::resolve(initialElement); if (initial_value != nullptr) { // array already initialized with null for (int index = 0; index < length; index++) { diff --git a/src/hotspot/share/prims/vectorSupport.cpp b/src/hotspot/share/prims/vectorSupport.cpp index c907ddb4885..002f737e788 100644 --- a/src/hotspot/share/prims/vectorSupport.cpp +++ b/src/hotspot/share/prims/vectorSupport.cpp @@ -28,6 +28,7 @@ #include "code/location.hpp" #include "jni.h" #include "jvm.h" +#include "memory/oopFactory.hpp" #include "oops/klass.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/vectorSupport.hpp" @@ -109,9 +110,7 @@ Handle VectorSupport::allocate_vector_payload_helper(InstanceKlass* ik, frame* f int elem_size = type2aelembytes(elem_bt); // On-heap vector values are represented as primitive arrays. - TypeArrayKlass* tak = Universe::typeArrayKlass(elem_bt); - - typeArrayOop arr = tak->allocate(num_elem, CHECK_NH); // safepoint + typeArrayOop arr = oopFactory::new_typeArray(elem_bt, num_elem, CHECK_NH); // safepoint if (location.is_register()) { // Value was in a callee-saved register. diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index a0d9dd00339..ed84f2b294f 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -1274,11 +1274,11 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap* assert(sv->field_size() % type2size[ak->element_type()] == 0, "non-integral array length"); int len = sv->field_size() / type2size[ak->element_type()]; InternalOOMEMark iom(THREAD); - obj = ak->allocate(len, THREAD); + obj = ak->allocate_instance(len, THREAD); } else if (k->is_objArray_klass()) { ObjArrayKlass* ak = ObjArrayKlass::cast(k); InternalOOMEMark iom(THREAD); - obj = ak->allocate(sv->field_size(), THREAD); + obj = ak->allocate_instance(sv->field_size(), THREAD); } if (obj == nullptr) { diff --git a/src/hotspot/share/runtime/reflection.cpp b/src/hotspot/share/runtime/reflection.cpp index 9e165f76829..2a0af9d2d09 100644 --- a/src/hotspot/share/runtime/reflection.cpp +++ b/src/hotspot/share/runtime/reflection.cpp @@ -320,9 +320,16 @@ void Reflection::array_set(jvalue* value, arrayOop a, int index, BasicType value } } + +// Conversion +static BasicType basic_type_mirror_to_basic_type(oop basic_type_mirror) { + assert(java_lang_Class::is_primitive(basic_type_mirror), + "just checking"); + return java_lang_Class::primitive_type(basic_type_mirror); +} + static Klass* basic_type_mirror_to_arrayklass(oop basic_type_mirror, TRAPS) { - assert(java_lang_Class::is_primitive(basic_type_mirror), "just checking"); - BasicType type = java_lang_Class::primitive_type(basic_type_mirror); + BasicType type = basic_type_mirror_to_basic_type(basic_type_mirror); if (type == T_VOID) { THROW_NULL(vmSymbols::java_lang_IllegalArgumentException()); } @@ -339,8 +346,11 @@ arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) { THROW_MSG_NULL(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length)); } if (java_lang_Class::is_primitive(element_mirror)) { - Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL); - return TypeArrayKlass::cast(tak)->allocate(length, THREAD); + BasicType type = basic_type_mirror_to_basic_type(element_mirror); + if (type == T_VOID) { + THROW_NULL(vmSymbols::java_lang_IllegalArgumentException()); + } + return oopFactory::new_typeArray(type, length, CHECK_NULL); } else { Klass* k = java_lang_Class::as_Klass(element_mirror); if (k->is_array_klass() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) { @@ -907,13 +917,6 @@ static methodHandle resolve_interface_call(InstanceKlass* klass, return methodHandle(THREAD, info.selected_method()); } -// Conversion -static BasicType basic_type_mirror_to_basic_type(oop basic_type_mirror) { - assert(java_lang_Class::is_primitive(basic_type_mirror), - "just checking"); - return java_lang_Class::primitive_type(basic_type_mirror); -} - // Narrowing of basic types. Used to create correct jvalues for // boolean, byte, char and short return return values from interpreter // which are returned as ints. Throws IllegalArgumentException. From fcb68ea22d020d567c560c7bd5976d3c070d9806 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Mon, 9 Jun 2025 19:03:21 +0000 Subject: [PATCH 103/143] 8358626: Emit UTF-8 CLDR resources Reviewed-by: erikj, vyazici --- make/modules/java.base/Gensrc.gmk | 5 ++++- make/modules/jdk.localedata/Gensrc.gmk | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/make/modules/java.base/Gensrc.gmk b/make/modules/java.base/Gensrc.gmk index 3a482388bec..e4a019ed584 100644 --- a/make/modules/java.base/Gensrc.gmk +++ b/make/modules/java.base/Gensrc.gmk @@ -46,6 +46,8 @@ CLDR_GEN_DONE := $(GENSRC_DIR)/_cldr-gensrc.marker TZ_DATA_DIR := $(MODULE_SRC)/share/data/tzdata ZONENAME_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/ZoneName.java.template +# The `-utf8` option is used even for US English, as some names +# may contain non-ASCII characters, such as “Türkiye”. $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \ $(wildcard $(CLDR_DATA_DIR)/main/en*.xml) \ $(wildcard $(CLDR_DATA_DIR)/supplemental/*.xml) \ @@ -61,7 +63,8 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \ -basemodule \ -year $(COPYRIGHT_YEAR) \ -zntempfile $(ZONENAME_TEMPLATE) \ - -tzdatadir $(TZ_DATA_DIR)) + -tzdatadir $(TZ_DATA_DIR) \ + -utf8) $(TOUCH) $@ TARGETS += $(CLDR_GEN_DONE) diff --git a/make/modules/jdk.localedata/Gensrc.gmk b/make/modules/jdk.localedata/Gensrc.gmk index a3c5cdf82e8..93b863df66f 100644 --- a/make/modules/jdk.localedata/Gensrc.gmk +++ b/make/modules/jdk.localedata/Gensrc.gmk @@ -45,7 +45,8 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \ -baselocales "en-US" \ -year $(COPYRIGHT_YEAR) \ -o $(GENSRC_DIR) \ - -tzdatadir $(TZ_DATA_DIR)) + -tzdatadir $(TZ_DATA_DIR) \ + -utf8) $(TOUCH) $@ TARGETS += $(CLDR_GEN_DONE) From cd9b1bc820540184c79dd1957edc7ad4e8e469dc Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Mon, 9 Jun 2025 20:49:33 +0000 Subject: [PATCH 104/143] 8358426: Improve lazy computation in Locale Reviewed-by: naoto, liach --- .../share/classes/java/util/Locale.java | 180 +++++------------- .../classes/java/util/LocaleISOData.java | 91 ++++++++- .../classes/sun/util/locale/BaseLocale.java | 18 +- 3 files changed, 148 insertions(+), 141 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 993495ff5ab..9059d196861 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -48,8 +48,8 @@ import java.io.Serializable; import java.text.NumberFormat; import java.text.MessageFormat; import java.text.ParsePosition; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Supplier; import java.util.spi.LocaleNameProvider; import java.util.stream.Stream; @@ -733,58 +733,24 @@ public final class Locale implements Cloneable, Serializable { * @see #getISOCountries(Locale.IsoCountryCode) * @since 9 */ - public static enum IsoCountryCode { + public enum IsoCountryCode { /** * PART1_ALPHA2 is used to represent the ISO3166-1 alpha-2 two letter * country codes. */ - PART1_ALPHA2 { - @Override - Set createCountryCodeSet() { - return Set.of(Locale.getISOCountries()); - } - }, + PART1_ALPHA2, /** * * PART1_ALPHA3 is used to represent the ISO3166-1 alpha-3 three letter * country codes. */ - PART1_ALPHA3 { - @Override - Set createCountryCodeSet() { - return LocaleISOData.computeISO3166_1Alpha3Countries(); - } - }, + PART1_ALPHA3, /** * PART3 is used to represent the ISO3166-3 four letter country codes. */ - PART3 { - @Override - Set createCountryCodeSet() { - return Set.of(LocaleISOData.ISO3166_3); - } - }; - - /** - * Concrete implementation of this method attempts to compute value - * for iso3166CodesMap for each IsoCountryCode type key. - */ - abstract Set createCountryCodeSet(); - - /** - * Map to hold country codes for each ISO3166 part. - */ - private static final Map> iso3166CodesMap = new ConcurrentHashMap<>(); - - /** - * This method is called from Locale class to retrieve country code set - * for getISOCountries(type) - */ - static Set retrieveISOCountryCodes(IsoCountryCode type) { - return iso3166CodesMap.computeIfAbsent(type, IsoCountryCode::createCountryCodeSet); - } + PART3 } /** @@ -1004,30 +970,28 @@ public final class Locale implements Cloneable, Serializable { return getInstance(baseloc, extensions); } - static Locale getInstance(BaseLocale baseloc, LocaleExtensions extensions) { if (extensions == null) { Locale locale = CONSTANT_LOCALES.get(baseloc); if (locale != null) { return locale; } - return LocaleCache.cache(baseloc); + return LOCALE_CACHE.get().computeIfAbsent(baseloc, LOCALE_CREATOR); } else { LocaleKey key = new LocaleKey(baseloc, extensions); - return LocaleCache.cache(key); + return LOCALE_CACHE.get().computeIfAbsent(key, LOCALE_CREATOR); } } - private static final class LocaleCache implements Function { - private static final ReferencedKeyMap LOCALE_CACHE - = ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier()); - - private static final Function LOCALE_CREATOR = new LocaleCache(); - - public static Locale cache(Object key) { - return LOCALE_CACHE.computeIfAbsent(key, LOCALE_CREATOR); - } + private static final Supplier> LOCALE_CACHE = + StableValue.supplier(new Supplier<>() { + @Override + public ReferencedKeyMap get() { + return ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier()); + } + }); + private static final Function LOCALE_CREATOR = new Function<>() { @Override public Locale apply(Object key) { if (key instanceof BaseLocale base) { @@ -1036,7 +1000,7 @@ public final class Locale implements Cloneable, Serializable { LocaleKey lk = (LocaleKey)key; return new Locale(lk.base, lk.exts); } - } + }; private static final class LocaleKey { @@ -1301,12 +1265,8 @@ public final class Locale implements Cloneable, Serializable { * @return An array of ISO 3166 two-letter country codes. */ public static String[] getISOCountries() { - if (isoCountries == null) { - isoCountries = getISO2Table(LocaleISOData.isoCountryTable); - } - String[] result = new String[isoCountries.length]; - System.arraycopy(isoCountries, 0, result, 0, isoCountries.length); - return result; + String[] countries = LocaleISOData.ISO_3166_1_ALPHA2.get(); + return Arrays.copyOf(countries, countries.length); } /** @@ -1319,7 +1279,11 @@ public final class Locale implements Cloneable, Serializable { */ public static Set getISOCountries(IsoCountryCode type) { Objects.requireNonNull(type); - return IsoCountryCode.retrieveISOCountryCodes(type); + return switch (type) { + case PART1_ALPHA2 -> Set.of(LocaleISOData.ISO_3166_1_ALPHA2.get()); + case PART1_ALPHA3 -> LocaleISOData.ISO_3166_1_ALPHA3.get(); + case PART3 -> LocaleISOData.ISO_3166_3.get(); + }; } /** @@ -1339,22 +1303,8 @@ public final class Locale implements Cloneable, Serializable { * @return An array of ISO 639 two-letter language codes. */ public static String[] getISOLanguages() { - String[] languages = Locale.isoLanguages; - if (languages == null) { - Locale.isoLanguages = languages = getISO2Table(LocaleISOData.isoLanguageTable); - } - String[] result = new String[languages.length]; - System.arraycopy(languages, 0, result, 0, languages.length); - return result; - } - - private static String[] getISO2Table(String table) { - int len = table.length() / 5; - String[] isoTable = new String[len]; - for (int i = 0, j = 0; i < len; i++, j += 5) { - isoTable[i] = table.substring(j, j + 2); - } - return isoTable; + String[] languages = LocaleISOData.ISO_639.get(); + return Arrays.copyOf(languages, languages.length); } /** @@ -1683,61 +1633,54 @@ public final class Locale implements Cloneable, Serializable { * @since 1.7 */ public String toLanguageTag() { - String lTag = this.languageTag; - if (lTag != null) { - return lTag; - } + return languageTag.get(); + } + private String computeLanguageTag() { LanguageTag tag = LanguageTag.parseLocale(baseLocale, localeExtensions); - StringBuilder buf = new StringBuilder(); + StringBuilder bldr = new StringBuilder(); String subtag = tag.language(); if (!subtag.isEmpty()) { - buf.append(LanguageTag.canonicalizeLanguage(subtag)); + bldr.append(LanguageTag.canonicalizeLanguage(subtag)); } subtag = tag.script(); if (!subtag.isEmpty()) { - buf.append(LanguageTag.SEP); - buf.append(LanguageTag.canonicalizeScript(subtag)); + bldr.append(LanguageTag.SEP); + bldr.append(LanguageTag.canonicalizeScript(subtag)); } subtag = tag.region(); if (!subtag.isEmpty()) { - buf.append(LanguageTag.SEP); - buf.append(LanguageTag.canonicalizeRegion(subtag)); + bldr.append(LanguageTag.SEP); + bldr.append(LanguageTag.canonicalizeRegion(subtag)); } Listsubtags = tag.variants(); for (String s : subtags) { - buf.append(LanguageTag.SEP); + bldr.append(LanguageTag.SEP); // preserve casing - buf.append(s); + bldr.append(s); } subtags = tag.extensions(); for (String s : subtags) { - buf.append(LanguageTag.SEP); - buf.append(LanguageTag.canonicalizeExtension(s)); + bldr.append(LanguageTag.SEP); + bldr.append(LanguageTag.canonicalizeExtension(s)); } subtag = tag.privateuse(); if (!subtag.isEmpty()) { - if (buf.length() > 0) { - buf.append(LanguageTag.SEP); + if (bldr.length() > 0) { + bldr.append(LanguageTag.SEP); } - buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP); + bldr.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP); // preserve casing - buf.append(subtag); + bldr.append(subtag); } - String langTag = buf.toString(); - synchronized (this) { - if (this.languageTag == null) { - this.languageTag = langTag; - } - } - return langTag; + return bldr.toString(); } /** @@ -1961,7 +1904,7 @@ public final class Locale implements Cloneable, Serializable { return lang; } - String language3 = getISO3Code(lang, LocaleISOData.isoLanguageTable); + String language3 = LocaleISOData.getISO3LangCode(lang); if (language3 == null) { throw new MissingResourceException("Couldn't find 3-letter language code for " + lang, "FormatData_" + toString(), "ShortLanguage"); @@ -1983,7 +1926,7 @@ public final class Locale implements Cloneable, Serializable { * three-letter country abbreviation is not available for this locale. */ public String getISO3Country() throws MissingResourceException { - String country3 = getISO3Code(baseLocale.getRegion(), LocaleISOData.isoCountryTable); + String country3 = LocaleISOData.getISO3CtryCode(baseLocale.getRegion()); if (country3 == null) { throw new MissingResourceException("Couldn't find 3-letter country code for " + baseLocale.getRegion(), "FormatData_" + toString(), "ShortCountry"); @@ -1991,27 +1934,6 @@ public final class Locale implements Cloneable, Serializable { return country3; } - private static String getISO3Code(String iso2Code, String table) { - int codeLength = iso2Code.length(); - if (codeLength == 0) { - return ""; - } - - int tableLength = table.length(); - int index = tableLength; - if (codeLength == 2) { - char c1 = iso2Code.charAt(0); - char c2 = iso2Code.charAt(1); - for (index = 0; index < tableLength; index += 5) { - if (table.charAt(index) == c1 - && table.charAt(index + 1) == c2) { - break; - } - } - } - return index < tableLength ? table.substring(index + 2, index + 5) : null; - } - /** * Returns a name for the locale's language that is appropriate for display to the * user. @@ -2393,7 +2315,13 @@ public final class Locale implements Cloneable, Serializable { private static volatile Locale defaultDisplayLocale; private static volatile Locale defaultFormatLocale; - private transient volatile String languageTag; + private final transient Supplier languageTag = + StableValue.supplier(new Supplier<>() { + @Override + public String get() { + return computeLanguageTag(); + } + }); /** * Return an array of the display names of the variant. @@ -2587,10 +2515,6 @@ public final class Locale implements Cloneable, Serializable { baseLocale.getRegion(), baseLocale.getVariant(), localeExtensions); } - private static volatile String[] isoLanguages; - - private static volatile String[] isoCountries; - private static String convertOldISOCodes(String language) { // we accept both the old and the new ISO codes for the languages whose ISO // codes have changed, but we always store the NEW code, unless the property diff --git a/src/java.base/share/classes/java/util/LocaleISOData.java b/src/java.base/share/classes/java/util/LocaleISOData.java index c2090d3be19..29e0b28be01 100644 --- a/src/java.base/share/classes/java/util/LocaleISOData.java +++ b/src/java.base/share/classes/java/util/LocaleISOData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -25,11 +25,47 @@ package java.util; +import java.util.function.Supplier; + +// Methods and suppliers for producing ISO 639/3166 resources used by Locale. class LocaleISOData { + + static final Supplier ISO_639 = + StableValue.supplier(new Supplier<>() { + @Override + public String[] get() { + return getISO2Table(isoLanguageTable); + } + }); + + static final Supplier ISO_3166_1_ALPHA2 = + StableValue.supplier(new Supplier<>() { + @Override + public String[] get() { + return getISO2Table(isoCountryTable); + } + }); + + static final Supplier> ISO_3166_1_ALPHA3 = + StableValue.supplier(new Supplier<>() { + @Override + public Set get() { + return computeISO3166_1Alpha3Countries(); + } + }); + + static final Supplier> ISO_3166_3 = + StableValue.supplier(new Supplier<>() { + @Override + public Set get() { + return Set.of(ISO3166_3); + } + }); + /** * The 2- and 3-letter ISO 639 language codes. */ - static final String isoLanguageTable = + private static final String isoLanguageTable = "aa" + "aar" // Afar + "ab" + "abk" // Abkhazian + "ae" + "ave" // Avestan @@ -223,7 +259,7 @@ class LocaleISOData { /** * The 2- and 3-letter ISO 3166 country codes. */ - static final String isoCountryTable = + private static final String isoCountryTable = "AD" + "AND" // Andorra, Principality of + "AE" + "ARE" // United Arab Emirates + "AF" + "AFG" // Afghanistan @@ -480,18 +516,60 @@ class LocaleISOData { /** * Array to hold country codes for ISO3166-3. */ - static final String[] ISO3166_3 = { + private static final String[] ISO3166_3 = { "AIDJ", "ANHH", "BQAQ", "BUMM", "BYAA", "CSHH", "CSXX", "CTKI", "DDDE", "DYBJ", "FQHH", "FXFR", "GEHH", "HVBF", "JTUM", "MIUM", "NHVU", "NQAQ", "NTHH", "PCHH", "PUUM", "PZPA", "RHZW", "SKIN", "SUHH", "TPTL", "VDVN", "WKUM", "YDYE", "YUCS", "ZRCD" }; + static String getISO3LangCode(String language) { + return getISO3Code(language, isoLanguageTable); + } + + static String getISO3CtryCode(String country) { + return getISO3Code(country, isoCountryTable); + } + + private static String getISO3Code(String iso2Code, String table) { + int codeLength = iso2Code.length(); + if (codeLength == 0) { + return ""; + } + + int tableLength = table.length(); + int index = tableLength; + if (codeLength == 2) { + char c1 = iso2Code.charAt(0); + char c2 = iso2Code.charAt(1); + for (index = 0; index < tableLength; index += 5) { + if (table.charAt(index) == c1 + && table.charAt(index + 1) == c2) { + break; + } + } + } + return index < tableLength ? table.substring(index + 2, index + 5) : null; + } + + /** + * This method computes an array of alpha-2 codes from either ISO639 or + * ISO3166. + */ + private static String[] getISO2Table(String table) { + int len = table.length() / 5; + String[] isoTable = new String[len]; + for (int i = 0, j = 0; i < len; i++, j += 5) { + isoTable[i] = table.substring(j, j + 2); + } + return isoTable; + } + /** * This method computes a set of ISO3166-1 alpha-3 country codes from * existing isoCountryTable. */ - static Set computeISO3166_1Alpha3Countries() { + private static Set computeISO3166_1Alpha3Countries() { int tableLength = isoCountryTable.length(); String[] isoTable = new String[tableLength / 5]; for (int i = 0, index = 0; index < tableLength; i++, index += 5) { @@ -500,6 +578,5 @@ class LocaleISOData { return Set.of(isoTable); } - private LocaleISOData() { - } + private LocaleISOData() {} } diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index d1fe8d24b72..529ca1b0c13 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -38,6 +38,7 @@ import jdk.internal.util.StaticProperty; import jdk.internal.vm.annotation.Stable; import java.util.StringJoiner; +import java.util.function.Supplier; public final class BaseLocale { @@ -90,6 +91,15 @@ public final class BaseLocale { } } + // Interned BaseLocale cache + private static final Supplier> CACHE = + StableValue.supplier(new Supplier<>() { + @Override + public ReferencedKeySet get() { + return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); + } + }); + public static final String SEP = "_"; private final String language; @@ -164,11 +174,7 @@ public final class BaseLocale { // Obtain the "interned" BaseLocale from the cache. The returned // "interned" instance can subsequently be used by the Locale // instance which guarantees the locale components are properly cased/interned. - class InterningCache { // TODO: StableValue - private static final ReferencedKeySet CACHE = - ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); - } - return InterningCache.CACHE.intern(new BaseLocale( + return CACHE.get().intern(new BaseLocale( language.intern(), // guaranteed to be lower-case LocaleUtils.toTitleString(script).intern(), region.intern(), // guaranteed to be upper-case From ef45c8154cea2ec910788d3c19e91d3eed75708e Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 9 Jun 2025 20:59:30 +0000 Subject: [PATCH 105/143] 8346237: Obsolete the UseOprofile flag Reviewed-by: coleenp, kvn --- src/hotspot/os/linux/globals_linux.hpp | 5 +--- src/hotspot/os/linux/os_linux.cpp | 38 ------------------------- src/hotspot/share/memory/heap.cpp | 10 ------- src/hotspot/share/memory/heap.hpp | 5 +--- src/hotspot/share/runtime/arguments.cpp | 4 ++- 5 files changed, 5 insertions(+), 57 deletions(-) diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 331690ec3ee..542e034f59f 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -35,9 +35,6 @@ range, \ constraint) \ \ - product(bool, UseOprofile, false, \ - "(Deprecated) enable support for Oprofile profiler") \ - \ product(bool, UseTransparentHugePages, false, \ "Use MADV_HUGEPAGE for large pages") \ \ diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 807014e7b0c..3aa83895ff1 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -2820,44 +2820,6 @@ void os::jvm_path(char *buf, jint buflen) { //////////////////////////////////////////////////////////////////////////////// // Virtual Memory -// Rationale behind this function: -// current (Mon Apr 25 20:12:18 MSD 2005) oprofile drops samples without executable -// mapping for address (see lookup_dcookie() in the kernel module), thus we cannot get -// samples for JITted code. Here we create private executable mapping over the code cache -// and then we can use standard (well, almost, as mapping can change) way to provide -// info for the reporting script by storing timestamp and location of symbol -void linux_wrap_code(char* base, size_t size) { - static volatile jint cnt = 0; - - static_assert(sizeof(off_t) == 8, "Expected Large File Support in this file"); - - if (!UseOprofile) { - return; - } - - char buf[PATH_MAX+1]; - int num = Atomic::add(&cnt, 1); - - snprintf(buf, sizeof(buf), "%s/hs-vm-%d-%d", - os::get_temp_directory(), os::current_process_id(), num); - unlink(buf); - - int fd = ::open(buf, O_CREAT | O_RDWR, S_IRWXU); - - if (fd != -1) { - off_t rv = ::lseek(fd, size-2, SEEK_SET); - if (rv != (off_t)-1) { - if (::write(fd, "", 1) == 1) { - mmap(base, size, - PROT_READ|PROT_WRITE|PROT_EXEC, - MAP_PRIVATE|MAP_FIXED|MAP_NORESERVE, fd, 0); - } - } - ::close(fd); - unlink(buf); - } -} - static bool recoverable_mmap_error(int err) { // See if the error is one we can let the caller handle. This // list of errno values comes from JBS-6843484. I can't find a diff --git a/src/hotspot/share/memory/heap.cpp b/src/hotspot/share/memory/heap.cpp index bcb9d2e6114..b111c61f15a 100644 --- a/src/hotspot/share/memory/heap.cpp +++ b/src/hotspot/share/memory/heap.cpp @@ -189,14 +189,6 @@ static size_t align_to_page_size(size_t size) { } -void CodeHeap::on_code_mapping(char* base, size_t size) { -#ifdef LINUX - extern void linux_wrap_code(char* base, size_t size); - linux_wrap_code(base, size); -#endif -} - - bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_size) { assert(rs.size() >= committed_size, "reserved < committed"); assert(is_aligned(committed_size, rs.page_size()), "must be page aligned"); @@ -213,7 +205,6 @@ bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_s return false; } - on_code_mapping(_memory.low(), _memory.committed_size()); _number_of_committed_segments = size_to_segments(_memory.committed_size()); _number_of_reserved_segments = size_to_segments(_memory.reserved_size()); assert(_number_of_reserved_segments >= _number_of_committed_segments, "just checking"); @@ -250,7 +241,6 @@ bool CodeHeap::expand_by(size_t size) { } char* base = _memory.low() + _memory.committed_size(); if (!_memory.expand_by(dm)) return false; - on_code_mapping(base, dm); size_t i = _number_of_committed_segments; _number_of_committed_segments = size_to_segments(_memory.committed_size()); assert(_number_of_reserved_segments == size_to_segments(_memory.reserved_size()), "number of reserved segments should not change"); diff --git a/src/hotspot/share/memory/heap.hpp b/src/hotspot/share/memory/heap.hpp index e54f99c8c54..d0e4230fe2b 100644 --- a/src/hotspot/share/memory/heap.hpp +++ b/src/hotspot/share/memory/heap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -146,9 +146,6 @@ class CodeHeap : public CHeapObj { void* next_used(HeapBlock* b) const; HeapBlock* block_start(void* p) const; - // to perform additional actions on creation of executable code - void on_code_mapping(char* base, size_t size); - public: CodeHeap(const char* name, const CodeBlobType code_blob_type); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 7592d233f0c..a4b2994ac55 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -530,7 +530,6 @@ static SpecialFlag const special_jvm_flags[] = { { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, #ifdef LINUX { "UseLinuxPosixThreadCPUClocks", JDK_Version::jdk(24), JDK_Version::jdk(25), JDK_Version::jdk(26) }, - { "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #endif { "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #ifdef _LP64 @@ -542,6 +541,9 @@ static SpecialFlag const special_jvm_flags[] = { // -------------- Obsolete Flags - sorted by expired_in -------------- { "PerfDataSamplingInterval", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::jdk(26) }, +#ifdef LINUX + { "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) }, +#endif { "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() }, { "ZGenerational", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::undefined() }, { "ZMarkStackSpaceLimit", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, From d186dacdb7b91dc9a28b703ce3c8ea007fc450b6 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Mon, 9 Jun 2025 21:54:55 +0000 Subject: [PATCH 106/143] 8357591: Re-enable CDS test cases for jvmci after JDK-8345826 Reviewed-by: dholmes, kvn --- .../runtime/cds/appcds/LambdaWithUseImplMethodHandle.java | 2 -- .../appcds/aotCache/AOTCacheSupportForCustomLoaders.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/HelloAOTCache.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/JavaAgent.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/ManagementAgent.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/PackageInfoClass.java | 2 -- .../runtime/cds/appcds/aotCache/SpecialCacheNames.java | 2 -- .../jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java | 2 -- .../runtime/cds/appcds/aotClassLinking/AOTCacheWithZGC.java | 2 -- .../appcds/aotClassLinking/AOTLoaderConstraintsTest.java | 2 -- .../runtime/cds/appcds/aotClassLinking/AddExports.java | 2 -- .../jtreg/runtime/cds/appcds/aotClassLinking/AddOpens.java | 2 -- .../jtreg/runtime/cds/appcds/aotClassLinking/AddReads.java | 2 -- .../runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java | 6 ------ .../cds/appcds/aotClassLinking/FakeCodeLocation.java | 2 -- .../cds/appcds/aotClassLinking/GeneratedInternedString.java | 2 -- .../cds/appcds/aotClassLinking/LambdaInExcludedClass.java | 2 -- .../cds/appcds/aotClassLinking/MethodHandleTest.java | 2 -- .../appcds/aotClassLinking/NonFinalStaticWithInitVal.java | 2 -- .../cds/appcds/aotClassLinking/StringConcatStress.java | 2 -- .../cds/appcds/aotClassLinking/TestSetupAOTTest.java | 2 -- .../runtime/cds/appcds/aotClassLinking/TrainingRun.java | 2 -- .../cds/appcds/aotClassLinking/WeakReferenceTest.java | 2 -- .../runtime/cds/appcds/aotProfile/AOTProfileFlags.java | 2 -- .../cds/appcds/cacheObject/ArchiveHeapTestClass.java | 2 -- .../appcds/methodHandles/MethodHandlesAsCollectorTest.java | 2 -- .../appcds/methodHandles/MethodHandlesCastFailureTest.java | 2 -- .../cds/appcds/methodHandles/MethodHandlesGeneralTest.java | 2 -- .../cds/appcds/methodHandles/MethodHandlesInvokersTest.java | 2 -- .../methodHandles/MethodHandlesPermuteArgumentsTest.java | 2 -- .../methodHandles/MethodHandlesSpreadArgumentsTest.java | 2 -- .../cds/appcds/resolvedConstants/AOTLinkedLambdas.java | 2 -- .../cds/appcds/resolvedConstants/AOTLinkedVarHandles.java | 2 -- 34 files changed, 72 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java index 0c747168945..6b7a4f68bbf 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java @@ -28,8 +28,6 @@ * @summary CDS cannot archive lambda proxy with useImplMethodHandle * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build pkg1.BaseWithProtectedMethod * @build pkg2.Child diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheSupportForCustomLoaders.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheSupportForCustomLoaders.java index ae00477e67b..9461957ef2c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheSupportForCustomLoaders.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheSupportForCustomLoaders.java @@ -27,8 +27,6 @@ * @summary Test AOT cache support for array classes in custom class loaders. * @bug 8353298 8356838 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build ReturnIntegerAsString * @build AOTCacheSupportForCustomLoaders diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java index 4f2ff4f0f76..c808cd95bc7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java @@ -26,8 +26,6 @@ * @test * @summary Test how various AOT optimizations handle classes that are excluded from the AOT cache. * @requires vm.cds.write.archived.java.heap - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib * /test/hotspot/jtreg/runtime/cds/appcds/aotCache/test-classes * @build ExcludedClasses CustyWithLoop diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HelloAOTCache.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HelloAOTCache.java index 007e51004ec..f3326ac1ed6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HelloAOTCache.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HelloAOTCache.java @@ -27,8 +27,6 @@ * @test * @summary Sanity test for AOTCache * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build HelloAOTCache * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HelloAOTCacheApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java index 7965be8d00a..070f6df9834 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java @@ -28,8 +28,6 @@ * @summary -javaagent should be allowed in AOT workflow. However, classes transformed/redefined by agents will not * be cached. * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build JavaAgent JavaAgentTransformer Util * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar JavaAgentApp JavaAgentApp$ShouldBeTransformed diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ManagementAgent.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ManagementAgent.java index abbdd3551b7..c558d293b2a 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ManagementAgent.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ManagementAgent.java @@ -28,8 +28,6 @@ * @bug 8352187 * @summary ManagementAgent will not be started during AOT cache creation. * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build HelloAOTCache * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HelloAOTCacheApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/PackageInfoClass.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/PackageInfoClass.java index 0fff74236f1..014524afde6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/PackageInfoClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/PackageInfoClass.java @@ -27,8 +27,6 @@ * @summary AOT cache handling for package-info class loaded by jdk/internal/loader/ClassLoaders$BootClassLoader * @bug 8354558 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/jdk/java/lang/Package/bootclasspath/boot * @build PackageInfoClass foo.Foo foo.MyAnnotation foo.package-info * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar foo.Foo foo.package-info foo.MyAnnotation diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java index 89aa9e441a4..e9d9c70a358 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SpecialCacheNames.java @@ -27,8 +27,6 @@ * @summary Use special characters in the name of the cache file specified by -XX:AOTCacheOutput * Make sure these characters are passed to the child JVM process that assembles the cache. * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build SpecialCacheNames * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar MyTestApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java index 18caa08c117..50b47e4424d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java @@ -26,8 +26,6 @@ * @test * @summary Sanity test for AOTCache * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build VerifierFailOver_Helper * @build VerifierFailOver diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTCacheWithZGC.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTCacheWithZGC.java index 96f08004fb2..a7c7362b845 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTCacheWithZGC.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTCacheWithZGC.java @@ -28,8 +28,6 @@ * @bug 8352775 * @requires vm.cds * @requires vm.gc.Z - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build AOTCacheWithZGC * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTCacheWithZGCApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java index 63be2038907..f82cf2737fb 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java @@ -26,8 +26,6 @@ * @test Make sure loader constraints are passed from AOT preimage to final image. * @bug 8348426 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib * @build AOTLoaderConstraintsTest BootClass * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar BootClass diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddExports.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddExports.java index 2a63a4837c2..fc3e945cf9d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddExports.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddExports.java @@ -28,8 +28,6 @@ * @bug 8352437 * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @run driver AddExports */ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddOpens.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddOpens.java index 5ae8c4a6bb8..d3626d30c7e 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddOpens.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddOpens.java @@ -26,8 +26,6 @@ * @test * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @run driver AddOpens * @summary sanity test the --add-opens option diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddReads.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddReads.java index c1dbc602755..5c9364aa079 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddReads.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AddReads.java @@ -27,8 +27,6 @@ * @bug 8354083 * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @run driver AddReads * @summary sanity test the --add-reads option diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java index 90fd0f33bab..30f7ce12ca0 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -29,8 +29,6 @@ /* * @test id=static * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build InitiatingLoaderTester BadOldClassA BadOldClassB * @build jdk.test.whitebox.WhiteBox BulkLoaderTest SimpleCusty @@ -45,8 +43,6 @@ /* * @test id=dynamic * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build InitiatingLoaderTester BadOldClassA BadOldClassB * @build jdk.test.whitebox.WhiteBox BulkLoaderTest SimpleCusty @@ -61,8 +57,6 @@ /* * @test id=aot * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build jdk.test.whitebox.WhiteBox InitiatingLoaderTester BadOldClassA BadOldClassB * @build BulkLoaderTest SimpleCusty diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/FakeCodeLocation.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/FakeCodeLocation.java index 1ffc904963a..5da2e2754e8 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/FakeCodeLocation.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/FakeCodeLocation.java @@ -26,8 +26,6 @@ * @test Do not cache classes that are loaded from a fake location. * @bug 8352001 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib * @build FakeCodeLocation * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar FakeCodeLocationApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GeneratedInternedString.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GeneratedInternedString.java index dcb1c59c09f..0ee59bf5afd 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GeneratedInternedString.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GeneratedInternedString.java @@ -28,8 +28,6 @@ * @requires vm.cds.write.archived.java.heap * @requires vm.cds.supports.aot.class.linking * @requires vm.debug - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @build GeneratedInternedString * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar GeneratedInternedStringApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/LambdaInExcludedClass.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/LambdaInExcludedClass.java index 9ed2524bff1..c91e999c40f 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/LambdaInExcludedClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/LambdaInExcludedClass.java @@ -27,8 +27,6 @@ * @bug 8349888 * @requires vm.cds.supports.aot.class.linking * @requires vm.gc.Epsilon - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib * @build LambdaInExcludedClass * @run driver jdk.test.lib.helpers.ClassFileInstaller LambdaInExcludedClassApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java index f7a26a39db1..972dc287af5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java @@ -27,8 +27,6 @@ * @requires vm.cds.write.archived.java.heap * @requires vm.cds.supports.aot.class.linking * @requires vm.debug - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @build MethodHandleTest * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar mh.jar diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/NonFinalStaticWithInitVal.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/NonFinalStaticWithInitVal.java index ba21f5f98fb..17ff399d23c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/NonFinalStaticWithInitVal.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/NonFinalStaticWithInitVal.java @@ -27,8 +27,6 @@ * @summary Handling of non-final static string that has an initial value * @bug 8356125 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build NonFinalStaticWithInitVal_Helper * @build NonFinalStaticWithInitVal diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/StringConcatStress.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/StringConcatStress.java index 3786c294791..81bf28010b1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/StringConcatStress.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/StringConcatStress.java @@ -34,8 +34,6 @@ /* * @test id=aot * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build StringConcatStress * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar StringConcatStressApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TestSetupAOTTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TestSetupAOTTest.java index f88af3caed5..118b4c6af1d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TestSetupAOTTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TestSetupAOTTest.java @@ -28,8 +28,6 @@ * is used for running HotSpot tests in the "AOT mode" * (E.g., make test JTREG=AOT_JDK=true TEST=open/test/hotspot/jtreg/runtime/invokedynamic) * @requires vm.cds - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/setup_aot * @build TestSetupAOTTest JavacBenchApp TestSetupAOT * @run driver jdk.test.lib.helpers.ClassFileInstaller diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java index fd896fd6958..df466151f98 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java @@ -27,8 +27,6 @@ * @summary -XX:AOTMode=record should not interfere with app execution: (1) thread creation; (2) exit code * @bug 8351327 * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib * @build TrainingRun * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar MyTestApp diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/WeakReferenceTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/WeakReferenceTest.java index ae5cd5b3429..3b33d63b0d2 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/WeakReferenceTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/WeakReferenceTest.java @@ -27,8 +27,6 @@ * @requires vm.cds.write.archived.java.heap * @requires vm.cds.supports.aot.class.linking * @requires vm.debug - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @build WeakReferenceTest * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar weakref.jar diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java index e2285a59600..fe9c7e7bbb7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java @@ -26,8 +26,6 @@ * @test * @summary Sanity test of combinations of the diagnostic flags [+-]AOTRecordTraining and [+-]AOTReplayTraining * @requires vm.cds - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @requires vm.cds.supports.aot.class.linking * @requires vm.flagless * @library /test/lib /test/setup_aot /test/hotspot/jtreg/runtime/cds/appcds/test-classes diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java index 819db99a549..fed56937f2f 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java @@ -28,8 +28,6 @@ * @summary Test for the -XX:ArchiveHeapTestClass flag * @requires vm.debug == true & vm.cds.write.archived.java.heap * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @modules java.logging * @library /test/jdk/lib/testlibrary /test/lib * /test/hotspot/jtreg/runtime/cds/appcds diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesAsCollectorTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesAsCollectorTest.java index ba7c0def86a..1061207f764 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesAsCollectorTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesAsCollectorTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesAsCollectorTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesCastFailureTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesCastFailureTest.java index 3f5008ca8c3..3349d146e10 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesCastFailureTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesCastFailureTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesCastFailureTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesGeneralTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesGeneralTest.java index 70f02b9a3d7..bba3f367505 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesGeneralTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesGeneralTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesGeneralTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesInvokersTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesInvokersTest.java index 7de76f5e99a..9ce57a9c66b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesInvokersTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesInvokersTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesInvokersTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesPermuteArgumentsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesPermuteArgumentsTest.java index 1e4bfebce9d..8f75eff0dc7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesPermuteArgumentsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesPermuteArgumentsTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesPermuteArgumentsTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesSpreadArgumentsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesSpreadArgumentsTest.java index 19755121282..8b431232825 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesSpreadArgumentsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/methodHandles/MethodHandlesSpreadArgumentsTest.java @@ -28,8 +28,6 @@ * @summary Run the MethodHandlesSpreadArgumentsTest.java test in CDSAppTester::AOT workflow. * @requires vm.cds & vm.compMode != "Xcomp" * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @comment Some of the tests run excessively slowly with -Xcomp. The original * tests aren't executed with -Xcomp in the CI pipeline, so let's exclude * the generated tests from -Xcomp execution as well. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java index e187f408fb4..ecfaa265923 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java @@ -28,8 +28,6 @@ * @bug 8340836 * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ * @build AOTLinkedLambdas * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java index e262c55beea..4586b94b519 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java @@ -28,8 +28,6 @@ * @bug 8343245 * @requires vm.cds * @requires vm.cds.supports.aot.class.linking - * @comment work around JDK-8345635 - * @requires !vm.jvmci.enabled * @library /test/lib * @build AOTLinkedVarHandles * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar From bcf860703dc0244fef1d380cb7323282de76970c Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 9 Jun 2025 22:25:20 +0000 Subject: [PATCH 107/143] 8355792: Remove expired flags in JDK 26 Reviewed-by: coleenp, kvn --- src/hotspot/share/runtime/arguments.cpp | 4 -- src/java.base/share/man/java.md | 65 ++----------------------- 2 files changed, 3 insertions(+), 66 deletions(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index a4b2994ac55..a49116fd91d 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -528,9 +528,6 @@ static SpecialFlag const special_jvm_flags[] = { { "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, -#ifdef LINUX - { "UseLinuxPosixThreadCPUClocks", JDK_Version::jdk(24), JDK_Version::jdk(25), JDK_Version::jdk(26) }, -#endif { "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #ifdef _LP64 { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::undefined() }, @@ -540,7 +537,6 @@ static SpecialFlag const special_jvm_flags[] = { // -------------- Obsolete Flags - sorted by expired_in -------------- - { "PerfDataSamplingInterval", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::jdk(26) }, #ifdef LINUX { "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #endif diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index bb010e6e1c7..fc8cbb23aff 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -2917,71 +2917,12 @@ when they're used. ## Removed Java Options -These `java` options have been removed in JDK @@VERSION_SPECIFICATION@@ and using them results in an error of: - -> `Unrecognized VM option` *option-name* - -`-XX:RTMAbortRatio=`*abort\_ratio* -: Specifies the RTM abort ratio is specified as a percentage (%) of all - executed RTM transactions. If a number of aborted transactions becomes - greater than this ratio, then the compiled code is deoptimized. This ratio - is used when the `-XX:+UseRTMDeopt` option is enabled. The default value of - this option is 50. This means that the compiled code is deoptimized if 50% - of all transactions are aborted. - -`-XX:RTMRetryCount=`*number\_of\_retries* -: Specifies the number of times that the RTM locking code is retried, when it - is aborted or busy, before falling back to the normal locking mechanism. - The default value for this option is 5. The `-XX:UseRTMLocking` option must - be enabled. - -`-XX:+UseRTMDeopt` -: Autotunes RTM locking depending on the abort ratio. This ratio is specified - by the `-XX:RTMAbortRatio` option. If the number of aborted transactions - exceeds the abort ratio, then the method containing the lock is deoptimized - and recompiled with all locks as normal locks. This option is disabled by - default. The `-XX:+UseRTMLocking` option must be enabled. - -`-XX:+UseRTMLocking` -: Generates Restricted Transactional Memory (RTM) locking code for all - inflated locks, with the normal locking mechanism as the fallback handler. - This option is disabled by default. Options related to RTM are available - only on x86 CPUs that support Transactional Synchronization Extensions (TSX). - - RTM is part of Intel's TSX, which is an x86 instruction set extension and - facilitates the creation of multithreaded applications. RTM introduces the - new instructions `XBEGIN`, `XABORT`, `XEND`, and `XTEST`. The `XBEGIN` and - `XEND` instructions enclose a set of instructions to run as a transaction. - If no conflict is found when running the transaction, then the memory and - register modifications are committed together at the `XEND` instruction. - The `XABORT` instruction can be used to explicitly abort a transaction and - the `XTEST` instruction checks if a set of instructions is being run in a - transaction. - - A lock on a transaction is inflated when another thread tries to access the - same transaction, thereby blocking the thread that didn't originally - request access to the transaction. RTM requires that a fallback set of - operations be specified in case a transaction aborts or fails. An RTM lock - is a lock that has been delegated to the TSX's system. - - RTM improves performance for highly contended locks with low conflict in a - critical region (which is code that must not be accessed by more than one - thread concurrently). RTM also improves the performance of coarse-grain - locking, which typically doesn't perform well in multithreaded - applications. (Coarse-grain locking is the strategy of holding locks for - long periods to minimize the overhead of taking and releasing locks, while - fine-grained locking is the strategy of trying to achieve maximum - parallelism by locking only when necessary and unlocking as soon as - possible.) Also, for lightly contended locks that are used by different - threads, RTM can reduce false cache line sharing, also known as cache line - ping-pong. This occurs when multiple threads from different processors are - accessing different resources, but the resources share the same cache line. - As a result, the processors repeatedly invalidate the cache lines of other - processors, which forces them to read from main memory instead of their - cache. +No documented java options have been removed in JDK @@VERSION_SPECIFICATION@@. For the lists and descriptions of options removed in previous releases see the *Removed Java Options* section in: +- [The `java` Command, Release 25](https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html) + - [The `java` Command, Release 24](https://docs.oracle.com/en/java/javase/24/docs/specs/man/java.html) - [The `java` Command, Release 23](https://docs.oracle.com/en/java/javase/23/docs/specs/man/java.html) From 92be7821f5d5cbf5fe0244b41b2b7b1ada898df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Tue, 10 Jun 2025 00:21:18 +0000 Subject: [PATCH 108/143] 8353950: Clipboard interaction on Windows is unstable 8332271: Reading data from the clipboard from multiple threads crashes the JVM Reviewed-by: abhiscxk, dnguyen --- .../sun/awt/datatransfer/SunClipboard.java | 22 ++---- .../classes/sun/awt/windows/WClipboard.java | 52 ++++++++++--- .../native/libawt/windows/awt_Clipboard.cpp | 45 +++++++---- .../ConcurrentClipboardAccessTest.java | 77 +++++++++++++++++++ 4 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 test/jdk/java/awt/Clipboard/ConcurrentClipboardAccessTest.java diff --git a/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java b/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java index 898cabd2855..edffbf59878 100644 --- a/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java +++ b/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -204,8 +204,9 @@ public abstract class SunClipboard extends Clipboard byte[] data = null; Transferable localeTransferable = null; + openClipboard(null); + try { - openClipboard(null); long[] formats = getClipboardFormats(); Long lFormat = DataTransferer.getInstance(). @@ -318,12 +319,7 @@ public abstract class SunClipboard extends Clipboard * @since 1.5 */ protected long[] getClipboardFormatsOpenClose() { - try { - openClipboard(null); - return getClipboardFormats(); - } finally { - closeClipboard(); - } + return getClipboardFormats(); } /** @@ -356,15 +352,7 @@ public abstract class SunClipboard extends Clipboard flavorListeners.add(listener); if (numberOfFlavorListeners++ == 0) { - long[] currentFormats = null; - try { - openClipboard(null); - currentFormats = getClipboardFormats(); - } catch (final IllegalStateException ignored) { - } finally { - closeClipboard(); - } - this.currentFormats = currentFormats; + this.currentFormats = getClipboardFormats(); registerClipboardViewerChecked(); } diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java b/src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java index e16b1805295..110dfb8d637 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WClipboard.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -29,7 +29,9 @@ import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; +import java.lang.System.Logger.Level; import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; import sun.awt.datatransfer.DataTransferer; import sun.awt.datatransfer.SunClipboard; @@ -51,8 +53,12 @@ final class WClipboard extends SunClipboard { private boolean isClipboardViewerRegistered; + private final ReentrantLock clipboardLocked = new ReentrantLock(); + WClipboard() { super("System"); + // Register java side of the clipboard with the native side + registerClipboard(); } @Override @@ -104,18 +110,42 @@ final class WClipboard extends SunClipboard { /** * Call the Win32 OpenClipboard function. If newOwner is non-null, - * we also call EmptyClipboard and take ownership. + * we also call EmptyClipboard and take ownership. If this method call + * succeeds, it must be followed by a call to {@link #closeClipboard()}. * * @throws IllegalStateException if the clipboard has not been opened */ @Override - public native void openClipboard(SunClipboard newOwner) throws IllegalStateException; + public void openClipboard(SunClipboard newOwner) throws IllegalStateException { + if (!clipboardLocked.tryLock()) { + throw new IllegalStateException("Failed to acquire clipboard lock"); + } + try { + openClipboard0(newOwner); + } catch (IllegalStateException ex) { + clipboardLocked.unlock(); + throw ex; + } + } + /** * Call the Win32 CloseClipboard function if we have clipboard ownership, * does nothing if we have not ownership. */ @Override - public native void closeClipboard(); + public void closeClipboard() { + if (clipboardLocked.isLocked()) { + try { + closeClipboard0(); + } finally { + clipboardLocked.unlock(); + } + } + } + + private native void openClipboard0(SunClipboard newOwner) throws IllegalStateException; + private native void closeClipboard0(); + /** * Call the Win32 SetClipboardData function. */ @@ -157,16 +187,12 @@ final class WClipboard extends SunClipboard { return; } - long[] formats = null; try { - openClipboard(null); - formats = getClipboardFormats(); - } catch (IllegalStateException exc) { - // do nothing to handle the exception, call checkChange(null) - } finally { - closeClipboard(); + long[] formats = getClipboardFormats(); + checkChange(formats); + } catch (Throwable ex) { + System.getLogger(WClipboard.class.getName()).log(Level.DEBUG, "Failed to process handleContentsChanged", ex); } - checkChange(formats); } /** @@ -214,4 +240,6 @@ final class WClipboard extends SunClipboard { } }; } + + private native void registerClipboard(); } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp index 2aadcd5a08c..1232f189ca2 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Clipboard.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -69,9 +69,8 @@ void AwtClipboard::RegisterClipboardViewer(JNIEnv *env, jobject jclipboard) { return; } - if (theCurrentClipboard == NULL) { - theCurrentClipboard = env->NewGlobalRef(jclipboard); - } + DASSERT(AwtClipboard::theCurrentClipboard != NULL); + DASSERT(env->IsSameObject(AwtClipboard::theCurrentClipboard, jclipboard)); jclass cls = env->GetObjectClass(jclipboard); AwtClipboard::handleContentsChangedMID = @@ -128,11 +127,13 @@ Java_sun_awt_windows_WClipboard_init(JNIEnv *env, jclass cls) * Signature: (Lsun/awt/windows/WClipboard;)V */ JNIEXPORT void JNICALL -Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self, +Java_sun_awt_windows_WClipboard_openClipboard0(JNIEnv *env, jobject self, jobject newOwner) { TRY; + DASSERT(AwtClipboard::theCurrentClipboard != NULL); + DASSERT(newOwner == NULL || env->IsSameObject(AwtClipboard::theCurrentClipboard, newOwner)); DASSERT(::GetOpenClipboardWindow() != AwtToolkit::GetInstance().GetHWnd()); if (!::OpenClipboard(AwtToolkit::GetInstance().GetHWnd())) { @@ -142,10 +143,6 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self, } if (newOwner != NULL) { AwtClipboard::GetOwnership(); - if (AwtClipboard::theCurrentClipboard != NULL) { - env->DeleteGlobalRef(AwtClipboard::theCurrentClipboard); - } - AwtClipboard::theCurrentClipboard = env->NewGlobalRef(newOwner); } CATCH_BAD_ALLOC; @@ -157,7 +154,7 @@ Java_sun_awt_windows_WClipboard_openClipboard(JNIEnv *env, jobject self, * Signature: ()V */ JNIEXPORT void JNICALL -Java_sun_awt_windows_WClipboard_closeClipboard(JNIEnv *env, jobject self) +Java_sun_awt_windows_WClipboard_closeClipboard0(JNIEnv *env, jobject self) { TRY; @@ -297,23 +294,25 @@ Java_sun_awt_windows_WClipboard_getClipboardFormats { TRY; - DASSERT(::GetOpenClipboardWindow() == AwtToolkit::GetInstance().GetHWnd()); + unsigned int cFormats = 128; // Allocate enough space to hold all + unsigned int pcFormatsOut = 0; + unsigned int lpuiFormats[128] = { 0 }; - jsize nFormats = ::CountClipboardFormats(); - jlongArray formats = env->NewLongArray(nFormats); + VERIFY(::GetUpdatedClipboardFormats(lpuiFormats, 128, &pcFormatsOut)); + + jlongArray formats = env->NewLongArray(pcFormatsOut); if (formats == NULL) { throw std::bad_alloc(); } - if (nFormats == 0) { + if (pcFormatsOut == 0) { return formats; } jboolean isCopy; jlong *lFormats = env->GetLongArrayElements(formats, &isCopy), *saveFormats = lFormats; - UINT num = 0; - for (jsize i = 0; i < nFormats; i++, lFormats++) { - *lFormats = num = ::EnumClipboardFormats(num); + for (unsigned int i = 0; i < pcFormatsOut; i++, lFormats++) { + *lFormats = lpuiFormats[i]; } env->ReleaseLongArrayElements(formats, saveFormats, 0); @@ -478,4 +477,16 @@ Java_sun_awt_windows_WClipboard_getClipboardData CATCH_BAD_ALLOC_RET(NULL); } +/* + * Class: sun_awt_windows_WClipboard + * Method: registerClipboard + * Signature: ()V + */ +JNIEXPORT void JNICALL +Java_sun_awt_windows_WClipboard_registerClipboard(JNIEnv *env, jobject self) +{ + DASSERT(AwtClipboard::theCurrentClipboard == NULL); + AwtClipboard::theCurrentClipboard = env->NewGlobalRef(self); +} + } /* extern "C" */ diff --git a/test/jdk/java/awt/Clipboard/ConcurrentClipboardAccessTest.java b/test/jdk/java/awt/Clipboard/ConcurrentClipboardAccessTest.java new file mode 100644 index 00000000000..db98b06ea9d --- /dev/null +++ b/test/jdk/java/awt/Clipboard/ConcurrentClipboardAccessTest.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + + /* + @test + @bug 8332271 + @summary tests that concurrent access to the clipboard does not crash the JVM + @key headful + @requires (os.family == "windows") + @run main ConcurrentClipboardAccessTest + */ +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; + +public class ConcurrentClipboardAccessTest { + + public static void main(String[] args) { + Thread clipboardLoader1 = new Thread(new ClipboardLoader()); + clipboardLoader1.setDaemon(true); + clipboardLoader1.start(); + Thread clipboardLoader2 = new Thread(new ClipboardLoader()); + clipboardLoader2.setDaemon(true); + clipboardLoader2.start(); + long start = System.currentTimeMillis(); + while (true) { + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + long now = System.currentTimeMillis(); + if ((now - start) > (10L * 1000L)) { + break; + } + } + // Test is considered successful if the concurrent repeated reading + // from clipboard succeeds for the allotted time and the JVM does not + // crash. + System.out.println("Shutdown normally"); + } + + public static class ClipboardLoader implements Runnable { + + @Override + public void run() { + final Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + while (true) { + try { + if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + systemClipboard.getData(DataFlavor.stringFlavor); + } + } catch (Exception ignored) { + } + } + } + } +} From ca7b885873712a5ae503cb82c915d709034a69f7 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 10 Jun 2025 06:15:13 +0000 Subject: [PATCH 109/143] 8358749: Fix input checks in Vector API intrinsics Co-authored-by: Vladimir Ivanov Reviewed-by: vlivanov, sviswanathan --- src/hotspot/share/opto/vectorIntrinsics.cpp | 79 +++++++++++++-------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index 13acc0469eb..5ff2590a190 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -318,8 +318,10 @@ bool LibraryCallKit::inline_vector_nary_operation(int n) { const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(4))->isa_int(); - if (opr == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - !opr->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (opr == nullptr || !opr->is_con() || + vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con()) { log_if_needed(" ** missing constant: opr=%s vclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -587,7 +589,11 @@ bool LibraryCallKit::inline_vector_mask_operation() { const TypeInt* vlen = gvn().type(argument(3))->isa_int(); Node* mask = argument(4); - if (mask_klass == nullptr || elem_klass == nullptr || mask->is_top() || vlen == nullptr) { + if (mask_klass == nullptr || mask_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + oper == nullptr || !oper->is_con() || + mask->is_top()) { return false; // dead code } @@ -647,9 +653,11 @@ bool LibraryCallKit::inline_vector_frombits_coerced() { // MODE_BITS_COERCED_LONG_TO_MASK for VectorMask.fromLong operation. const TypeInt* mode = gvn().type(argument(5))->isa_int(); - if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || mode == nullptr || - bits_type == nullptr || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || - !vlen->is_con() || !mode->is_con()) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + bits_type == nullptr || + mode == nullptr || !mode->is_con()) { log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s bitwise=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -775,8 +783,10 @@ bool LibraryCallKit::inline_vector_mem_operation(bool is_store) { const TypeInt* vlen = gvn().type(argument(2))->isa_int(); const TypeInt* from_ms = gvn().type(argument(6))->isa_int(); - if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || !from_ms->is_con() || - vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + from_ms == nullptr || !from_ms->is_con()) { log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s from_ms=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -983,9 +993,11 @@ bool LibraryCallKit::inline_vector_mem_masked_operation(bool is_store) { const TypeInt* vlen = gvn().type(argument(3))->isa_int(); const TypeInt* from_ms = gvn().type(argument(7))->isa_int(); - if (vector_klass == nullptr || mask_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - vector_klass->const_oop() == nullptr || mask_klass->const_oop() == nullptr || from_ms == nullptr || - elem_klass->const_oop() == nullptr || !vlen->is_con() || !from_ms->is_con()) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + mask_klass == nullptr || mask_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + from_ms == nullptr || !from_ms->is_con()) { log_if_needed(" ** missing constant: vclass=%s mclass=%s etype=%s vlen=%s from_ms=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -1222,8 +1234,10 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { const TypeInt* vlen = gvn().type(argument(3))->isa_int(); const TypeInstPtr* vector_idx_klass = gvn().type(argument(4))->isa_instptr(); - if (vector_klass == nullptr || elem_klass == nullptr || vector_idx_klass == nullptr || vlen == nullptr || - vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || vector_idx_klass->const_oop() == nullptr || !vlen->is_con()) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + vector_idx_klass == nullptr || vector_idx_klass->const_oop() == nullptr) { log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s viclass=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(2)->Opcode()], @@ -1409,8 +1423,10 @@ bool LibraryCallKit::inline_vector_reduction() { const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(4))->isa_int(); - if (opr == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - !opr->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (opr == nullptr || !opr->is_con() || + vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con()) { log_if_needed(" ** missing constant: opr=%s vclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -1547,8 +1563,10 @@ bool LibraryCallKit::inline_vector_test() { const TypeInstPtr* elem_klass = gvn().type(argument(2))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(3))->isa_int(); - if (cond == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - !cond->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (cond == nullptr || !cond->is_con() || + vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con()) { log_if_needed(" ** missing constant: cond=%s vclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -2505,10 +2523,10 @@ bool LibraryCallKit::inline_vector_extract() { const TypeInt* vlen = gvn().type(argument(2))->isa_int(); const TypeInt* idx = gvn().type(argument(4))->isa_int(); - if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || idx == nullptr) { - return false; // dead code - } - if (vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() || + idx == nullptr || !idx->is_con()) { log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -2811,9 +2829,11 @@ bool LibraryCallKit::inline_vector_compress_expand() { const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(4))->isa_int(); - if (vector_klass == nullptr || elem_klass == nullptr || mask_klass == nullptr || vlen == nullptr || - vector_klass->const_oop() == nullptr || mask_klass->const_oop() == nullptr || - elem_klass->const_oop() == nullptr || !vlen->is_con() || !opr->is_con()) { + if (opr == nullptr || !opr->is_con() || + vector_klass == nullptr || vector_klass->const_oop() == nullptr || + mask_klass == nullptr || mask_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con()) { log_if_needed(" ** missing constant: opr=%s vclass=%s mclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -2892,9 +2912,9 @@ bool LibraryCallKit::inline_index_vector() { const TypeInstPtr* elem_klass = gvn().type(argument(1))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(2))->isa_int(); - if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - vector_klass->const_oop() == nullptr || !vlen->is_con() || - elem_klass->const_oop() == nullptr) { + if (vector_klass == nullptr || vector_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con() ) { log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], @@ -3026,8 +3046,9 @@ bool LibraryCallKit::inline_index_partially_in_upper_range() { const TypeInstPtr* elem_klass = gvn().type(argument(1))->isa_instptr(); const TypeInt* vlen = gvn().type(argument(2))->isa_int(); - if (mask_klass == nullptr || elem_klass == nullptr || vlen == nullptr || - mask_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) { + if (mask_klass == nullptr || mask_klass->const_oop() == nullptr || + elem_klass == nullptr || elem_klass->const_oop() == nullptr || + vlen == nullptr || !vlen->is_con()) { log_if_needed(" ** missing constant: mclass=%s etype=%s vlen=%s", NodeClassNames[argument(0)->Opcode()], NodeClassNames[argument(1)->Opcode()], From 7c9c8ba363521a7bfb58e1a8285459f717769889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Tue, 10 Jun 2025 07:27:10 +0000 Subject: [PATCH 110/143] 8356780: PhaseMacroExpand::_has_locks is unused Reviewed-by: mhaessig, chagedorn, kvn, mchevalier --- src/hotspot/share/opto/macro.cpp | 2 -- src/hotspot/share/opto/macro.hpp | 7 ++----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index c6ed2411fe3..8f21ee13e79 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -2407,7 +2407,6 @@ void PhaseMacroExpand::eliminate_macro_nodes() { } } // Next, attempt to eliminate allocations - _has_locks = false; progress = true; while (progress) { progress = false; @@ -2431,7 +2430,6 @@ void PhaseMacroExpand::eliminate_macro_nodes() { case Node::Class_Lock: case Node::Class_Unlock: assert(!n->as_AbstractLock()->is_eliminated(), "sanity"); - _has_locks = true; break; case Node::Class_ArrayCopy: break; diff --git a/src/hotspot/share/opto/macro.hpp b/src/hotspot/share/opto/macro.hpp index 4654789a290..7f27688a57a 100644 --- a/src/hotspot/share/opto/macro.hpp +++ b/src/hotspot/share/opto/macro.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -83,9 +83,6 @@ private: // projections extracted from a call node CallProjections _callprojs; - // Additional data collected during macro expansion - bool _has_locks; - void expand_allocate(AllocateNode *alloc); void expand_allocate_array(AllocateArrayNode *alloc); void expand_allocate_common(AllocateNode* alloc, @@ -199,7 +196,7 @@ private: Node* make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node* ctl, Node* mem, BasicType ft, const Type *ftype, AllocateNode *alloc); public: - PhaseMacroExpand(PhaseIterGVN &igvn) : Phase(Macro_Expand), _igvn(igvn), _has_locks(false) { + PhaseMacroExpand(PhaseIterGVN &igvn) : Phase(Macro_Expand), _igvn(igvn) { _igvn.set_delay_transform(true); } From 3ff83ec49e561c44dd99508364b8ba068274b63a Mon Sep 17 00:00:00 2001 From: Varada M Date: Tue, 10 Jun 2025 08:17:52 +0000 Subject: [PATCH 111/143] 8358159: Empty mode/padding in cipher transformations Reviewed-by: amitkumar, valeriep --- .../share/classes/javax/crypto/Cipher.java | 18 ++++-- .../crypto/Cipher/TestEmptyModePadding.java | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 test/jdk/javax/crypto/Cipher/TestEmptyModePadding.java diff --git a/src/java.base/share/classes/javax/crypto/Cipher.java b/src/java.base/share/classes/javax/crypto/Cipher.java index 3de732c6687..e729a984811 100644 --- a/src/java.base/share/classes/javax/crypto/Cipher.java +++ b/src/java.base/share/classes/javax/crypto/Cipher.java @@ -454,19 +454,25 @@ public class Cipher { String[] parts = tokenizeTransformation(transformation); String alg = parts[0]; - String mode = parts[1]; - String pad = parts[2]; + String mode = (parts[1].length() == 0 ? null : parts[1]); + String pad = (parts[2].length() == 0 ? null : parts[2]); - if ((mode.length() == 0) && (pad.length() == 0)) { + if ((mode == null) && (pad == null)) { // Algorithm only Transform tr = new Transform(alg, "", null, null); return Collections.singletonList(tr); } else { // Algorithm w/ at least mode or padding or both List list = new ArrayList<>(4); - list.add(new Transform(alg, "/" + mode + "/" + pad, null, null)); - list.add(new Transform(alg, "/" + mode, null, pad)); - list.add(new Transform(alg, "//" + pad, mode, null)); + if ((mode != null) && (pad != null)) { + list.add(new Transform(alg, "/" + mode + "/" + pad, null, null)); + } + if (mode != null) { + list.add(new Transform(alg, "/" + mode, null, pad)); + } + if (pad != null) { + list.add(new Transform(alg, "//" + pad, mode, null)); + } list.add(new Transform(alg, "", mode, pad)); return list; } diff --git a/test/jdk/javax/crypto/Cipher/TestEmptyModePadding.java b/test/jdk/javax/crypto/Cipher/TestEmptyModePadding.java new file mode 100644 index 00000000000..bd36e7f3ca7 --- /dev/null +++ b/test/jdk/javax/crypto/Cipher/TestEmptyModePadding.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 IBM Corporation. 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. + */ + +/* + * @test + * @bug 8358159 + * @summary test that the Cipher.getInstance() handles + * transformations with empty mode and/or padding + * @run main TestEmptyModePadding + */ + + +import java.security.*; +import javax.crypto.*; + +public class TestEmptyModePadding { + + public static void main(String[] args) throws Exception { + Provider provider = Security.getProvider(System.getProperty("test.provider.name", "SunJCE")); + + test("AES", provider); + test("AES/ECB/PKCS5Padding", provider); + test("AES//PKCS5Padding", provider); // Empty mode + test("AES/CBC/", provider); // Empty padding + test("AES/ /NoPadding", provider); // Mode is a space + test("AES/CBC/ ", provider); // Padding is a space + test("AES/ / ", provider); // Both mode and padding are spaces + test("AES//", provider); // Both mode and padding are missing + + } + + private static void test(String transformation, Provider provider) throws Exception { + Cipher c = Cipher.getInstance(transformation, provider); + } +} From 0582bd290d5a8b6344ae7ada36492cc2f33df050 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Tue, 10 Jun 2025 11:01:50 +0000 Subject: [PATCH 112/143] 8357639: DigestEchoClient fails intermittently due to: java.io.IOException: Data received while in pool Reviewed-by: djelinski --- .../jdk/internal/net/http/SocketTube.java | 78 +++++++++---------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java index cc083d7e066..ef935b008d3 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java @@ -432,7 +432,7 @@ final class SocketTube implements FlowTube { } void signalError(Throwable error) { - debug.log(() -> "write error: " + error); + if (debug.on()) debug.log(() -> "write error: " + error); if (Log.channel()) { Log.logChannel("Failed to write to channel ({0}: {1})", channelDescr(), error); @@ -557,34 +557,15 @@ final class SocketTube implements FlowTube { implements Flow.Publisher> { private final InternalReadSubscription subscriptionImpl = new InternalReadSubscription(); - ConcurrentLinkedQueue pendingSubscriptions = new ConcurrentLinkedQueue<>(); + private final AtomicReference pendingSubscriptions + = new AtomicReference<>(); private volatile ReadSubscription subscription; @Override public void subscribe(Flow.Subscriber> s) { Objects.requireNonNull(s); - - TubeSubscriber sub = FlowTube.asTubeSubscriber(s); - ReadSubscription previous; - while ((previous = pendingSubscriptions.poll()) != null) { - if (debug.on()) - debug.log("read publisher: dropping pending subscriber: " - + previous.subscriber); - previous.errorRef.compareAndSet(null, errorRef.get()); - // make sure no data will be routed to the old subscriber. - previous.stopReading(); - previous.signalOnSubscribe(); - if (subscriptionImpl.completed) { - previous.signalCompletion(); - } else { - previous.subscriber.dropSubscription(); - } - } - ReadSubscription target = new ReadSubscription(subscriptionImpl, sub); - pendingSubscriptions.offer(target); - - if (debug.on()) debug.log("read publisher got new subscriber: " + s); - subscriptionImpl.signalSubscribe(); + if (debug.on()) debug.log("Offering new subscriber: %s", s); + subscriptionImpl.offer(FlowTube.asTubeSubscriber(s)); debugState("leaving read.subscribe: "); } @@ -676,7 +657,6 @@ final class SocketTube implements FlowTube { */ synchronized void stopReading() { stopped = true; - impl.demand.reset(); } synchronized boolean tryDecrementDemand() { @@ -733,14 +713,7 @@ final class SocketTube implements FlowTube { assert client.isSelectorThread(); debug.log("subscribe event raised"); if (Log.channel()) Log.logChannel("Start reading from {0}", channelDescr()); - readScheduler.runOrSchedule(); - if (readScheduler.isStopped() || completed) { - // if already completed or stopped we can handle any - // pending connection directly from here. - if (debug.on()) - debug.log("handling pending subscription when completed"); - handlePending(); - } + handlePending(); } @@ -953,22 +926,46 @@ final class SocketTube implements FlowTube { } } - boolean handlePending() { - ReadSubscription pending; + synchronized void offer(TubeSubscriber sub) { + ReadSubscription target = new ReadSubscription(this, sub); + ReadSubscription previous = pendingSubscriptions.getAndSet(target); + if (previous != null) { + if (debug.on()) + debug.log("read publisher: dropping pending subscriber: " + + previous.subscriber); + previous.errorRef.compareAndSet(null, errorRef.get()); + // make sure no data will be routed to the old subscriber. + previous.stopReading(); + previous.signalOnSubscribe(); + if (completed) { + previous.signalCompletion(); + } else { + previous.subscriber.dropSubscription(); + } + } + if (debug.on()) { + debug.log("read publisher got new subscriber: " + sub); + } + signalSubscribe(); + } + + synchronized boolean handlePending() { boolean subscribed = false; - while ((pending = pendingSubscriptions.poll()) != null) { + ReadSubscription current = subscription; + ReadSubscription pending = pendingSubscriptions.getAndSet(null); + if (pending != null) { subscribed = true; if (debug.on()) debug.log("handling pending subscription for %s", pending.subscriber); - ReadSubscription current = subscription; - if (current != null && current != pending && !completed) { - debug.log("dropping pending subscription for current %s", + if (current != null && !completed) { + debug.log("dropping subscription for current %s", current.subscriber); + current.stopReading(); current.subscriber.dropSubscription(); } if (debug.on()) debug.log("read demand reset to 0"); - subscriptionImpl.demand.reset(); // subscriber will increase demand if it needs to. + demand.reset(); // subscriber will increase demand if it needs to. pending.errorRef.compareAndSet(null, errorRef.get()); if (!readScheduler.isStopped()) { subscription = pending; @@ -1335,7 +1332,6 @@ final class SocketTube implements FlowTube { writePublisher.subscribe(this); } - @Override public String toString() { return dbgString(); From a2f99fd88bd03337e1ba73b413ffe4e39f3584cf Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Tue, 10 Jun 2025 14:19:19 +0000 Subject: [PATCH 113/143] 8354383: C2: enable sinking of Type nodes out of loop Reviewed-by: chagedorn, thartmann --- src/hotspot/share/opto/loopopts.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 912d8452d21..ddeb5f84d49 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1676,6 +1676,10 @@ bool PhaseIdealLoop::safe_for_if_replacement(const Node* dom) const { // like various versions of induction variable+offset. Clone the // computation per usage to allow it to sink out of the loop. void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { + bool is_raw_to_oop_cast = n->is_ConstraintCast() && + n->in(1)->bottom_type()->isa_rawptr() && + !n->bottom_type()->isa_rawptr(); + if (has_ctrl(n) && !n->is_Phi() && !n->is_Bool() && @@ -1685,7 +1689,9 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { !n->is_OpaqueNotNull() && !n->is_OpaqueInitializedAssertionPredicate() && !n->is_OpaqueTemplateAssertionPredicate() && - !n->is_Type()) { + !is_raw_to_oop_cast && // don't extend live ranges of raw oops + (KillPathsReachableByDeadTypeNode || !n->is_Type()) + ) { Node *n_ctrl = get_ctrl(n); IdealLoopTree *n_loop = get_loop(n_ctrl); From 500a3a2d0af0a3f7cf58b909bbbc2aa25926d8b4 Mon Sep 17 00:00:00 2001 From: Calvin Cheung Date: Tue, 10 Jun 2025 16:20:33 +0000 Subject: [PATCH 114/143] 8358799: Refactor os::jvm_path() Reviewed-by: dholmes, jsjolen --- src/hotspot/os/aix/os_aix.cpp | 63 ---------------------- src/hotspot/os/bsd/os_bsd.cpp | 77 -------------------------- src/hotspot/os/linux/os_linux.cpp | 71 ------------------------ src/hotspot/os/posix/os_posix.cpp | 89 +++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 211 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index e7b401701ac..9c6218aee16 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -1261,69 +1261,6 @@ void os::pd_print_cpu_info(outputStream* st, char* buf, size_t buflen) { // Nothing to do beyond of what os::print_cpu_info() does. } -static char saved_jvm_path[MAXPATHLEN] = {0}; - -// Find the full path to the current module, libjvm.so. -void os::jvm_path(char *buf, jint buflen) { - // Error checking. - if (buflen < MAXPATHLEN) { - assert(false, "must use a large-enough buffer"); - buf[0] = '\0'; - return; - } - // Lazy resolve the path to current module. - if (saved_jvm_path[0] != 0) { - strcpy(buf, saved_jvm_path); - return; - } - - Dl_info dlinfo; - int ret = dladdr(CAST_FROM_FN_PTR(void *, os::jvm_path), &dlinfo); - assert(ret != 0, "cannot locate libjvm"); - char* rp = os::realpath((char *)dlinfo.dli_fname, buf, buflen); - assert(rp != nullptr, "error in realpath(): maybe the 'path' argument is too long?"); - - // If executing unit tests we require JAVA_HOME to point to the real JDK. - if (Arguments::executing_unit_tests()) { - // Look for JAVA_HOME in the environment. - char* java_home_var = ::getenv("JAVA_HOME"); - if (java_home_var != nullptr && java_home_var[0] != 0) { - - // Check the current module name "libjvm.so". - const char* p = strrchr(buf, '/'); - if (p == nullptr) { - return; - } - assert(strstr(p, "/libjvm") == p, "invalid library name"); - - stringStream ss(buf, buflen); - rp = os::realpath(java_home_var, buf, buflen); - if (rp == nullptr) { - return; - } - - assert((int)strlen(buf) < buflen, "Ran out of buffer room"); - ss.print("%s/lib", buf); - - if (0 == access(buf, F_OK)) { - // Use current module name "libjvm.so" - ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX); - assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0, - "buf has been truncated"); - } else { - // Go back to path of .so - rp = os::realpath((char *)dlinfo.dli_fname, buf, buflen); - if (rp == nullptr) { - return; - } - } - } - } - - strncpy(saved_jvm_path, buf, sizeof(saved_jvm_path)); - saved_jvm_path[sizeof(saved_jvm_path) - 1] = '\0'; -} - //////////////////////////////////////////////////////////////////////////////// // Virtual Memory diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 3535d027dbc..9f77d5a4bde 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1482,83 +1482,6 @@ void os::print_memory_info(outputStream* st) { st->cr(); } -static char saved_jvm_path[MAXPATHLEN] = {0}; - -// Find the full path to the current module, libjvm -void os::jvm_path(char *buf, jint buflen) { - // Error checking. - if (buflen < MAXPATHLEN) { - assert(false, "must use a large-enough buffer"); - buf[0] = '\0'; - return; - } - // Lazy resolve the path to current module. - if (saved_jvm_path[0] != 0) { - strcpy(buf, saved_jvm_path); - return; - } - - char dli_fname[MAXPATHLEN]; - dli_fname[0] = '\0'; - bool ret = dll_address_to_library_name( - CAST_FROM_FN_PTR(address, os::jvm_path), - dli_fname, sizeof(dli_fname), nullptr); - assert(ret, "cannot locate libjvm"); - char *rp = nullptr; - if (ret && dli_fname[0] != '\0') { - rp = os::realpath(dli_fname, buf, buflen); - } - if (rp == nullptr) { - return; - } - - // If executing unit tests we require JAVA_HOME to point to the real JDK. - if (Arguments::executing_unit_tests()) { - // Look for JAVA_HOME in the environment. - char* java_home_var = ::getenv("JAVA_HOME"); - if (java_home_var != nullptr && java_home_var[0] != 0) { - - // Check the current module name "libjvm" - const char* p = strrchr(buf, '/'); - assert(strstr(p, "/libjvm") == p, "invalid library name"); - - stringStream ss(buf, buflen); - rp = os::realpath(java_home_var, buf, buflen); - if (rp == nullptr) { - return; - } - - assert((int)strlen(buf) < buflen, "Ran out of buffer space"); - // Add the appropriate library and JVM variant subdirs - ss.print("%s/lib/%s", buf, Abstract_VM_Version::vm_variant()); - - if (0 != access(buf, F_OK)) { - ss.reset(); - ss.print("%s/lib", buf); - } - - // If the path exists within JAVA_HOME, add the JVM library name - // to complete the path to JVM being overridden. Otherwise fallback - // to the path to the current library. - if (0 == access(buf, F_OK)) { - // Use current module name "libjvm" - ss.print("/libjvm%s", JNI_LIB_SUFFIX); - assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0, - "buf has been truncated"); - } else { - // Fall back to path of current library - rp = os::realpath(dli_fname, buf, buflen); - if (rp == nullptr) { - return; - } - } - } - } - - strncpy(saved_jvm_path, buf, MAXPATHLEN); - saved_jvm_path[MAXPATHLEN - 1] = '\0'; -} - //////////////////////////////////////////////////////////////////////////////// // Virtual Memory diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 3aa83895ff1..ff2195d3839 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -2746,77 +2746,6 @@ void os::get_summary_cpu_info(char* cpuinfo, size_t length) { #endif } -static char saved_jvm_path[MAXPATHLEN] = {0}; - -// Find the full path to the current module, libjvm.so -void os::jvm_path(char *buf, jint buflen) { - // Error checking. - if (buflen < MAXPATHLEN) { - assert(false, "must use a large-enough buffer"); - buf[0] = '\0'; - return; - } - // Lazy resolve the path to current module. - if (saved_jvm_path[0] != 0) { - strcpy(buf, saved_jvm_path); - return; - } - - char dli_fname[MAXPATHLEN]; - dli_fname[0] = '\0'; - bool ret = dll_address_to_library_name( - CAST_FROM_FN_PTR(address, os::jvm_path), - dli_fname, sizeof(dli_fname), nullptr); - assert(ret, "cannot locate libjvm"); - char *rp = nullptr; - if (ret && dli_fname[0] != '\0') { - rp = os::realpath(dli_fname, buf, buflen); - } - if (rp == nullptr) { - return; - } - - // If executing unit tests we require JAVA_HOME to point to the real JDK. - if (Arguments::executing_unit_tests()) { - // Look for JAVA_HOME in the environment. - char* java_home_var = ::getenv("JAVA_HOME"); - if (java_home_var != nullptr && java_home_var[0] != 0) { - - // Check the current module name "libjvm.so". - const char* p = strrchr(buf, '/'); - if (p == nullptr) { - return; - } - assert(strstr(p, "/libjvm") == p, "invalid library name"); - - stringStream ss(buf, buflen); - rp = os::realpath(java_home_var, buf, buflen); - if (rp == nullptr) { - return; - } - - assert((int)strlen(buf) < buflen, "Ran out of buffer room"); - ss.print("%s/lib", buf); - - if (0 == access(buf, F_OK)) { - // Use current module name "libjvm.so" - ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX); - assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0, - "buf has been truncated"); - } else { - // Go back to path of .so - rp = os::realpath(dli_fname, buf, buflen); - if (rp == nullptr) { - return; - } - } - } - } - - strncpy(saved_jvm_path, buf, MAXPATHLEN); - saved_jvm_path[MAXPATHLEN - 1] = '\0'; -} - //////////////////////////////////////////////////////////////////////////////// // Virtual Memory diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index 448ebce620a..68bdec3875c 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -1060,6 +1060,95 @@ bool os::same_files(const char* file1, const char* file2) { return is_same; } +static char saved_jvm_path[MAXPATHLEN] = {0}; + +// Find the full path to the current module, libjvm.so +void os::jvm_path(char *buf, jint buflen) { + // Error checking. + if (buflen < MAXPATHLEN) { + assert(false, "must use a large-enough buffer"); + buf[0] = '\0'; + return; + } + // Lazy resolve the path to current module. + if (saved_jvm_path[0] != 0) { + strcpy(buf, saved_jvm_path); + return; + } + + char* fname; +#ifdef AIX + Dl_info dlinfo; + int ret = dladdr(CAST_FROM_FN_PTR(void *, os::jvm_path), &dlinfo); + assert(ret != 0, "cannot locate libjvm"); + if (ret == 0) { + return; + } + fname = dlinfo.dli_fname; +#else + char dli_fname[MAXPATHLEN]; + dli_fname[0] = '\0'; + bool ret = dll_address_to_library_name( + CAST_FROM_FN_PTR(address, os::jvm_path), + dli_fname, sizeof(dli_fname), nullptr); + assert(ret, "cannot locate libjvm"); + if (!ret) { + return; + } + fname = dli_fname; +#endif // AIX + char* rp = nullptr; + if (fname[0] != '\0') { + rp = os::realpath(dli_fname, buf, buflen); + } + if (rp == nullptr) { + return; + } + + // If executing unit tests we require JAVA_HOME to point to the real JDK. + if (Arguments::executing_unit_tests()) { + // Look for JAVA_HOME in the environment. + char* java_home_var = ::getenv("JAVA_HOME"); + if (java_home_var != nullptr && java_home_var[0] != 0) { + + // Check the current module name "libjvm.so". + const char* p = strrchr(buf, '/'); + if (p == nullptr) { + return; + } + assert(strstr(p, "/libjvm") == p, "invalid library name"); + + stringStream ss(buf, buflen); + rp = os::realpath(java_home_var, buf, buflen); + if (rp == nullptr) { + return; + } + + assert((int)strlen(buf) < buflen, "Ran out of buffer room"); + ss.print("%s/lib", buf); + + // If the path exists within JAVA_HOME, add the VM variant directory and JVM + // library name to complete the path to JVM being overridden. Otherwise fallback + // to the path to the current library. + if (0 == access(buf, F_OK)) { + // Use current module name "libjvm.so" + ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX); + assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0, + "buf has been truncated"); + } else { + // Go back to path of .so + rp = os::realpath(dli_fname, buf, buflen); + if (rp == nullptr) { + return; + } + } + } + } + + strncpy(saved_jvm_path, buf, MAXPATHLEN); + saved_jvm_path[MAXPATHLEN - 1] = '\0'; +} + // Called when creating the thread. The minimum stack sizes have already been calculated size_t os::Posix::get_initial_stack_size(ThreadType thr_type, size_t req_stack_size) { size_t stack_size; From 8f487d26c0f219d4df32be48ff1790e6f98d74a0 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Tue, 10 Jun 2025 19:05:08 +0000 Subject: [PATCH 115/143] 8358577: Test serviceability/jvmti/thread/GetCurrentContendedMonitor/contmon01/contmon01.java failed: unexpexcted monitor object Reviewed-by: cjplummer, syan, sspitsyn --- .../GetCurrentContendedMonitor/contmon01/contmon01.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/thread/GetCurrentContendedMonitor/contmon01/contmon01.java b/test/hotspot/jtreg/serviceability/jvmti/thread/GetCurrentContendedMonitor/contmon01/contmon01.java index 5bc4d4fd2b2..a4912c7e6d2 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/thread/GetCurrentContendedMonitor/contmon01/contmon01.java +++ b/test/hotspot/jtreg/serviceability/jvmti/thread/GetCurrentContendedMonitor/contmon01/contmon01.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -154,9 +154,9 @@ class contmon01Task implements Runnable { System.out.println("check #2 done"); System.out.println("notifying main thread"); + System.out.println("thread is going to loop while is true ..."); contmon01.startingBarrier = false; - System.out.println("thread is going to loop while is true ..."); int i = 0; int n = 1000; while (flag) { From 38b877e941918cc5f0463b256d4672d765d40302 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 10 Jun 2025 20:10:19 +0000 Subject: [PATCH 116/143] 8358294: Remove unnecessary GenAlignment Reviewed-by: iwalulya, tschatzl --- .../share/gc/parallel/parallelArguments.cpp | 11 ++- .../share/gc/parallel/parallelInitLogger.cpp | 2 - .../gc/parallel/parallelScavengeHeap.cpp | 6 +- src/hotspot/share/gc/parallel/psOldGen.cpp | 2 +- src/hotspot/share/gc/parallel/psYoungGen.cpp | 4 +- src/hotspot/share/gc/serial/serialHeap.cpp | 4 +- src/hotspot/share/gc/shared/gcArguments.hpp | 2 +- src/hotspot/share/gc/shared/genArguments.cpp | 77 +++++++++---------- src/hotspot/share/gc/shared/genArguments.hpp | 2 - src/hotspot/share/prims/whitebox.cpp | 26 ------- .../gtest/gc/shared/test_collectorPolicy.cpp | 2 +- .../TestMinAndInitialSurvivorRatioFlags.java | 10 +-- .../jtreg/gc/arguments/TestNewRatioFlag.java | 4 +- .../jtreg/gc/arguments/TestNewSizeFlags.java | 6 +- .../gc/arguments/TestSurvivorRatioFlag.java | 2 +- test/lib/jdk/test/whitebox/WhiteBox.java | 4 - 16 files changed, 59 insertions(+), 105 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelArguments.cpp b/src/hotspot/share/gc/parallel/parallelArguments.cpp index 2cddbafd871..cae6f9a93d5 100644 --- a/src/hotspot/share/gc/parallel/parallelArguments.cpp +++ b/src/hotspot/share/gc/parallel/parallelArguments.cpp @@ -98,15 +98,15 @@ void ParallelArguments::initialize() { FullGCForwarding::initialize_flags(heap_reserved_size_bytes()); } -// The alignment used for boundary between young gen and old gen -static size_t default_gen_alignment() { +// The alignment used for spaces in young gen and old gen +static size_t default_space_alignment() { return 64 * K * HeapWordSize; } void ParallelArguments::initialize_alignments() { // Initialize card size before initializing alignments CardTable::initialize_card_size(); - SpaceAlignment = GenAlignment = default_gen_alignment(); + SpaceAlignment = default_space_alignment(); HeapAlignment = compute_heap_alignment(); } @@ -123,9 +123,8 @@ void ParallelArguments::initialize_heap_flags_and_sizes() { // Can a page size be something else than a power of two? assert(is_power_of_2((intptr_t)page_sz), "must be a power of 2"); - size_t new_alignment = align_up(page_sz, GenAlignment); - if (new_alignment != GenAlignment) { - GenAlignment = new_alignment; + size_t new_alignment = align_up(page_sz, SpaceAlignment); + if (new_alignment != SpaceAlignment) { SpaceAlignment = new_alignment; // Redo everything from the start initialize_heap_flags_and_sizes_one_pass(); diff --git a/src/hotspot/share/gc/parallel/parallelInitLogger.cpp b/src/hotspot/share/gc/parallel/parallelInitLogger.cpp index 1d9c9f840ba..d7eb265d113 100644 --- a/src/hotspot/share/gc/parallel/parallelInitLogger.cpp +++ b/src/hotspot/share/gc/parallel/parallelInitLogger.cpp @@ -29,10 +29,8 @@ void ParallelInitLogger::print_heap() { log_info_p(gc, init)("Alignments:" " Space " EXACTFMT "," - " Generation " EXACTFMT "," " Heap " EXACTFMT, EXACTFMTARGS(SpaceAlignment), - EXACTFMTARGS(GenAlignment), EXACTFMTARGS(HeapAlignment)); GCInitLogger::print_heap(); } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 0235864992f..139a5fa52f1 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -69,8 +69,8 @@ jint ParallelScavengeHeap::initialize() { initialize_reserved_region(heap_rs); // Layout the reserved space for the generations. - ReservedSpace old_rs = heap_rs.first_part(MaxOldSize, GenAlignment); - ReservedSpace young_rs = heap_rs.last_part(MaxOldSize, GenAlignment); + ReservedSpace old_rs = heap_rs.first_part(MaxOldSize, SpaceAlignment); + ReservedSpace young_rs = heap_rs.last_part(MaxOldSize, SpaceAlignment); assert(young_rs.size() == MaxNewSize, "Didn't reserve all of the heap"); PSCardTable* card_table = new PSCardTable(_reserved); @@ -107,7 +107,7 @@ jint ParallelScavengeHeap::initialize() { new PSAdaptiveSizePolicy(eden_capacity, initial_promo_size, young_gen()->to_space()->capacity_in_bytes(), - GenAlignment, + SpaceAlignment, max_gc_pause_sec, GCTimeRatio ); diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp index 0d1c3b60672..bc25683e00f 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.cpp +++ b/src/hotspot/share/gc/parallel/psOldGen.cpp @@ -41,7 +41,7 @@ PSOldGen::PSOldGen(ReservedSpace rs, size_t initial_size, size_t min_size, _min_gen_size(min_size), _max_gen_size(max_size) { - initialize(rs, initial_size, GenAlignment); + initialize(rs, initial_size, SpaceAlignment); } void PSOldGen::initialize(ReservedSpace rs, size_t initial_size, size_t alignment) { diff --git a/src/hotspot/share/gc/parallel/psYoungGen.cpp b/src/hotspot/share/gc/parallel/psYoungGen.cpp index f9cb067411e..bf77a0dde93 100644 --- a/src/hotspot/share/gc/parallel/psYoungGen.cpp +++ b/src/hotspot/share/gc/parallel/psYoungGen.cpp @@ -47,7 +47,7 @@ PSYoungGen::PSYoungGen(ReservedSpace rs, size_t initial_size, size_t min_size, s _from_counters(nullptr), _to_counters(nullptr) { - initialize(rs, initial_size, GenAlignment); + initialize(rs, initial_size, SpaceAlignment); } void PSYoungGen::initialize_virtual_space(ReservedSpace rs, @@ -746,7 +746,7 @@ size_t PSYoungGen::available_to_live() { } size_t delta_in_bytes = unused_committed + delta_in_survivor; - delta_in_bytes = align_down(delta_in_bytes, GenAlignment); + delta_in_bytes = align_down(delta_in_bytes, SpaceAlignment); return delta_in_bytes; } diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 27d94593765..fbbaabf618a 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -188,8 +188,8 @@ jint SerialHeap::initialize() { initialize_reserved_region(heap_rs); - ReservedSpace young_rs = heap_rs.first_part(MaxNewSize, GenAlignment); - ReservedSpace old_rs = heap_rs.last_part(MaxNewSize, GenAlignment); + ReservedSpace young_rs = heap_rs.first_part(MaxNewSize, SpaceAlignment); + ReservedSpace old_rs = heap_rs.last_part(MaxNewSize, SpaceAlignment); _rem_set = new CardTableRS(_reserved); _rem_set->initialize(young_rs.base(), old_rs.base()); diff --git a/src/hotspot/share/gc/shared/gcArguments.hpp b/src/hotspot/share/gc/shared/gcArguments.hpp index 40b612d75a6..fff41e85d8c 100644 --- a/src/hotspot/share/gc/shared/gcArguments.hpp +++ b/src/hotspot/share/gc/shared/gcArguments.hpp @@ -35,7 +35,7 @@ extern size_t SpaceAlignment; class GCArguments { protected: - // Initialize HeapAlignment, SpaceAlignment, and extra alignments (E.g. GenAlignment) + // Initialize HeapAlignment, SpaceAlignment virtual void initialize_alignments() = 0; virtual void initialize_heap_flags_and_sizes(); virtual void initialize_size_info(); diff --git a/src/hotspot/share/gc/shared/genArguments.cpp b/src/hotspot/share/gc/shared/genArguments.cpp index 90617b1675f..9618c515b7d 100644 --- a/src/hotspot/share/gc/shared/genArguments.cpp +++ b/src/hotspot/share/gc/shared/genArguments.cpp @@ -42,17 +42,15 @@ size_t MaxOldSize = 0; // See more in JDK-8346005 size_t OldSize = ScaleForWordSize(4*M); -size_t GenAlignment = 0; - size_t GenArguments::conservative_max_heap_alignment() { return (size_t)Generation::GenGrain; } static size_t young_gen_size_lower_bound() { // The young generation must be aligned and have room for eden + two survivors - return align_up(3 * SpaceAlignment, GenAlignment); + return 3 * SpaceAlignment; } static size_t old_gen_size_lower_bound() { - return align_up(SpaceAlignment, GenAlignment); + return SpaceAlignment; } size_t GenArguments::scale_by_NewRatio_aligned(size_t base_size, size_t alignment) { @@ -69,23 +67,20 @@ static size_t bound_minus_alignment(size_t desired_size, void GenArguments::initialize_alignments() { // Initialize card size before initializing alignments CardTable::initialize_card_size(); - SpaceAlignment = GenAlignment = (size_t)Generation::GenGrain; + SpaceAlignment = (size_t)Generation::GenGrain; HeapAlignment = compute_heap_alignment(); } void GenArguments::initialize_heap_flags_and_sizes() { GCArguments::initialize_heap_flags_and_sizes(); - assert(GenAlignment != 0, "Generation alignment not set up properly"); - assert(HeapAlignment >= GenAlignment, - "HeapAlignment: %zu less than GenAlignment: %zu", - HeapAlignment, GenAlignment); - assert(GenAlignment % SpaceAlignment == 0, - "GenAlignment: %zu not aligned by SpaceAlignment: %zu", - GenAlignment, SpaceAlignment); - assert(HeapAlignment % GenAlignment == 0, - "HeapAlignment: %zu not aligned by GenAlignment: %zu", - HeapAlignment, GenAlignment); + assert(SpaceAlignment != 0, "Generation alignment not set up properly"); + assert(HeapAlignment >= SpaceAlignment, + "HeapAlignment: %zu less than SpaceAlignment: %zu", + HeapAlignment, SpaceAlignment); + assert(HeapAlignment % SpaceAlignment == 0, + "HeapAlignment: %zu not aligned by SpaceAlignment: %zu", + HeapAlignment, SpaceAlignment); // All generational heaps have a young gen; handle those flags here @@ -106,7 +101,7 @@ void GenArguments::initialize_heap_flags_and_sizes() { // Make sure NewSize allows an old generation to fit even if set on the command line if (FLAG_IS_CMDLINE(NewSize) && NewSize >= InitialHeapSize) { - size_t revised_new_size = bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment); + size_t revised_new_size = bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment); log_warning(gc, ergo)("NewSize (%zuk) is equal to or greater than initial heap size (%zuk). A new " "NewSize of %zuk will be used to accomodate an old generation.", NewSize/K, InitialHeapSize/K, revised_new_size/K); @@ -115,8 +110,8 @@ void GenArguments::initialize_heap_flags_and_sizes() { // Now take the actual NewSize into account. We will silently increase NewSize // if the user specified a smaller or unaligned value. - size_t bounded_new_size = bound_minus_alignment(NewSize, MaxHeapSize, GenAlignment); - bounded_new_size = MAX2(smallest_new_size, align_down(bounded_new_size, GenAlignment)); + size_t bounded_new_size = bound_minus_alignment(NewSize, MaxHeapSize, SpaceAlignment); + bounded_new_size = MAX2(smallest_new_size, align_down(bounded_new_size, SpaceAlignment)); if (bounded_new_size != NewSize) { FLAG_SET_ERGO(NewSize, bounded_new_size); } @@ -125,7 +120,7 @@ void GenArguments::initialize_heap_flags_and_sizes() { if (!FLAG_IS_DEFAULT(MaxNewSize)) { if (MaxNewSize >= MaxHeapSize) { // Make sure there is room for an old generation - size_t smaller_max_new_size = MaxHeapSize - GenAlignment; + size_t smaller_max_new_size = MaxHeapSize - SpaceAlignment; if (FLAG_IS_CMDLINE(MaxNewSize)) { log_warning(gc, ergo)("MaxNewSize (%zuk) is equal to or greater than the entire " "heap (%zuk). A new max generation size of %zuk will be used.", @@ -137,8 +132,8 @@ void GenArguments::initialize_heap_flags_and_sizes() { } } else if (MaxNewSize < NewSize) { FLAG_SET_ERGO(MaxNewSize, NewSize); - } else if (!is_aligned(MaxNewSize, GenAlignment)) { - FLAG_SET_ERGO(MaxNewSize, align_down(MaxNewSize, GenAlignment)); + } else if (!is_aligned(MaxNewSize, SpaceAlignment)) { + FLAG_SET_ERGO(MaxNewSize, align_down(MaxNewSize, SpaceAlignment)); } } @@ -166,13 +161,13 @@ void GenArguments::initialize_heap_flags_and_sizes() { // exceed it. Adjust New/OldSize as necessary. size_t calculated_size = NewSize + OldSize; double shrink_factor = (double) MaxHeapSize / calculated_size; - size_t smaller_new_size = align_down((size_t)(NewSize * shrink_factor), GenAlignment); + size_t smaller_new_size = align_down((size_t)(NewSize * shrink_factor), SpaceAlignment); FLAG_SET_ERGO(NewSize, MAX2(young_gen_size_lower_bound(), smaller_new_size)); // OldSize is already aligned because above we aligned MaxHeapSize to // HeapAlignment, and we just made sure that NewSize is aligned to - // GenAlignment. In initialize_flags() we verified that HeapAlignment - // is a multiple of GenAlignment. + // SpaceAlignment. In initialize_flags() we verified that HeapAlignment + // is a multiple of SpaceAlignment. OldSize = MaxHeapSize - NewSize; } else { FLAG_SET_ERGO(MaxHeapSize, align_up(NewSize + OldSize, HeapAlignment)); @@ -200,7 +195,7 @@ void GenArguments::initialize_size_info() { // Determine maximum size of the young generation. if (FLAG_IS_DEFAULT(MaxNewSize)) { - max_young_size = scale_by_NewRatio_aligned(MaxHeapSize, GenAlignment); + max_young_size = scale_by_NewRatio_aligned(MaxHeapSize, SpaceAlignment); // Bound the maximum size by NewSize below (since it historically // would have been NewSize and because the NewRatio calculation could // yield a size that is too small) and bound it by MaxNewSize above. @@ -229,18 +224,18 @@ void GenArguments::initialize_size_info() { // If NewSize is set on the command line, we should use it as // the initial size, but make sure it is within the heap bounds. initial_young_size = - MIN2(max_young_size, bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment)); - MinNewSize = bound_minus_alignment(initial_young_size, MinHeapSize, GenAlignment); + MIN2(max_young_size, bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment)); + MinNewSize = bound_minus_alignment(initial_young_size, MinHeapSize, SpaceAlignment); } else { // For the case where NewSize is not set on the command line, use // NewRatio to size the initial generation size. Use the current // NewSize as the floor, because if NewRatio is overly large, the resulting // size can be too small. initial_young_size = - clamp(scale_by_NewRatio_aligned(InitialHeapSize, GenAlignment), NewSize, max_young_size); + clamp(scale_by_NewRatio_aligned(InitialHeapSize, SpaceAlignment), NewSize, max_young_size); // Derive MinNewSize from MinHeapSize - MinNewSize = MIN2(scale_by_NewRatio_aligned(MinHeapSize, GenAlignment), initial_young_size); + MinNewSize = MIN2(scale_by_NewRatio_aligned(MinHeapSize, SpaceAlignment), initial_young_size); } } @@ -252,7 +247,7 @@ void GenArguments::initialize_size_info() { // The maximum old size can be determined from the maximum young // and maximum heap size since no explicit flags exist // for setting the old generation maximum. - MaxOldSize = MAX2(MaxHeapSize - max_young_size, GenAlignment); + MaxOldSize = MAX2(MaxHeapSize - max_young_size, SpaceAlignment); MinOldSize = MIN3(MaxOldSize, InitialHeapSize - initial_young_size, MinHeapSize - MinNewSize); @@ -315,10 +310,10 @@ void GenArguments::assert_flags() { assert(NewSize >= MinNewSize, "Ergonomics decided on a too small young gen size"); assert(NewSize <= MaxNewSize, "Ergonomics decided on incompatible initial and maximum young gen sizes"); assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young gen and heap sizes"); - assert(NewSize % GenAlignment == 0, "NewSize alignment"); - assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize % GenAlignment == 0, "MaxNewSize alignment"); + assert(NewSize % SpaceAlignment == 0, "NewSize alignment"); + assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize % SpaceAlignment == 0, "MaxNewSize alignment"); assert(OldSize + NewSize <= MaxHeapSize, "Ergonomics decided on incompatible generation and heap sizes"); - assert(OldSize % GenAlignment == 0, "OldSize alignment"); + assert(OldSize % SpaceAlignment == 0, "OldSize alignment"); } void GenArguments::assert_size_info() { @@ -327,19 +322,19 @@ void GenArguments::assert_size_info() { assert(MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young and heap sizes"); assert(MinNewSize <= NewSize, "Ergonomics decided on incompatible minimum and initial young gen sizes"); assert(NewSize <= MaxNewSize, "Ergonomics decided on incompatible initial and maximum young gen sizes"); - assert(MinNewSize % GenAlignment == 0, "_min_young_size alignment"); - assert(NewSize % GenAlignment == 0, "_initial_young_size alignment"); - assert(MaxNewSize % GenAlignment == 0, "MaxNewSize alignment"); - assert(MinNewSize <= bound_minus_alignment(MinNewSize, MinHeapSize, GenAlignment), + assert(MinNewSize % SpaceAlignment == 0, "_min_young_size alignment"); + assert(NewSize % SpaceAlignment == 0, "_initial_young_size alignment"); + assert(MaxNewSize % SpaceAlignment == 0, "MaxNewSize alignment"); + assert(MinNewSize <= bound_minus_alignment(MinNewSize, MinHeapSize, SpaceAlignment), "Ergonomics made minimum young generation larger than minimum heap"); - assert(NewSize <= bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment), + assert(NewSize <= bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment), "Ergonomics made initial young generation larger than initial heap"); - assert(MaxNewSize <= bound_minus_alignment(MaxNewSize, MaxHeapSize, GenAlignment), + assert(MaxNewSize <= bound_minus_alignment(MaxNewSize, MaxHeapSize, SpaceAlignment), "Ergonomics made maximum young generation lager than maximum heap"); assert(MinOldSize <= OldSize, "Ergonomics decided on incompatible minimum and initial old gen sizes"); assert(OldSize <= MaxOldSize, "Ergonomics decided on incompatible initial and maximum old gen sizes"); - assert(MaxOldSize % GenAlignment == 0, "MaxOldSize alignment"); - assert(OldSize % GenAlignment == 0, "OldSize alignment"); + assert(MaxOldSize % SpaceAlignment == 0, "MaxOldSize alignment"); + assert(OldSize % SpaceAlignment == 0, "OldSize alignment"); assert(MaxHeapSize <= (MaxNewSize + MaxOldSize), "Total maximum heap sizes must be sum of generation maximum sizes"); assert(MinNewSize + MinOldSize <= MinHeapSize, "Minimum generation sizes exceed minimum heap size"); assert(NewSize + OldSize == InitialHeapSize, "Initial generation sizes should match initial heap size"); diff --git a/src/hotspot/share/gc/shared/genArguments.hpp b/src/hotspot/share/gc/shared/genArguments.hpp index ae20c6126fc..80133bd1ec1 100644 --- a/src/hotspot/share/gc/shared/genArguments.hpp +++ b/src/hotspot/share/gc/shared/genArguments.hpp @@ -35,8 +35,6 @@ extern size_t MaxOldSize; extern size_t OldSize; -extern size_t GenAlignment; - class GenArguments : public GCArguments { friend class TestGenCollectorPolicy; // Testing private: diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index cf3b48c11b5..374e877faa6 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -583,28 +583,6 @@ WB_ENTRY(jboolean, WB_G1HasRegionsToUncommit(JNIEnv* env, jobject o)) THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_G1HasRegionsToUncommit: G1 GC is not enabled"); WB_END -#endif // INCLUDE_G1GC - -#if INCLUDE_PARALLELGC - -WB_ENTRY(jlong, WB_PSVirtualSpaceAlignment(JNIEnv* env, jobject o)) - if (UseParallelGC) { - return GenAlignment; - } - THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_PSVirtualSpaceAlignment: Parallel GC is not enabled"); -WB_END - -WB_ENTRY(jlong, WB_PSHeapGenerationAlignment(JNIEnv* env, jobject o)) - if (UseParallelGC) { - return GenAlignment; - } - THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_PSHeapGenerationAlignment: Parallel GC is not enabled"); -WB_END - -#endif // INCLUDE_PARALLELGC - -#if INCLUDE_G1GC - WB_ENTRY(jobject, WB_G1AuxiliaryMemoryUsage(JNIEnv* env)) if (UseG1GC) { ResourceMark rm(THREAD); @@ -2773,10 +2751,6 @@ static JNINativeMethod methods[] = { {CC"g1MemoryNodeIds", CC"()[I", (void*)&WB_G1MemoryNodeIds }, {CC"g1GetMixedGCInfo", CC"(I)[J", (void*)&WB_G1GetMixedGCInfo }, #endif // INCLUDE_G1GC -#if INCLUDE_PARALLELGC - {CC"psVirtualSpaceAlignment",CC"()J", (void*)&WB_PSVirtualSpaceAlignment}, - {CC"psHeapGenerationAlignment",CC"()J", (void*)&WB_PSHeapGenerationAlignment}, -#endif {CC"NMTMalloc", CC"(J)J", (void*)&WB_NMTMalloc }, {CC"NMTMallocWithPseudoStack", CC"(JI)J", (void*)&WB_NMTMallocWithPseudoStack}, {CC"NMTMallocWithPseudoStackAndType", CC"(JII)J", (void*)&WB_NMTMallocWithPseudoStackAndType}, diff --git a/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp b/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp index e335a25cb50..b5e9c6de6a0 100644 --- a/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp +++ b/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp @@ -117,7 +117,7 @@ class TestGenCollectorPolicy { initial_heap_size = InitialHeapSize; } - size_t expected = scale_by_NewRatio_aligned(initial_heap_size, GenAlignment); + size_t expected = scale_by_NewRatio_aligned(initial_heap_size, SpaceAlignment); ASSERT_EQ(expected, NewSize); } }; diff --git a/test/hotspot/jtreg/gc/arguments/TestMinAndInitialSurvivorRatioFlags.java b/test/hotspot/jtreg/gc/arguments/TestMinAndInitialSurvivorRatioFlags.java index 5f585575e7a..6323e5c50cf 100644 --- a/test/hotspot/jtreg/gc/arguments/TestMinAndInitialSurvivorRatioFlags.java +++ b/test/hotspot/jtreg/gc/arguments/TestMinAndInitialSurvivorRatioFlags.java @@ -162,12 +162,12 @@ public class TestMinAndInitialSurvivorRatioFlags { MemoryUsage survivorUsage = HeapRegionUsageTool.getSurvivorUsage(); long alignedNewSize = edenUsage.getMax() + 2 * survivorUsage.getMax(); - long generationAlignment = wb.psHeapGenerationAlignment(); + long spaceAlignment = wb.getHeapSpaceAlignment(); if (survivorRatio >= 0) { // -XX:SurvivorRatio was passed to JVM, actual ratio should be SurvivorRatio + 2 long expectedSize = HeapRegionUsageTool.alignDown(alignedNewSize / (survivorRatio + 2), - generationAlignment); + spaceAlignment); if (survivorUsage.getCommitted() != expectedSize) { throw new RuntimeException("Expected survivor size is: " + expectedSize @@ -177,7 +177,7 @@ public class TestMinAndInitialSurvivorRatioFlags { // In case of initial ratio verification or disabled adaptive size policy // ratio should be equal to InitialSurvivorRatio value long expectedSize = HeapRegionUsageTool.alignDown(alignedNewSize / initRatio, - generationAlignment); + spaceAlignment); if (survivorUsage.getCommitted() != expectedSize) { throw new RuntimeException("Expected survivor size is: " + expectedSize + ", but observed size is: " + survivorUsage.getCommitted()); @@ -186,9 +186,9 @@ public class TestMinAndInitialSurvivorRatioFlags { // In any other case actual survivor ratio should not be lower than MinSurvivorRatio // or is should be equal to InitialSurvivorRatio long expectedMinSize = HeapRegionUsageTool.alignDown(alignedNewSize / minRatio, - generationAlignment); + spaceAlignment); long expectedInitSize = HeapRegionUsageTool.alignDown(alignedNewSize / initRatio, - generationAlignment); + spaceAlignment); if (survivorUsage.getCommitted() != expectedInitSize && survivorUsage.getCommitted() < expectedMinSize) { throw new RuntimeException("Expected survivor size should be " + expectedMinSize diff --git a/test/hotspot/jtreg/gc/arguments/TestNewRatioFlag.java b/test/hotspot/jtreg/gc/arguments/TestNewRatioFlag.java index 86c4c7af8bc..41fa1826799 100644 --- a/test/hotspot/jtreg/gc/arguments/TestNewRatioFlag.java +++ b/test/hotspot/jtreg/gc/arguments/TestNewRatioFlag.java @@ -146,10 +146,8 @@ public class TestNewRatioFlag { long newSize = initEden + 2 * initSurv; // See GenArguments::scale_by_NewRatio_aligned for calculation in the JVM. - long alignedDownNewSize = HeapRegionUsageTool.alignDown(initHeap / (expectedRatio + 1), + long expectedNewSize = HeapRegionUsageTool.alignDown(initHeap / (expectedRatio + 1), wb.getHeapSpaceAlignment()); - long expectedNewSize = HeapRegionUsageTool.alignUp(alignedDownNewSize, - wb.psVirtualSpaceAlignment()); if (expectedNewSize != newSize) { throw new RuntimeException("Expected young gen size is: " + expectedNewSize diff --git a/test/hotspot/jtreg/gc/arguments/TestNewSizeFlags.java b/test/hotspot/jtreg/gc/arguments/TestNewSizeFlags.java index d3152073c55..2f454940863 100644 --- a/test/hotspot/jtreg/gc/arguments/TestNewSizeFlags.java +++ b/test/hotspot/jtreg/gc/arguments/TestNewSizeFlags.java @@ -182,8 +182,6 @@ public class TestNewSizeFlags { private static final GCTypes.YoungGCType YOUNG_GC_TYPE = GCTypes.YoungGCType.getYoungGCType(); private static final long HEAP_SPACE_ALIGNMENT = WB.getHeapSpaceAlignment(); private static final long HEAP_ALIGNMENT = WB.getHeapAlignment(); - private static final long PS_VIRTUAL_SPACE_ALIGNMENT = - (YOUNG_GC_TYPE == GCTypes.YoungGCType.PSNew) ? WB.psVirtualSpaceAlignment() : 0; public static final int ARRAY_LENGTH = 100; public static final int CHUNK_SIZE = 1024; @@ -303,9 +301,7 @@ public class TestNewSizeFlags { case DefNew: return HeapRegionUsageTool.alignDown(value, HEAP_SPACE_ALIGNMENT); case PSNew: - return HeapRegionUsageTool.alignUp(HeapRegionUsageTool.alignDown(value, - HEAP_SPACE_ALIGNMENT), - PS_VIRTUAL_SPACE_ALIGNMENT); + return HeapRegionUsageTool.alignDown(value, HEAP_SPACE_ALIGNMENT); case G1: return HeapRegionUsageTool.alignUp(value, WB.g1RegionSize()); default: diff --git a/test/hotspot/jtreg/gc/arguments/TestSurvivorRatioFlag.java b/test/hotspot/jtreg/gc/arguments/TestSurvivorRatioFlag.java index 90a29e78c12..ea2a20b3dc9 100644 --- a/test/hotspot/jtreg/gc/arguments/TestSurvivorRatioFlag.java +++ b/test/hotspot/jtreg/gc/arguments/TestSurvivorRatioFlag.java @@ -154,7 +154,7 @@ public class TestSurvivorRatioFlag { long youngGenSize = edenUsage.getMax() + 2 * survivorUsage.getMax(); // for Paralle GC Min/InitialSurvivorRatio = SurvivorRatio + 2 long expectedSize = HeapRegionUsageTool.alignDown(youngGenSize / (expectedRatio + 2), - wb.psHeapGenerationAlignment()); + wb.getHeapSpaceAlignment()); if (expectedSize != survivorUsage.getCommitted()) { throw new RuntimeException("Expected survivor size is: " + expectedSize diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 10fad866ca3..d1f2c5822af 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -297,10 +297,6 @@ public class WhiteBox { public native int g1ActiveMemoryNodeCount(); public native int[] g1MemoryNodeIds(); - // Parallel GC - public native long psVirtualSpaceAlignment(); - public native long psHeapGenerationAlignment(); - /** * Enumerates old regions with liveness less than specified and produces some statistics * @param liveness percent of region's liveness (live_objects / total_region_size * 100). From 9586817cea3f1cad8a49d43e9106e25dafa04765 Mon Sep 17 00:00:00 2001 From: Rajan Halade Date: Tue, 10 Jun 2025 21:59:29 +0000 Subject: [PATCH 117/143] 8359170: Add 2 TLS and 2 CS Sectigo roots Reviewed-by: mullan --- .../share/data/cacerts/sectigocodesignroote46 | 21 ++ .../share/data/cacerts/sectigocodesignrootr46 | 39 +++ .../share/data/cacerts/sectigotlsroote46 | 21 ++ .../share/data/cacerts/sectigotlsrootr46 | 39 +++ .../certification/CAInterop.java | 39 ++- .../certification/SectigoCSRootCAs.java | 310 ++++++++++++++++++ .../security/lib/cacerts/VerifyCACerts.java | 14 +- 7 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 src/java.base/share/data/cacerts/sectigocodesignroote46 create mode 100644 src/java.base/share/data/cacerts/sectigocodesignrootr46 create mode 100644 src/java.base/share/data/cacerts/sectigotlsroote46 create mode 100644 src/java.base/share/data/cacerts/sectigotlsrootr46 create mode 100644 test/jdk/security/infra/java/security/cert/CertPathValidator/certification/SectigoCSRootCAs.java diff --git a/src/java.base/share/data/cacerts/sectigocodesignroote46 b/src/java.base/share/data/cacerts/sectigocodesignroote46 new file mode 100644 index 00000000000..7f13b28a401 --- /dev/null +++ b/src/java.base/share/data/cacerts/sectigocodesignroote46 @@ -0,0 +1,21 @@ +Owner: CN=Sectigo Public Code Signing Root E46, O=Sectigo Limited, C=GB +Issuer: CN=Sectigo Public Code Signing Root E46, O=Sectigo Limited, C=GB +Serial number: 50249ba2ef8ea6bf6c2c1f1a6385d4c3 +Valid from: Mon Mar 22 00:00:00 GMT 2021 until: Wed Mar 21 23:59:59 GMT 2046 +Signature algorithm name: SHA384withECDSA +Subject Public Key Algorithm: 384-bit EC (secp384r1) key +Version: 3 +-----BEGIN CERTIFICATE----- +MIICKDCCAa+gAwIBAgIQUCSbou+Opr9sLB8aY4XUwzAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRT +ZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBFNDYwHhcNMjEwMzIyMDAw +MDAwWhcNNDYwMzIxMjM1OTU5WjBWMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2Vj +dGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25p +bmcgUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQIMoEDH487om+BR4zl +e7m6wWmyW0nAKLkUWG8kM85Qm3PZO8FoOZx6Yc5c0iJHRKuAhanllayqrmZYhlan +uIODzLTRDqlR+EtnOX+MubY5aDSPGUq6jiHrQrisVp0J3AejQjBAMB0GA1UdDgQW +BBTPfSygkHqYHd22XoXC4NoVcdLlXjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjACd++zAerlV83j8HflRwwwlLmgchbs +aGX/4g44dv/oG8KfzCVTRg6sZHMobtK0IqYCMGk5W6+oBFyZMtOebrSwXs8lGjll +/zHz43Zy8DMXO+iiqzSEwWGneZ6KupkGGqfVKw== +-----END CERTIFICATE----- diff --git a/src/java.base/share/data/cacerts/sectigocodesignrootr46 b/src/java.base/share/data/cacerts/sectigocodesignrootr46 new file mode 100644 index 00000000000..38270cb0c52 --- /dev/null +++ b/src/java.base/share/data/cacerts/sectigocodesignrootr46 @@ -0,0 +1,39 @@ +Owner: CN=Sectigo Public Code Signing Root R46, O=Sectigo Limited, C=GB +Issuer: CN=Sectigo Public Code Signing Root R46, O=Sectigo Limited, C=GB +Serial number: 4b2c3b01018bad2abc8c7b5b3eed9057 +Valid from: Mon Mar 22 00:00:00 GMT 2021 until: Wed Mar 21 23:59:59 GMT 2046 +Signature algorithm name: SHA384withRSA +Subject Public Key Algorithm: 4096-bit RSA key +Version: 3 +-----BEGIN CERTIFICATE----- +MIIFeDCCA2CgAwIBAgIQSyw7AQGLrSq8jHtbPu2QVzANBgkqhkiG9w0BAQwFADBW +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQD +EyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwHhcNMjEwMzIy +MDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBWMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP +U2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNp +Z25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCN +55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+shJHjUoq14pbe0IdjJImK +/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCDJ9qaDStQ6Utbs7hkNqR+ +Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7P2bSlDFp+m2zNKzBenjc +klDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extmeme/G3h+pDHazJyCh1rr9 +gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUzT2MuuC3hv2WnBGsY2HH6 +zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6qRT5uWl+PoVvLnTCGMOgD +s0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mczmrYI4IAFSEDu9oJkRqj1 +c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEcQNYWFyn8XJwYK+pF9e+9 +1WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2TOglmmVhcKaO5DKYwODzQ +RjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/AZwQsRb8zG4Y3G9i/qZQ +p7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QIDAQABo0IwQDAdBgNVHQ4E +FgQUMuuSmv81lkgvKEBCcCA2kVwXheYwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAHZlwuPXIkrXHYle/2lexhQCTXOm +zc0oyrA36r+nySGqql/av/aDbNCA0QpcAKTL88w5D55BcYjVPOiKe4wXI/fKNHSR +bAauUD8AWbImPDwXg1cDPi3RGj3UzwdUskMLUnKoiPXEF/Jv0Vil0WjkPZgIGO42 +9EhImvpUcPCI1HAWMEJJ0Nk/dUtFcdiuorthDoiFUFe5uhErNikfjyBynlyeidGC +2kWNapnahHFrM6UQu3nwl/Z0gaA/V8eGjDCMDjiVrgHGHqvcqB9vL9f/dh6uF3Nt +5bl1s2EGqJUzwk5vsjfylb6FVBK5yL1iQnb3Kvz1NzEDJlf+0ebb8BYCcoOMCLOE +rKnkB/ihiMQTWlBHVEKm7dBBNCyYsT6iNKEMXb2s9395p79tDFYyhRtLl7jhrOSk +PHHxo+FOY9b0Rrr1CwjhYzztolkvCtQsayOinqFN7tESzRgzUO1Bbst/PUFgC2ML +ePV170MVtzYLEK/cXBipmNk22R3YhLMGioLjexskp0LO7g8+VlwyfexL3lYrOzu6 ++XpY0FG2bNb2WKJSJHpEhqEcYD9J0/z6+YQcBcI0v+Lm8RkqmS9WVzWctfUHw0Yv +3jg9GQ37o/HfE57nqXJYMa+96trX1m13MzOO9Kz9wb9Jh9JwBWd0Bqb2eEAtFgSR +Dx/TFsS4ehcNJMmy +-----END CERTIFICATE----- diff --git a/src/java.base/share/data/cacerts/sectigotlsroote46 b/src/java.base/share/data/cacerts/sectigotlsroote46 new file mode 100644 index 00000000000..60e0b83c97e --- /dev/null +++ b/src/java.base/share/data/cacerts/sectigotlsroote46 @@ -0,0 +1,21 @@ +Owner: CN=Sectigo Public Server Authentication Root E46, O=Sectigo Limited, C=GB +Issuer: CN=Sectigo Public Server Authentication Root E46, O=Sectigo Limited, C=GB +Serial number: 42f2ccda1b6937445f15fe752810b8f4 +Valid from: Mon Mar 22 00:00:00 GMT 2021 until: Wed Mar 21 23:59:59 GMT 2046 +Signature algorithm name: SHA384withECDSA +Subject Public Key Algorithm: 384-bit EC (secp384r1) key +Version: 3 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- diff --git a/src/java.base/share/data/cacerts/sectigotlsrootr46 b/src/java.base/share/data/cacerts/sectigotlsrootr46 new file mode 100644 index 00000000000..7ec1b263de7 --- /dev/null +++ b/src/java.base/share/data/cacerts/sectigotlsrootr46 @@ -0,0 +1,39 @@ +Owner: CN=Sectigo Public Server Authentication Root R46, O=Sectigo Limited, C=GB +Issuer: CN=Sectigo Public Server Authentication Root R46, O=Sectigo Limited, C=GB +Serial number: 758dfd8bae7c0700faa925a7e1c7ad14 +Valid from: Mon Mar 22 00:00:00 GMT 2021 until: Wed Mar 21 23:59:59 GMT 2046 +Signature algorithm name: SHA384withRSA +Subject Public Key Algorithm: 4096-bit RSA key +Version: 3 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index 5612ad4a8a2..0822205bbaf 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -557,6 +557,36 @@ * @run main/othervm/manual -Djava.security.debug=certpath CAInterop ssltlsrootrsa2022 CRL */ +/* + * @test id=sectigotlsrootr46 + * @bug 8359170 + * @summary Interoperability tests with Sectigo Public Server Authentication + * Root R46 + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop + * sectigotlsrootr46 OCSP + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp + * -Dcom.sun.security.ocsp.useget=false CAInterop sectigotlsrootr46 OCSP + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop + * sectigotlsrootr46 CRL + */ + +/* + * @test id=sectigotlsroote46 + * @bug 8359170 + * @summary Interoperability tests with Sectigo Public Server Authentication + * Root E46 + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop + * sectigotlsroote46 OCSP + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp + * -Dcom.sun.security.ocsp.useget=false CAInterop sectigotlsroote46 OCSP + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop + * sectigotlsroote46 CRL + */ + /** * Collection of certificate validation tests for interoperability with external CAs. * These tests are marked as manual as they depend on external infrastructure and may fail @@ -742,6 +772,13 @@ public class CAInterop { new CATestURLs("https://test-root-2022-rsa.ssl.com", "https://revoked-root-2022-rsa.ssl.com"); + case "sectigotlsrootr46" -> + new CATestURLs("https://sectigopublicserverauthenticationrootr46-ev.sectigo.com", + "https://sectigopublicserverauthenticationrootr46-ev.sectigo.com:444"); + case "sectigotlsroote46" -> + new CATestURLs("https://sectigopublicserverauthenticationroote46-ev.sectigo.com", + "https://sectigopublicserverauthenticationroote46-ev.sectigo.com:444"); + default -> throw new RuntimeException("No test setup found for: " + alias); }; } diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/SectigoCSRootCAs.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/SectigoCSRootCAs.java new file mode 100644 index 00000000000..80cd1d41cbc --- /dev/null +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/SectigoCSRootCAs.java @@ -0,0 +1,310 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8359170 + * @summary Interoperability tests with Sectigo Public Code Signing Root CAs + * @build ValidatePathWithParams + * @run main/othervm/manual -Djava.security.debug=ocsp,certpath SectigoCSRootCAs OCSP + * @run main/othervm/manual -Djava.security.debug=certpath SectigoCSRootCAs CRL + */ + +public class SectigoCSRootCAs { + + public static void main(String[] args) throws Exception { + + ValidatePathWithParams pathValidator = new ValidatePathWithParams(null); + + if (args.length >= 1 && "CRL".equalsIgnoreCase(args[0])) { + pathValidator.enableCRLCheck(); + } else { + // OCSP check by default + pathValidator.enableOCSPCheck(); + } + + new SectigoCSRootCA_R46().runTest(pathValidator); + new SectigoCSRootCA_E46().runTest(pathValidator); + } +} + +class SectigoCSRootCA_R46 { + + // Owner: CN=Sectigo Public Code Signing CA R36, O=Sectigo Limited, C=GB + // Issuer: CN=Sectigo Public Code Signing Root R46, O=Sectigo Limited, C=GB + // Serial number: 621d6d0c52019e3b9079152089211c0a + // Valid from: Sun Mar 21 17:00:00 PDT 2021 until: Fri Mar 21 16:59:59 + // PDT 2036 + private static final String INT = "-----BEGIN CERTIFICATE-----\n" + + "MIIGGjCCBAKgAwIBAgIQYh1tDFIBnjuQeRUgiSEcCjANBgkqhkiG9w0BAQwFADBW\n" + + "MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQD\n" + + "EyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwHhcNMjEwMzIy\n" + + "MDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP\n" + + "U2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNp\n" + + "Z25pbmcgQ0EgUjM2MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAmyud\n" + + "U/o1P45gBkNqwM/1f/bIU1MYyM7TbH78WAeVF3llMwsRHgBGRmxDeEDIArCS2VCo\n" + + "Vk4Y/8j6stIkmYV5Gej4NgNjVQ4BYoDjGMwdjioXan1hlaGFt4Wk9vT0k2oWJMJj\n" + + "L9G//N523hAm4jF4UjrW2pvv9+hdPX8tbbAfI3v0VdJiJPFy/7XwiunD7mBxNtec\n" + + "M6ytIdUlh08T2z7mJEXZD9OWcJkZk5wDuf2q52PN43jc4T9OkoXZ0arWZVeffvMr\n" + + "/iiIROSCzKoDmWABDRzV/UiQ5vqsaeFaqQdzFf4ed8peNWh1OaZXnYvZQgWx/SXi\n" + + "JDRSAolRzZEZquE6cbcH747FHncs/Kzcn0Ccv2jrOW+LPmnOyB+tAfiWu01TPhCr\n" + + "9VrkxsHC5qFNxaThTG5j4/Kc+ODD2dX/fmBECELcvzUHf9shoFvrn35XGf2RPaNT\n" + + "O2uSZ6n9otv7jElspkfK9qEATHZcodp+R4q2OIypxR//YEb3fkDn3UayWW9bAgMB\n" + + "AAGjggFkMIIBYDAfBgNVHSMEGDAWgBQy65Ka/zWWSC8oQEJwIDaRXBeF5jAdBgNV\n" + + "HQ4EFgQUDyrLIIcouOxvSK4rVKYpqhekzQwwDgYDVR0PAQH/BAQDAgGGMBIGA1Ud\n" + + "EwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwGwYDVR0gBBQwEjAG\n" + + "BgRVHSAAMAgGBmeBDAEEATBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsLnNl\n" + + "Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RSNDYuY3JsMHsG\n" + + "CCsGAQUFBwEBBG8wbTBGBggrBgEFBQcwAoY6aHR0cDovL2NydC5zZWN0aWdvLmNv\n" + + "bS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdSb290UjQ2LnA3YzAjBggrBgEFBQcw\n" + + "AYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggIBAAb/\n" + + "guF3YzZue6EVIJsT/wT+mHVEYcNWlXHRkT+FoetAQLHI1uBy/YXKZDk8+Y1LoNqH\n" + + "rp22AKMGxQtgCivnDHFyAQ9GXTmlk7MjcgQbDCx6mn7yIawsppWkvfPkKaAQsiqa\n" + + "T9DnMWBHVNIabGqgQSGTrQWo43MOfsPynhbz2Hyxf5XWKZpRvr3dMapandPfYgoZ\n" + + "8iDL2OR3sYztgJrbG6VZ9DoTXFm1g0Rf97Aaen1l4c+w3DC+IkwFkvjFV3jS49ZS\n" + + "c4lShKK6BrPTJYs4NG1DGzmpToTnwoqZ8fAmi2XlZnuchC4NPSZaPATHvNIzt+z1\n" + + "PHo35D/f7j2pO1S8BCysQDHCbM5Mnomnq5aYcKCsdbh0czchOm8bkinLrYrKpii+\n" + + "Tk7pwL7TjRKLXkomm5D1Umds++pip8wH2cQpf93at3VDcOK4N7EwoIJB0kak6pSz\n" + + "Eu4I64U6gZs7tS/dGNSljf2OSSnRr7KWzq03zl8l75jy+hOds9TWSenLbjBQUGR9\n" + + "6cFr6lEUfAIEHVC1L68Y1GGxx4/eRI82ut83axHMViw1+sVpbPxg51Tbnio1lB93\n" + + "079WPFnYaOvfGAA0e0zcfF/M9gXr+korwQTh2Prqooq2bYNMvUoUKD85gnJ+t0sm\n" + + "rWrb8dee2CvYZXD5laGtaAxOfy/VKNmwuWuAh9kc\n" + + "-----END CERTIFICATE-----"; + + // Owner: CN=Sectigo Limited, O=Sectigo Limited, ST=West Yorkshire, C=GB + // Issuer: CN=Sectigo Public Code Signing CA R36, O=Sectigo Limited, C=GB + // Serial number: c1de046377578f1605414f3fa91bf5f6 + // Valid from: Wed Jun 04 17:00:00 PDT 2025 until: Fri Jun 05 16:59:59 + // PDT 2026 + private static final String VALID = "-----BEGIN CERTIFICATE-----\n" + + "MIIGRDCCBKygAwIBAgIRAMHeBGN3V48WBUFPP6kb9fYwDQYJKoZIhvcNAQEMBQAw\n" + + "VDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UE\n" + + "AxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNjAeFw0yNTA2MDUw\n" + + "MDAwMDBaFw0yNjA2MDUyMzU5NTlaMFoxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5X\n" + + "ZXN0IFlvcmtzaGlyZTEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMRgwFgYDVQQD\n" + + "DA9TZWN0aWdvIExpbWl0ZWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" + + "AQDCYjakgsoYkqVpENW0MuN5hBZDdIM60WJgBXU7zTAXORntSu/Grn/SQywwTg4o\n" + + "ltRcKuCp0Cd5zLAIjtgpVDDACWHgxKUtxerjjBZeGp0+viR+biLL0mVNPXgZZ5bQ\n" + + "AnDYVKJaGnPsXQD8l+Bn/R2c4cw7mXjBYp2KrTuqOBkPzk4LmdgpKXjxiw1yYb+n\n" + + "WKZ+3BMLIU6/k+LB9+WB6Odrl4Lff1jB4C6XhQELGjZAbpkFB2+Qr0ajIA3ZFXqU\n" + + "IMh0j5oD5juuXxryOvCgSBkEwxPHnlXxZBNd3DmrZ9NGClBIGE2f9FOjzo5Rl7lV\n" + + "KlzFdFmcH8LaLtWjniF+iT+YZw3Ld1O9VMK7RaHePsS4JYfbjeapoCEgudecmIz4\n" + + "5Q2tTjCdR5s/SxiVbynfEw+cAGeiv4sRXNSg0uhZ2eGMHh6mPley2pUoRMR8Qx1U\n" + + "0Uzg2NtPsHAo0DIH3jKEWU2zP5UPwCfqKYGaZLNLeGh07NZHBCp3TGp9kPVen5Ib\n" + + "tnJssu+pab7fixvbpDM4/r9MkKU6C1IsE6lffyON0PA6LaywwecYTJGpieXqoz03\n" + + "5UmQXvAzkb9omIAcQ6yWMZNrqwwG9XRKQEhvG3v7sbFkck1KZOz4r/KHkLx9sIxm\n" + + "vngdD/qLFebxPIvPT0GKnvSzuGcdQXVTdkZBBBrunv+XpQIDAQABo4IBiTCCAYUw\n" + + "HwYDVR0jBBgwFoAUDyrLIIcouOxvSK4rVKYpqhekzQwwHQYDVR0OBBYEFGMpbbJO\n" + + "xiuD6t+HEyA3hjp4devXMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMG\n" + + "A1UdJQQMMAoGCCsGAQUFBwMDMEoGA1UdIARDMEEwNQYMKwYBBAGyMQECAQMCMCUw\n" + + "IwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEATBJ\n" + + "BgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29Q\n" + + "dWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNybDB5BggrBgEFBQcBAQRtMGswRAYIKwYB\n" + + "BQUHMAKGOGh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVT\n" + + "aWduaW5nQ0FSMzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdv\n" + + "LmNvbTANBgkqhkiG9w0BAQwFAAOCAYEAP2OCzJ+0hrg4XK3w+Ypoe0G5hKfzZ9RH\n" + + "nDcgY8BjgWYVOlN9ad2bU70RfgkZzylkaSt02KHOpkXmYpgfjZsovmyUchvlZ4fU\n" + + "RmivZleuO3G/ZvDFX373S40QFDd+lC1LYYUolRVz7/ZU2Vzql4FxsM1psRaW17xj\n" + + "jf9qaAvDlOH45eEEkfRUbIDdn1UYqDxdCN+90jtD1vRWkYINvE1T6mq3rHpEVoTS\n" + + "dIOgmcSL3MAKMB0LxWUPfoVdhnoUuoIxIAcV1SuR6zej4wHjClEaR8ReT/C23Jr3\n" + + "hQ4VDbfGu3gvlZG8/+lNmT+t4WaNPECxbFP0BgbD70FP594mSVH3fgptYiiRN7ez\n" + + "iUfOSBeCZpSMm7Z5P0KkxkagyFIR3vzgvqbJS/iaomvd9ZIkd9AwMEhugJpITeZj\n" + + "lKSXs+2q2UHQdLTPGVoOjmqyPhDGKAeVVF+jLIUWwtAaAoJm6ooXSp8sAeLA+e+K\n" + + "6RUFETVxhCefCjkbWq64OYLXriDb0l+W\n" + + "-----END CERTIFICATE-----"; + + // Owner: CN=Sectigo Limited, O=Sectigo Limited, ST=West Yorkshire, C=GB + // Issuer: CN=Sectigo Public Code Signing CA R36, O=Sectigo Limited, C=GB + // Serial number: 5ca6fb60da04db99dedbbc0c37131ec + // Valid from: Wed Jun 04 17:00:00 PDT 2025 until: Sun Jun 04 16:59:59 + // PDT 2028 + private static final String REVOKED = "-----BEGIN CERTIFICATE-----\n" + + "MIIGQzCCBKugAwIBAgIQBcpvtg2gTbmd7bvAw3Ex7DANBgkqhkiG9w0BAQwFADBU\n" + + "MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQD\n" + + "EyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgUjM2MB4XDTI1MDYwNTAw\n" + + "MDAwMFoXDTI4MDYwNDIzNTk1OVowWjELMAkGA1UEBhMCR0IxFzAVBgNVBAgMDldl\n" + + "c3QgWW9ya3NoaXJlMRgwFgYDVQQKDA9TZWN0aWdvIExpbWl0ZWQxGDAWBgNVBAMM\n" + + "D1NlY3RpZ28gTGltaXRlZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\n" + + "ALDjoFCMN16zlrcv/saieSYT7FXEhTVDPLNzGAbNdBEX09FoL5EIkiN3biVIbDki\n" + + "bIocCFpo/yTrjARG/zz82AjdWHyomdtDjL35CQmgiUX7V8tu64xUfUAgadqm+0PL\n" + + "by1LRddKE7chpdJu+EDEmeYDPqcRGM+u8suPgosFf6XfVNFy/FZJiD1c7q6JNZ8i\n" + + "5NrvTs0zA9HckKE3uvPO9rw56EyF3SfUz9+zHKHwSElv8nCYpREudUf4yCzPNisK\n" + + "MVovzeCo36nzJFEdWTnDOr4mtvfCEGvJOU3mgzpECK7QF+yFifr90SG4lvrwzkig\n" + + "wYQymukXmB2gxN1tGOvgLig3Q/b4vljBiEeRPEba/L8YQnaXpR/BnPze8yb2t39l\n" + + "bzmnghkWkGA0PAB2vrzpi7pq12fGOD0+ErtAzAl/TAD/UFWwXDQLWX9LXRRKi5E+\n" + + "ScTlqLl9U1q9HsWYfM6CvLbc32TByaQ8yBytvsSRB0C0blp7CtP5MAc8j9xJdwAs\n" + + "Mj2bvSOfA+NJ0Kdg/tqdHHU6hex2HnGzDiEhovm6u/oAfDp/i2bBKLgARglMfGaC\n" + + "hFWeHLL6GAyBezMv+AQNCDCTYDMlqAihVMRUAfYgoHcVCfvTSETTTGdRUDFzIdCA\n" + + "wNwSVfykpadsev43I2IF+F3aNgJYuXnpxSCLPngemcgxAgMBAAGjggGJMIIBhTAf\n" + + "BgNVHSMEGDAWgBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQUlff/C/GC\n" + + "faJ+Y7ua3hKsCsrW9y4wDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYD\n" + + "VR0lBAwwCgYIKwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIBAwIwJTAj\n" + + "BggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQBMEkG\n" + + "A1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1\n" + + "YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBEBggrBgEF\n" + + "BQcwAoY4aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNp\n" + + "Z25pbmdDQVIzNi5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28u\n" + + "Y29tMA0GCSqGSIb3DQEBDAUAA4IBgQAfVq3mY7ggMcTJeWKKrGxs9RUiuAY0p4Xv\n" + + "CHNQViM/tHAn0t/nkPp+d2Ji3Zr6PefN+1F6zmsxsbZHse52JNHWYUwCb/Dx4Vw6\n" + + "3Wnc1zhXtZnvUTUfgrivuIsMjUG8yzTdEt/taMKEO0KqlKPsBPgFKveDVVaq9UZa\n" + + "FfxTWqgrnvkvP/Lag/YeKKj4cJG+a/MJZJm7kvyaBNKXVAamr/bumoxKDzpD67ds\n" + + "n9qwBi2Mv0rRXvZ2SHQXzsJ/zjNKWUhpPVrpypaER7EUxjNuSgC4L8AmfvHiO67v\n" + + "9EVIEud+beP3FtCXl/cSHhVeDxiC0KBXXBl9zLBaYvCH+8iABnZLStLgBDtfdkfk\n" + + "TZEAGbrNOXJDMnKRxr8y377Zq+KHwfiTnyizACHyMMTi+CCwg1ZFGcLOHa5shByc\n" + + "Ln9lYysM1/5vrEjt3ZUw11+pDqbPCGS++xgAwcftKfJ0TZrW/g6NZ9URg+11H9ad\n" + + "WalBv2VkhJAFJam9P2Y+pi9luk85sGo=\n" + + "-----END CERTIFICATE-----"; + + public void runTest(ValidatePathWithParams pathValidator) throws Exception { + // Validate valid + pathValidator.validate(new String[]{VALID, INT}, + ValidatePathWithParams.Status.GOOD, null, System.out); + + // Validate Revoked + pathValidator.validate(new String[]{REVOKED, INT}, + ValidatePathWithParams.Status.REVOKED, + "Thu Jun 05 10:27:45 PDT 2025", System.out); + } +} + +class SectigoCSRootCA_E46 { + + // Owner: CN=Sectigo Public Code Signing CA EV E36, O=Sectigo Limited, C=GB + // Issuer: CN=Sectigo Public Code Signing Root E46, O=Sectigo Limited, C=GB + // Serial number: 3774434f9eb40e221f9236ca1f2f2717 + // Valid from: Sun Mar 21 17:00:00 PDT 2021 until: Fri Mar 21 16:59:59 + // PDT 2036 + private static final String INT_VALID = "-----BEGIN CERTIFICATE-----\n" + + "MIIDMDCCAragAwIBAgIQN3RDT560DiIfkjbKHy8nFzAKBggqhkjOPQQDAzBWMQsw\n" + + "CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRT\n" + + "ZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBFNDYwHhcNMjEwMzIyMDAw\n" + + "MDAwWhcNMzYwMzIxMjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2Vj\n" + + "dGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25p\n" + + "bmcgQ0EgRVYgRTM2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3mMV9nNViYoH\n" + + "4aSrPwFjpbbeXHw2pMbqezwDGb45uEZQr3qI9Hgt0k4R26o5upfXzJt03F8efu0r\n" + + "RNEs4yDDz6OCAWMwggFfMB8GA1UdIwQYMBaAFM99LKCQepgd3bZehcLg2hVx0uVe\n" + + "MB0GA1UdDgQWBBQadKQ417m2DrNb+txerj+28HM9iDAOBgNVHQ8BAf8EBAMCAYYw\n" + + "EgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcDAzAaBgNVHSAE\n" + + "EzARMAYGBFUdIAAwBwYFZ4EMAQMwSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2Ny\n" + + "bC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdSb290RTQ2LmNy\n" + + "bDB7BggrBgEFBQcBAQRvMG0wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jcnQuc2VjdGln\n" + + "by5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nUm9vdEU0Ni5wN2MwIwYIKwYB\n" + + "BQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMAoGCCqGSM49BAMDA2gAMGUC\n" + + "MQCger3L4CYx2W7HyHzvLaAnNee9QVqOwOrBYZyyqXERLtZg1DscsdoYZ2gszEW3\n" + + "zaUCMAaLtcwdoV35ADpru29wChS7kFgXt599Ex27wmL++uJCJth6xYr3nyF2b2YJ\n" + + "DAatOw==\n" + + "-----END CERTIFICATE-----"; + + // Owner: CN=Sectigo Public Code Signing CA E36, O=Sectigo Limited, C=GB + // Issuer: CN=Sectigo Public Code Signing Root E46, O=Sectigo Limited, C=GB + // Serial number: 3602617636e7034b9cc1fc5ffeac2d54 + // Valid from: Sun Mar 21 17:00:00 PDT 2021 until: Fri Mar 21 16:59:59 + // PDT 2036 + private static final String INT_REVOKED = "-----BEGIN CERTIFICATE-----\n" + + "MIIDLjCCArSgAwIBAgIQNgJhdjbnA0ucwfxf/qwtVDAKBggqhkjOPQQDAzBWMQsw\n" + + "CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRT\n" + + "ZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBFNDYwHhcNMjEwMzIyMDAw\n" + + "MDAwWhcNMzYwMzIxMjM1OTU5WjBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2Vj\n" + + "dGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25p\n" + + "bmcgQ0EgRTM2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElDe1m6jawDhrwMxJ\n" + + "yFPhKYf8EGu+lBw3bF5CzmfaH1I7Zi+WAmkeEwS3tiNxzPh8GbBBLtdaRuqGuyWc\n" + + "W6ERmaOCAWQwggFgMB8GA1UdIwQYMBaAFM99LKCQepgd3bZehcLg2hVx0uVeMB0G\n" + + "A1UdDgQWBBQlDZtt2Bh3t4rDOFFW5cfytf+DajAOBgNVHQ8BAf8EBAMCAYYwEgYD\n" + + "VR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDAS\n" + + "MAYGBFUdIAAwCAYGZ4EMAQQBMEsGA1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwu\n" + + "c2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nUm9vdEU0Ni5jcmww\n" + + "ewYIKwYBBQUHAQEEbzBtMEYGCCsGAQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28u\n" + + "Y29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RFNDYucDdjMCMGCCsGAQUF\n" + + "BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAKBggqhkjOPQQDAwNoADBlAjBM\n" + + "ykNTSVvegC1m17yIi87qgx6QIGbw1Mw2bQ4gtOWBVb/C8ALByC1YK7yQJNLJFTkC\n" + + "MQCNBv3fe1eLrGELS5KQD0cEFbXGlzQ5r1mnOHePMqlK5d+rmMxff58/t6bo3QEb\n" + + "8SQ=\n" + + "-----END CERTIFICATE-----"; + + // Owner: CN=Sectigo Limited, O=Sectigo Limited, ST=West Yorkshire, C=GB, + // OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.3=GB, + // SERIALNUMBER=04058690 + // Issuer: CN=Sectigo Public Code Signing CA EV E36, O=Sectigo Limited, C=GB + // Serial number: fa2aa131f36b337717ac73f606a6ec49 + // Valid from: Tue Feb 13 16:00:00 PST 2024 until: Sat Feb 13 15:59:59 + // PST 2027 + private static final String VALID = "-----BEGIN CERTIFICATE-----\n" + + "MIIDrjCCA1SgAwIBAgIRAPoqoTHzazN3F6xz9gam7EkwCgYIKoZIzj0EAwIwVzEL\n" + + "MAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEuMCwGA1UEAxMl\n" + + "U2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIEVWIEUzNjAeFw0yNDAyMTQw\n" + + "MDAwMDBaFw0yNzAyMTMyMzU5NTlaMIGhMREwDwYDVQQFEwgwNDA1ODY5MDETMBEG\n" + + "CysGAQQBgjc8AgEDEwJHQjEdMBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24x\n" + + "CzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5XZXN0IFlvcmtzaGlyZTEYMBYGA1UECgwP\n" + + "U2VjdGlnbyBMaW1pdGVkMRgwFgYDVQQDDA9TZWN0aWdvIExpbWl0ZWQwWTATBgcq\n" + + "hkjOPQIBBggqhkjOPQMBBwNCAASwXGEU01WkW/hWNYza08ZT7i0ZeZ9M1s93JYEB\n" + + "rZ/f1Ho1YzxtToqgIK2o+32afablPFYWlE6wGyuL/TYggBpKo4IBtDCCAbAwHwYD\n" + + "VR0jBBgwFoAUGnSkONe5tg6zW/rcXq4/tvBzPYgwHQYDVR0OBBYEFHEcsJgcYuDO\n" + + "dv1raL6h83a6j9C/MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1Ud\n" + + "JQQMMAoGCCsGAQUFBwMDMEkGA1UdIARCMEAwNQYMKwYBBAGyMQECAQYBMCUwIwYI\n" + + "KwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAcGBWeBDAEDMEsGA1Ud\n" + + "HwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1Ymxp\n" + + "Y0NvZGVTaWduaW5nQ0FFVkUzNi5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsGAQUF\n" + + "BzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2ln\n" + + "bmluZ0NBRVZFMzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdv\n" + + "LmNvbTAmBgNVHREEHzAdoBsGCCsGAQUFBwgDoA8wDQwLR0ItMDQwNTg2OTAwCgYI\n" + + "KoZIzj0EAwIDSAAwRQIgQVp7IIkEZNmC7GfmT1MSEhDebIjjzd75ZVEEbPP/4ocC\n" + + "IQDSyfPDKNMbKNOKrweDLwSE2GZV6nDWbiLz6ZmSZHob8w==\n" + + "-----END CERTIFICATE-----"; + + // Owner: CN=Sectigo Limited, O=Sectigo Limited, ST=West Yorkshire, C=GB + // Issuer: CN=Sectigo Public Code Signing CA E36, O=Sectigo Limited, C=GB + // Serial number: a1f601514271f24ca0a31c0d7b856705 + // Valid from: Wed Jun 04 17:00:00 PDT 2025 until: Sun Jun 04 16:59:59 + // PDT 2028 + private static final String REVOKED = "-----BEGIN CERTIFICATE-----\n" + + "MIIDOTCCAt6gAwIBAgIRAKH2AVFCcfJMoKMcDXuFZwUwCgYIKoZIzj0EAwIwVDEL\n" + + "MAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UEAxMi\n" + + "U2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIEUzNjAeFw0yNTA2MDUwMDAw\n" + + "MDBaFw0yODA2MDQyMzU5NTlaMFoxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5XZXN0\n" + + "IFlvcmtzaGlyZTEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMRgwFgYDVQQDDA9T\n" + + "ZWN0aWdvIExpbWl0ZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASwXGEU01Wk\n" + + "W/hWNYza08ZT7i0ZeZ9M1s93JYEBrZ/f1Ho1YzxtToqgIK2o+32afablPFYWlE6w\n" + + "GyuL/TYggBpKo4IBiTCCAYUwHwYDVR0jBBgwFoAUJQ2bbdgYd7eKwzhRVuXH8rX/\n" + + "g2owHQYDVR0OBBYEFHEcsJgcYuDOdv1raL6h83a6j9C/MA4GA1UdDwEB/wQEAwIH\n" + + "gDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMEoGA1UdIARDMEEw\n" + + "NQYMKwYBBAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5j\n" + + "b20vQ1BTMAgGBmeBDAEEATBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLnNl\n" + + "Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBRTM2LmNybDB5Bggr\n" + + "BgEFBQcBAQRtMGswRAYIKwYBBQUHMAKGOGh0dHA6Ly9jcnQuc2VjdGlnby5jb20v\n" + + "U2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FFMzYuY3J0MCMGCCsGAQUFBzABhhdo\n" + + "dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAKBggqhkjOPQQDAgNJADBGAiEAlEkiISLz\n" + + "PdJsFmVzJ2VZ8hnnVsOBXKbqISFQvIdguJoCIQCH4T0vwxn6uVkJpMvtxiMG/rYg\n" + + "jRFhfbxDcVee6likOw==\n" + + "-----END CERTIFICATE-----"; + + public void runTest(ValidatePathWithParams pathValidator) throws Exception { + // Validate valid + pathValidator.validate(new String[]{VALID, INT_VALID}, + ValidatePathWithParams.Status.GOOD, null, System.out); + + // Validate Revoked + pathValidator.validate(new String[]{REVOKED, INT_REVOKED}, + ValidatePathWithParams.Status.REVOKED, + "Thu Jun 05 10:27:19 PDT 2025", System.out); + } +} diff --git a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java index d176a93784d..86a612ac3ba 100644 --- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java +++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java @@ -28,7 +28,7 @@ * 8223499 8225392 8232019 8234245 8233223 8225068 8225069 8243321 8243320 * 8243559 8225072 8258630 8259312 8256421 8225081 8225082 8225083 8245654 * 8305975 8304760 8307134 8295894 8314960 8317373 8317374 8318759 8319187 - * 8321408 8316138 8341057 8303770 8350498 + * 8321408 8316138 8341057 8303770 8350498 8359170 * @summary Check root CA entries in cacerts file */ import java.io.ByteArrayInputStream; @@ -47,12 +47,12 @@ public class VerifyCACerts { + File.separator + "security" + File.separator + "cacerts"; // The numbers of certs now. - private static final int COUNT = 109; + private static final int COUNT = 113; // SHA-256 of cacerts, can be generated with // shasum -a 256 cacerts | sed -e 's/../&:/g' | tr '[:lower:]' '[:upper:]' | cut -c1-95 private static final String CHECKSUM - = "BD:6B:AB:BB:17:87:0D:D5:8D:53:D3:63:A5:DD:70:57:0F:4E:D3:57:4F:E5:FB:05:41:1C:A9:6E:B0:BF:79:38"; + = "18:36:49:15:B6:71:85:FF:F1:8E:C0:10:BE:0A:41:52:5B:DC:F7:B3:1F:51:7A:45:7D:7A:14:10:3A:59:42:4C"; // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX = HexFormat.ofDelimiter(":").withUpperCase(); @@ -279,6 +279,14 @@ public class VerifyCACerts { "C3:2F:FD:9F:46:F9:36:D1:6C:36:73:99:09:59:43:4B:9A:D6:0A:AF:BB:9E:7C:F3:36:54:F1:44:CC:1B:A1:43"); put("ssltlsrootrsa2022 [jdk]", "8F:AF:7D:2E:2C:B4:70:9B:B8:E0:B3:36:66:BF:75:A5:DD:45:B5:DE:48:0F:8E:A8:D4:BF:E6:BE:BC:17:F2:ED"); + put("sectigotlsrootr46 [jdk]", + "7B:B6:47:A6:2A:EE:AC:88:BF:25:7A:A5:22:D0:1F:FE:A3:95:E0:AB:45:C7:3F:93:F6:56:54:EC:38:F2:5A:06"); + put("sectigotlsroote46 [jdk]", + "C9:0F:26:F0:FB:1B:40:18:B2:22:27:51:9B:5C:A2:B5:3E:2C:A5:B3:BE:5C:F1:8E:FE:1B:EF:47:38:0C:53:83"); + put("sectigocodesignrootr46 [jdk]", + "7E:76:26:0A:E6:9A:55:D3:F0:60:B0:FD:18:B2:A8:C0:14:43:C8:7B:60:79:10:30:C9:FA:0B:05:85:10:1A:38"); + put("sectigocodesignroote46 [jdk]", + "8F:63:71:D8:CC:5A:A7:CA:14:96:67:A9:8B:54:96:39:89:51:E4:31:9F:7A:FB:CC:6A:66:0D:67:3E:43:8D:0B"); } }; From abc76c6b5b3e2eabd3fd3ceb96ffe02979dc8956 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Wed, 11 Jun 2025 08:28:31 +0000 Subject: [PATCH 118/143] 8359126: [AIX] new test TestImplicitNullChecks.java fails Reviewed-by: rcastanedalo, dbriemann --- .../jtreg/compiler/gcbarriers/TestImplicitNullChecks.java | 5 ++++- .../compiler/lib/ir_framework/test/IREncodingPrinter.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java index a77a51312de..34583b8fea9 100644 --- a/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java @@ -67,7 +67,10 @@ public class TestImplicitNullChecks { } @Test - @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, + // On AIX, implicit null checks are limited because the zero page is + // readable (but not writable). See os::zero_page_read_protected(). + @IR(applyIfPlatform = {"aix", "false"}, + applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, counts = {IRNode.NULL_CHECK, "1"}, phase = CompilePhase.FINAL_CODE) static Object testLoad(Outer o) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java index 4ad95ab786f..eef9998ebf8 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java @@ -62,6 +62,7 @@ public class IREncodingPrinter { // as adding non-existent platforms can lead to skipped tests. private static final List irTestingPlatforms = new ArrayList(Arrays.asList( // os.family + "aix", "linux", "mac", "windows", @@ -346,7 +347,9 @@ public class IREncodingPrinter { } String os = ""; - if (Platform.isLinux()) { + if (Platform.isAix()) { + os = "aix"; + } else if (Platform.isLinux()) { os = "linux"; } else if (Platform.isOSX()) { os = "mac"; From 56ce70c5dfe1ecaf371d462d4198765d4a86db3c Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Wed, 11 Jun 2025 08:28:48 +0000 Subject: [PATCH 119/143] 8359165: AIX build broken after 8358799 Reviewed-by: kbarrett, jkern --- src/hotspot/os/posix/os_posix.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index 68bdec3875c..303e44eadcb 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -59,6 +59,7 @@ #ifdef AIX #include "loadlib_aix.hpp" #include "os_aix.hpp" +#include "porting_aix.hpp" #endif #ifdef LINUX #include "os_linux.hpp" @@ -1076,7 +1077,7 @@ void os::jvm_path(char *buf, jint buflen) { return; } - char* fname; + const char* fname; #ifdef AIX Dl_info dlinfo; int ret = dladdr(CAST_FROM_FN_PTR(void *, os::jvm_path), &dlinfo); @@ -1099,7 +1100,7 @@ void os::jvm_path(char *buf, jint buflen) { #endif // AIX char* rp = nullptr; if (fname[0] != '\0') { - rp = os::realpath(dli_fname, buf, buflen); + rp = os::realpath(fname, buf, buflen); } if (rp == nullptr) { return; @@ -1137,7 +1138,7 @@ void os::jvm_path(char *buf, jint buflen) { "buf has been truncated"); } else { // Go back to path of .so - rp = os::realpath(dli_fname, buf, buflen); + rp = os::realpath(fname, buf, buflen); if (rp == nullptr) { return; } From 5ae32c4c86916120d2a337be9765cb6e14f3f443 Mon Sep 17 00:00:00 2001 From: Khalid Boulanouare Date: Wed, 11 Jun 2025 10:25:28 +0000 Subject: [PATCH 120/143] 8352149: Test java/awt/Frame/MultiScreenTest.java fails: Window list is empty Reviewed-by: aivanov, abhiscxk --- test/jdk/java/awt/Frame/MultiScreenTest.java | 673 ++++++++++--------- 1 file changed, 343 insertions(+), 330 deletions(-) diff --git a/test/jdk/java/awt/Frame/MultiScreenTest.java b/test/jdk/java/awt/Frame/MultiScreenTest.java index 845f601138b..cbd781f94dd 100644 --- a/test/jdk/java/awt/Frame/MultiScreenTest.java +++ b/test/jdk/java/awt/Frame/MultiScreenTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -43,6 +43,7 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.TextField; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -74,7 +75,7 @@ public class MultiScreenTest { ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); gs = ge.getScreenDevices(); if (gs.length < 2) { - throw new SkippedException("You have only one monitor in your system - test passed"); + throw new SkippedException("You have only one monitor in your system"); } MultiScreenTest obj = new MultiScreenTest(); String INSTRUCTIONS = @@ -82,38 +83,42 @@ public class MultiScreenTest { "You have " + gs.length + " monitors in your system.\n" + "Actively drag the DitherTest frames on the secondary screen and " + "if you see garbage appearing on your primary screen " + - "test failed otherwise it passed.";; + "test failed otherwise it passed."; PassFailJFrame.builder() - .title("MultiScreenTest Instruction") .instructions(INSTRUCTIONS) - .rows((int) INSTRUCTIONS.lines().count() + 2) .columns(40) .testUI(obj::init) + .positionTestUI(MultiScreenTest::positionTestWindows) .build() .awaitAndCheck(); } + private static void positionTestWindows(List windows, PassFailJFrame.InstructionUI instructionUI) { + // Do nothing - the location of each window is set when they're created + } + public List init() { List list = new ArrayList<>(); for (int j = 0; j < gs.length; j++) { GraphicsConfiguration[] gc = gs[j].getConfigurations(); if (gc.length > 0) { - for (int i = 0; i < gc.length / 2; i++) { - JFrame f = new JFrame(gc[i]); //test JFrame( gc ) - GCCanvas c = new GCCanvas(gc[i]);//test canvas( gc ) - Rectangle gcBounds = gc[i].getBounds(); //test getBounds() + for (int i = 0; i < gc.length && i < 10; i++) { + JFrame f = new JFrame(gc[i]); + GCCanvas c = new GCCanvas(gc[i]); + Rectangle gcBounds = gc[i].getBounds(); int xoffs = gcBounds.x; int yoffs = gcBounds.y; f.getContentPane().add(c); - f.setTitle("Screen# " + Integer.toString(j) + ", GC#" + Integer.toString(i)); + f.setTitle("Screen# " + j + ", GC#" + i); f.setSize(300, 200); - f.setLocation(400 + xoffs, (i * 150) + yoffs);//test displaying in right location + // test displaying in right location + f.setLocation(400 + xoffs, (i * 150) + yoffs); list.add(f); - Frame ditherfs = new Frame("DitherTest GC#" + Integer.toString(i), gc[i]); - ditherfs.setLayout(new BorderLayout()); //showDitherTest + Frame ditherfs = new Frame("DitherTest GC#" + i, gc[i]); + ditherfs.setLayout(new BorderLayout()); DitherTest ditherTest = new DitherTest(gc[i]); ditherfs.add("Center", ditherTest); ditherfs.setBounds(300, 200, 300, 200); @@ -126,360 +131,368 @@ public class MultiScreenTest { } return list; } -} -class GCCanvas extends Canvas { - GraphicsConfiguration gc; - Rectangle bounds; - Graphics g = this.getGraphics(); - Dimension size = getSize(); + static class GCCanvas extends Canvas { - public GCCanvas(GraphicsConfiguration gc) { - super(gc); - this.gc = gc; - bounds = gc.getBounds(); - } + GraphicsConfiguration gc; + Rectangle bounds; + Dimension size = getSize(); - public void paint( Graphics _g ) { + public GCCanvas(GraphicsConfiguration gc) { + super(gc); + this.gc = gc; + bounds = gc.getBounds(); + } - Graphics2D g = (Graphics2D) _g; + @Override + public void paint( Graphics _g ) { - g.drawRect(0, 0, size.width-1, size.height-1); - g.setColor(Color.lightGray); - g.draw3DRect(1, 1, size.width-3, size.height-3, true); + Graphics2D g = (Graphics2D) _g; - g.setColor(Color.red); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.drawRect(0, 0, size.width-1, size.height-1); + g.setColor(Color.lightGray); + g.draw3DRect(1, 1, size.width-3, size.height-3, true); - g.drawString("HELLO!", 110, 10); + g.setColor(Color.red); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(Color.blue); - g.drawString("ScreenSize="+Integer.toString(bounds.width)+"X"+ - Integer.toString(bounds.height), 10, 20); - g.setColor(Color.green); - g.drawString(gc.toString(), 10, 30); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g.drawString("HELLO!", 110, 10); - g.setColor(Color.orange); - g.fillRect(40, 20, 50, 50); + g.setColor(Color.blue); + g.drawString("ScreenSize="+Integer.toString(bounds.width)+"X"+ + Integer.toString(bounds.height), 10, 20); + g.setColor(Color.green); + g.drawString(gc.toString(), 10, 30); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.setColor(Color.red); - g.drawRect(100, 20, 30, 30); + g.setColor(Color.orange); + g.fillRect(40, 20, 50, 50); - g.setColor(Color.gray); - g.drawLine(220, 20, 280, 40); + g.setColor(Color.red); + g.drawRect(100, 20, 30, 30); - g.setColor(Color.cyan); - g.fillArc(150, 30, 30, 30, 0, 200); - } + g.setColor(Color.gray); + g.drawLine(220, 20, 280, 40); - public Dimension getPreferredSize(){ - return new Dimension(300, 200); - } -} + g.setColor(Color.cyan); + g.fillArc(150, 30, 30, 30, 0, 200); + } -class DitherCanvas extends Canvas { - Image img; - static String calcString = "Calculating..."; - - GraphicsConfiguration mGC; - - public DitherCanvas(GraphicsConfiguration gc) { - super(gc); - mGC = gc; - } - - public GraphicsConfiguration getGraphicsConfig() { - return mGC; - } - - public void paint(Graphics g) { - int w = getSize().width; - int h = getSize().height; - if (img == null) { - super.paint(g); - g.setColor(Color.black); - FontMetrics fm = g.getFontMetrics(); - int x = (w - fm.stringWidth(calcString)) / 2; - int y = h / 2; - g.drawString(calcString, x, y); - } else { - g.drawImage(img, 0, 0, w, h, this); + @Override + public Dimension getPreferredSize(){ + return new Dimension(300, 200); } } - public void update(Graphics g) { - paint(g); - } + static class DitherCanvas extends Canvas { + Image img; + static String calcString = "Calculating..."; - public Dimension getMinimumSize() { - return new Dimension(20, 20); - } + GraphicsConfiguration mGC; - public Dimension getPreferredSize() { - return new Dimension(200, 200); - } - - public Image getImage() { - return img; - } - - public void setImage(Image img) { - this.img = img; - paint(getGraphics()); - } -} - -class DitherTest extends Panel implements Runnable { - final static int NOOP = 0; - final static int RED = 1; - final static int GREEN = 2; - final static int BLUE = 3; - final static int ALPHA = 4; - final static int SATURATION = 5; - - Thread runner; - - DitherControls XControls; - DitherControls YControls; - DitherCanvas canvas; - - public DitherTest(GraphicsConfiguration gc) { - String xspec, yspec; - int xvals[] = new int[2]; - int yvals[] = new int[2]; - - xspec = "red"; - yspec = "blue"; - int xmethod = colormethod(xspec, xvals); - int ymethod = colormethod(yspec, yvals); - - setLayout(new BorderLayout()); - XControls = new DitherControls(this, xvals[0], xvals[1], - xmethod, false); - YControls = new DitherControls(this, yvals[0], yvals[1], - ymethod, true); - YControls.addRenderButton(); - add("North", XControls); - add("South", YControls); - add("Center", canvas = new DitherCanvas(gc)); - } - - public void start() { - runner = new Thread(this); - runner.start(); - } - - int colormethod(String s, int vals[]) { - int method = NOOP; - - if (s == null) { - s = ""; + public DitherCanvas(GraphicsConfiguration gc) { + super(gc); + mGC = gc; } - String lower = s.toLowerCase(); - int len = 0; - if (lower.startsWith("red")) { - method = RED; - lower = lower.substring(3); - } else if (lower.startsWith("green")) { - method = GREEN; - lower = lower.substring(5); - } else if (lower.startsWith("blue")) { - method = BLUE; - lower = lower.substring(4); - } else if (lower.startsWith("alpha")) { - method = ALPHA; - lower = lower.substring(4); - } else if (lower.startsWith("saturation")) { - method = SATURATION; - lower = lower.substring(10); + public GraphicsConfiguration getGraphicsConfig() { + return mGC; } - if (method == NOOP) { - vals[0] = 0; - vals[1] = 0; + @Override + public void paint(Graphics g) { + int w = getSize().width; + int h = getSize().height; + if (img == null) { + super.paint(g); + g.setColor(Color.black); + FontMetrics fm = g.getFontMetrics(); + int x = (w - fm.stringWidth(calcString)) / 2; + int y = h / 2; + g.drawString(calcString, x, y); + } else { + g.drawImage(img, 0, 0, w, h, this); + } + } + + @Override + public void update(Graphics g) { + paint(g); + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(20, 20); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(200, 200); + } + + public Image getImage() { + return img; + } + + public void setImage(Image img) { + this.img = img; + paint(getGraphics()); + } + } + + static class DitherTest extends Panel implements Runnable { + final static int NOOP = 0; + final static int RED = 1; + final static int GREEN = 2; + final static int BLUE = 3; + final static int ALPHA = 4; + final static int SATURATION = 5; + + Thread runner; + + DitherControls XControls; + DitherControls YControls; + DitherCanvas canvas; + + public DitherTest(GraphicsConfiguration gc) { + String xspec, yspec; + int xvals[] = new int[2]; + int yvals[] = new int[2]; + + xspec = "red"; + yspec = "blue"; + int xmethod = colormethod(xspec, xvals); + int ymethod = colormethod(yspec, yvals); + + setLayout(new BorderLayout()); + XControls = new DitherControls(this, xvals[0], xvals[1], + xmethod, false); + YControls = new DitherControls(this, yvals[0], yvals[1], + ymethod, true); + YControls.addRenderButton(); + add("North", XControls); + add("South", YControls); + add("Center", canvas = new DitherCanvas(gc)); + } + + public void start() { + runner = new Thread(this); + runner.start(); + } + + int colormethod(String s, int vals[]) { + int method = NOOP; + + if (s == null) { + s = ""; + } + + String lower = s.toLowerCase(); + int len = 0; + if (lower.startsWith("red")) { + method = RED; + lower = lower.substring(3); + } else if (lower.startsWith("green")) { + method = GREEN; + lower = lower.substring(5); + } else if (lower.startsWith("blue")) { + method = BLUE; + lower = lower.substring(4); + } else if (lower.startsWith("alpha")) { + method = ALPHA; + lower = lower.substring(4); + } else if (lower.startsWith("saturation")) { + method = SATURATION; + lower = lower.substring(10); + } + + if (method == NOOP) { + vals[0] = 0; + vals[1] = 0; + return method; + } + + int begval = 0; + int endval = 255; + + try { + int dash = lower.indexOf('-'); + if (dash < 0) { + begval = endval = Integer.parseInt(lower); + } else { + begval = Integer.parseInt(lower.substring(0, dash)); + endval = Integer.parseInt(lower.substring(dash + 1)); + } + } catch (Exception e) { + } + + if (begval < 0) { + begval = 0; + } + if (endval < 0) { + endval = 0; + } + if (begval > 255) { + begval = 255; + } + if (endval > 255) { + endval = 255; + } + + vals[0] = begval; + vals[1] = endval; + return method; } - int begval = 0; - int endval = 255; - - try { - int dash = lower.indexOf('-'); - if (dash < 0) { - begval = endval = Integer.parseInt(lower); - } else { - begval = Integer.parseInt(lower.substring(0, dash)); - endval = Integer.parseInt(lower.substring(dash + 1)); - } - } catch (Exception e) { - } - - if (begval < 0) { - begval = 0; - } - if (endval < 0) { - endval = 0; - } - if (begval > 255) { - begval = 255; - } - if (endval > 255) { - endval = 255; - } - - vals[0] = begval; - vals[1] = endval; - - return method; - } - - void applymethod(int c[], int method, int step, int total, int vals[]) { - if (method == NOOP) - return; - int val = ((total < 2) - ? vals[0] - : vals[0] + ((vals[1] - vals[0]) * step / (total - 1))); - switch (method) { - case RED: - c[0] = val; - break; - case GREEN: - c[1] = val; - break; - case BLUE: - c[2] = val; - break; - case ALPHA: - c[3] = val; - break; - case SATURATION: - int max = Math.max(Math.max(c[0], c[1]), c[2]); - int min = max * (255 - val) / 255; - if (c[0] == 0) { - c[0] = min; - } - if (c[1] == 0) { - c[1] = min; - } - if (c[2] == 0) { - c[2] = min; - } - break; - } - } - - public void run() { - canvas.setImage(null); // Wipe previous image - Image img = calculateImage(); - synchronized (this) { - if (img != null && runner == Thread.currentThread()) { - canvas.setImage(img); - } - } - } - - /** - * Calculates and returns the image. Halts the calculation and returns - * null if stopped during the calculation. - */ - Image calculateImage() { - Thread me = Thread.currentThread(); - - int width = canvas.getSize().width; - int height = canvas.getSize().height; - int xvals[] = new int[2]; - int yvals[] = new int[2]; - int xmethod = XControls.getParams(xvals); - int ymethod = YControls.getParams(yvals); - int pixels[] = new int[width * height]; - int c[] = new int[4]; - int index = 0; - - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i++) { - c[0] = c[1] = c[2] = 0; - c[3] = 255; - if (xmethod < ymethod) { - applymethod(c, xmethod, i, width, xvals); - applymethod(c, ymethod, j, height, yvals); - } else { - applymethod(c, ymethod, j, height, yvals); - applymethod(c, xmethod, i, width, xvals); - } - pixels[index++] = ((c[3] << 24) | - (c[0] << 16) | - (c[1] << 8) | - (c[2] << 0)); - - } - // Poll once per row to see if we've been told to stop. - if (runner != me) { - return null; + void applymethod(int c[], int method, int step, int total, int vals[]) { + if (method == NOOP) + return; + int val = ((total < 2) + ? vals[0] + : vals[0] + ((vals[1] - vals[0]) * step / (total - 1))); + switch (method) { + case RED: + c[0] = val; + break; + case GREEN: + c[1] = val; + break; + case BLUE: + c[2] = val; + break; + case ALPHA: + c[3] = val; + break; + case SATURATION: + int max = Math.max(Math.max(c[0], c[1]), c[2]); + int min = max * (255 - val) / 255; + if (c[0] == 0) { + c[0] = min; + } + if (c[1] == 0) { + c[1] = min; + } + if (c[2] == 0) { + c[2] = min; + } + break; } } - return createImage(new MemoryImageSource(width, height, - ColorModel.getRGBdefault(), pixels, 0, width)); + @Override + public void run() { + canvas.setImage(null); // Wipe previous image + Image img = calculateImage(); + synchronized (this) { + if (img != null && runner == Thread.currentThread()) { + canvas.setImage(img); + } + } + } + + /** + * Calculates and returns the image. Halts the calculation and returns + * null if stopped during the calculation. + */ + Image calculateImage() { + Thread me = Thread.currentThread(); + + int width = canvas.getSize().width; + int height = canvas.getSize().height; + int xvals[] = new int[2]; + int yvals[] = new int[2]; + int xmethod = XControls.getParams(xvals); + int ymethod = YControls.getParams(yvals); + int pixels[] = new int[width * height]; + int c[] = new int[4]; + int index = 0; + + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + c[0] = c[1] = c[2] = 0; + c[3] = 255; + if (xmethod < ymethod) { + applymethod(c, xmethod, i, width, xvals); + applymethod(c, ymethod, j, height, yvals); + } else { + applymethod(c, ymethod, j, height, yvals); + applymethod(c, xmethod, i, width, xvals); + } + pixels[index++] = ((c[3] << 24) | + (c[0] << 16) | + (c[1] << 8) | + (c[2] << 0)); + + } + // Poll once per row to see if we've been told to stop. + if (runner != me) { + return null; + } + } + + return createImage(new MemoryImageSource(width, height, + ColorModel.getRGBdefault(), pixels, 0, width)); + } + + public String getInfo() { + return "An interactive demonstration of dithering."; + } + + public String[][] getParameterInfo() { + String[][] info = { + {"xaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}", + "The color of the Y axis. Default is RED."}, + {"yaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}", + "The color of the X axis. Default is BLUE."} + }; + return info; + } } - public String getInfo() { - return "An interactive demonstration of dithering."; - } + static class DitherControls extends Panel implements ActionListener { + TextField start; + TextField end; + Button button; + Choice choice; + DitherTest dt; - public String[][] getParameterInfo() { - String[][] info = { - {"xaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}", - "The color of the Y axis. Default is RED."}, - {"yaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}", - "The color of the X axis. Default is BLUE."} - }; - return info; - } -} + static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER, 10, 5); -class DitherControls extends Panel implements ActionListener { - TextField start; - TextField end; - Button button; - Choice choice; - DitherTest dt; + public DitherControls(DitherTest app, int s, int e, int type, + boolean vertical) { + dt = app; + setLayout(dcLayout); + add(new Label(vertical ? "Vertical" : "Horizontal")); + add(choice = new Choice()); + choice.addItem("Noop"); + choice.addItem("Red"); + choice.addItem("Green"); + choice.addItem("Blue"); + choice.addItem("Alpha"); + choice.addItem("Saturation"); + choice.select(type); + add(start = new TextField(Integer.toString(s), 4)); + add(end = new TextField(Integer.toString(e), 4)); + } - static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER, 10, 5); + public void addRenderButton() { + add(button = new Button("New Image")); + button.addActionListener(this); + } - public DitherControls(DitherTest app, int s, int e, int type, - boolean vertical) { - dt = app; - setLayout(dcLayout); - add(new Label(vertical ? "Vertical" : "Horizontal")); - add(choice = new Choice()); - choice.addItem("Noop"); - choice.addItem("Red"); - choice.addItem("Green"); - choice.addItem("Blue"); - choice.addItem("Alpha"); - choice.addItem("Saturation"); - choice.select(type); - add(start = new TextField(Integer.toString(s), 4)); - add(end = new TextField(Integer.toString(e), 4)); - } + public int getParams(int vals[]) { + vals[0] = Integer.parseInt(start.getText()); + vals[1] = Integer.parseInt(end.getText()); + return choice.getSelectedIndex(); + } - public void addRenderButton() { - add(button = new Button("New Image")); - button.addActionListener(this); - } - - public int getParams(int vals[]) { - vals[0] = Integer.parseInt(start.getText()); - vals[1] = Integer.parseInt(end.getText()); - return choice.getSelectedIndex(); - } - - public void actionPerformed(ActionEvent e) { - if (e.getSource() == button) { - dt.start(); + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == button) { + dt.start(); + } } } } From bf7d40d0486b7b4e4820bb5d08a63c446ea3291d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Wed, 11 Jun 2025 11:08:38 +0000 Subject: [PATCH 121/143] 8356751: IGV: clean up redundant field _should_send_method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel Hässig Reviewed-by: mhaessig, thartmann, dfenacci --- src/hotspot/share/opto/idealGraphPrinter.cpp | 16 ++++++---------- src/hotspot/share/opto/idealGraphPrinter.hpp | 1 - 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 3721394c112..1ecb46eaf5a 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -155,7 +155,6 @@ void IdealGraphPrinter::init(const char* file_name, bool use_multiple_files, boo // in the mach where kill projections have no users but should // appear in the dump. _traverse_outs = true; - _should_send_method = true; _output = nullptr; buffer[0] = 0; _depth = 0; @@ -300,13 +299,11 @@ void IdealGraphPrinter::print_inline_tree(InlineTree *tree) { void IdealGraphPrinter::print_inlining() { // Print inline tree - if (_should_send_method) { - InlineTree *inlineTree = C->ilt(); - if (inlineTree != nullptr) { - print_inline_tree(inlineTree); - } else { - // print this method only - } + InlineTree *inlineTree = C->ilt(); + if (inlineTree != nullptr) { + print_inline_tree(inlineTree); + } else { + // print this method only } } @@ -382,7 +379,6 @@ void IdealGraphPrinter::begin_method() { tail(PROPERTIES_ELEMENT); - _should_send_method = true; this->_current_method = method; _xml->flush(); @@ -975,7 +971,7 @@ void IdealGraphPrinter::print_graph(const char* name, const frame* fr) { // Print current ideal graph void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray& visible_nodes, const frame* fr) { - if (!_current_method || !_should_send_method || node == nullptr) return; + if (!_current_method || node == nullptr) return; if (name == nullptr) { stringStream graph_name; diff --git a/src/hotspot/share/opto/idealGraphPrinter.hpp b/src/hotspot/share/opto/idealGraphPrinter.hpp index 69ba2841506..7e68ce6c00f 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.hpp +++ b/src/hotspot/share/opto/idealGraphPrinter.hpp @@ -110,7 +110,6 @@ class IdealGraphPrinter : public CHeapObj { ciMethod *_current_method; int _depth; char buffer[2048]; - bool _should_send_method; PhaseChaitin* _chaitin; bool _traverse_outs; Compile *C; From 42ab8fcfb98eacb2d93f59c012360a99a16e5450 Mon Sep 17 00:00:00 2001 From: Anton Artemov Date: Wed, 11 Jun 2025 12:42:57 +0000 Subject: [PATCH 122/143] 8265754: Move suspend/resume API from HandshakeState Reviewed-by: coleenp, dholmes, pchilanomate --- src/hotspot/share/runtime/handshake.cpp | 126 +------------- src/hotspot/share/runtime/handshake.hpp | 34 +--- src/hotspot/share/runtime/javaThread.cpp | 5 +- src/hotspot/share/runtime/javaThread.hpp | 7 +- .../share/runtime/suspendResumeManager.cpp | 158 ++++++++++++++++++ .../share/runtime/suspendResumeManager.hpp | 70 ++++++++ 6 files changed, 241 insertions(+), 159 deletions(-) create mode 100644 src/hotspot/share/runtime/suspendResumeManager.cpp create mode 100644 src/hotspot/share/runtime/suspendResumeManager.hpp diff --git a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp index 6bed5e9d546..2c827b61602 100644 --- a/src/hotspot/share/runtime/handshake.cpp +++ b/src/hotspot/share/runtime/handshake.cpp @@ -465,9 +465,7 @@ HandshakeState::HandshakeState(JavaThread* target) : _queue(), _lock(Monitor::nosafepoint, "HandshakeState_lock"), _active_handshaker(), - _async_exceptions_blocked(false), - _suspended(false), - _async_suspend_handshake(false) { + _async_exceptions_blocked(false) { } HandshakeState::~HandshakeState() { @@ -699,128 +697,8 @@ HandshakeState::ProcessResult HandshakeState::try_process(HandshakeOperation* ma return op == match_op ? HandshakeState::_succeeded : HandshakeState::_processed; } -void HandshakeState::do_self_suspend() { - assert(Thread::current() == _handshakee, "should call from _handshakee"); - assert(_lock.owned_by_self(), "Lock must be held"); - assert(!_handshakee->has_last_Java_frame() || _handshakee->frame_anchor()->walkable(), "should have walkable stack"); - assert(_handshakee->thread_state() == _thread_blocked, "Caller should have transitioned to _thread_blocked"); - - while (is_suspended()) { - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended", p2i(_handshakee)); - _lock.wait_without_safepoint_check(); - } - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " resumed", p2i(_handshakee)); -} - -// This is the closure that prevents a suspended JavaThread from -// escaping the suspend request. -class ThreadSelfSuspensionHandshake : public AsyncHandshakeClosure { - public: - ThreadSelfSuspensionHandshake() : AsyncHandshakeClosure("ThreadSelfSuspensionHandshake") {} - void do_thread(Thread* thr) { - JavaThread* current = JavaThread::cast(thr); - assert(current == Thread::current(), "Must be self executed."); - JavaThreadState jts = current->thread_state(); - - current->set_thread_state(_thread_blocked); - current->handshake_state()->do_self_suspend(); - current->set_thread_state(jts); - current->handshake_state()->set_async_suspend_handshake(false); - } - virtual bool is_suspend() { return true; } -}; - -bool HandshakeState::suspend_with_handshake(bool register_vthread_SR) { - assert(_handshakee->threadObj() != nullptr, "cannot suspend with a null threadObj"); - if (_handshakee->is_exiting()) { - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " exiting", p2i(_handshakee)); - return false; - } - if (has_async_suspend_handshake()) { - if (is_suspended()) { - // Target is already suspended. - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " already suspended", p2i(_handshakee)); - return false; - } else { - // Target is going to wake up and leave suspension. - // Let's just stop the thread from doing that. - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " re-suspended", p2i(_handshakee)); - set_suspended(true, register_vthread_SR); - return true; - } - } - // no suspend request - assert(!is_suspended(), "cannot be suspended without a suspend request"); - // Thread is safe, so it must execute the request, thus we can count it as suspended - // from this point. - set_suspended(true, register_vthread_SR); - set_async_suspend_handshake(true); - log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended, arming ThreadSuspension", p2i(_handshakee)); - ThreadSelfSuspensionHandshake* ts = new ThreadSelfSuspensionHandshake(); - Handshake::execute(ts, _handshakee); - return true; -} - -// This is the closure that synchronously honors the suspend request. -class SuspendThreadHandshake : public HandshakeClosure { - bool _register_vthread_SR; - bool _did_suspend; -public: - SuspendThreadHandshake(bool register_vthread_SR) : HandshakeClosure("SuspendThread"), - _register_vthread_SR(register_vthread_SR), _did_suspend(false) {} - void do_thread(Thread* thr) { - JavaThread* target = JavaThread::cast(thr); - _did_suspend = target->handshake_state()->suspend_with_handshake(_register_vthread_SR); - } - bool did_suspend() { return _did_suspend; } -}; - -bool HandshakeState::suspend(bool register_vthread_SR) { - JVMTI_ONLY(assert(!_handshakee->is_in_VTMS_transition(), "no suspend allowed in VTMS transition");) - JavaThread* self = JavaThread::current(); - if (_handshakee == self) { - // If target is the current thread we can bypass the handshake machinery - // and just suspend directly - ThreadBlockInVM tbivm(self); - MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag); - set_suspended(true, register_vthread_SR); - do_self_suspend(); - return true; - } else { - SuspendThreadHandshake st(register_vthread_SR); - Handshake::execute(&st, _handshakee); - return st.did_suspend(); - } -} - -bool HandshakeState::resume(bool register_vthread_SR) { - MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag); - if (!is_suspended()) { - assert(!_handshakee->is_suspended(), "cannot be suspended without a suspend request"); - return false; - } - // Resume the thread. - set_suspended(false, register_vthread_SR); - _lock.notify(); - return true; -} - -void HandshakeState::set_suspended(bool is_suspend, bool register_vthread_SR) { -#if INCLUDE_JVMTI - if (register_vthread_SR) { - assert(_handshakee->is_vthread_mounted(), "sanity check"); - if (is_suspend) { - JvmtiVTSuspender::register_vthread_suspend(_handshakee->vthread()); - } else { - JvmtiVTSuspender::register_vthread_resume(_handshakee->vthread()); - } - } -#endif - Atomic::store(&_suspended, is_suspend); -} - void HandshakeState::handle_unsafe_access_error() { - if (is_suspended()) { + if (_handshakee->is_suspended()) { // A suspend handshake was added to the queue after the // unsafe access error. Since the suspender has already // considered this JT as suspended and assumes it won't go diff --git a/src/hotspot/share/runtime/handshake.hpp b/src/hotspot/share/runtime/handshake.hpp index 9e963003053..ca9cef76f5d 100644 --- a/src/hotspot/share/runtime/handshake.hpp +++ b/src/hotspot/share/runtime/handshake.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -35,8 +35,6 @@ class HandshakeOperation; class AsyncHandshakeOperation; class JavaThread; -class SuspendThreadHandshake; -class ThreadSelfSuspensionHandshake; class UnsafeAccessErrorHandshake; class ThreadsListHandle; @@ -88,8 +86,6 @@ class JvmtiRawMonitor; // operation is only done by either VMThread/Handshaker on behalf of the // JavaThread or by the target JavaThread itself. class HandshakeState { - friend ThreadSelfSuspensionHandshake; - friend SuspendThreadHandshake; friend UnsafeAccessErrorHandshake; friend JavaThread; // This a back reference to the JavaThread, @@ -98,7 +94,7 @@ class HandshakeState { // The queue containing handshake operations to be performed on _handshakee. FilterQueue _queue; // Provides mutual exclusion to this state and queue. Also used for - // JavaThread suspend/resume operations. + // JavaThread suspend/resume operations performed by SuspendResumeManager. Monitor _lock; // Set to the thread executing the handshake operation. Thread* volatile _active_handshaker; @@ -160,31 +156,5 @@ class HandshakeState { bool async_exceptions_blocked() { return _async_exceptions_blocked; } void set_async_exceptions_blocked(bool b) { _async_exceptions_blocked = b; } void handle_unsafe_access_error(); - - // Suspend/resume support - private: - // This flag is true when the thread owning this - // HandshakeState (the _handshakee) is suspended. - volatile bool _suspended; - // This flag is true while there is async handshake (trap) - // on queue. Since we do only need one, we can reuse it if - // thread gets suspended again (after a resume) - // and we have not yet processed it. - bool _async_suspend_handshake; - - // Called from the suspend handshake. - bool suspend_with_handshake(bool register_vthread_SR); - // Called from the async handshake (the trap) - // to stop a thread from continuing execution when suspended. - void do_self_suspend(); - - bool is_suspended() { return Atomic::load(&_suspended); } - void set_suspended(bool to, bool register_vthread_SR); - bool has_async_suspend_handshake() { return _async_suspend_handshake; } - void set_async_suspend_handshake(bool to) { _async_suspend_handshake = to; } - - bool suspend(bool register_vthread_SR); - bool resume(bool register_vthread_SR); }; - #endif // SHARE_RUNTIME_HANDSHAKE_HPP diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index f3ad0e0a325..37ecb1c6f9a 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -498,6 +498,7 @@ JavaThread::JavaThread(MemTag mem_tag) : _pending_interrupted_exception(false), _handshake(this), + _suspend_resume_manager(this, &_handshake._lock), _popframe_preserved_args(nullptr), _popframe_preserved_args_size(0), @@ -1200,13 +1201,13 @@ bool JavaThread::java_suspend(bool register_vthread_SR) { guarantee(Thread::is_JavaThread_protected(/* target */ this), "target JavaThread is not protected in calling context."); - return this->handshake_state()->suspend(register_vthread_SR); + return this->suspend_resume_manager()->suspend(register_vthread_SR); } bool JavaThread::java_resume(bool register_vthread_SR) { guarantee(Thread::is_JavaThread_protected_by_TLH(/* target */ this), "missing ThreadsListHandle in calling context."); - return this->handshake_state()->resume(register_vthread_SR); + return this->suspend_resume_manager()->resume(register_vthread_SR); } // Wait for another thread to perform object reallocation and relocking on behalf of diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 968dfd0ce48..af46492622d 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -40,6 +40,7 @@ #include "runtime/safepointMechanism.hpp" #include "runtime/stackWatermarkSet.hpp" #include "runtime/stackOverflow.hpp" +#include "runtime/suspendResumeManager.hpp" #include "runtime/thread.hpp" #include "runtime/threadHeapSampler.hpp" #include "runtime/threadIdentifier.hpp" @@ -694,9 +695,13 @@ private: // Suspend/resume support for JavaThread // higher-level suspension/resume logic called by the public APIs +private: + SuspendResumeManager _suspend_resume_manager; +public: bool java_suspend(bool register_vthread_SR); bool java_resume(bool register_vthread_SR); - bool is_suspended() { return _handshake.is_suspended(); } + bool is_suspended() { return _suspend_resume_manager.is_suspended(); } + SuspendResumeManager* suspend_resume_manager() { return &_suspend_resume_manager; } // Check for async exception in addition to safepoint. static void check_special_condition_for_native_trans(JavaThread *thread); diff --git a/src/hotspot/share/runtime/suspendResumeManager.cpp b/src/hotspot/share/runtime/suspendResumeManager.cpp new file mode 100644 index 00000000000..fd14f73f553 --- /dev/null +++ b/src/hotspot/share/runtime/suspendResumeManager.cpp @@ -0,0 +1,158 @@ +/* + * 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. + * + */ + +#include "logging/log.hpp" +#include "logging/logStream.hpp" +#include "memory/resourceArea.hpp" +#include "prims/jvmtiThreadState.hpp" +#include "runtime/atomic.hpp" +#include "runtime/globals.hpp" +#include "runtime/handshake.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/javaThread.inline.hpp" +#include "runtime/suspendResumeManager.hpp" + +// This is the closure that prevents a suspended JavaThread from +// escaping the suspend request. +class ThreadSelfSuspensionHandshake : public AsyncHandshakeClosure { +public: + ThreadSelfSuspensionHandshake() : AsyncHandshakeClosure("ThreadSelfSuspensionHandshake") {} + void do_thread(Thread* thr) { + JavaThread* current = JavaThread::cast(thr); + assert(current == Thread::current(), "Must be self executed."); + JavaThreadState jts = current->thread_state(); + + current->set_thread_state(_thread_blocked); + current->suspend_resume_manager()->do_owner_suspend(); + current->set_thread_state(jts); + current->suspend_resume_manager()->set_async_suspend_handshake(false); + } + virtual bool is_suspend() { return true; } +}; + +// This is the closure that synchronously honors the suspend request. +class SuspendThreadHandshake : public HandshakeClosure { + bool _register_vthread_SR; + bool _did_suspend; +public: + SuspendThreadHandshake(bool register_vthread_SR) : HandshakeClosure("SuspendThread"), + _register_vthread_SR(register_vthread_SR), _did_suspend(false) { + } + void do_thread(Thread* thr) { + JavaThread* target = JavaThread::cast(thr); + _did_suspend = target->suspend_resume_manager()->suspend_with_handshake(_register_vthread_SR); + } + bool did_suspend() { return _did_suspend; } +}; + +void SuspendResumeManager::set_suspended(bool is_suspend, bool register_vthread_SR) { +#if INCLUDE_JVMTI + if (register_vthread_SR) { + assert(_target->is_vthread_mounted(), "sanity check"); + if (is_suspend) { + JvmtiVTSuspender::register_vthread_suspend(_target->vthread()); + } + else { + JvmtiVTSuspender::register_vthread_resume(_target->vthread()); + } + } +#endif + Atomic::store(&_suspended, is_suspend); +} + +bool SuspendResumeManager::suspend(bool register_vthread_SR) { + JVMTI_ONLY(assert(!_target->is_in_VTMS_transition(), "no suspend allowed in VTMS transition");) + JavaThread* self = JavaThread::current(); + if (_target == self) { + // If target is the current thread we can bypass the handshake machinery + // and just suspend directly + ThreadBlockInVM tbivm(self); + MutexLocker ml(_state_lock, Mutex::_no_safepoint_check_flag); + set_suspended(true, register_vthread_SR); + do_owner_suspend(); + return true; + } else { + SuspendThreadHandshake st(register_vthread_SR); + Handshake::execute(&st, _target); + return st.did_suspend(); + } +} + +bool SuspendResumeManager::resume(bool register_vthread_SR) { + MutexLocker ml(_state_lock, Mutex::_no_safepoint_check_flag); + if (!is_suspended()) { + assert(!_target->is_suspended(), "cannot be suspended without a suspend request"); + return false; + } + // Resume the thread. + set_suspended(false, register_vthread_SR); + _state_lock->notify(); + return true; +} + +void SuspendResumeManager::do_owner_suspend() { + assert(Thread::current() == _target, "should call from _target"); + assert(_state_lock->owned_by_self(), "Lock must be held"); + assert(!_target->has_last_Java_frame() || _target->frame_anchor()->walkable(), "should have walkable stack"); + assert(_target->thread_state() == _thread_blocked, "Caller should have transitioned to _thread_blocked"); + + while (is_suspended()) { + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended", p2i(_target)); + _state_lock->wait_without_safepoint_check(); + } + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " resumed", p2i(_target)); +} + +bool SuspendResumeManager::suspend_with_handshake(bool register_vthread_SR) { + assert(_target->threadObj() != nullptr, "cannot suspend with a null threadObj"); + if (_target->is_exiting()) { + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " exiting", p2i(_target)); + return false; + } + if (has_async_suspend_handshake()) { + if (is_suspended()) { + // Target is already suspended. + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " already suspended", p2i(_target)); + return false; + } else { + // Target is going to wake up and leave suspension. + // Let's just stop the thread from doing that. + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " re-suspended", p2i(_target)); + set_suspended(true, register_vthread_SR); + return true; + } + } + // no suspend request + assert(!is_suspended(), "cannot be suspended without a suspend request"); + // Thread is safe, so it must execute the request, thus we can count it as suspended + // from this point. + set_suspended(true, register_vthread_SR); + set_async_suspend_handshake(true); + log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended, arming ThreadSuspension", p2i(_target)); + ThreadSelfSuspensionHandshake* ts = new ThreadSelfSuspensionHandshake(); + Handshake::execute(ts, _target); + return true; +} + +SuspendResumeManager::SuspendResumeManager(JavaThread* thread, Monitor* state_lock) : _target(thread), _state_lock(state_lock), _suspended(false), _async_suspend_handshake(false) {} diff --git a/src/hotspot/share/runtime/suspendResumeManager.hpp b/src/hotspot/share/runtime/suspendResumeManager.hpp new file mode 100644 index 00000000000..fed3b34055e --- /dev/null +++ b/src/hotspot/share/runtime/suspendResumeManager.hpp @@ -0,0 +1,70 @@ +/* + * 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. + * + */ + +#ifndef SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP +#define SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP + +class SuspendThreadHandshake; +class ThreadSelfSuspensionHandshake; + +class SuspendResumeManager { + friend SuspendThreadHandshake; + friend ThreadSelfSuspensionHandshake; + friend JavaThread; + + JavaThread* _target; + Monitor* _state_lock; + + SuspendResumeManager(JavaThread* thread, Monitor* state_lock); + + // This flag is true when the thread owning this + // SuspendResumeManager (the _target) is suspended. + volatile bool _suspended; + // This flag is true while there is async handshake (trap) + // on queue. Since we do only need one, we can reuse it if + // thread gets suspended again (after a resume) + // and we have not yet processed it. + bool _async_suspend_handshake; + + bool suspend(bool register_vthread_SR); + bool resume(bool register_vthread_SR); + + // Called from the async handshake (the trap) + // to stop a thread from continuing execution when suspended. + void do_owner_suspend(); + + // Called from the suspend handshake. + bool suspend_with_handshake(bool register_vthread_SR); + + void set_suspended(bool to, bool register_vthread_SR); + + bool is_suspended() { + return Atomic::load(&_suspended); + } + + bool has_async_suspend_handshake() { return _async_suspend_handshake; } + void set_async_suspend_handshake(bool to) { _async_suspend_handshake = to; } +}; + +#endif // SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP From 7d7fc69355e6f5421cf09f93290270bb16d13182 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 11 Jun 2025 13:32:57 +0000 Subject: [PATCH 123/143] 8357570: [macOS] os::Bsd::available_memory() might return too low values Reviewed-by: clanger, mdoerr, lucy --- src/hotspot/os/bsd/os_bsd.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 9f77d5a4bde..6f7d9a6de37 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -154,7 +154,8 @@ julong os::Bsd::available_memory() { assert(kerr == KERN_SUCCESS, "host_statistics64 failed - check mach_host_self() and count"); if (kerr == KERN_SUCCESS) { - available = vmstat.free_count * os::vm_page_size(); + // free_count is just a lowerbound, other page categories can be freed too and make memory available + available = (vmstat.free_count + vmstat.inactive_count + vmstat.purgeable_count) * os::vm_page_size(); } #endif return available; From c98dffa186d48c41e76fd3a60e0129a8da60310f Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Wed, 11 Jun 2025 13:48:56 +0000 Subject: [PATCH 124/143] 8357982: Fix several failing BMI tests with -XX:+UseAPX Reviewed-by: epeter, sviswanathan --- src/hotspot/cpu/x86/x86_64.ad | 14 ++++++++---- .../intrinsics/bmi/verifycode/AndnTestI.java | 15 ++++++++++++- .../intrinsics/bmi/verifycode/BlsiTestI.java | 17 ++++++++++++++ .../bmi/verifycode/BlsmskTestI.java | 19 +++++++++++++++- .../intrinsics/bmi/verifycode/BlsrTestI.java | 20 ++++++++++++++++- .../bmi/verifycode/BmiIntrinsicBase.java | 22 +++++++++++++++---- .../bmi/verifycode/BzhiTestI2L.java | 17 +++++++++++++- .../intrinsics/bmi/verifycode/LZcntTestI.java | 6 ++++- .../intrinsics/bmi/verifycode/TZcntTestI.java | 6 ++++- 9 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index d17bdf5e2c9..7b545f0f5f7 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -10531,7 +10531,8 @@ instruct xorI_rReg_im1_ndd(rRegI dst, rRegI src, immI_M1 imm) // Xor Register with Immediate instruct xorI_rReg_imm(rRegI dst, immI src, rFlagsReg cr) %{ - predicate(!UseAPX); + // Strict predicate check to make selection of xorI_rReg_im1 cost agnostic if immI src is -1. + predicate(!UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1); match(Set dst (XorI dst src)); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); @@ -10545,7 +10546,8 @@ instruct xorI_rReg_imm(rRegI dst, immI src, rFlagsReg cr) instruct xorI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) %{ - predicate(UseAPX); + // Strict predicate check to make selection of xorI_rReg_im1_ndd cost agnostic if immI src2 is -1. + predicate(UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1); match(Set dst (XorI src1 src2)); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); @@ -10563,6 +10565,7 @@ instruct xorI_rReg_mem_imm_ndd(rRegI dst, memory src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (XorI (LoadI src1) src2)); effect(KILL cr); + ins_cost(150); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} @@ -11205,7 +11208,8 @@ instruct xorL_rReg_im1_ndd(rRegL dst,rRegL src, immL_M1 imm) // Xor Register with Immediate instruct xorL_rReg_imm(rRegL dst, immL32 src, rFlagsReg cr) %{ - predicate(!UseAPX); + // Strict predicate check to make selection of xorL_rReg_im1 cost agnostic if immL32 src is -1. + predicate(!UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L); match(Set dst (XorL dst src)); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); @@ -11219,7 +11223,8 @@ instruct xorL_rReg_imm(rRegL dst, immL32 src, rFlagsReg cr) instruct xorL_rReg_rReg_imm(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr) %{ - predicate(UseAPX); + // Strict predicate check to make selection of xorL_rReg_im1_ndd cost agnostic if immL32 src2 is -1. + predicate(UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L); match(Set dst (XorL src1 src2)); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); @@ -11238,6 +11243,7 @@ instruct xorL_rReg_mem_imm(rRegL dst, memory src1, immL32 src2, rFlagsReg cr) match(Set dst (XorL (LoadL src1) src2)); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); + ins_cost(150); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/AndnTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/AndnTestI.java index 4ff18b0744c..4145b0f5641 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/AndnTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/AndnTestI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -57,6 +57,19 @@ public class AndnTestI extends BmiIntrinsicBase.BmiTestCase { (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x00, (byte) 0xF2}; + // from intel apx specifications EVEX.128.NP.0F38.W0 F2 /r + instrMaskAPX = new byte[]{ + (byte) 0xFF, + (byte) 0x07, + (byte) 0x00, + (byte) 0x00, + (byte) 0xFF}; + instrPatternAPX = new byte[]{ + (byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction + (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes + (byte) 0x00, + (byte) 0x00, + (byte) 0xF2}; } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsiTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsiTestI.java index 17a11146f4e..346169f1aad 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsiTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsiTestI.java @@ -59,6 +59,23 @@ public class BlsiTestI extends BmiIntrinsicBase.BmiTestCase { (byte) 0x00, (byte) 0xF3, (byte) 0b0001_1000}; // bits 543 == 011 (3) + + // from intel apx specifications EVEX.128.NP.0F38.W0 F3 /3(opcode extension) + instrMaskAPX = new byte[]{ + (byte) 0xFF, + (byte) 0x07, + (byte) 0x00, + (byte) 0x00, + (byte) 0xFF, + (byte) 0x38}; + + instrPatternAPX = new byte[]{ + (byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction + (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes + (byte) 0x00, + (byte) 0x00, + (byte) 0xF3, + (byte) 0b0001_1000}; // bits 543 == 011 (3) } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsmskTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsmskTestI.java index 1fc0e99fe08..55d0368b84b 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsmskTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsmskTestI.java @@ -57,7 +57,24 @@ public class BlsmskTestI extends BmiIntrinsicBase.BmiTestCase { (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x00, (byte) 0xF3, - (byte) 0b0001_0000}; // bits 543 == 011 (3) + (byte) 0b0001_0000}; // bits 543 == 010 (2) + + // from intel apx specifications EVEX.128.NP.0F38.W1 F3 /2(opcode extension part of ModRM.REG) + instrMaskAPX = new byte[]{ + (byte) 0xFF, + (byte) 0x07, + (byte) 0x00, + (byte) 0x00, + (byte) 0xFF, + (byte) 0x38}; + + instrPatternAPX = new byte[]{ + (byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction + (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes + (byte) 0x00, + (byte) 0x00, + (byte) 0xF3, + (byte) 0b0001_0000}; // bits 543 == 010 (2) } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsrTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsrTestI.java index 57523f11086..3841a2f155c 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsrTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BlsrTestI.java @@ -58,7 +58,25 @@ public class BlsrTestI extends BmiIntrinsicBase.BmiTestCase { (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x00, (byte) 0xF3, - (byte) 0b0000_1000}; // bits 543 == 011 (3) + (byte) 0b0000_1000}; // bits 543 == 001 (1) + + // from intel apx specifications EVEX.128.NP.0F38.W1 F3 /1(opcode extension part of ModRM.REG) + instrMaskAPX = new byte[]{ + (byte) 0xFF, + (byte) 0x07, + (byte) 0x00, + (byte) 0x00, + (byte) 0xFF, + (byte) 0x38}; + + instrPatternAPX = new byte[]{ + (byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction + (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes + (byte) 0x00, + (byte) 0x00, + (byte) 0xF3, + (byte) 0b0000_1000}; // bits 543 == 001 (1) + } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BmiIntrinsicBase.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BmiIntrinsicBase.java index 743e371d5c2..0e8c8fe9514 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BmiIntrinsicBase.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BmiIntrinsicBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -111,7 +111,8 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest { protected void checkEmittedCode(Executable executable) { final byte[] nativeCode = NMethod.get(executable, false).insts; final byte[] matchInstrPattern = (((BmiTestCase) testCase).getTestCaseX64() && Platform.isX64()) ? ((BmiTestCase_x64) testCase).getInstrPattern_x64() : ((BmiTestCase) testCase).getInstrPattern(); - if (!((BmiTestCase) testCase).verifyPositive(nativeCode)) { + boolean use_apx = CPUInfo.hasFeature("apx_f"); + if (!((BmiTestCase) testCase).verifyPositive(nativeCode, use_apx)) { throw new AssertionError(testCase.name() + " " + "CPU instructions expected not found in nativeCode: " + Utils.toHexString(nativeCode) + " ---- Expected instrPattern: " + Utils.toHexString(matchInstrPattern)); } else { @@ -124,6 +125,8 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest { private final Method method; protected byte[] instrMask; protected byte[] instrPattern; + protected byte[] instrMaskAPX; + protected byte[] instrPatternAPX; protected boolean isLongOperation; protected String cpuFlag = "bmi1"; protected String vmFlag = "UseBMI1Instructions"; @@ -160,6 +163,13 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest { return countCpuInstructions(nativeCode, instrMask, instrPattern); } + protected int countCpuInstructionsAPX(byte[] nativeCode) { + if (instrMaskAPX == null || instrPatternAPX == null) { + return 0; + } + return countCpuInstructions(nativeCode, instrMaskAPX, instrPatternAPX); + } + public static int countCpuInstructions(byte[] nativeCode, byte[] instrMask, byte[] instrPattern) { int count = 0; int patternSize = Math.min(instrMask.length, instrPattern.length); @@ -181,8 +191,12 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest { return count; } - public boolean verifyPositive(byte[] nativeCode) { - final int cnt = countCpuInstructions(nativeCode); + public boolean verifyPositive(byte[] nativeCode, boolean use_apx) { + int cnt = countCpuInstructions(nativeCode); + if (use_apx) { + System.out.println("CHECKING APX INST PATTERNS"); + cnt += countCpuInstructionsAPX(nativeCode); + } if (Platform.isX86()) { return cnt >= (isLongOperation ? 2 : 1); } else { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BzhiTestI2L.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BzhiTestI2L.java index daba485be50..4cf94a0eb8b 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BzhiTestI2L.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/BzhiTestI2L.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -73,6 +73,21 @@ public class BzhiTestI2L extends BmiIntrinsicBase.BmiTestCase_x64 { (byte) 0x62, // 00010 implied 0F 38 leading opcode bytes (byte) 0xA8, (byte) 0xF5}; + + // from intel apx specifications EVEX.128.NP.0F38.W0 F5 /r + instrMaskAPX = new byte[]{ + (byte) 0xFF, + (byte) 0x07, + (byte) 0x00, + (byte) 0x00, + (byte) 0xFF}; + + instrPatternAPX = new byte[]{ + (byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction + (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes + (byte) 0x00, + (byte) 0x00, + (byte) 0xF5}; } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/LZcntTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/LZcntTestI.java index d1b88ffd9d0..c905fca34a2 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/LZcntTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/LZcntTestI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -51,6 +51,10 @@ public class LZcntTestI extends BmiIntrinsicBase.BmiTestCase_x64 { instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF}; instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBD}; + + // REX2 variant + instrMaskAPX = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte)0x80, (byte) 0xFF}; + instrPatternAPX = new byte[]{(byte) 0xF3, (byte) 0xD5, (byte) 0x80, (byte) 0xBD}; } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/TZcntTestI.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/TZcntTestI.java index 641fdb0b203..8a8ce4508fa 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/TZcntTestI.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/verifycode/TZcntTestI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -50,6 +50,10 @@ public class TZcntTestI extends BmiIntrinsicBase.BmiTestCase_x64 { instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF}; instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBC}; + + // REX2 variant + instrMaskAPX = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte)0x80, (byte) 0xFF}; + instrPatternAPX = new byte[]{(byte) 0xF3, (byte) 0xD5, (byte) 0x80, (byte) 0xBC}; } public static void main(String[] args) throws Exception { From e5196fc24d2ec9e581af7803ac47036111fee029 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Wed, 11 Jun 2025 14:09:45 +0000 Subject: [PATCH 125/143] 8358764: (sc) SocketChannel.close when thread blocked in read causes connection to be reset (win) Reviewed-by: jpai, vyazici --- .../share/classes/sun/nio/ch/Net.java | 11 + .../classes/sun/nio/ch/SocketChannelImpl.java | 42 ++-- src/java.base/unix/native/libnio/ch/Net.c | 5 + src/java.base/windows/native/libnio/ch/Net.c | 7 +- .../PeerReadsAfterAsyncClose.java | 195 ++++++++++++++++++ 5 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 test/jdk/java/nio/channels/SocketChannel/PeerReadsAfterAsyncClose.java diff --git a/src/java.base/share/classes/sun/nio/ch/Net.java b/src/java.base/share/classes/sun/nio/ch/Net.java index 5cd14a8b301..9ec7975a35c 100644 --- a/src/java.base/share/classes/sun/nio/ch/Net.java +++ b/src/java.base/share/classes/sun/nio/ch/Net.java @@ -94,6 +94,15 @@ public class Net { return EXCLUSIVE_BIND; } + private static final StableValue SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of(); + + /** + * Tells whether a TCP connection should be shutdown for writing before closing. + */ + static boolean shouldShutdownWriteBeforeClose() { + return SHUTDOWN_WRITE_BEFORE_CLOSE.orElseSet(Net::shouldShutdownWriteBeforeClose0); + } + /** * Tells whether both IPV6_XXX and IP_XXX socket options should be set on * IPv6 sockets. On some kernels, both IPV6_XXX and IP_XXX socket options @@ -462,6 +471,8 @@ public class Net { */ private static native int isExclusiveBindAvailable(); + private static native boolean shouldShutdownWriteBeforeClose0(); + private static native boolean shouldSetBothIPv4AndIPv6Options0(); private static native boolean canIPv6SocketJoinIPv4Group0(); diff --git a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java index c1241a51f85..37e6a71d80d 100644 --- a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java @@ -846,7 +846,7 @@ class SocketChannelImpl /** * Marks the beginning of a connect operation that might block. * @param blocking true if configured blocking - * @param isa the remote address + * @param sa the remote socket address * @throws ClosedChannelException if the channel is closed * @throws AlreadyConnectedException if already connected * @throws ConnectionPendingException is a connection is pending @@ -1070,8 +1070,8 @@ class SocketChannelImpl } /** - * Closes the socket if there are no I/O operations in progress and the - * channel is not registered with a Selector. + * Closes the socket if there are no I/O operations in progress (or no I/O + * operations tracked), and the channel is not registered with a Selector. */ private boolean tryClose() throws IOException { assert Thread.holdsLock(stateLock) && state == ST_CLOSING; @@ -1096,11 +1096,21 @@ class SocketChannelImpl } /** - * Closes this channel when configured in blocking mode. + * Closes this channel when configured in blocking mode. If there are no I/O + * operations in progress (or tracked), then the channel's socket is closed. If + * there are I/O operations in progress then the behavior is platform specific. * - * If there is an I/O operation in progress then the socket is pre-closed - * and the I/O threads signalled, in which case the final close is deferred - * until all I/O operations complete. + * On Unix systems, the channel's socket is pre-closed. This unparks any virtual + * threads that are blocked in I/O operations on this channel. If there are + * platform threads blocked on the channel's socket then the socket is dup'ed + * and the platform threads signalled. The final close is deferred until all I/O + * operations complete. + * + * On Windows, the channel's socket is pre-closed. This unparks any virtual + * threads that are blocked in I/O operations on this channel. If there are no + * virtual threads blocked in I/O operations on this channel then the channel's + * socket is closed. If there are virtual threads in I/O then the final close is + * deferred until all I/O operations on virtual threads complete. * * Note that a channel configured blocking may be registered with a Selector * This arises when a key is canceled and the channel configured to blocking @@ -1112,17 +1122,17 @@ class SocketChannelImpl boolean connected = (state == ST_CONNECTED); state = ST_CLOSING; - if (!tryClose()) { + if (connected && Net.shouldShutdownWriteBeforeClose()) { // shutdown output when linger interval not set to 0 - if (connected) { - try { - var SO_LINGER = StandardSocketOptions.SO_LINGER; - if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) { - Net.shutdown(fd, Net.SHUT_WR); - } - } catch (IOException ignore) { } - } + try { + var SO_LINGER = StandardSocketOptions.SO_LINGER; + if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) { + Net.shutdown(fd, Net.SHUT_WR); + } + } catch (IOException ignore) { } + } + if (!tryClose()) { // prepare file descriptor for closing nd.preClose(fd, readerThread, writerThread); } diff --git a/src/java.base/unix/native/libnio/ch/Net.c b/src/java.base/unix/native/libnio/ch/Net.c index 98445c93731..28c1814f422 100644 --- a/src/java.base/unix/native/libnio/ch/Net.c +++ b/src/java.base/unix/native/libnio/ch/Net.c @@ -205,6 +205,11 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) { return -1; } +JNIEXPORT jboolean JNICALL +Java_sun_nio_ch_Net_shouldShutdownWriteBeforeClose0(JNIEnv *env, jclass clazz) { + return JNI_FALSE; +} + JNIEXPORT jboolean JNICALL Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl) { diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index 3ccdbcc4752..105cb9cf743 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -117,6 +117,11 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) { return 1; } +JNIEXPORT jboolean JNICALL +Java_sun_nio_ch_Net_shouldShutdownWriteBeforeClose0(JNIEnv *env, jclass clazz) { + return JNI_TRUE; +} + JNIEXPORT jboolean JNICALL Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl) { diff --git a/test/jdk/java/nio/channels/SocketChannel/PeerReadsAfterAsyncClose.java b/test/jdk/java/nio/channels/SocketChannel/PeerReadsAfterAsyncClose.java new file mode 100644 index 00000000000..2b6a6edf1d4 --- /dev/null +++ b/test/jdk/java/nio/channels/SocketChannel/PeerReadsAfterAsyncClose.java @@ -0,0 +1,195 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8358764 + * @summary Test closing a socket while a thread is blocked in read. The connection + * should be closed gracefuly so that the peer reads EOF. + * @run junit PeerReadsAfterAsyncClose + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SocketChannel; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +class PeerReadsAfterAsyncClose { + + static Stream factories() { + return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory()); + } + + /** + * Close SocketChannel while a thread is blocked reading from the channel's socket. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseDuringSocketChannelRead(ThreadFactory factory) throws Exception { + var loopback = InetAddress.getLoopbackAddress(); + try (var listener = new ServerSocket()) { + listener.bind(new InetSocketAddress(loopback, 0)); + + try (SocketChannel sc = SocketChannel.open(listener.getLocalSocketAddress()); + Socket peer = listener.accept()) { + + // start thread to read from channel + var cceThrown = new AtomicBoolean(); + Thread thread = factory.newThread(() -> { + try { + sc.read(ByteBuffer.allocate(1)); + fail(); + } catch (ClosedChannelException e) { + cceThrown.set(true); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + thread.start(); + try { + // close SocketChannel when thread sampled in implRead + onReach(thread, "sun.nio.ch.SocketChannelImpl.implRead", () -> { + try { + sc.close(); + } catch (IOException ignore) { } + }); + + // peer should read EOF + int n = peer.getInputStream().read(); + assertEquals(-1, n); + } finally { + thread.join(); + } + assertEquals(true, cceThrown.get(), "ClosedChannelException not thrown"); + } + } + } + + /** + * Close Socket while a thread is blocked reading from the socket. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseDuringSocketUntimedRead(ThreadFactory factory) throws Exception { + testCloseDuringSocketRead(factory, 0); + } + + /** + * Close Socket while a thread is blocked reading from the socket with a timeout. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseDuringSockeTimedRead(ThreadFactory factory) throws Exception { + testCloseDuringSocketRead(factory, 60_000); + } + + private void testCloseDuringSocketRead(ThreadFactory factory, int timeout) throws Exception { + var loopback = InetAddress.getLoopbackAddress(); + try (var listener = new ServerSocket()) { + listener.bind(new InetSocketAddress(loopback, 0)); + + try (Socket s = new Socket(loopback, listener.getLocalPort()); + Socket peer = listener.accept()) { + + // start thread to read from socket + var seThrown = new AtomicBoolean(); + Thread thread = factory.newThread(() -> { + try { + s.setSoTimeout(timeout); + s.getInputStream().read(); + fail(); + } catch (SocketException e) { + seThrown.set(true); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + thread.start(); + try { + // close Socket when thread sampled in implRead + onReach(thread, "sun.nio.ch.NioSocketImpl.implRead", () -> { + try { + s.close(); + } catch (IOException ignore) { } + }); + + // peer should read EOF + int n = peer.getInputStream().read(); + assertEquals(-1, n); + } finally { + thread.join(); + } + assertEquals(true, seThrown.get(), "SocketException not thrown"); + } + } + } + + /** + * Runs the given action when the given target thread is sampled at the given + * location. The location takes the form "{@code c.m}" where + * {@code c} is the fully qualified class name and {@code m} is the method name. + */ + private void onReach(Thread target, String location, Runnable action) { + int index = location.lastIndexOf('.'); + String className = location.substring(0, index); + String methodName = location.substring(index + 1); + Thread.ofPlatform().daemon(true).start(() -> { + try { + boolean found = false; + while (!found) { + found = contains(target.getStackTrace(), className, methodName); + if (!found) { + Thread.sleep(20); + } + } + action.run(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * Returns true if the given stack trace contains an element for the given class + * and method name. + */ + private boolean contains(StackTraceElement[] stack, String className, String methodName) { + return Arrays.stream(stack) + .anyMatch(e -> className.equals(e.getClassName()) + && methodName.equals(e.getMethodName())); + } +} \ No newline at end of file From e9216efefc12b04c372a0a7f47167c984be544b7 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 11 Jun 2025 16:04:26 +0000 Subject: [PATCH 126/143] 8358734: Remove JavaTimeSupplementary resource bundles Reviewed-by: jlu, joehw, iris --- src/java.base/share/classes/module-info.java | 3 +- .../sun/text/resources/FormatData.java | 349 +++++++++++++---- .../text/resources/JavaTimeSupplementary.java | 353 ------------------ .../JavaTimeSupplementaryProvider.java | 35 -- .../util/locale/provider/LocaleResources.java | 9 +- .../sun/util/resources/LocaleData.java | 101 +---- .../resources/ParallelListResourceBundle.java | 259 ------------- .../share/classes/module-info.java | 4 +- .../sun/text/resources/ext/FormatData.java | 6 +- .../sun/text/resources/ext/FormatData_ja.java | 8 +- .../provider/LocaleDataProvider.java | 4 +- 11 files changed, 301 insertions(+), 830 deletions(-) delete mode 100644 src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java delete mode 100644 src/java.base/share/classes/sun/text/resources/JavaTimeSupplementaryProvider.java delete mode 100644 src/java.base/share/classes/sun/util/resources/ParallelListResourceBundle.java diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 07de3f2c57f..fe043dadab5 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -402,8 +402,7 @@ module java.base { uses sun.text.spi.JavaTimeDateTimePatternProvider; uses sun.util.spi.CalendarProvider; uses sun.util.locale.provider.LocaleDataMetaInfo; - uses sun.util.resources.LocaleData.CommonResourceBundleProvider; - uses sun.util.resources.LocaleData.SupplementaryResourceBundleProvider; + uses sun.util.resources.LocaleData.LocaleDataResourceBundleProvider; // Built-in service providers that are located via ServiceLoader diff --git a/src/java.base/share/classes/sun/text/resources/FormatData.java b/src/java.base/share/classes/sun/text/resources/FormatData.java index f90317ee69f..541cb63fff9 100644 --- a/src/java.base/share/classes/sun/text/resources/FormatData.java +++ b/src/java.base/share/classes/sun/text/resources/FormatData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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,6 @@ * questions. */ -/* - */ - /* * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved @@ -79,11 +76,11 @@ package sun.text.resources; -import sun.util.resources.ParallelListResourceBundle; +import sun.util.resources.OpenListResourceBundle; -public class FormatData extends ParallelListResourceBundle { +public class FormatData extends OpenListResourceBundle { /** - * Overrides ListResourceBundle + * Overrides OpenListResourceBundle */ @Override protected final Object[][] getContents() { @@ -119,6 +116,109 @@ public class FormatData extends ParallelListResourceBundle { "Reiwa", }; + // Moved from JavaTimeSupplementary + final String[] sharedQuarterNames = { + "Q1", + "Q2", + "Q3", + "Q4", + }; + + final String[] sharedQuarterNarrows = { + "1", + "2", + "3", + "4", + }; + + final String[] sharedDatePatterns = { + "GGGG y MMMM d, EEEE", + "GGGG y MMMM d", + "GGGG y MMM d", + "G y-MM-dd", + }; + + final String[] sharedDayAbbrs = { + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + }; + + final String[] sharedDayNarrows = { + "S", + "M", + "T", + "W", + "T", + "F", + "S", + }; + + final String[] sharedEras = { + "", + "AH", + }; + + final String[] sharedMonthNarrows = { + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "", + }; + + final String[] sharedTimePatterns = { + "HH:mm:ss zzzz", + "HH:mm:ss z", + "HH:mm:ss", + "HH:mm", + }; + + final String[] sharedAmPmMarkers = { + "AM", + "PM", + }; + + final String[] sharedJavaTimeDatePatterns = { + "G y MMMM d, EEEE", + "G y MMMM d", + "G y MMM d", + "GGGGG y-MM-dd", + }; + + final String[] sharedShortEras = { + "Before R.O.C.", + "R.O.C.", + }; + + final String[] sharedMonthAbbrs = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + "", + }; + return new Object[][] { { "MonthNames", new String[] { @@ -138,39 +238,9 @@ public class FormatData extends ParallelListResourceBundle { } }, { "MonthAbbreviations", - new String[] { - "Jan", // abb january - "Feb", // abb february - "Mar", // abb march - "Apr", // abb april - "May", // abb may - "Jun", // abb june - "Jul", // abb july - "Aug", // abb august - "Sep", // abb september - "Oct", // abb october - "Nov", // abb november - "Dec", // abb december - "" // abb month 13 if applicable - } - }, + sharedMonthAbbrs }, { "MonthNarrows", - new String[] { - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "", - } - }, + sharedMonthNarrows }, { "DayNames", new String[] { "Sunday", // Sunday @@ -183,33 +253,11 @@ public class FormatData extends ParallelListResourceBundle { } }, { "DayAbbreviations", - new String[] { - "Sun", // abb Sunday - "Mon", // abb Monday - "Tue", // abb Tuesday - "Wed", // abb Wednesday - "Thu", // abb Thursday - "Fri", // abb Friday - "Sat" // abb Saturday - } - }, + sharedDayAbbrs }, { "DayNarrows", - new String[] { - "S", - "M", - "T", - "W", - "T", - "F", - "S", - } - }, + sharedDayNarrows }, { "AmPmMarkers", - new String[] { - "AM", // am marker - "PM" // pm marker - } - }, + sharedAmPmMarkers }, { "narrow.AmPmMarkers", new String[] { "a", // am marker @@ -227,22 +275,17 @@ public class FormatData extends ParallelListResourceBundle { } }, { "buddhist.Eras", - buddhistEras - }, + buddhistEras }, { "buddhist.short.Eras", - buddhistEras - }, + buddhistEras }, { "buddhist.narrow.Eras", - buddhistEras - }, + buddhistEras }, { "japanese.Eras", japaneseEras }, { "japanese.short.Eras", - japaneseEraAbbrs - }, + japaneseEraAbbrs }, { "japanese.narrow.Eras", - japaneseEraAbbrs - }, + japaneseEraAbbrs }, { "japanese.FirstYear", new String[] { // Japanese imperial calendar year name // empty in English @@ -898,6 +941,164 @@ public class FormatData extends ParallelListResourceBundle { } }, { "DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ" }, + + // Moved from JavaTimeSupplementary + { "QuarterAbbreviations", + sharedQuarterNames }, + { "QuarterNames", + sharedQuarterNames }, + { "QuarterNarrows", + sharedQuarterNarrows }, + { "field.dayperiod", + "Dayperiod" }, + { "field.era", + "Era" }, + { "field.hour", + "Hour" }, + { "field.minute", + "Minute" }, + { "field.month", + "Month" }, + { "field.second", + "Second" }, + { "field.week", + "Week" }, + { "field.weekday", + "Day of the Week" }, + { "field.year", + "Year" }, + { "field.zone", + "Zone" }, + { "islamic.DatePatterns", + sharedDatePatterns }, + { "islamic.DayAbbreviations", + sharedDayAbbrs }, + { "islamic.DayNames", + sharedDayAbbrs }, + { "islamic.DayNarrows", + sharedDayNarrows }, + { "islamic.Eras", + sharedEras }, + { "islamic.MonthAbbreviations", + new String[] { + "Muh.", + "Saf.", + "Rab. I", + "Rab. II", + "Jum. I", + "Jum. II", + "Raj.", + "Sha.", + "Ram.", + "Shaw.", + "Dhuʻl-Q.", + "Dhuʻl-H.", + "", + } + }, + { "islamic.MonthNames", + new String[] { + "Muharram", + "Safar", + "Rabiʻ I", + "Rabiʻ II", + "Jumada I", + "Jumada II", + "Rajab", + "Shaʻban", + "Ramadan", + "Shawwal", + "Dhuʻl-Qiʻdah", + "Dhuʻl-Hijjah", + "", + } + }, + { "islamic.MonthNarrows", + sharedMonthNarrows }, + { "islamic.QuarterNames", + sharedQuarterNames }, + { "islamic.QuarterNarrows", + sharedQuarterNarrows }, + { "islamic.TimePatterns", + sharedTimePatterns }, + { "islamic.abbreviated.AmPmMarkers", + sharedAmPmMarkers }, + { "islamic.long.Eras", + sharedEras }, + { "islamic.narrow.Eras", + sharedEras }, + { "islamic.short.Eras", + sharedEras }, + { "java.time.buddhist.DatePatterns", + sharedJavaTimeDatePatterns }, + { "java.time.buddhist.long.Eras", + new String[] { + "BC", + "BE", + } + }, + { "java.time.buddhist.short.Eras", + buddhistEras }, + { "java.time.islamic.DatePatterns", + sharedJavaTimeDatePatterns }, + { "java.time.japanese.DatePatterns", + new String[] { + "G y MMMM d (EEEE)", + "G y MMMM d", + "G y MMM d", + "GGGGGy.MM.dd", + } + }, + { "java.time.japanese.long.Eras", + japaneseEras }, + { "java.time.japanese.short.Eras", + japaneseEras }, + { "java.time.long.Eras", + new String[] { + "BCE", + "CE", + } + }, + { "java.time.roc.DatePatterns", + sharedJavaTimeDatePatterns }, + { "java.time.short.Eras", + julianEras }, + { "roc.AmPmMarkers", + sharedAmPmMarkers }, + { "roc.DatePatterns", + sharedDatePatterns }, + { "roc.DayNames", + sharedDayAbbrs }, + { "roc.DayNarrows", + sharedDayNarrows }, + { "roc.Eras", + sharedShortEras }, + { "roc.MonthAbbreviations", + sharedMonthAbbrs }, + { "roc.MonthNames", + sharedMonthAbbrs }, + { "roc.MonthNarrows", + sharedMonthNarrows }, + { "roc.QuarterNames", + sharedQuarterNames }, + { "roc.QuarterNarrows", + sharedQuarterNarrows }, + { "roc.TimePatterns", + sharedTimePatterns }, + { "roc.abbreviated.AmPmMarkers", + sharedAmPmMarkers }, + { "roc.long.Eras", + sharedShortEras }, + { "roc.narrow.AmPmMarkers", + sharedAmPmMarkers }, + { "roc.narrow.Eras", + sharedShortEras }, + { "roc.short.Eras", + sharedShortEras }, + { "timezone.gmtFormat", + "GMT{0}" }, + { "timezone.hourFormat", + "+HH:mm;-HH:mm" }, }; } } diff --git a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java b/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java deleted file mode 100644 index cbcb724d258..00000000000 --- a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementary.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (c) 2013, 2019, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - -/* - * COPYRIGHT AND PERMISSION NOTICE - * - * Copyright (C) 1991-2016 Unicode, Inc. All rights reserved. - * Distributed under the Terms of Use in - * http://www.unicode.org/copyright.html. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of the Unicode data files and any associated documentation - * (the "Data Files") or Unicode software and any associated documentation - * (the "Software") to deal in the Data Files or Software - * without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, and/or sell copies of - * the Data Files or Software, and to permit persons to whom the Data Files - * or Software are furnished to do so, provided that - * (a) this copyright and permission notice appear with all copies - * of the Data Files or Software, - * (b) this copyright and permission notice appear in associated - * documentation, and - * (c) there is clear notice in each modified Data File or in the Software - * as well as in the documentation associated with the Data File(s) or - * Software that the data or software has been modified. - * - * THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT OF THIRD PARTY RIGHTS. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - * NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - * DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THE DATA FILES OR SOFTWARE. - * - * Except as contained in this notice, the name of a copyright holder - * shall not be used in advertising or otherwise to promote the sale, - * use or other dealings in these Data Files or Software without prior - * written authorization of the copyright holder. - */ - -// Note: this file has been generated by a tool. - -package sun.text.resources; - -import sun.util.resources.OpenListResourceBundle; - -public class JavaTimeSupplementary extends OpenListResourceBundle { - @Override - protected final Object[][] getContents() { - final String[] sharedQuarterNames = { - "Q1", - "Q2", - "Q3", - "Q4", - }; - - final String[] sharedQuarterNarrows = { - "1", - "2", - "3", - "4", - }; - - final String[] sharedDatePatterns = { - "GGGG y MMMM d, EEEE", - "GGGG y MMMM d", - "GGGG y MMM d", - "G y-MM-dd", - }; - - final String[] sharedDayNames = { - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - }; - - final String[] sharedDayNarrows = { - "S", - "M", - "T", - "W", - "T", - "F", - "S", - }; - - final String[] sharedEras = { - "", - "AH", - }; - - final String[] sharedMonthNarrows = { - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "", - }; - - final String[] sharedTimePatterns = { - "HH:mm:ss zzzz", - "HH:mm:ss z", - "HH:mm:ss", - "HH:mm", - }; - - final String[] sharedAmPmMarkers = { - "AM", - "PM", - }; - - final String[] sharedJavaTimeDatePatterns = { - "G y MMMM d, EEEE", - "G y MMMM d", - "G y MMM d", - "GGGGG y-MM-dd", - }; - - final String[] sharedJavaTimeLongEras = { - "", - "Meiji", - "Taisho", - "Showa", - "Heisei", - "Reiwa", - }; - - final String[] sharedShortEras = { - "Before R.O.C.", - "R.O.C.", - }; - - final String[] sharedMonthNames = { - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - "", - }; - - return new Object[][] { - { "QuarterAbbreviations", - sharedQuarterNames }, - { "QuarterNames", - sharedQuarterNames }, - { "QuarterNarrows", - sharedQuarterNarrows }, - { "field.dayperiod", - "Dayperiod" }, - { "field.era", - "Era" }, - { "field.hour", - "Hour" }, - { "field.minute", - "Minute" }, - { "field.month", - "Month" }, - { "field.second", - "Second" }, - { "field.week", - "Week" }, - { "field.weekday", - "Day of the Week" }, - { "field.year", - "Year" }, - { "field.zone", - "Zone" }, - { "islamic.DatePatterns", - sharedDatePatterns }, - { "islamic.DayAbbreviations", - sharedDayNames }, - { "islamic.DayNames", - sharedDayNames }, - { "islamic.DayNarrows", - sharedDayNarrows }, - { "islamic.Eras", - sharedEras }, - { "islamic.MonthAbbreviations", - new String[] { - "Muh.", - "Saf.", - "Rab. I", - "Rab. II", - "Jum. I", - "Jum. II", - "Raj.", - "Sha.", - "Ram.", - "Shaw.", - "Dhuʻl-Q.", - "Dhuʻl-H.", - "", - } - }, - { "islamic.MonthNames", - new String[] { - "Muharram", - "Safar", - "Rabiʻ I", - "Rabiʻ II", - "Jumada I", - "Jumada II", - "Rajab", - "Shaʻban", - "Ramadan", - "Shawwal", - "Dhuʻl-Qiʻdah", - "Dhuʻl-Hijjah", - "", - } - }, - { "islamic.MonthNarrows", - sharedMonthNarrows }, - { "islamic.QuarterNames", - sharedQuarterNames }, - { "islamic.QuarterNarrows", - sharedQuarterNarrows }, - { "islamic.TimePatterns", - sharedTimePatterns }, - { "islamic.abbreviated.AmPmMarkers", - sharedAmPmMarkers }, - { "islamic.long.Eras", - sharedEras }, - { "islamic.narrow.Eras", - sharedEras }, - { "islamic.short.Eras", - sharedEras }, - { "java.time.buddhist.DatePatterns", - sharedJavaTimeDatePatterns }, - { "java.time.buddhist.long.Eras", - new String[] { - "BC", - "BE", - } - }, - { "java.time.buddhist.short.Eras", - new String[] { - "BC", - "B.E.", - } - }, - { "java.time.islamic.DatePatterns", - sharedJavaTimeDatePatterns }, - { "java.time.japanese.DatePatterns", - new String[] { - "G y MMMM d (EEEE)", - "G y MMMM d", - "G y MMM d", - "GGGGGy.MM.dd", - } - }, - { "java.time.japanese.long.Eras", - sharedJavaTimeLongEras }, - { "java.time.japanese.short.Eras", - sharedJavaTimeLongEras }, - { "java.time.long.Eras", - new String[] { - "BCE", - "CE", - } - }, - { "java.time.roc.DatePatterns", - sharedJavaTimeDatePatterns }, - { "java.time.short.Eras", - new String[] { - "BC", - "AD", - } - }, - { "roc.AmPmMarkers", - sharedAmPmMarkers }, - { "roc.DatePatterns", - sharedDatePatterns }, - { "roc.DayNames", - sharedDayNames }, - { "roc.DayNarrows", - sharedDayNarrows }, - { "roc.Eras", - sharedShortEras }, - { "roc.MonthAbbreviations", - sharedMonthNames }, - { "roc.MonthNames", - sharedMonthNames }, - { "roc.MonthNarrows", - sharedMonthNarrows }, - { "roc.QuarterNames", - sharedQuarterNames }, - { "roc.QuarterNarrows", - sharedQuarterNarrows }, - { "roc.TimePatterns", - sharedTimePatterns }, - { "roc.abbreviated.AmPmMarkers", - sharedAmPmMarkers }, - { "roc.long.Eras", - sharedShortEras }, - { "roc.narrow.AmPmMarkers", - sharedAmPmMarkers }, - { "roc.narrow.Eras", - sharedShortEras }, - { "roc.short.Eras", - sharedShortEras }, - { "timezone.gmtFormat", - "GMT{0}" }, - { "timezone.hourFormat", - "+HH:mm;-HH:mm" }, - }; - } -} diff --git a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementaryProvider.java b/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementaryProvider.java deleted file mode 100644 index 261e9803015..00000000000 --- a/src/java.base/share/classes/sun/text/resources/JavaTimeSupplementaryProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2015, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 sun.text.resources; - -import java.util.spi.ResourceBundleProvider; - -/** - * An interface for the internal locale data provider for which {@code ResourceBundle} - * searches. - */ -public interface JavaTimeSupplementaryProvider extends ResourceBundleProvider { -} diff --git a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java index 4ae0275fdda..c539f57141e 100644 --- a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java +++ b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -65,7 +65,6 @@ import java.util.stream.Stream; import jdk.internal.util.StaticProperty; import sun.util.resources.LocaleData; import sun.util.resources.OpenListResourceBundle; -import sun.util.resources.ParallelListResourceBundle; import sun.util.resources.TimeZoneNamesBundle; /** @@ -579,11 +578,7 @@ public class LocaleResources { * resources required by JSR 310. */ public ResourceBundle getJavaTimeFormatData() { - ResourceBundle rb = localeData.getDateFormatData(locale); - if (rb instanceof ParallelListResourceBundle) { - localeData.setSupplementary((ParallelListResourceBundle) rb); - } - return rb; + return localeData.getDateFormatData(locale); } /** diff --git a/src/java.base/share/classes/sun/util/resources/LocaleData.java b/src/java.base/share/classes/sun/util/resources/LocaleData.java index 7bf14343531..20e8e0f8fe9 100644 --- a/src/java.base/share/classes/sun/util/resources/LocaleData.java +++ b/src/java.base/share/classes/sun/util/resources/LocaleData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -44,7 +44,6 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -143,30 +142,6 @@ public class LocaleData { return getBundle(type.getTextResourcesPackage() + ".FormatData", locale); } - public void setSupplementary(ParallelListResourceBundle formatData) { - if (!formatData.areParallelContentsComplete()) { - String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary"; - setSupplementary(suppName, formatData); - } - } - - private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) { - ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent(); - boolean resetKeySet = false; - if (parent != null) { - resetKeySet = setSupplementary(suppName, parent); - } - OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale()); - formatData.setParallelContents(supp); - resetKeySet |= supp != null; - // If any parents or this bundle has parallel data, reset keyset to create - // a new keyset with the data. - if (resetKeySet) { - formatData.resetKeySet(); - } - return resetKeySet; - } - /** * Gets a number format data resource bundle, using privileges * to allow accessing a sun.* package. @@ -179,18 +154,7 @@ public class LocaleData { return Bundles.of(baseName, locale, LocaleDataStrategy.INSTANCE); } - private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) { - OpenListResourceBundle rb = null; - try { - rb = (OpenListResourceBundle) Bundles.of(baseName, locale, - SupplementaryStrategy.INSTANCE); - } catch (MissingResourceException e) { - // return null if no supplementary is available - } - return rb; - } - - private abstract static class LocaleDataResourceBundleProvider + public abstract static class LocaleDataResourceBundleProvider implements ResourceBundleProvider { /** * Changes baseName to its module dependent package name and @@ -212,20 +176,6 @@ public class LocaleData { } } - /** - * A ResourceBundleProvider implementation for loading locale data - * resource bundles except for the java.time supplementary data. - */ - public abstract static class CommonResourceBundleProvider extends LocaleDataResourceBundleProvider { - } - - /** - * A ResourceBundleProvider implementation for loading supplementary - * resource bundles for java.time. - */ - public abstract static class SupplementaryResourceBundleProvider extends LocaleDataResourceBundleProvider { - } - // Bundles.Strategy implementations private static class LocaleDataStrategy implements Bundles.Strategy { @@ -254,18 +204,20 @@ public class LocaleData { if (candidates == null) { LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE; LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type); - candidates = adapter instanceof ResourceBundleBasedAdapter ? - ((ResourceBundleBasedAdapter)adapter).getCandidateLocales(baseName, locale) : + candidates = adapter instanceof ResourceBundleBasedAdapter rbba ? + rbba.getCandidateLocales(baseName, locale) : defaultControl.getCandidateLocales(baseName, locale); // Weed out Locales which are known to have no resource bundles int lastDot = baseName.lastIndexOf('.'); String category = (lastDot >= 0) ? baseName.substring(lastDot + 1) : baseName; - Set langtags = ((JRELocaleProviderAdapter)adapter).getLanguageTagSet(category); - if (!langtags.isEmpty()) { - for (Iterator itr = candidates.iterator(); itr.hasNext();) { - if (!adapter.isSupportedProviderLocale(itr.next(), langtags)) { - itr.remove(); + if (adapter instanceof JRELocaleProviderAdapter jlpa) { + var langtags = jlpa.getLanguageTagSet(category); + if (!langtags.isEmpty()) { + for (Iterator itr = candidates.iterator(); itr.hasNext();) { + if (!jlpa.isSupportedProviderLocale(itr.next(), langtags)) { + itr.remove(); + } } } } @@ -302,36 +254,7 @@ public class LocaleData { public Class getResourceBundleProviderType(String baseName, Locale locale) { return inJavaBaseModule(baseName, locale) ? - null : CommonResourceBundleProvider.class; - } - } - - private static class SupplementaryStrategy extends LocaleDataStrategy { - private static final SupplementaryStrategy INSTANCE - = new SupplementaryStrategy(); - // TODO: avoid hard-coded Locales - private static final Set JAVA_BASE_LOCALES - = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US); - - private SupplementaryStrategy() { - } - - @Override - public List getCandidateLocales(String baseName, Locale locale) { - // Specify only the given locale - return List.of(locale); - } - - @Override - public Class getResourceBundleProviderType(String baseName, - Locale locale) { - return inJavaBaseModule(baseName, locale) ? - null : SupplementaryResourceBundleProvider.class; - } - - @Override - boolean inJavaBaseModule(String baseName, Locale locale) { - return JAVA_BASE_LOCALES.contains(locale); + null : LocaleDataResourceBundleProvider.class; } } } diff --git a/src/java.base/share/classes/sun/util/resources/ParallelListResourceBundle.java b/src/java.base/share/classes/sun/util/resources/ParallelListResourceBundle.java deleted file mode 100644 index 35319575010..00000000000 --- a/src/java.base/share/classes/sun/util/resources/ParallelListResourceBundle.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2013, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 sun.util.resources; - -import java.util.AbstractSet; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicMarkableReference; - -/** - * ParallelListResourceBundle is another variant of ListResourceBundle - * supporting "parallel" contents provided by another resource bundle - * (OpenListResourceBundle). Parallel contents, if any, are added into this - * bundle on demand. - * - * @author Masayoshi Okutsu - */ -public abstract class ParallelListResourceBundle extends ResourceBundle { - private volatile ConcurrentMap lookup; - private volatile Set keyset; - private final AtomicMarkableReference parallelContents - = new AtomicMarkableReference<>(null, false); - - /** - * Sole constructor. (For invocation by subclass constructors, typically - * implicit.) - */ - protected ParallelListResourceBundle() { - } - - /** - * Returns an array in which each item is a pair of objects in an - * Object array. The first element of each pair is the key, which - * must be a String, and the second element is the value - * associated with that key. See the class description for - * details. - * - * @return an array of an Object array representing a key-value pair. - */ - protected abstract Object[][] getContents(); - - /** - * Returns the parent of this resource bundle or null if there's no parent. - * - * @return the parent or null if no parent - */ - ResourceBundle getParent() { - return parent; - } - - /** - * Sets the parallel contents to the data given by rb. If rb is null, this - * bundle will be marked as `complete'. - * - * @param rb an OpenResourceBundle for parallel contents, or null indicating - * there are no parallel contents for this bundle - */ - public void setParallelContents(OpenListResourceBundle rb) { - if (rb == null) { - parallelContents.compareAndSet(null, null, false, true); - } else { - parallelContents.compareAndSet(null, rb.getContents(), false, false); - } - } - - /** - * Returns true if any parallel contents have been set or if this bundle is - * marked as complete. - * - * @return true if any parallel contents have been processed - */ - boolean areParallelContentsComplete() { - // Quick check for `complete' - if (parallelContents.isMarked()) { - return true; - } - boolean[] done = new boolean[1]; - Object[][] data = parallelContents.get(done); - return data != null || done[0]; - } - - @Override - protected Object handleGetObject(String key) { - if (key == null) { - throw new NullPointerException(); - } - - loadLookupTablesIfNecessary(); - return lookup.get(key); - } - - @Override - public Enumeration getKeys() { - return Collections.enumeration(keySet()); - } - - @Override - public boolean containsKey(String key) { - return keySet().contains(key); - } - - @Override - protected Set handleKeySet() { - loadLookupTablesIfNecessary(); - return lookup.keySet(); - } - - @Override - @SuppressWarnings("UnusedAssignment") - public Set keySet() { - Set ks; - while ((ks = keyset) == null) { - ks = new KeySet(handleKeySet(), parent); - synchronized (this) { - if (keyset == null) { - keyset = ks; - } - } - } - return ks; - } - - /** - * Discards any cached keyset value. This method is called from - * LocaleData for re-creating a KeySet. - */ - synchronized void resetKeySet() { - keyset = null; - } - - /** - * Loads the lookup table if they haven't been loaded already. - */ - void loadLookupTablesIfNecessary() { - ConcurrentMap map = lookup; - if (map == null) { - map = new ConcurrentHashMap<>(); - for (Object[] item : getContents()) { - map.put((String) item[0], item[1]); - } - } - - // If there's any parallel contents data, merge the data into map. - Object[][] data = parallelContents.getReference(); - if (data != null) { - for (Object[] item : data) { - map.putIfAbsent((String) item[0], item[1]); - } - parallelContents.set(null, true); - } - if (lookup == null) { - synchronized (this) { - if (lookup == null) { - lookup = map; - } - } - } - } - - /** - * This class implements the Set interface for - * ParallelListResourceBundle methods. - */ - private static class KeySet extends AbstractSet { - private final Set set; - private final ResourceBundle parent; - - private KeySet(Set set, ResourceBundle parent) { - this.set = set; - this.parent = parent; - } - - @Override - public boolean contains(Object o) { - if (set.contains(o)) { - return true; - } - return (parent != null) ? parent.containsKey((String) o) : false; - } - - @Override - public Iterator iterator() { - if (parent == null) { - return set.iterator(); - } - return new Iterator<>() { - private Iterator itr = set.iterator(); - private boolean usingParent; - - @Override - public boolean hasNext() { - if (itr.hasNext()) { - return true; - } - if (!usingParent) { - Set nextset = new HashSet<>(parent.keySet()); - nextset.removeAll(set); - itr = nextset.iterator(); - usingParent = true; - } - return itr.hasNext(); - } - - @Override - public String next() { - if (hasNext()) { - return itr.next(); - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public int size() { - if (parent == null) { - return set.size(); - } - Set allset = new HashSet<>(set); - allset.addAll(parent.keySet()); - return allset.size(); - } - } -} diff --git a/src/jdk.localedata/share/classes/module-info.java b/src/jdk.localedata/share/classes/module-info.java index 3705cf3137e..a04cf2f6f63 100644 --- a/src/jdk.localedata/share/classes/module-info.java +++ b/src/jdk.localedata/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -33,6 +33,6 @@ module jdk.localedata { provides sun.util.locale.provider.LocaleDataMetaInfo with sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo, sun.util.resources.provider.NonBaseLocaleDataMetaInfo; - provides sun.util.resources.LocaleData.CommonResourceBundleProvider with + provides sun.util.resources.LocaleData.LocaleDataResourceBundleProvider with sun.util.resources.provider.LocaleDataProvider; } diff --git a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData.java b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData.java index 0bbbd735f48..f393d5fd041 100644 --- a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData.java +++ b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -25,9 +25,9 @@ package sun.text.resources.ext; -import sun.util.resources.ParallelListResourceBundle; +import sun.util.resources.OpenListResourceBundle; -public class FormatData extends ParallelListResourceBundle { +public class FormatData extends OpenListResourceBundle { /** * Exists to keep sun.text.resources.ext package alive * with IncludeLocales jlink plugin diff --git a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java index d9a3ebafe14..4ae7bf08a0a 100644 --- a/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java +++ b/src/jdk.localedata/share/classes/sun/text/resources/ext/FormatData_ja.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -76,11 +76,11 @@ package sun.text.resources.ext; -import sun.util.resources.ParallelListResourceBundle; +import sun.util.resources.OpenListResourceBundle; -public class FormatData_ja extends ParallelListResourceBundle { +public class FormatData_ja extends OpenListResourceBundle { /** - * Overrides ParallelListResourceBundle + * Overrides OpenListResourceBundle */ @Override protected final Object[][] getContents() { diff --git a/src/jdk.localedata/share/classes/sun/util/resources/provider/LocaleDataProvider.java b/src/jdk.localedata/share/classes/sun/util/resources/provider/LocaleDataProvider.java index 96041fea03f..c901af3a41b 100644 --- a/src/jdk.localedata/share/classes/sun/util/resources/provider/LocaleDataProvider.java +++ b/src/jdk.localedata/share/classes/sun/util/resources/provider/LocaleDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, 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 @@ -32,7 +32,7 @@ import sun.util.resources.LocaleData; /** * Service Provider for loading locale data resource bundles in jdk.localedata */ -public class LocaleDataProvider extends LocaleData.CommonResourceBundleProvider { +public class LocaleDataProvider extends LocaleData.LocaleDataResourceBundleProvider { @Override public ResourceBundle getBundle(String baseName, Locale locale) { var bundleName = toBundleName(baseName, locale); From ef4cbec6fba77fd84b328d333f8592c6eedf1a2d Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Wed, 11 Jun 2025 17:47:03 +0000 Subject: [PATCH 127/143] 8358556: Assert when running with -XX:-UseLibmIntrinsic Reviewed-by: sviswanathan, kvn --- .../x86/templateInterpreterGenerator_x86_64.cpp | 14 ++++++++++---- src/hotspot/share/classfile/vmIntrinsics.cpp | 9 +++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp index 9ea4aeeccfa..46af93e9760 100644 --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp @@ -465,13 +465,19 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M __ call_VM_leaf0(CAST_FROM_FN_PTR(address, SharedRuntime::dtan)); } } else if (kind == Interpreter::java_lang_math_tanh) { - assert(StubRoutines::dtanh() != nullptr, "not initialized"); + if (StubRoutines::dtanh() != nullptr) { __ movdbl(xmm0, Address(rsp, wordSize)); __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dtanh()))); + } else { + return nullptr; // Fallback to default implementation + } } else if (kind == Interpreter::java_lang_math_cbrt) { - assert(StubRoutines::dcbrt() != nullptr, "not initialized"); - __ movdbl(xmm0, Address(rsp, wordSize)); - __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt()))); + if (StubRoutines::dcbrt() != nullptr) { + __ movdbl(xmm0, Address(rsp, wordSize)); + __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt()))); + } else { + return nullptr; // Fallback to default implementation + } } else if (kind == Interpreter::java_lang_math_abs) { assert(StubRoutines::x86::double_sign_mask() != nullptr, "not initialized"); __ movdbl(xmm0, Address(rsp, wordSize)); diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index 6fd5c07e137..baa945cdddf 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -289,8 +289,6 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_dsin: case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: - case vmIntrinsics::_dtanh: - case vmIntrinsics::_dcbrt: case vmIntrinsics::_dlog: case vmIntrinsics::_dexp: case vmIntrinsics::_dpow: @@ -316,6 +314,13 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_fmaF: if (!InlineMathNatives || !UseFMA) return true; break; + case vmIntrinsics::_dtanh: + case vmIntrinsics::_dcbrt: + if (!InlineMathNatives || !InlineIntrinsics) return true; +#if defined(AMD64) && (defined(COMPILER1) || defined(COMPILER2)) + if (!UseLibmIntrinsic) return true; +#endif + break; case vmIntrinsics::_floatToFloat16: case vmIntrinsics::_float16ToFloat: if (!InlineIntrinsics) return true; From 429158218b52964ad1e79ba9d2fa5618d6b3398e Mon Sep 17 00:00:00 2001 From: Calvin Cheung Date: Wed, 11 Jun 2025 18:10:34 +0000 Subject: [PATCH 128/143] 8357382: runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java#aot fails with Xcomp and C1 Reviewed-by: iklam, kvn --- .../runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java index 30f7ce12ca0..0f7707edae3 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -72,6 +72,7 @@ import java.io.File; import java.lang.StackWalker.StackFrame; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -312,12 +313,15 @@ class BulkLoaderTestApp { } } + static ArrayList savedLoaders = new ArrayList<>(); + static Object initFromCustomLoader() throws Exception { String path = "cust.jar"; URL url = new File(path).toURI().toURL(); URL[] urls = new URL[] {url}; URLClassLoader urlClassLoader = new URLClassLoader("MyLoader", urls, null); + savedLoaders.add(urlClassLoader); Class c = Class.forName("SimpleCusty", true, urlClassLoader); return c.newInstance(); } From 8f733570040a7d7a24775e72244f47e946af191b Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Wed, 11 Jun 2025 18:51:54 +0000 Subject: [PATCH 129/143] 8358815: Exception event spec has stale reference to catch_klass parameter Reviewed-by: cjplummer, alanb --- src/hotspot/share/prims/jvmti.xml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index a15dbb5703a..fef71737c78 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -12862,16 +12862,17 @@ myInit() { parameters uniquely identify the current location (where the exception was detected) and allow the mapping to source file and line number when that information is - available. The exception field identifies the thrown + available. The exception parameter identifies the thrown exception object. The catch_method and catch_location identify the location of the catch clause, if any, that handles the thrown exception. If there is no such catch clause, - each field is set to 0. There is no guarantee that the thread will ever + the catch_method is set to null and the catch_locationis set to 0. + There is no guarantee that the thread will ever reach this catch clause. If there are native methods on the call stack between the throw location and the catch clause, the exception may be reset by one of those native methods. - Similarly, exceptions that are reported as uncaught (catch_klass - et al. set to 0) may in fact be caught by native code. + Similarly, exceptions that are reported as uncaught (catch_method + set to null) may in fact be caught by native code. Agents can check for these occurrences by monitoring events. Note that finally clauses are implemented as catch and re-throw. Therefore they @@ -12960,7 +12961,7 @@ myInit() { available. For exceptions caught in a Java programming language method, the exception object identifies the exception object. Exceptions caught in native methods are not necessarily available by the time the - exception catch is reported, so the exception field is set + exception catch is reported, so the exception parameter is set to null. jvmdi From 3b32f6a8ec37338764d3e6713247ff96e49bf5b3 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 12 Jun 2025 00:41:39 +0000 Subject: [PATCH 130/143] 8344556: [Graal] compiler/intrinsics/bmi/* fail when AOTCache cannot be loaded Reviewed-by: dnsimon, kvn --- test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java index b005d767287..9d2a731c6e5 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java @@ -122,10 +122,11 @@ public class BMITestRunner { List vmOpts = new LinkedList(); Collections.addAll(vmOpts, additionalVMOpts); - // Hide timestamps from warnings (e.g. due to potential CDS + // Hide timestamps from warnings (e.g. due to potential AOT // saved/runtime state mismatch), to avoid false positives when // comparing output across runs. vmOpts.add("-Xlog:all=warning:stdout:level,tags"); + vmOpts.add("-Xlog:aot=off"); //setup mode-specific options switch (testVMMode) { From d7aa34982053bad37b3b726539f1245d054258f4 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 12 Jun 2025 07:08:39 +0000 Subject: [PATCH 131/143] 8357826: Avoid running some jtreg tests when asan is configured Reviewed-by: sspitsyn, amitkumar, lmesnik, syan, lucy, cjplummer --- src/hotspot/share/prims/whitebox.cpp | 26 +++++++++++++++++++ src/hotspot/share/prims/whitebox.hpp | 5 +++- test/hotspot/jtreg/TEST.ROOT | 2 ++ .../TestUseCompressedOopsFlagsWithUlimit.java | 2 ++ .../Thread/TestBreakSignalThreadDump.java | 2 ++ .../runtime/XCheckJniJsig/XCheckJSig.java | 2 ++ .../dcmd/vm/SystemDumpMapTest.java | 2 ++ .../serviceability/dcmd/vm/SystemMapTest.java | 4 +++ test/jdk/TEST.ROOT | 2 ++ test/jdk/build/AbsPathsInImage.java | 4 ++- test/jtreg-ext/requires/VMProps.java | 11 ++++++++ test/lib/jdk/test/whitebox/WhiteBox.java | 4 +++ 12 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 374e877faa6..1507696c4d3 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1075,6 +1075,22 @@ bool WhiteBox::validate_cgroup(bool cgroups_v2_enabled, } #endif +bool WhiteBox::is_asan_enabled() { +#ifdef ADDRESS_SANITIZER + return true; +#else + return false; +#endif +} + +bool WhiteBox::is_ubsan_enabled() { +#ifdef UNDEFINED_BEHAVIOR_SANITIZER + return true; +#else + return false; +#endif +} + bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThread* THREAD) { // Screen for unavailable/bad comp level or null method AbstractCompiler* comp = CompileBroker::compiler(comp_level); @@ -1886,6 +1902,14 @@ WB_ENTRY(jboolean, WB_IsMonitorInflated(JNIEnv* env, jobject wb, jobject obj)) return (jboolean) obj_oop->mark().has_monitor(); WB_END +WB_ENTRY(jboolean, WB_IsAsanEnabled(JNIEnv* env)) + return (jboolean) WhiteBox::is_asan_enabled(); +WB_END + +WB_ENTRY(jboolean, WB_IsUbsanEnabled(JNIEnv* env)) + return (jboolean) WhiteBox::is_ubsan_enabled(); +WB_END + WB_ENTRY(jlong, WB_getInUseMonitorCount(JNIEnv* env, jobject wb)) return (jlong) WhiteBox::get_in_use_monitor_count(); WB_END @@ -2882,6 +2906,8 @@ static JNINativeMethod methods[] = { (void*)&WB_AddModuleExportsToAll }, {CC"deflateIdleMonitors", CC"()Z", (void*)&WB_DeflateIdleMonitors }, {CC"isMonitorInflated0", CC"(Ljava/lang/Object;)Z", (void*)&WB_IsMonitorInflated }, + {CC"isAsanEnabled", CC"()Z", (void*)&WB_IsAsanEnabled }, + {CC"isUbsanEnabled", CC"()Z", (void*)&WB_IsUbsanEnabled }, {CC"getInUseMonitorCount", CC"()J", (void*)&WB_getInUseMonitorCount }, {CC"getLockStackCapacity", CC"()I", (void*)&WB_getLockStackCapacity }, {CC"supportsRecursiveLightweightLocking", CC"()Z", (void*)&WB_supportsRecursiveLightweightLocking }, diff --git a/src/hotspot/share/prims/whitebox.hpp b/src/hotspot/share/prims/whitebox.hpp index 4ba684fc09a..c20d35abbbb 100644 --- a/src/hotspot/share/prims/whitebox.hpp +++ b/src/hotspot/share/prims/whitebox.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -72,6 +72,9 @@ class WhiteBox : public AllStatic { #ifdef LINUX static bool validate_cgroup(bool cgroups_v2_enabled, const char* controllers_file, const char* proc_self_cgroup, const char* proc_self_mountinfo, u1* cg_flags); #endif + // provide info about enabling of Address Sanitizer / Undefined Behavior Sanitizer + static bool is_asan_enabled(); + static bool is_ubsan_enabled(); }; #endif // SHARE_PRIMS_WHITEBOX_HPP diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 8f4ef153907..9071dfa2fbf 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -91,6 +91,8 @@ requires.properties= \ vm.compiler1.enabled \ vm.compiler2.enabled \ vm.musl \ + vm.asan \ + vm.ubsan \ vm.flagless \ container.support \ systemd.support \ diff --git a/test/hotspot/jtreg/gc/arguments/TestUseCompressedOopsFlagsWithUlimit.java b/test/hotspot/jtreg/gc/arguments/TestUseCompressedOopsFlagsWithUlimit.java index 40d83739ef5..40f607bf8a1 100644 --- a/test/hotspot/jtreg/gc/arguments/TestUseCompressedOopsFlagsWithUlimit.java +++ b/test/hotspot/jtreg/gc/arguments/TestUseCompressedOopsFlagsWithUlimit.java @@ -31,6 +31,8 @@ package gc.arguments; * @library /test/lib * @library / * @requires vm.bits == "64" + * @comment ulimit clashes with the memory requirements of ASAN + * @requires !vm.asan * @requires os.family == "linux" * @requires vm.gc != "Z" * @requires vm.opt.UseCompressedOops == null diff --git a/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java b/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java index b985e15bd17..011f6979431 100644 --- a/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java +++ b/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java @@ -38,6 +38,8 @@ * @requires os.family != "windows" & os.family != "aix" * @comment TODO: Decide libjsig support on static JDK with 8351367 * @requires !jdk.static + * @comment loading of the jsig lib does currently not work well with ASAN lib + * @requires !vm.asan * @library /vmTestbase * /test/lib * @run driver TestBreakSignalThreadDump load_libjsig diff --git a/test/hotspot/jtreg/runtime/XCheckJniJsig/XCheckJSig.java b/test/hotspot/jtreg/runtime/XCheckJniJsig/XCheckJSig.java index a8010506d96..6dbe42cebb0 100644 --- a/test/hotspot/jtreg/runtime/XCheckJniJsig/XCheckJSig.java +++ b/test/hotspot/jtreg/runtime/XCheckJniJsig/XCheckJSig.java @@ -29,6 +29,8 @@ * @modules java.base/jdk.internal.misc * java.management * @requires os.family == "linux" | os.family == "mac" + * @comment loading of the jsig lib does currently not work well with ASAN lib + * @requires !vm.asan * @comment TODO: Decide libjsig support on static JDK with 8351367 * @requires !jdk.static * @run driver XCheckJSig diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java index 1911862b361..8b8ed0b53ab 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemDumpMapTest.java @@ -36,6 +36,8 @@ import java.util.regex.Pattern; * @summary Test of diagnostic command System.map * @library /test/lib * @requires (os.family == "linux" | os.family == "windows" | os.family == "mac") + * @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements + * @requires !vm.asan * @requires os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*") * @modules java.base/jdk.internal.misc * java.compiler diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java index 283dd4bae72..dcc451d41be 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/SystemMapTest.java @@ -32,6 +32,8 @@ import jdk.test.lib.process.OutputAnalyzer; * @summary Test of diagnostic command System.map * @library /test/lib * @requires (vm.gc != "Z") & (os.family == "linux" | os.family == "windows" | os.family == "mac") + * @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements + * @requires !vm.asan * @modules java.base/jdk.internal.misc * java.compiler * java.management @@ -47,6 +49,8 @@ import jdk.test.lib.process.OutputAnalyzer; * @summary Test of diagnostic command System.map using ZGC * @library /test/lib * @requires vm.gc.Z & (os.family == "linux" | os.family == "windows" | os.family == "mac") + * @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements + * @requires !vm.asan * @modules java.base/jdk.internal.misc * java.compiler * java.management diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 97efc340019..0fa78bebc3f 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -102,6 +102,8 @@ requires.properties= \ vm.cds.write.archived.java.heap \ vm.continuations \ vm.musl \ + vm.asan \ + vm.ubsan \ vm.debug \ vm.hasSA \ vm.hasJFR \ diff --git a/test/jdk/build/AbsPathsInImage.java b/test/jdk/build/AbsPathsInImage.java index 7821b60670a..1aa7e59941e 100644 --- a/test/jdk/build/AbsPathsInImage.java +++ b/test/jdk/build/AbsPathsInImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -40,6 +40,8 @@ import java.util.zip.ZipInputStream; * @bug 8226346 * @summary Check all output files for absolute path fragments * @requires !vm.debug + * @comment ASAN keeps the 'unwanted' paths in the binaries because of its build options + * @requires !vm.asan * @run main/othervm -Xmx900m AbsPathsInImage */ public class AbsPathsInImage { diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 360c96c74ef..3c7a81cd2fa 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -138,6 +138,8 @@ public class VMProps implements Callable> { map.put("container.support", this::containerSupport); map.put("systemd.support", this::systemdSupport); map.put("vm.musl", this::isMusl); + map.put("vm.asan", this::isAsanEnabled); + map.put("vm.ubsan", this::isUbsanEnabled); map.put("release.implementor", this::implementor); map.put("jdk.containerized", this::jdkContainerized); map.put("vm.flagless", this::isFlagless); @@ -728,6 +730,15 @@ public class VMProps implements Callable> { return Boolean.toString(WB.getLibcName().contains("musl")); } + // Sanitizer support + protected String isAsanEnabled() { + return "" + WB.isAsanEnabled(); + } + + protected String isUbsanEnabled() { + return "" + WB.isUbsanEnabled(); + } + private String implementor() { try (InputStream in = new BufferedInputStream(new FileInputStream( System.getProperty("java.home") + "/release"))) { diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index d1f2c5822af..f3d9ba7b6e9 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -322,6 +322,10 @@ public class WhiteBox { public native void NMTFreeArena(long arena); public native void NMTArenaMalloc(long arena, long size); + // Sanitizers + public native boolean isAsanEnabled(); + public native boolean isUbsanEnabled(); + // Compiler // Determines if the libgraal shared library file is present. From 5886ef728fc1efe43e90e056c03725c3ee982ad6 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Thu, 12 Jun 2025 07:51:29 +0000 Subject: [PATCH 132/143] 8359182: Use @requires instead of SkippedException for MaxPath.java Reviewed-by: bpb, bchristi --- test/jdk/java/io/File/MaxPath.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/test/jdk/java/io/File/MaxPath.java b/test/jdk/java/io/File/MaxPath.java index 269b291709c..30951ac0d85 100644 --- a/test/jdk/java/io/File/MaxPath.java +++ b/test/jdk/java/io/File/MaxPath.java @@ -24,21 +24,14 @@ /* @test @bug 6481955 @summary Path length less than MAX_PATH (260) works on Windows - @library /test/lib + @requires (os.family == "windows") */ import java.io.File; import java.io.IOException; -import jtreg.SkippedException; - public class MaxPath { public static void main(String[] args) throws Exception { - String osName = System.getProperty("os.name"); - if (!osName.startsWith("Windows")) { - throw new SkippedException("This test is run only on Windows"); - } - int MAX_PATH = 260; String dir = new File(".").getAbsolutePath() + "\\"; String padding = "1234567890123456789012345678901234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890"; From 7b7136b4eca15693cfcd46ae63d644efc8a88d2c Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Thu, 12 Jun 2025 08:10:27 +0000 Subject: [PATCH 133/143] 8359181: Error messages generated by configure --help after 8301197 Reviewed-by: erikj, ihse --- make/autoconf/configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/autoconf/configure b/make/autoconf/configure index 443a37bae77..98126d1558d 100644 --- a/make/autoconf/configure +++ b/make/autoconf/configure @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 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 @@ -366,7 +366,7 @@ EOT # Print additional help, e.g. a list of toolchains and JVM features. # This must be done by the autoconf script. - ( CONFIGURE_PRINT_ADDITIONAL_HELP=true . $generated_script PRINTF=printf ) + ( CONFIGURE_PRINT_ADDITIONAL_HELP=true . $generated_script PRINTF=printf ECHO=echo ) cat < Date: Thu, 12 Jun 2025 08:18:00 +0000 Subject: [PATCH 134/143] 8359083: Test jdkCheckHtml.java should report SkippedException rather than report fails when miss tidy Reviewed-by: hannesw --- test/docs/jdk/javadoc/doccheck/DocCheck.java | 5 ++++- test/docs/jdk/javadoc/doccheck/checks/jdkCheckHtml.java | 4 ++-- .../doccheck/doccheckutils/checkers/TidyChecker.java | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/docs/jdk/javadoc/doccheck/DocCheck.java b/test/docs/jdk/javadoc/doccheck/DocCheck.java index acd61b0e76e..5819f641d6e 100644 --- a/test/docs/jdk/javadoc/doccheck/DocCheck.java +++ b/test/docs/jdk/javadoc/doccheck/DocCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -140,6 +140,9 @@ public class DocCheck extends TestRunner { var baseDir = DOCS_DIR.resolve(DIR); fileTester.processFiles(baseDir); files = fileTester.getFiles(); + if (html) { + new TidyChecker(); + } } public List getCheckers() { diff --git a/test/docs/jdk/javadoc/doccheck/checks/jdkCheckHtml.java b/test/docs/jdk/javadoc/doccheck/checks/jdkCheckHtml.java index 3e9f3ab9f82..fdf6ab7e78d 100644 --- a/test/docs/jdk/javadoc/doccheck/checks/jdkCheckHtml.java +++ b/test/docs/jdk/javadoc/doccheck/checks/jdkCheckHtml.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -26,6 +26,6 @@ * @bug 8337109 * @summary Check the html in the generated documentation * @library /test/langtools/tools/lib ../../doccheck /test/lib ../../../../tools/tester - * @build DocTester toolbox.TestRunner + * @build DocTester toolbox.TestRunner jtreg.SkippedException * @run main/othervm -Ddoccheck.checks=html DocCheck */ diff --git a/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/TidyChecker.java b/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/TidyChecker.java index 727e90a76e3..36a6a4a7b7a 100644 --- a/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/TidyChecker.java +++ b/test/docs/jdk/javadoc/doccheck/doccheckutils/checkers/TidyChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -38,6 +38,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import jtreg.SkippedException; public class TidyChecker implements FileChecker, AutoCloseable { private final Path TIDY; @@ -164,8 +165,7 @@ public class TidyChecker implements FileChecker, AutoCloseable { if (p.isPresent()) { tidyExePath = p.get(); } else { - System.err.println("tidy not found on PATH"); - return Path.of("tidy"); //non-null placeholder return; exception would be better + throw new jtreg.SkippedException("tidy not found on PATH"); } } From 3f0fef2c9c323cb7b6e9191b17eac4296ff22dac Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Thu, 12 Jun 2025 08:54:21 +0000 Subject: [PATCH 135/143] 8359135: New test TestCPUTimeSampleThrottling fails intermittently Reviewed-by: mdoerr --- .../TestCPUTimeSampleThrottling.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java index 55b350ad096..b0b9d6d2be7 100644 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java @@ -61,19 +61,27 @@ public class TestCPUTimeSampleThrottling { private static void testThrottleSettingsPeriod() throws Exception { float rate = countEvents(1000, "10ms").rate(); - Asserts.assertTrue(rate > 90 && rate < 110, "Expected around 100 events per second, got " + rate); + Asserts.assertTrue(rate > 75 && rate < 110, "Expected around 100 events per second, got " + rate); } - private record EventCount(long count, float time) { + private record EventCount(long count, float cpuTime) { float rate() { - return count / time; + return count / cpuTime; } } - private static EventCount countEvents(int timeMs, String rate) throws Exception { - try(Recording recording = new Recording()) { + /** + * Counting the events that are emitted for a given throttle in a given time. + *

+ * The result is wall-clock independent; it only records the CPU-time and the number of + * emitted events. The result, therefore, does not depend on the load of the machine. + * And because failed events are counted too, the result is not affected by the thread + * doing other in-JVM work (like garbage collection). + */ + private static EventCount countEvents(int timeMs, String throttle) throws Exception { + try (Recording recording = new Recording()) { recording.enable(EventNames.CPUTimeSample) - .with("throttle", rate); + .with("throttle", throttle); var bean = ManagementFactory.getThreadMXBean(); @@ -92,8 +100,6 @@ public class TestCPUTimeSampleThrottling { .equals(Thread.currentThread().getName())) .count(); - System.out.println("Event count: " + eventCount + ", CPU time: " + spendCPUTime / 1_000_000_000f + "s"); - return new EventCount(eventCount, spendCPUTime / 1_000_000_000f); } } From 65e63b6ab4241fc9d683e2ffa5bfe6e1a30059b6 Mon Sep 17 00:00:00 2001 From: Anjian Wen Date: Thu, 12 Jun 2025 10:44:47 +0000 Subject: [PATCH 136/143] 8359218: RISC-V: Only enable CRC32 intrinsic when AvoidUnalignedAccess == false Reviewed-by: fyang, fjiang --- src/hotspot/cpu/riscv/vm_version_riscv.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.cpp b/src/hotspot/cpu/riscv/vm_version_riscv.cpp index eca1bb83ab6..947d78477da 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.cpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.cpp @@ -203,15 +203,15 @@ void VM_Version::common_initialize() { } } - // Misc Intrinsics could depend on RVV + // Misc Intrinsics that could depend on RVV. - if (UseZba || UseRVV) { + if (!AvoidUnalignedAccesses && (UseZba || UseRVV)) { if (FLAG_IS_DEFAULT(UseCRC32Intrinsics)) { FLAG_SET_DEFAULT(UseCRC32Intrinsics, true); } } else { if (!FLAG_IS_DEFAULT(UseCRC32Intrinsics)) { - warning("CRC32 intrinsic requires Zba or RVV instructions (not available on this CPU)"); + warning("CRC32 intrinsic are not available on this CPU."); } FLAG_SET_DEFAULT(UseCRC32Intrinsics, false); } From b6ec93b038c411d0c49be671c3b44dd231d01305 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Thu, 12 Jun 2025 11:40:31 +0000 Subject: [PATCH 137/143] 8359121: C2: Region added by vectorizedMismatch intrinsic can survive as a dead node after IGVN Reviewed-by: thartmann, chagedorn --- src/hotspot/share/opto/library_call.cpp | 4 ++ ...RegionFromVectorizedMismatchIntrinsic.java | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/igvn/RemoveDeadRegionFromVectorizedMismatchIntrinsic.java diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 29f737bce08..e9878cc0c77 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -6580,6 +6580,10 @@ bool LibraryCallKit::inline_vectorizedMismatch() { memory_phi = _gvn.transform(memory_phi); result_phi = _gvn.transform(result_phi); + record_for_igvn(exit_block); + record_for_igvn(memory_phi); + record_for_igvn(result_phi); + set_control(exit_block); set_all_memory(memory_phi); set_result(result_phi); diff --git a/test/hotspot/jtreg/compiler/igvn/RemoveDeadRegionFromVectorizedMismatchIntrinsic.java b/test/hotspot/jtreg/compiler/igvn/RemoveDeadRegionFromVectorizedMismatchIntrinsic.java new file mode 100644 index 00000000000..e2f0bbf547a --- /dev/null +++ b/test/hotspot/jtreg/compiler/igvn/RemoveDeadRegionFromVectorizedMismatchIntrinsic.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/** + * @test + * @bug 8359121 + * @summary Region node introduced by ArraysSupport.mismatch must be disconnected, + * and not just put aside as dead: when simplifying + * Proj -> Region -> If -> ... + * into + * Proj -> If -> ... + * -> Region + * the dead Region node must be removed from Proj's outputs. + * @modules java.base/jdk.internal.util + * @run main/othervm -Xcomp + * -XX:CompileCommand=compileonly,compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic::test + * compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic + * @run main compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic + */ +package compiler.igvn; + +import jdk.internal.util.ArraysSupport; + +public class RemoveDeadRegionFromVectorizedMismatchIntrinsic { + public static void main(String[] args) { + ArraysSupport.mismatch(new int[0], new int[0], 0); // loads ArraysSupport + test(new byte[0], new byte[0]); + } + + public static int test(byte[] a, byte[] b) { + int i = ArraysSupport.vectorizedMismatch(a, 0, b, 0, 0, 0); + return i >= 0 ? i : 0; + } +} From 91fdd72c97ab109c08893e5b613cbe4412b645aa Mon Sep 17 00:00:00 2001 From: kabutz Date: Thu, 12 Jun 2025 11:44:04 +0000 Subject: [PATCH 138/143] 8355726: LinkedBlockingDeque fixes and improvements Reviewed-by: vklang, dl --- .../util/concurrent/LinkedBlockingDeque.java | 60 +++++----- .../tck/LinkedBlockingDequeTest.java | 110 ++++++++++++++++++ 2 files changed, 142 insertions(+), 28 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java b/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java index 8d0bc7ccdde..1e3ffb81150 100644 --- a/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java +++ b/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java @@ -150,7 +150,7 @@ public class LinkedBlockingDeque transient Node last; /** Number of items in the deque */ - private transient int count; + private transient volatile int count; /** @serial Maximum number of items in the deque */ private final int capacity; @@ -206,10 +206,13 @@ public class LinkedBlockingDeque /** * Links node as first element, or returns false if full. + * + * @return true if the node was added; false otherwise */ private boolean linkFirst(Node node) { // assert lock.isHeldByCurrentThread(); - if (count >= capacity) + int c; + if ((c = count) >= capacity) return false; Node f = first; node.next = f; @@ -218,17 +221,20 @@ public class LinkedBlockingDeque last = node; else f.prev = node; - ++count; + count = c + 1; notEmpty.signal(); return true; } /** * Links node as last element, or returns false if full. + * + * @return true if the node was added; false otherwise */ private boolean linkLast(Node node) { // assert lock.isHeldByCurrentThread(); - if (count >= capacity) + int c; + if ((c = count) >= capacity) return false; Node l = last; node.prev = l; @@ -237,7 +243,7 @@ public class LinkedBlockingDeque first = node; else l.next = node; - ++count; + count = c + 1; notEmpty.signal(); return true; } @@ -334,6 +340,8 @@ public class LinkedBlockingDeque */ public boolean offerFirst(E e) { if (e == null) throw new NullPointerException(); + if (count >= capacity) + return false; Node node = new Node(e); final ReentrantLock lock = this.lock; lock.lock(); @@ -349,6 +357,8 @@ public class LinkedBlockingDeque */ public boolean offerLast(E e) { if (e == null) throw new NullPointerException(); + if (count >= capacity) + return false; Node node = new Node(e); final ReentrantLock lock = this.lock; lock.lock(); @@ -367,7 +377,7 @@ public class LinkedBlockingDeque if (e == null) throw new NullPointerException(); Node node = new Node(e); final ReentrantLock lock = this.lock; - lock.lock(); + lock.lockInterruptibly(); try { while (!linkFirst(node)) notFull.await(); @@ -384,7 +394,7 @@ public class LinkedBlockingDeque if (e == null) throw new NullPointerException(); Node node = new Node(e); final ReentrantLock lock = this.lock; - lock.lock(); + lock.lockInterruptibly(); try { while (!linkLast(node)) notFull.await(); @@ -458,6 +468,7 @@ public class LinkedBlockingDeque } public E pollFirst() { + if (count == 0) return null; final ReentrantLock lock = this.lock; lock.lock(); try { @@ -468,6 +479,7 @@ public class LinkedBlockingDeque } public E pollLast() { + if (count == 0) return null; final ReentrantLock lock = this.lock; lock.lock(); try { @@ -479,7 +491,7 @@ public class LinkedBlockingDeque public E takeFirst() throws InterruptedException { final ReentrantLock lock = this.lock; - lock.lock(); + lock.lockInterruptibly(); try { E x; while ( (x = unlinkFirst()) == null) @@ -492,7 +504,7 @@ public class LinkedBlockingDeque public E takeLast() throws InterruptedException { final ReentrantLock lock = this.lock; - lock.lock(); + lock.lockInterruptibly(); try { E x; while ( (x = unlinkLast()) == null) @@ -558,6 +570,7 @@ public class LinkedBlockingDeque } public E peekFirst() { + if (count == 0) return null; final ReentrantLock lock = this.lock; lock.lock(); try { @@ -568,6 +581,7 @@ public class LinkedBlockingDeque } public E peekLast() { + if (count == 0) return null; final ReentrantLock lock = this.lock; lock.lock(); try { @@ -718,13 +732,7 @@ public class LinkedBlockingDeque * insert or remove an element. */ public int remainingCapacity() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - return capacity - count; - } finally { - lock.unlock(); - } + return capacity - count; } /** @@ -806,13 +814,7 @@ public class LinkedBlockingDeque * @return the number of elements in this deque */ public int size() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - return count; - } finally { - lock.unlock(); - } + return count; } /** @@ -858,7 +860,7 @@ public class LinkedBlockingDeque // Copy c into a private chain of Nodes Node beg = null, end = null; - int n = 0; + long n = 0; for (E e : c) { Objects.requireNonNull(e); n++; @@ -878,14 +880,15 @@ public class LinkedBlockingDeque final ReentrantLock lock = this.lock; lock.lock(); try { - if (count + n <= capacity) { + long cnt; + if ((cnt = count + n) <= capacity) { beg.prev = last; if (first == null) first = beg; else last.next = beg; last = end; - count += n; + count = (int)cnt; notEmpty.signalAll(); return true; } @@ -894,6 +897,7 @@ public class LinkedBlockingDeque } // Fall back to historic non-atomic implementation, failing // with IllegalStateException when the capacity is exceeded. + beg = end = null; // help GC return super.addAll(c); } @@ -994,8 +998,8 @@ public class LinkedBlockingDeque for (Node f = first; f != null; ) { f.item = null; Node n = f.next; - f.prev = null; - f.next = null; + f.prev = f; + f.next = f; f = n; } first = last = null; diff --git a/test/jdk/java/util/concurrent/tck/LinkedBlockingDequeTest.java b/test/jdk/java/util/concurrent/tck/LinkedBlockingDequeTest.java index 02b1b67d643..e8775b868f2 100644 --- a/test/jdk/java/util/concurrent/tck/LinkedBlockingDequeTest.java +++ b/test/jdk/java/util/concurrent/tck/LinkedBlockingDequeTest.java @@ -43,8 +43,10 @@ import java.util.Queue; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingDeque; import junit.framework.Test; @@ -1886,4 +1888,112 @@ public class LinkedBlockingDequeTest extends JSR166TestCase { } } + public void testInterruptedExceptionThrownInBlockingMethods() throws InterruptedException { + // Ensure that putFirst(), putLast(), takeFirst(), and takeLast() + // immediately throw an InterruptedException if the thread is + // interrupted, to be consistent with other blocking queues such as + // ArrayBlockingQueue and LinkedBlockingQueue + try (var pool = Executors.newSingleThreadExecutor()) { + Future success = pool.submit(() -> { + var queue = new LinkedBlockingDeque<>(); + Thread.currentThread().interrupt(); + try { + queue.putFirst(42); + fail("Expected InterruptedException in putFirst()"); + } catch (InterruptedException expected) { + // good that's what we want + assertFalse(Thread.currentThread().isInterrupted()); + } + + Thread.currentThread().interrupt(); + try { + queue.putLast(42); + fail("Expected InterruptedException in putLast()"); + } catch (InterruptedException expected) { + // good that's what we want + assertFalse(Thread.currentThread().isInterrupted()); + } + + queue.add(42); + Thread.currentThread().interrupt(); + try { + queue.takeFirst(); + fail("Expected InterruptedException in takeFirst()"); + } catch (InterruptedException expected) { + // good that's what we want + assertFalse(Thread.currentThread().isInterrupted()); + } + + queue.add(42); + Thread.currentThread().interrupt(); + try { + queue.takeLast(); + fail("Expected InterruptedException in takeLast()"); + } catch (InterruptedException expected) { + // good that's what we want + assertFalse(Thread.currentThread().isInterrupted()); + } + return null; + }); + try { + success.get(); + } catch (ExecutionException e) { + try { + throw e.getCause(); + } catch (Error | RuntimeException unchecked) { + throw unchecked; + } catch (Throwable cause) { + throw new AssertionError(cause); + } + } + } + } + + public void testWeaklyConsistentIterationWithClear() { + final LinkedBlockingDeque q = new LinkedBlockingDeque<>(); + q.add(one); + q.add(two); + q.add(three); + final Iterator it = q.iterator(); + mustEqual(one, it.next()); + q.clear(); + q.add(four); + q.add(five); + q.add(six); + mustEqual(two, it.next()); + mustEqual(four, it.next()); + mustEqual(five, it.next()); + mustEqual(six, it.next()); + mustEqual(3, q.size()); + } + + public void testWeaklyConsistentIterationWithIteratorRemove() { + final LinkedBlockingDeque q = new LinkedBlockingDeque<>(); + q.add(one); + q.add(two); + q.add(three); + q.add(four); + q.add(five); + final Iterator it1 = q.iterator(); + final Iterator it2 = q.iterator(); + final Iterator it3 = q.iterator(); + mustEqual(one, it1.next()); + mustEqual(two, it1.next()); + it1.remove(); // removing "two" + mustEqual(one, it2.next()); + it2.remove(); // removing "one" + mustEqual(three, it2.next()); + mustEqual(four, it2.next()); + it2.remove(); // removing "four" + mustEqual(one, it3.next()); + mustEqual(three, it3.next()); + mustEqual(five, it3.next()); + assertFalse(it3.hasNext()); + mustEqual(three, it1.next()); + mustEqual(five, it1.next()); + assertFalse(it1.hasNext()); + mustEqual(five, it2.next()); + assertFalse(it2.hasNext()); + mustEqual(2, q.size()); + } } From e5ce5c57c83972ff52758a804c942986cab74ca7 Mon Sep 17 00:00:00 2001 From: Rohitash Kumar Date: Thu, 12 Jun 2025 12:23:42 +0000 Subject: [PATCH 139/143] 8357959: (bf) ByteBuffer.allocateDirect initialization can result in large TTSP spikes Reviewed-by: shade, alanb --- .../share/classes/java/nio/Bits.java | 26 +++++++- .../java/nio/Direct-X-Buffer.java.template | 4 +- .../java/nio/Buffer/AllocateDirectInit.java | 59 +++++++++++++----- .../bench/java/nio/DirectByteBufferAlloc.java | 60 +++++++++++++++++++ 4 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/nio/DirectByteBufferAlloc.java diff --git a/src/java.base/share/classes/java/nio/Bits.java b/src/java.base/share/classes/java/nio/Bits.java index b11cb4947db..a9a22d6079e 100644 --- a/src/java.base/share/classes/java/nio/Bits.java +++ b/src/java.base/share/classes/java/nio/Bits.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -234,4 +234,28 @@ class Bits { // package-private // of an element by element copy. These numbers may change over time. static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6; static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6; + + // Maximum number of bytes to set in one call to {@code Unsafe.setMemory}. + // This threshold allows safepoint polling during large memory operations. + static final long UNSAFE_SET_THRESHOLD = 1024 * 1024; + + /** + * Sets a block of memory starting from a given address to a specified byte value. + * + * @param srcAddr + * the starting memory address + * @param count + * the number of bytes to set + * @param value + * the byte value to set + */ + static void setMemory(long srcAddr, long count, byte value) { + long offset = 0; + while (offset < count) { + long len = Math.min(UNSAFE_SET_THRESHOLD, count - offset); + UNSAFE.setMemory(srcAddr + offset, len, value); + offset += len; + } + } + } diff --git a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template index 0da1dc66a9a..580109a2eb0 100644 --- a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template +++ b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -114,7 +114,7 @@ class Direct$Type$Buffer$RW$$BO$ Bits.unreserveMemory(size, cap); throw x; } - UNSAFE.setMemory(base, size, (byte) 0); + Bits.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); diff --git a/test/jdk/java/nio/Buffer/AllocateDirectInit.java b/test/jdk/java/nio/Buffer/AllocateDirectInit.java index c54536df8ba..472187fdcda 100644 --- a/test/jdk/java/nio/Buffer/AllocateDirectInit.java +++ b/test/jdk/java/nio/Buffer/AllocateDirectInit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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,29 +23,60 @@ /** * @test - * @bug 4490253 6535542 + * @bug 4490253 6535542 8357959 + * @key randomness + * @library /test/lib + * @build jdk.test.lib.RandomFactory * @summary Verify that newly allocated direct buffers are initialized. + * @run main/othervm AllocateDirectInit */ import java.nio.ByteBuffer; +import java.util.Random; + +import jdk.test.lib.RandomFactory; public class AllocateDirectInit { + private static final int MAX_BIN_LIMIT = 16 * 1024 * 1024; + private static final int MAX_DEC_LIMIT = 10 * 1000 * 1000; + private static final int TRIES_PER_LIMIT = 1024; + + private static final Random RND = RandomFactory.getRandom(); + public static void main(String [] args){ - for (int i = 0; i < 1024; i++) { - ByteBuffer bb = ByteBuffer.allocateDirect(1024); -// printByteBuffer(bb); - for (bb.position(0); bb.position() < bb.limit(); ) { - if ((bb.get() & 0xff) != 0) - throw new RuntimeException("uninitialized buffer, position = " - + bb.position()); + // Try power of two limits + for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) { + check(ByteBuffer.allocateDirect(limit - 1)); + check(ByteBuffer.allocateDirect(limit)); + check(ByteBuffer.allocateDirect(limit + 1)); + } + + // Try power of ten limits + for (int limit = 1; limit < MAX_DEC_LIMIT; limit *= 10) { + check(ByteBuffer.allocateDirect(limit - 1)); + check(ByteBuffer.allocateDirect(limit)); + check(ByteBuffer.allocateDirect(limit + 1)); + } + + // Try random sizes within power of two limits + for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) { + for (int t = 0; t < TRIES_PER_LIMIT; t++) { + check(ByteBuffer.allocateDirect(RND.nextInt(limit))); } } } - private static void printByteBuffer(ByteBuffer bb) { - System.out.print("byte ["); - for (bb.position(0); bb.position() < bb.limit(); ) - System.out.print(" " + Integer.toHexString(bb.get() & 0xff)); - System.out.println(" ]"); + private static void check(ByteBuffer bb) { + while (bb.hasRemaining()) { + if (bb.get() != 0) { + int mismatchPos = bb.position(); + System.out.print("byte ["); + for (bb.position(0); bb.position() < bb.limit(); ) { + System.out.print(" " + Integer.toHexString(bb.get() & 0xff)); + } + System.out.println(" ]"); + throw new RuntimeException("uninitialized buffer, position = " + mismatchPos); + } + } } } diff --git a/test/micro/org/openjdk/bench/java/nio/DirectByteBufferAlloc.java b/test/micro/org/openjdk/bench/java/nio/DirectByteBufferAlloc.java new file mode 100644 index 00000000000..8e82b9a4487 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/nio/DirectByteBufferAlloc.java @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com Inc. 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 org.openjdk.bench.java.nio; + + +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(3) +public class DirectByteBufferAlloc { + + @Param({ + "128", // 128 bytes + "1024", // 1KB + "1048576", // 1 MB + "16777216" // 16MB + }) + public int bytes; + + @Benchmark + public ByteBuffer allocateDirectBuffer() { + return ByteBuffer.allocateDirect(bytes); + } +} From e18277b470a162b9668297e8e286c812c4b0b604 Mon Sep 17 00:00:00 2001 From: Radim Vansa Date: Thu, 12 Jun 2025 12:29:15 +0000 Subject: [PATCH 140/143] 8352075: Perf regression accessing fields Reviewed-by: coleenp, iklam, jsjolen --- .../share/classfile/classFileParser.cpp | 8 + .../share/classfile/classFileParser.hpp | 3 +- .../share/classfile/fieldLayoutBuilder.cpp | 4 +- src/hotspot/share/classfile/javaClasses.cpp | 7 + src/hotspot/share/oops/fieldInfo.cpp | 231 ++++++++++++++++-- src/hotspot/share/oops/fieldInfo.hpp | 35 ++- src/hotspot/share/oops/fieldInfo.inline.hpp | 34 ++- src/hotspot/share/oops/fieldStreams.hpp | 29 ++- .../share/oops/fieldStreams.inline.hpp | 37 ++- src/hotspot/share/oops/instanceKlass.cpp | 28 ++- src/hotspot/share/oops/instanceKlass.hpp | 4 + .../share/prims/jvmtiRedefineClasses.cpp | 7 + src/hotspot/share/runtime/globals.hpp | 4 + src/hotspot/share/utilities/packedTable.cpp | 113 +++++++++ src/hotspot/share/utilities/packedTable.hpp | 123 ++++++++++ src/hotspot/share/utilities/unsigned5.hpp | 11 +- .../gtest/utilities/test_packedTable.cpp | 158 ++++++++++++ .../FieldStream/LocalFieldLookupTest.java | 140 +++++++++++ 18 files changed, 905 insertions(+), 71 deletions(-) create mode 100644 src/hotspot/share/utilities/packedTable.cpp create mode 100644 src/hotspot/share/utilities/packedTable.hpp create mode 100644 test/hotspot/gtest/utilities/test_packedTable.cpp create mode 100644 test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index bfb41f8384a..edec019bd70 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -3740,6 +3740,7 @@ void ClassFileParser::apply_parsed_class_metadata( _cp->set_pool_holder(this_klass); this_klass->set_constants(_cp); this_klass->set_fieldinfo_stream(_fieldinfo_stream); + this_klass->set_fieldinfo_search_table(_fieldinfo_search_table); this_klass->set_fields_status(_fields_status); this_klass->set_methods(_methods); this_klass->set_inner_classes(_inner_classes); @@ -3749,6 +3750,8 @@ void ClassFileParser::apply_parsed_class_metadata( this_klass->set_permitted_subclasses(_permitted_subclasses); this_klass->set_record_components(_record_components); + DEBUG_ONLY(FieldInfoStream::validate_search_table(_cp, _fieldinfo_stream, _fieldinfo_search_table)); + // Delay the setting of _local_interfaces and _transitive_interfaces until after // initialize_supers() in fill_instance_klass(). It is because the _local_interfaces could // be shared with _transitive_interfaces and _transitive_interfaces may be shared with @@ -5056,6 +5059,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, // note that is not safe to use the fields in the parser from this point on assert(nullptr == _cp, "invariant"); assert(nullptr == _fieldinfo_stream, "invariant"); + assert(nullptr == _fieldinfo_search_table, "invariant"); assert(nullptr == _fields_status, "invariant"); assert(nullptr == _methods, "invariant"); assert(nullptr == _inner_classes, "invariant"); @@ -5276,6 +5280,7 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, _super_klass(), _cp(nullptr), _fieldinfo_stream(nullptr), + _fieldinfo_search_table(nullptr), _fields_status(nullptr), _methods(nullptr), _inner_classes(nullptr), @@ -5352,6 +5357,7 @@ void ClassFileParser::clear_class_metadata() { // deallocated if classfile parsing returns an error. _cp = nullptr; _fieldinfo_stream = nullptr; + _fieldinfo_search_table = nullptr; _fields_status = nullptr; _methods = nullptr; _inner_classes = nullptr; @@ -5374,6 +5380,7 @@ ClassFileParser::~ClassFileParser() { if (_fieldinfo_stream != nullptr) { MetadataFactory::free_array(_loader_data, _fieldinfo_stream); } + MetadataFactory::free_array(_loader_data, _fieldinfo_search_table); if (_fields_status != nullptr) { MetadataFactory::free_array(_loader_data, _fields_status); @@ -5774,6 +5781,7 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st _fieldinfo_stream = FieldInfoStream::create_FieldInfoStream(_temp_field_info, _java_fields_count, injected_fields_count, loader_data(), CHECK); + _fieldinfo_search_table = FieldInfoStream::create_search_table(_cp, _fieldinfo_stream, _loader_data, CHECK); _fields_status = MetadataFactory::new_array(_loader_data, _temp_field_info->length(), FieldStatus(0), CHECK); diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index 8f9f4ebea4d..707fbf6985f 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -123,6 +123,7 @@ class ClassFileParser { const InstanceKlass* _super_klass; ConstantPool* _cp; Array* _fieldinfo_stream; + Array* _fieldinfo_search_table; Array* _fields_status; Array* _methods; Array* _inner_classes; diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index b6007923907..03afe89f4f8 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -301,7 +301,7 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance BasicType last_type; int last_offset = -1; while (ik != nullptr) { - for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) { + for (AllFieldStream fs(ik); !fs.done(); fs.next()) { BasicType type = Signature::basic_type(fs.signature()); // distinction between static and non-static fields is missing if (fs.access_flags().is_static()) continue; @@ -461,7 +461,7 @@ void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlas bool found = false; const InstanceKlass* ik = super; while (!found && ik != nullptr) { - for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) { + for (AllFieldStream fs(ik); !fs.done(); fs.next()) { if (fs.offset() == b->offset()) { output->print_cr(" @%d \"%s\" %s %d/%d %s", b->offset(), diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 8d32d73fb47..0e8467289ad 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -967,6 +967,13 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) { Array* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, k->class_loader_data(), CHECK); ik->set_fieldinfo_stream(new_fis); MetadataFactory::free_array(k->class_loader_data(), old_stream); + + Array* old_table = ik->fieldinfo_search_table(); + Array* search_table = FieldInfoStream::create_search_table(ik->constants(), new_fis, k->class_loader_data(), CHECK); + ik->set_fieldinfo_search_table(search_table); + MetadataFactory::free_array(k->class_loader_data(), old_table); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(ik->constants(), new_fis, search_table)); } } diff --git a/src/hotspot/share/oops/fieldInfo.cpp b/src/hotspot/share/oops/fieldInfo.cpp index 300b45277ad..d0825ba6df8 100644 --- a/src/hotspot/share/oops/fieldInfo.cpp +++ b/src/hotspot/share/oops/fieldInfo.cpp @@ -22,8 +22,11 @@ * */ +#include "memory/resourceArea.hpp" +#include "cds/cdsConfig.hpp" #include "oops/fieldInfo.inline.hpp" #include "runtime/atomic.hpp" +#include "utilities/packedTable.hpp" void FieldInfo::print(outputStream* os, ConstantPool* cp) { os->print_cr("index=%d name_index=%d name=%s signature_index=%d signature=%s offset=%d " @@ -37,8 +40,10 @@ void FieldInfo::print(outputStream* os, ConstantPool* cp) { field_flags().as_uint(), initializer_index(), generic_signature_index(), - _field_flags.is_injected() ? lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8(), - contended_group()); + _field_flags.is_generic() ? (_field_flags.is_injected() ? + lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8() + ) : "", + is_contended() ? contended_group() : 0); } void FieldInfo::print_from_growable_array(outputStream* os, GrowableArray* array, ConstantPool* cp) { @@ -62,13 +67,17 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie StreamSizer s; StreamFieldSizer sizer(&s); + assert(fields->length() == java_fields + injected_fields, "must be"); + sizer.consumer()->accept_uint(java_fields); sizer.consumer()->accept_uint(injected_fields); for (int i = 0; i < fields->length(); i++) { FieldInfo* fi = fields->adr_at(i); sizer.map_field_info(*fi); } - int storage_size = sizer.consumer()->position() + 1; + // Originally there was an extra byte with 0 terminating the reading; + // now we check limits instead. + int storage_size = sizer.consumer()->position(); Array* const fis = MetadataFactory::new_array(loader_data, storage_size, CHECK_NULL); using StreamWriter = UNSIGNED5::Writer*, int, ArrayHelper*, int>>; @@ -79,15 +88,14 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie writer.consumer()->accept_uint(java_fields); writer.consumer()->accept_uint(injected_fields); for (int i = 0; i < fields->length(); i++) { - FieldInfo* fi = fields->adr_at(i); - writer.map_field_info(*fi); + writer.map_field_info(fields->at(i)); } #ifdef ASSERT FieldInfoReader r(fis); - int jfc = r.next_uint(); + int jfc, ifc; + r.read_field_counts(&jfc, &ifc); assert(jfc == java_fields, "Must be"); - int ifc = r.next_uint(); assert(ifc == injected_fields, "Must be"); for (int i = 0; i < jfc + ifc; i++) { FieldInfo fi; @@ -113,30 +121,221 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie return fis; } -GrowableArray* FieldInfoStream::create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count) { - int length = FieldInfoStream::num_total_fields(fis); - GrowableArray* array = new GrowableArray(length); +int FieldInfoStream::compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2) { + int cmp = n1->fast_compare(n2); + return cmp != 0 ? cmp : s1->fast_compare(s2); +} + + +// We use both name and signature during the comparison; while JLS require unique +// names for fields, JVMS requires only unique name + signature combination. +struct field_pos { + Symbol* _name; + Symbol* _signature; + int _index; + int _position; +}; + +class FieldInfoSupplier: public PackedTableBuilder::Supplier { + const field_pos* _positions; + size_t _elements; + +public: + FieldInfoSupplier(const field_pos* positions, size_t elements): _positions(positions), _elements(elements) {} + + bool next(uint32_t* key, uint32_t* value) override { + if (_elements == 0) { + return false; + } + *key = _positions->_position; + *value = _positions->_index; + ++_positions; + --_elements; + return true; + } +}; + +Array* FieldInfoStream::create_search_table(ConstantPool* cp, const Array* fis, ClassLoaderData* loader_data, TRAPS) { + if (CDSConfig::is_dumping_dynamic_archive()) { + // We cannot use search table; in case of dynamic archives it should be sorted by "requested" addresses, + // but Symbol* addresses are coming from _constants, which has "buffered" addresses. + // For background, see new comments inside allocate_node_impl in symbolTable.cpp + return nullptr; + } + FieldInfoReader r(fis); - *java_fields_count = r.next_uint(); - *injected_fields_count = r.next_uint(); + int java_fields; + int injected_fields; + r.read_field_counts(&java_fields, &injected_fields); + assert(java_fields >= 0, "must be"); + if (java_fields == 0 || fis->length() == 0 || static_cast(java_fields) < BinarySearchThreshold) { + return nullptr; + } + + ResourceMark rm; + field_pos* positions = NEW_RESOURCE_ARRAY(field_pos, java_fields); + for (int i = 0; i < java_fields; ++i) { + assert(r.has_next(), "number of fields must match"); + + positions[i]._position = r.position(); + FieldInfo fi; + r.read_field_info(fi); + + positions[i]._name = fi.name(cp); + positions[i]._signature = fi.signature(cp); + positions[i]._index = i; + } + auto compare_pair = [](const void* v1, const void* v2) { + const field_pos* p1 = reinterpret_cast(v1); + const field_pos* p2 = reinterpret_cast(v2); + return compare_name_and_sig(p1->_name, p1->_signature, p2->_name, p2->_signature); + }; + qsort(positions, java_fields, sizeof(field_pos), compare_pair); + + PackedTableBuilder builder(fis->length() - 1, java_fields - 1); + Array* table = MetadataFactory::new_array(loader_data, java_fields * builder.element_bytes(), CHECK_NULL); + FieldInfoSupplier supplier(positions, java_fields); + builder.fill(table->data(), static_cast(table->length()), supplier); + return table; +} + +GrowableArray* FieldInfoStream::create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count) { + FieldInfoReader r(fis); + r.read_field_counts(java_fields_count, injected_fields_count); + int length = *java_fields_count + *injected_fields_count; + + GrowableArray* array = new GrowableArray(length); while (r.has_next()) { FieldInfo fi; r.read_field_info(fi); array->append(fi); } assert(array->length() == length, "Must be"); - assert(array->length() == *java_fields_count + *injected_fields_count, "Must be"); return array; } void FieldInfoStream::print_from_fieldinfo_stream(Array* fis, outputStream* os, ConstantPool* cp) { - int length = FieldInfoStream::num_total_fields(fis); FieldInfoReader r(fis); - int java_field_count = r.next_uint(); - int injected_fields_count = r.next_uint(); + int java_fields_count; + int injected_fields_count; + r.read_field_counts(&java_fields_count, &injected_fields_count); while (r.has_next()) { FieldInfo fi; r.read_field_info(fi); fi.print(os, cp); } } + +class FieldInfoComparator: public PackedTableLookup::Comparator { + const FieldInfoReader* _reader; + ConstantPool* _cp; + const Symbol* _name; + const Symbol* _signature; + +public: + FieldInfoComparator(const FieldInfoReader* reader, ConstantPool* cp, const Symbol* name, const Symbol* signature): + _reader(reader), _cp(cp), _name(name), _signature(signature) {} + + int compare_to(uint32_t position) override { + FieldInfoReader r2(*_reader); + r2.set_position_and_next_index(position, -1); + u2 name_index, sig_index; + r2.read_name_and_signature(&name_index, &sig_index); + Symbol* mid_name = _cp->symbol_at(name_index); + Symbol* mid_sig = _cp->symbol_at(sig_index); + + return FieldInfoStream::compare_name_and_sig(_name, _signature, mid_name, mid_sig); + } + +#ifdef ASSERT + void reset(uint32_t position) override { + FieldInfoReader r2(*_reader); + r2.set_position_and_next_index(position, -1); + u2 name_index, signature_index; + r2.read_name_and_signature(&name_index, &signature_index); + _name = _cp->symbol_at(name_index); + _signature = _cp->symbol_at(signature_index); + } +#endif // ASSERT +}; + +#ifdef ASSERT +void FieldInfoStream::validate_search_table(ConstantPool* cp, const Array* fis, const Array* search_table) { + if (search_table == nullptr) { + return; + } + FieldInfoReader reader(fis); + int java_fields, injected_fields; + reader.read_field_counts(&java_fields, &injected_fields); + assert(java_fields > 0, "must be"); + + PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); + assert(lookup.element_bytes() * java_fields == static_cast(search_table->length()), "size does not match"); + + FieldInfoComparator comparator(&reader, cp, nullptr, nullptr); + // Check 1: assert that elements have the correct order based on the comparison function + lookup.validate_order(comparator); + + // Check 2: Iterate through the original stream (not just search_table) and try if lookup works as expected + reader.set_position_and_next_index(0, 0); + reader.read_field_counts(&java_fields, &injected_fields); + while (reader.has_next()) { + int field_start = reader.position(); + FieldInfo fi; + reader.read_field_info(fi); + if (fi.field_flags().is_injected()) { + // checking only java fields that precede injected ones + break; + } + + FieldInfoReader r2(fis); + int index = r2.search_table_lookup(search_table, fi.name(cp), fi.signature(cp), cp, java_fields); + assert(index == static_cast(fi.index()), "wrong index: %d != %u", index, fi.index()); + assert(index == r2.next_index(), "index should match"); + assert(field_start == r2.position(), "must find the same position"); + } +} +#endif // ASSERT + +void FieldInfoStream::print_search_table(outputStream* st, ConstantPool* cp, const Array* fis, const Array* search_table) { + if (search_table == nullptr) { + return; + } + FieldInfoReader reader(fis); + int java_fields, injected_fields; + reader.read_field_counts(&java_fields, &injected_fields); + assert(java_fields > 0, "must be"); + PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); + auto printer = [&] (size_t offset, uint32_t position, uint32_t index) { + reader.set_position_and_next_index(position, -1); + u2 name_index, sig_index; + reader.read_name_and_signature(&name_index, &sig_index); + Symbol* name = cp->symbol_at(name_index); + Symbol* sig = cp->symbol_at(sig_index); + st->print(" [%zu] #%d,#%d = ", offset, name_index, sig_index); + name->print_symbol_on(st); + st->print(":"); + sig->print_symbol_on(st); + st->print(" @ %p,%p", name, sig); + st->cr(); + }; + + lookup.iterate(printer); +} + +int FieldInfoReader::search_table_lookup(const Array* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields) { + assert(java_fields >= 0, "must be"); + if (java_fields == 0) { + return -1; + } + FieldInfoComparator comp(this, cp, name, signature); + PackedTableLookup lookup(_r.limit() - 1, java_fields - 1, search_table); + uint32_t position; + static_assert(sizeof(uint32_t) == sizeof(_next_index), "field size assert"); + if (lookup.search(comp, &position, reinterpret_cast(&_next_index))) { + _r.set_position(static_cast(position)); + return _next_index; + } else { + return -1; + } +} diff --git a/src/hotspot/share/oops/fieldInfo.hpp b/src/hotspot/share/oops/fieldInfo.hpp index 8740d539c8f..a98895da9cf 100644 --- a/src/hotspot/share/oops/fieldInfo.hpp +++ b/src/hotspot/share/oops/fieldInfo.hpp @@ -222,29 +222,28 @@ public: void map_field_info(const FieldInfo& fi); }; - // Gadget for decoding and reading the stream of field records. class FieldInfoReader { - friend class FieldInfoStream; - friend class ClassFileParser; - friend class FieldStreamBase; - friend class FieldInfo; - UNSIGNED5::Reader _r; int _next_index; - public: +public: FieldInfoReader(const Array* fi); - private: - uint32_t next_uint() { return _r.next_uint(); } +private: + inline uint32_t next_uint() { return _r.next_uint(); } void skip(int n) { int s = _r.try_skip(n); assert(s == n,""); } public: - int has_next() { return _r.has_next(); } - int position() { return _r.position(); } - int next_index() { return _next_index; } + void read_field_counts(int* java_fields, int* injected_fields); + int has_next() const { return _r.position() < _r.limit(); } + int position() const { return _r.position(); } + int next_index() const { return _next_index; } + void read_name_and_signature(u2* name_index, u2* signature_index); void read_field_info(FieldInfo& fi); + + int search_table_lookup(const Array* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields); + // skip a whole field record, both required and optional bits FieldInfoReader& skip_field_info(); @@ -271,6 +270,11 @@ class FieldInfoStream : AllStatic { friend class JavaFieldStream; friend class FieldStreamBase; friend class ClassFileParser; + friend class FieldInfoReader; + friend class FieldInfoComparator; + + private: + static int compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2); public: static int num_java_fields(const Array* fis); @@ -278,9 +282,14 @@ class FieldInfoStream : AllStatic { static int num_total_fields(const Array* fis); static Array* create_FieldInfoStream(GrowableArray* fields, int java_fields, int injected_fields, - ClassLoaderData* loader_data, TRAPS); + ClassLoaderData* loader_data, TRAPS); + static Array* create_search_table(ConstantPool* cp, const Array* fis, ClassLoaderData* loader_data, TRAPS); static GrowableArray* create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count); static void print_from_fieldinfo_stream(Array* fis, outputStream* os, ConstantPool* cp); + + DEBUG_ONLY(static void validate_search_table(ConstantPool* cp, const Array* fis, const Array* search_table);) + + static void print_search_table(outputStream* st, ConstantPool* cp, const Array* fis, const Array* search_table); }; class FieldStatus { diff --git a/src/hotspot/share/oops/fieldInfo.inline.hpp b/src/hotspot/share/oops/fieldInfo.inline.hpp index d3d4d765081..842393729b2 100644 --- a/src/hotspot/share/oops/fieldInfo.inline.hpp +++ b/src/hotspot/share/oops/fieldInfo.inline.hpp @@ -56,16 +56,27 @@ inline Symbol* FieldInfo::lookup_symbol(int symbol_index) const { inline int FieldInfoStream::num_injected_java_fields(const Array* fis) { FieldInfoReader fir(fis); - fir.skip(1); - return fir.next_uint(); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return injected_fields_count; } inline int FieldInfoStream::num_total_fields(const Array* fis) { FieldInfoReader fir(fis); - return fir.next_uint() + fir.next_uint(); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return java_fields_count + injected_fields_count; } -inline int FieldInfoStream::num_java_fields(const Array* fis) { return FieldInfoReader(fis).next_uint(); } +inline int FieldInfoStream::num_java_fields(const Array* fis) { + FieldInfoReader fir(fis); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return java_fields_count; +} template inline void Mapper::map_field_info(const FieldInfo& fi) { @@ -94,13 +105,22 @@ inline void Mapper::map_field_info(const FieldInfo& fi) { inline FieldInfoReader::FieldInfoReader(const Array* fi) - : _r(fi->data(), 0), + : _r(fi->data(), fi->length()), _next_index(0) { } +inline void FieldInfoReader::read_field_counts(int* java_fields, int* injected_fields) { + *java_fields = next_uint(); + *injected_fields = next_uint(); +} + +inline void FieldInfoReader::read_name_and_signature(u2* name_index, u2* signature_index) { + *name_index = checked_cast(next_uint()); + *signature_index = checked_cast(next_uint()); +} + inline void FieldInfoReader::read_field_info(FieldInfo& fi) { fi._index = _next_index++; - fi._name_index = checked_cast(next_uint()); - fi._signature_index = checked_cast(next_uint()); + read_name_and_signature(&fi._name_index, &fi._signature_index); fi._offset = next_uint(); fi._access_flags = AccessFlags(checked_cast(next_uint())); fi._field_flags = FieldInfo::FieldFlags(next_uint()); diff --git a/src/hotspot/share/oops/fieldStreams.hpp b/src/hotspot/share/oops/fieldStreams.hpp index a1c5d77eeb6..0ae828d73d9 100644 --- a/src/hotspot/share/oops/fieldStreams.hpp +++ b/src/hotspot/share/oops/fieldStreams.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -56,17 +56,23 @@ class FieldStreamBase : public StackObj { inline FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants, int start, int limit); - inline FieldStreamBase(Array* fieldinfo_stream, ConstantPool* constants); + inline FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants); - private: + private: void initialize() { - int java_fields_count = _reader.next_uint(); - int injected_fields_count = _reader.next_uint(); - assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); + int java_fields_count; + int injected_fields_count; + _reader.read_field_counts(&java_fields_count, &injected_fields_count); + if (_limit < _index) { + _limit = java_fields_count + injected_fields_count; + } else { + assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); + } if (_limit != 0) { _reader.read_field_info(_fi_buf); } } + public: inline FieldStreamBase(InstanceKlass* klass); @@ -138,8 +144,11 @@ class FieldStreamBase : public StackObj { // Iterate over only the Java fields class JavaFieldStream : public FieldStreamBase { + Array* _search_table; + public: - JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()) {} + JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()), + _search_table(k->fieldinfo_search_table()) {} u2 name_index() const { assert(!field()->field_flags().is_injected(), "regular only"); @@ -149,7 +158,6 @@ class JavaFieldStream : public FieldStreamBase { u2 signature_index() const { assert(!field()->field_flags().is_injected(), "regular only"); return field()->signature_index(); - return -1; } u2 generic_signature_index() const { @@ -164,6 +172,10 @@ class JavaFieldStream : public FieldStreamBase { assert(!field()->field_flags().is_injected(), "regular only"); return field()->initializer_index(); } + + // Performs either a linear search or binary search through the stream + // looking for a matching name/signature combo + bool lookup(const Symbol* name, const Symbol* signature); }; @@ -176,7 +188,6 @@ class InternalFieldStream : public FieldStreamBase { class AllFieldStream : public FieldStreamBase { public: - AllFieldStream(Array* fieldinfo, ConstantPool* constants): FieldStreamBase(fieldinfo, constants) {} AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {} }; diff --git a/src/hotspot/share/oops/fieldStreams.inline.hpp b/src/hotspot/share/oops/fieldStreams.inline.hpp index 776bcd1671c..51faf88c678 100644 --- a/src/hotspot/share/oops/fieldStreams.inline.hpp +++ b/src/hotspot/share/oops/fieldStreams.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -33,22 +33,18 @@ FieldStreamBase::FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants, int start, int limit) : _fieldinfo_stream(fieldinfo_stream), _reader(FieldInfoReader(_fieldinfo_stream)), - _constants(constantPoolHandle(Thread::current(), constants)), _index(start) { - _index = start; - if (limit < start) { - _limit = FieldInfoStream::num_total_fields(_fieldinfo_stream); - } else { - _limit = limit; - } + _constants(constantPoolHandle(Thread::current(), constants)), + _index(start), + _limit(limit) { initialize(); } -FieldStreamBase::FieldStreamBase(Array* fieldinfo_stream, ConstantPool* constants) : +FieldStreamBase::FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants) : _fieldinfo_stream(fieldinfo_stream), _reader(FieldInfoReader(_fieldinfo_stream)), _constants(constantPoolHandle(Thread::current(), constants)), _index(0), - _limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { + _limit(-1) { initialize(); } @@ -57,9 +53,28 @@ FieldStreamBase::FieldStreamBase(InstanceKlass* klass) : _reader(FieldInfoReader(_fieldinfo_stream)), _constants(constantPoolHandle(Thread::current(), klass->constants())), _index(0), - _limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { + _limit(-1) { assert(klass == field_holder(), ""); initialize(); } +inline bool JavaFieldStream::lookup(const Symbol* name, const Symbol* signature) { + if (_search_table != nullptr) { + int index = _reader.search_table_lookup(_search_table, name, signature, _constants(), _limit); + if (index >= 0) { + assert(index < _limit, "must be"); + _index = index; + _reader.read_field_info(_fi_buf); + return true; + } + } else { + for (; !done(); next()) { + if (this->name() == name && this->signature() == signature) { + return true; + } + } + } + return false; +} + #endif // SHARE_OOPS_FIELDSTREAMS_INLINE_HPP diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 32d7943e1e8..1bb6b3bdf98 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -686,6 +686,11 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { } set_fieldinfo_stream(nullptr); + if (fieldinfo_search_table() != nullptr && !fieldinfo_search_table()->is_shared()) { + MetadataFactory::free_array(loader_data, fieldinfo_search_table()); + } + set_fieldinfo_search_table(nullptr); + if (fields_status() != nullptr && !fields_status()->is_shared()) { MetadataFactory::free_array(loader_data, fields_status()); } @@ -1786,13 +1791,12 @@ FieldInfo InstanceKlass::field(int index) const { } bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const { - for (JavaFieldStream fs(this); !fs.done(); fs.next()) { - Symbol* f_name = fs.name(); - Symbol* f_sig = fs.signature(); - if (f_name == name && f_sig == sig) { - fd->reinitialize(const_cast(this), fs.to_FieldInfo()); - return true; - } + JavaFieldStream fs(this); + if (fs.lookup(name, sig)) { + assert(fs.name() == name, "name must match"); + assert(fs.signature() == sig, "signature must match"); + fd->reinitialize(const_cast(this), fs.to_FieldInfo()); + return true; } return false; } @@ -2610,6 +2614,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { } it->push(&_fieldinfo_stream); + it->push(&_fieldinfo_search_table); // _fields_status might be written into by Rewriter::scan_method() -> fd.set_has_initialized_final_update() it->push(&_fields_status, MetaspaceClosure::_writable); @@ -2710,6 +2715,8 @@ void InstanceKlass::remove_unshareable_info() { DEBUG_ONLY(_shared_class_load_count = 0); remove_unshareable_flags(); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table)); } void InstanceKlass::remove_unshareable_flags() { @@ -2816,6 +2823,8 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation() && !is_value_based()) { set_is_value_based(); } + + DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table)); } // Check if a class or any of its supertypes has a version older than 50. @@ -3760,6 +3769,11 @@ void InstanceKlass::print_on(outputStream* st) const { map++; } st->cr(); + + if (fieldinfo_search_table() != nullptr) { + st->print_cr(BULLET"---- field info search table:"); + FieldInfoStream::print_search_table(st, _constants, _fieldinfo_stream, _fieldinfo_search_table); + } } void InstanceKlass::print_value_on(outputStream* st) const { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 078c5b81841..55ab0996a4a 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -276,6 +276,7 @@ class InstanceKlass: public Klass { // Fields information is stored in an UNSIGNED5 encoded stream (see fieldInfo.hpp) Array* _fieldinfo_stream; + Array* _fieldinfo_search_table; Array* _fields_status; // embedded Java vtable follows here @@ -398,6 +399,9 @@ class InstanceKlass: public Klass { Array* fieldinfo_stream() const { return _fieldinfo_stream; } void set_fieldinfo_stream(Array* fis) { _fieldinfo_stream = fis; } + Array* fieldinfo_search_table() const { return _fieldinfo_search_table; } + void set_fieldinfo_search_table(Array* table) { _fieldinfo_search_table = table; } + Array* fields_status() const {return _fields_status; } void set_fields_status(Array* array) { _fields_status = array; } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index af9d973d2f1..305af6df9be 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -3550,6 +3550,13 @@ void VM_RedefineClasses::set_new_constant_pool( Array* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, scratch_class->class_loader_data(), CHECK); scratch_class->set_fieldinfo_stream(new_fis); MetadataFactory::free_array(scratch_class->class_loader_data(), old_stream); + + Array* old_table = scratch_class->fieldinfo_search_table(); + Array* search_table = FieldInfoStream::create_search_table(scratch_class->constants(), new_fis, scratch_class->class_loader_data(), CHECK); + scratch_class->set_fieldinfo_search_table(search_table); + MetadataFactory::free_array(scratch_class->class_loader_data(), old_table); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(scratch_class->constants(), new_fis, search_table)); } // Update constant pool indices in the inner classes info to use diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 290ce0d3cba..75736d0dc7d 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2005,6 +2005,10 @@ const int ObjectAlignmentInBytes = 8; product(bool, UseThreadsLockThrottleLock, true, DIAGNOSTIC, \ "Use an extra lock during Thread start and exit to alleviate" \ "contention on Threads_lock.") \ + \ + develop(uint, BinarySearchThreshold, 16, \ + "Minimal number of elements in a sorted collection to prefer" \ + "binary search over simple linear search." ) \ // end of RUNTIME_FLAGS diff --git a/src/hotspot/share/utilities/packedTable.cpp b/src/hotspot/share/utilities/packedTable.cpp new file mode 100644 index 00000000000..134cd2cd751 --- /dev/null +++ b/src/hotspot/share/utilities/packedTable.cpp @@ -0,0 +1,113 @@ +/* + * 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. + * + */ +#include + +#include "utilities/align.hpp" +#include "utilities/count_leading_zeros.hpp" +#include "utilities/packedTable.hpp" + +// The thresholds are inclusive, and in practice the limits are rounded +// to the nearest power-of-two - 1. +// Based on the max_key and max_value we figure out the number of bits required to store +// key and value; imagine that only as bits (not aligned to byte boundary... yet). +// Then we concatenate the bits for key and value, and 'add' 1-7 padding zeroes +// (high-order bits) to align on bytes. +// In the end we have each element in the table consuming 1-8 bytes (case with 0 bits for key +// is ruled out). +PackedTableBase::PackedTableBase(uint32_t max_key, uint32_t max_value) { + unsigned int key_bits = max_key == 0 ? 0 : 32 - count_leading_zeros(max_key); + unsigned int value_bits = max_value == 0 ? 0 : 32 - count_leading_zeros(max_value); + _element_bytes = align_up(key_bits + value_bits, 8) / 8; + // shifting left by 32 is undefined behaviour, and in practice returns 1 + _key_mask = key_bits >= 32 ? -1 : (1U << key_bits) - 1; + _value_shift = key_bits; + _value_mask = value_bits >= 32 ? -1 : (1U << value_bits) - 1; + guarantee(_element_bytes > 0, "wouldn't work"); + assert(_element_bytes <= sizeof(uint64_t), "shouldn't happen"); +} + +// Note: we require the supplier to provide the elements in the final order as we can't easily sort +// within this method - qsort() accepts only pure function as comparator. +void PackedTableBuilder::fill(u1* table, size_t table_length, Supplier &supplier) const { + uint32_t key, value; + size_t offset = 0; + for (; offset <= table_length && supplier.next(&key, &value); offset += _element_bytes) { + assert((key & ~_key_mask) == 0, "key out of bounds"); + assert((value & ~_value_mask) == 0, "value out of bounds: %x vs. %x (%x)", value, _value_mask, ~_value_mask); + uint64_t element = static_cast(key) | (static_cast(value) << _value_shift); + for (unsigned int i = 0; i < _element_bytes; ++i) { + table[offset + i] = static_cast(0xFF & element); + element >>= 8; + } + } + + assert(offset == table_length, "Did not fill whole array"); + assert(!supplier.next(&key, &value), "Supplier has more elements"); +} + +uint64_t PackedTableLookup::read_element(size_t offset) const { + uint64_t element = 0; + for (unsigned int i = 0; i < _element_bytes; ++i) { + element |= static_cast(_table[offset + i]) << (8 * i); + } + assert((element & ~((uint64_t) _key_mask | ((uint64_t) _value_mask << _value_shift))) == 0, "read too much"); + return element; +} + +bool PackedTableLookup::search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const { + unsigned int low = 0, high = checked_cast(_table_length / _element_bytes); + assert(low < high, "must be"); + while (low < high) { + unsigned int mid = low + (high - low) / 2; + assert(mid >= low && mid < high, "integer overflow?"); + uint64_t element = read_element(_element_bytes * mid); + // Ignoring high 32 bits in element on purpose + uint32_t key = static_cast(element) & _key_mask; + int cmp = comparator.compare_to(key); + if (cmp == 0) { + *found_key = key; + // Since __builtin_memcpy in read_element does not copy bits outside the element + // anything above _value_mask << _value_shift should be zero. + *found_value = checked_cast(element >> _value_shift) & _value_mask; + return true; + } else if (cmp < 0) { + high = mid; + } else { + low = mid + 1; + } + } + return false; +} + +#ifdef ASSERT +void PackedTableLookup::validate_order(Comparator &comparator) const { + auto validator = [&] (size_t offset, uint32_t key, uint32_t value) { + if (offset != 0) { + assert(comparator.compare_to(key) < 0, "not sorted"); + } + comparator.reset(key); + }; + iterate(validator); +} +#endif diff --git a/src/hotspot/share/utilities/packedTable.hpp b/src/hotspot/share/utilities/packedTable.hpp new file mode 100644 index 00000000000..ed08a2b4c21 --- /dev/null +++ b/src/hotspot/share/utilities/packedTable.hpp @@ -0,0 +1,123 @@ +/* + * 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. + * + */ + +#include "oops/array.hpp" +#include "utilities/globalDefinitions.hpp" + +// Base for space-optimized structure supporting binary search. Each element +// consists of up to 32-bit key, and up to 32-bit value; these are packed +// into a bit-record with 1-byte alignment. +// The keys are ordered according to a custom comparator. +class PackedTableBase { +protected: + unsigned int _element_bytes; + uint32_t _key_mask; + unsigned int _value_shift; + uint32_t _value_mask; + +public: + PackedTableBase(uint32_t max_key, uint32_t max_value); + + // Returns number of bytes each element will occupy. + inline unsigned int element_bytes(void) const { return _element_bytes; } +}; + +// Helper class for constructing a packed table in the provided array. +class PackedTableBuilder: public PackedTableBase { +public: + class Supplier { + public: + // Returns elements with already ordered keys. + // This function should return true when the key and value was set, + // and false when there's no more elements. + // Packed table does NOT support duplicate keys. + virtual bool next(uint32_t* key, uint32_t* value) = 0; + }; + + // The thresholds are inclusive, and in practice the limits are rounded + // to the nearest power-of-two - 1. + // See PackedTableBase constructor for details. + PackedTableBuilder(uint32_t max_key, uint32_t max_value): PackedTableBase(max_key, max_value) {} + + // Constructs a packed table in the provided array, filling it with elements + // from the supplier. Note that no comparator is requied by this method - + // the supplier must return elements with already ordered keys. + // The table_length (in bytes) should match number of elements provided + // by the supplier (when Supplier::next() returns false the whole array should + // be filled). + void fill(u1* table, size_t table_length, Supplier &supplier) const; +}; + +// Helper class for lookup in a packed table. +class PackedTableLookup: public PackedTableBase { + const u1* const _table; + const size_t _table_length; + + uint64_t read_element(size_t offset) const; + +public: + + // The comparator implementation does not have to store a key (uint32_t); + // the idea is that key can point into a different structure that hosts data + // suitable for the actual comparison. That's why PackedTableLookup::search(...) + // returns the key it found as well as the value. + class Comparator { + public: + // Returns negative/0/positive if the target referred to by this comparator + // is lower/equal/higher than the target referred to by the key. + virtual int compare_to(uint32_t key) = 0; + // Changes the target this comparator refers to. + DEBUG_ONLY(virtual void reset(uint32_t key) = 0); + }; + + // The thresholds are inclusive, and in practice the limits are rounded + // to the nearest power-of-two - 1. + // See PackedTableBase constructor for details. + PackedTableLookup(uint32_t max_key, uint32_t max_value, const u1 *table, size_t table_length): + PackedTableBase(max_key, max_value), _table(table), _table_length(table_length) {} + + PackedTableLookup(uint32_t max_key, uint32_t max_value, const Array *table): + PackedTableLookup(max_key, max_value, table->data(), static_cast(table->length())) {} + + // Performs a binary search in the packed table, looking for an element with key + // referring to a target equal according to the comparator. + // When the element is found, found_key and found_value are updated from the element + // and the function returns true. + // When the element is not found, found_key and found_value are not changed and + // the function returns false. + bool search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const; + + // Asserts that elements in the packed table follow the order defined by the comparator. + DEBUG_ONLY(void validate_order(Comparator &comparator) const); + + template + void iterate(Function func) const { + for (size_t offset = 0; offset < _table_length; offset += _element_bytes) { + uint64_t element = read_element(offset); + uint32_t key = static_cast(element) & _key_mask; + uint32_t value = checked_cast(element >> _value_shift) & _value_mask; + func(offset, key, value); + } + } +}; diff --git a/src/hotspot/share/utilities/unsigned5.hpp b/src/hotspot/share/utilities/unsigned5.hpp index 7f74d6e8498..8e3724a0012 100644 --- a/src/hotspot/share/utilities/unsigned5.hpp +++ b/src/hotspot/share/utilities/unsigned5.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -261,7 +261,7 @@ class UNSIGNED5 : AllStatic { ARR _array; OFF _limit; OFF _position; - int next_length() { + int next_length() const { return UNSIGNED5::check_length(_array, _position, _limit, GET()); } public: @@ -270,7 +270,7 @@ class UNSIGNED5 : AllStatic { uint32_t next_uint() { return UNSIGNED5::read_uint(_array, _position, _limit, GET()); } - bool has_next() { + bool has_next() const { return next_length() != 0; } // tries to skip count logical entries; returns actual number skipped @@ -284,8 +284,9 @@ class UNSIGNED5 : AllStatic { return actual; } ARR array() { return _array; } - OFF limit() { return _limit; } - OFF position() { return _position; } + OFF limit() const { return _limit; } + OFF position() const { return _position; } + void set_limit(OFF limit) { _limit = limit; } void set_position(OFF position) { _position = position; } // For debugging, even in product builds (see debug.cpp). diff --git a/test/hotspot/gtest/utilities/test_packedTable.cpp b/test/hotspot/gtest/utilities/test_packedTable.cpp new file mode 100644 index 00000000000..2adf8a1f08d --- /dev/null +++ b/test/hotspot/gtest/utilities/test_packedTable.cpp @@ -0,0 +1,158 @@ +/* + * 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. + * + */ +#include "utilities/packedTable.hpp" +#include "unittest.hpp" + +class Supplier: public PackedTableBuilder::Supplier { + uint32_t* _keys; + uint32_t* _values; + size_t _num_keys; + +public: + Supplier(uint32_t* keys, uint32_t* values, size_t num_keys): + _keys(keys), _values(values), _num_keys(num_keys) {} + + bool next(uint32_t* key, uint32_t* value) override { + if (_num_keys == 0) { + return false; + } + *key = *_keys; + ++_keys; + if (_values != nullptr) { + *value = *_values; + ++_values; + } else { + *value = 0; + } + --_num_keys; + return true; + } +}; + +class Comparator: public PackedTableLookup::Comparator { + uint32_t _current; + +public: + int compare_to(uint32_t key) override { + return _current < key ? -1 : (_current > key ? 1 : 0); + } + + void reset(uint32_t key) DEBUG_ONLY(override) { + _current = key; + } +}; + +static void test(uint32_t max_key, uint32_t max_value, unsigned int length) { + if (length > max_key + 1) { + // can't generate more keys, as keys must be unique + return; + } + PackedTableBuilder builder(max_key, max_value); + size_t table_length = length * builder.element_bytes(); + u1* table = new u1[table_length]; + + uint32_t* keys = new uint32_t[length]; + uint32_t* values = max_value != 0 ? new uint32_t[length] : nullptr; + for (unsigned int i = 0; i < length; ++i) { + keys[i] = i; + if (values != nullptr) { + values[i] = i % max_value; + } + } + Supplier sup(keys, values, length); + builder.fill(table, table_length, sup); + + Comparator comparator; + PackedTableLookup lookup(max_key, max_value, table, table_length); +#ifdef ASSERT + lookup.validate_order(comparator); +#endif + + for (unsigned int i = 0; i < length; ++i) { + uint32_t key, value; + comparator.reset(keys[i]); + EXPECT_TRUE(lookup.search(comparator, &key, &value)); + EXPECT_EQ(key, keys[i]); + if (values != nullptr) { + EXPECT_EQ(value, values[i]); + } else { + EXPECT_EQ(value, 0U); + } + } + + delete[] keys; + delete[] values; +} + +static void test_with_bits(uint32_t max_key, uint32_t max_value) { + // Some small sizes + for (unsigned int i = 0; i <= 100; ++i) { + test(max_key, max_value, i); + } + test(max_key, max_value, 10000); +} + +TEST(PackedTableLookup, lookup) { + for (int key_bits = 1; key_bits <= 32; ++key_bits) { + for (int value_bits = 0; value_bits <= 32; ++value_bits) { + test_with_bits(static_cast((1ULL << key_bits) - 1), + static_cast((1ULL << value_bits) - 1)); + } + } +} + +TEST(PackedTableBase, element_bytes) { + { + PackedTableBuilder builder(1, 0); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(15, 15); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(15, 16); + EXPECT_EQ(builder.element_bytes(), 2U); + } + { + PackedTableBuilder builder(31, 7); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(32, 7); + EXPECT_EQ(builder.element_bytes(), 2U); + } + { + PackedTableBuilder builder(-1, 0); + EXPECT_EQ(builder.element_bytes(), 4U); + } + { + PackedTableBuilder builder(-1, 1); + EXPECT_EQ(builder.element_bytes(), 5U); + } + { + PackedTableBuilder builder(-1, -1); + EXPECT_EQ(builder.element_bytes(), 8U); + } +} diff --git a/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java b/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java new file mode 100644 index 00000000000..bebef5acd0c --- /dev/null +++ b/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java @@ -0,0 +1,140 @@ +/* + * 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. + */ + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; +import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; +import static org.objectweb.asm.Opcodes.*; + +/* + * @test id=defaults + * @bug 8352075 + * @library /test/lib + * @library /testlibrary/asm + * @run main/othervm LocalFieldLookupTest + */ +/* + * @test id=custom-threshold + * @bug 8352075 + * @library /test/lib + * @library /testlibrary/asm + * @requires vm.debug == true + * @run main/othervm LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=0 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=1 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=15 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=100000 LocalFieldLookupTest + */ +public class LocalFieldLookupTest { + private static final String TEST_CLASS_NAME = "Test"; + private static final int MAX_FIELDS_IN_METHOD = 10000; + + public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + // Test small classes, covering the tested thresholds + for (int i = 0; i <= 33; ++i) { + makeClass(i).newInstance(); + } + // Test classes around 256 fields (index encoding 1/2 bytes) to check off-by-one errors + for (int i = 254; i <= 259; ++i) { + makeClass(255).newInstance(); + } + // We would like to test #fields that create have the stream about 65536 bytes long; + // this value is not exposed, though, so these are rather experimentally found values, + // hence fragile. Moreover, since the stream length is incremented by about 8 bytes + // for each field we cannot test for off-by-one errors reliably. + for (int i = 8433; i <= 8437; ++i) { + makeClass(i).newInstance(); + } + // The largest class we can create - this one has 65533 entries in the constant pool + makeClass(26205).newInstance(); + } + + public static Class makeClass(int fields) throws ClassNotFoundException { + ClassWriter writer = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); + writer.visit(49, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, TEST_CLASS_NAME,null, "java/lang/Object", null); + + for (int i = 0; i < fields; i += 2) { + writer.visitField(ACC_PUBLIC, "f" + i, "I", null, null); + // Let's use duplicate names to confirm search takes signatures into account + if (i + 1 < fields) { + writer.visitField(ACC_PUBLIC, "f" + i, "J", null, null); + } + } + // We initialize fields in multiple methods to avoid running into bytecode limit per method + MethodVisitor fi = null; + for (int i = 0; i < fields; i+= 2) { + if (fi == null) { + fi = writer.visitMethod(ACC_PRIVATE, "init" + i, "()V", null, null); + fi.visitCode(); + } + fi.visitVarInsn(Opcodes.ALOAD, 0); + fi.visitInsn(Opcodes.ICONST_2); + fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "I"); + if (i + 1 < fields) { + fi.visitVarInsn(Opcodes.ALOAD, 0); + fi.visitInsn(Opcodes.LCONST_1); + fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "J"); + } + if (i % MAX_FIELDS_IN_METHOD == MAX_FIELDS_IN_METHOD - 2) { + fi.visitInsn(Opcodes.RETURN); + fi.visitMaxs(0, 0); + fi.visitEnd(); + fi = null; + } + } + if (fi != null) { + fi.visitInsn(Opcodes.RETURN); + fi.visitMaxs(0, 0); + fi.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + for (int i = 0; i < fields; i += MAX_FIELDS_IN_METHOD) { + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, TEST_CLASS_NAME, "init" + i, "()V", false); + } + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + writer.visitEnd(); + + byte[] bytecode = writer.toByteArray(); + ClassLoader cl = new ClassLoader() { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (!TEST_CLASS_NAME.equals(name)) { + throw new ClassNotFoundException(); + } + return defineClass(TEST_CLASS_NAME, bytecode, 0, bytecode.length); + } + }; + return cl.loadClass(TEST_CLASS_NAME); + } +} From b85fe02be5966b72ea1a92bfb3faf088d310219a Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 12 Jun 2025 14:12:14 +0000 Subject: [PATCH 141/143] 8358600: Template-Framework Library: Template for TestFramework test class Reviewed-by: chagedorn, mhaessig --- .../library/TestFrameworkClass.java | 119 +++++++++++++ .../examples/TestWithTestFrameworkClass.java | 163 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java new file mode 100644 index 00000000000..5194b75af43 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -0,0 +1,119 @@ +/* + * 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 compiler.lib.template_framework.library; + +import java.util.List; +import java.util.Set; + +import compiler.lib.ir_framework.TestFramework; +import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +/** + * This class provides a {@link #render} method that can be used to simplify generating + * source code when using the {@link TestFramework} (also known as IR Framework) to run + * a list of tests. + * + *

+ * The idea is that the user only has to generate the code for the individual tests, + * and can then pass the corresponding list of {@link TemplateToken}s to this + * provided {@link #render} method which generates the surrounding class and the main + * method that invokes the {@link TestFramework}, so that all the generated tests + * are run. + */ +public final class TestFrameworkClass { + + // Ensure there can be no instance, and we do not have to document the constructor. + private TestFrameworkClass() {} + + /** + * This method renders a list of {@code testTemplateTokens} into the body of a class + * and generates a {@code main} method which launches the {@link TestFramework} + * to run the generated tests. + * + *

+ * The generated {@code main} method is to be invoked with a {@code vmFlags} argument, + * which must be a {@link String[]}, specifying the VM flags for the Test VM, in which + * the tests will be run. Thus, one can generate the test class once, and invoke its + * {@code main} method multiple times, each time with a different set of VM flags. + * + *

+ * The internal {@link Template} sets the {@link Hooks#CLASS_HOOK} for the scope of + * all test methods. + * + * @param packageName The package name of the test class. + * @param className The name of the test class. + * @param imports A set of imports. + * @param classpath The classpath from {@link CompileFramework#getEscapedClassPathOfCompiledClasses}, + * so that the Test VM has access to the class files that are compiled from the + * generated source code. + * @param testTemplateTokens The list of tests to be generated into the test class. + * Every test must be annotated with {@code @Test}, so that + * the {@link TestFramework} can later find and run them. + * @return The generated source code of the test class as a {@link String}. + */ + public static String render(final String packageName, + final String className, + final Set imports, + final String classpath, + final List testTemplateTokens) { + var template = Template.make(() -> body( + let("packageName", packageName), + let("className", className), + let("classpath", classpath), + """ + package #packageName; + // --- IMPORTS start --- + import compiler.lib.ir_framework.*; + """, + imports.stream().map(i -> "import " + i + ";\n").toList(), + """ + // --- IMPORTS end --- + public class #className { + // --- CLASS_HOOK insertions start --- + """, + Hooks.CLASS_HOOK.anchor( + """ + // --- CLASS_HOOK insertions end --- + public static void main(String[] vmFlags) { + TestFramework framework = new TestFramework(#className.class); + framework.addFlags("-classpath", "#classpath"); + framework.addFlags(vmFlags); + framework.start(); + } + // --- LIST OF TESTS start --- + """, + testTemplateTokens + ), + """ + // --- LIST OF TESTS end --- + } + """ + )); + return template.render(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java new file mode 100644 index 00000000000..813f2976ef2 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -0,0 +1,163 @@ +/* + * 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. + */ + +/* + * @test + * @summary Test TestFrameworkClass.TEMPLATE which allows generating many tests and running them with the IR TestFramework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/ir_framework/TestFramework.java + * @compile ../../../compiler/lib/generators/Generators.java + * @compile ../../../compiler/lib/verify/Verify.java + * @run driver template_framework.examples.TestWithTestFrameworkClass + */ + +package template_framework.examples; + +import java.util.List; +import java.util.Set; + +import compiler.lib.compile_framework.CompileFramework; + +import compiler.lib.generators.Generators; + +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +import compiler.lib.template_framework.library.Hooks; +import compiler.lib.template_framework.library.TestFrameworkClass; + +/** + * This is a basic IR verification test, in combination with Generators for random input generation + * and Verify for output verification. + *

+ * The "@compile" command for JTREG is required so that the frameworks used in the Template code + * are compiled and available for the Test-VM. + *

+ * Additionally, we must set the classpath for the Test VM, so that it has access to all compiled + * classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}). + */ +public class TestWithTestFrameworkClass { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp)); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnterTest.main(new String[] {}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {}}); + + // We can also pass VM flags for the Test VM. + // p.xyz.InnterTest.main(new String[] {"-Xbatch"}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {"-Xbatch"}}); + } + + // Generate a source Java file as String + public static String generate(CompileFramework comp) { + // A simple template that adds a comment. + var commentTemplate = Template.make(() -> body( + """ + // Comment inserted from test method to class hook. + """ + )); + + // We define a Test-Template: + // - static fields for inputs: INPUT_A and INPUT_B + // - Data generated with Generators and hashtag replacement #con1. + // - GOLD value precomputed with dedicated call to test. + // - This ensures that the GOLD value is computed in the interpreter + // most likely, since the test method is not yet compiled. + // This allows us later to compare to the results of the compiled + // code. + // The input data is cloned, so that the original INPUT_A is never + // modified and can serve as identical input in later calls to test. + // - In the Setup method, we clone the input data, since the input data + // could be modified inside the test method. + // - The test method makes use of hashtag replacements (#con2 and #op). + // - The Check method verifies the results of the test method with the + // GOLD value. + var testTemplate = Template.make("op", (String op) -> body( + let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), + let("con1", Generators.G.ints().next()), + let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), + """ + // --- $test start --- + // $test with size=#size and op=#op + private static int[] $INPUT_A = new int[#size]; + static { + Generators.G.fill(Generators.G.ints(), $INPUT_A); + } + private static int $INPUT_B = #con1; + private static Object $GOLD = $test($INPUT_A.clone(), $INPUT_B); + + @Setup + public static Object[] $setup() { + // Must make sure to clone input arrays, if it is mutated in the test. + return new Object[] {$INPUT_A.clone(), $INPUT_B}; + } + + @Test + @Arguments(setup = "$setup") + public static Object $test(int[] a, int b) { + for (int i = 0; i < a.length; i++) { + int con = #con2; + a[i] = (a[i] * con) #op b; + } + return a; + } + + @Check(test = "$test") + public static void $check(Object result) { + Verify.checkEQ(result, $GOLD); + } + // --- $test end --- + """, + // Good to know: we can insert to the class hook, which is set for the + // TestFrameworkClass scope: + Hooks.CLASS_HOOK.insert(commentTemplate.asToken()) + )); + + // Create a test for each operator. + List ops = List.of("+", "-", "*", "&", "|"); + List testTemplateTokens = ops.stream().map(testTemplate::asToken).toList(); + + // Create the test class, which runs all testTemplateTokens. + return TestFrameworkClass.render( + // package and class name. + "p.xyz", "InnerTest", + // Set of imports. + Set.of("compiler.lib.generators.*", + "compiler.lib.verify.*"), + // classpath, so the Test VM has access to the compiled class files. + comp.getEscapedClassPathOfCompiledClasses(), + // The list of tests. + testTemplateTokens); + } +} From dd68829017c3adea4068d5311cab3fbef87b9577 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 12 Jun 2025 14:19:08 +0000 Subject: [PATCH 142/143] 8347273: C2: VerifyIterativeGVN for Ideal and Identity Reviewed-by: chagedorn, mhaessig --- src/hotspot/share/opto/c2_globals.hpp | 10 +- src/hotspot/share/opto/phaseX.cpp | 914 +++++++++++++++++- src/hotspot/share/opto/phaseX.hpp | 15 +- .../flags/jvmFlagConstraintsCompiler.cpp | 5 +- .../compiler/c2/TestVerifyIterativeGVN.java | 6 +- 5 files changed, 925 insertions(+), 25 deletions(-) diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index fd55f2fd666..227817612ce 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -678,10 +678,12 @@ "Print progress during Iterative Global Value Numbering") \ \ develop(uint, VerifyIterativeGVN, 0, \ - "Verify Iterative Global Value Numbering" \ - "=XY, with Y: verify Def-Use modifications during IGVN" \ - " X: verify that type(n) == n->Value() after IGVN" \ - "X and Y in 0=off; 1=on") \ + "Verify Iterative Global Value Numbering =DCBA, with:" \ + " D: verify Node::Identity did not miss opportunities" \ + " C: verify Node::Ideal did not miss opportunities" \ + " B: verify that type(n) == n->Value() after IGVN" \ + " A: verify Def-Use modifications during IGVN" \ + "Each can be 0=off or 1=on") \ constraint(VerifyIterativeGVNConstraintFunc, AtParse) \ \ develop(bool, TraceCISCSpill, false, \ diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 953afb0e830..1ba8e145e7d 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -1072,7 +1072,11 @@ void PhaseIterGVN::optimize() { #ifdef ASSERT void PhaseIterGVN::verify_optimize() { - if (is_verify_Value()) { + assert(_worklist.size() == 0, "igvn worklist must be empty before verify"); + + if (is_verify_Value() || + is_verify_Ideal() || + is_verify_Identity()) { ResourceMark rm; Unique_Node_List worklist; bool failure = false; @@ -1080,7 +1084,10 @@ void PhaseIterGVN::verify_optimize() { worklist.push(C->root()); for (uint j = 0; j < worklist.size(); ++j) { Node* n = worklist.at(j); - failure |= verify_node_value(n); + if (is_verify_Value()) { failure |= verify_Value_for(n); } + if (is_verify_Ideal()) { failure |= verify_Ideal_for(n, false); } + if (is_verify_Ideal()) { failure |= verify_Ideal_for(n, true); } + if (is_verify_Identity()) { failure |= verify_Identity_for(n); } // traverse all inputs and outputs for (uint i = 0; i < n->req(); i++) { if (n->in(i) != nullptr) { @@ -1097,6 +1104,27 @@ void PhaseIterGVN::verify_optimize() { // in the verification code above if that is not possible for some reason (like Load nodes). assert(!failure, "Missed optimization opportunity in PhaseIterGVN"); } + + verify_empty_worklist(nullptr); +} + +void PhaseIterGVN::verify_empty_worklist(Node* node) { + // Verify that the igvn worklist is empty. If no optimization happened, then + // nothing needs to be on the worklist. + if (_worklist.size() == 0) { return; } + + stringStream ss; // Print as a block without tty lock. + for (uint j = 0; j < _worklist.size(); j++) { + Node* n = _worklist.at(j); + ss.print("igvn.worklist[%d] ", j); + n->dump("\n", false, &ss); + } + if (_worklist.size() != 0 && node != nullptr) { + ss.print_cr("Previously optimized:"); + node->dump("\n", false, &ss); + } + tty->print_cr("%s", ss.as_string()); + assert(false, "igvn worklist must still be empty after verify"); } // Check that type(n) == n->Value(), return true if we have a failure. @@ -1104,7 +1132,7 @@ void PhaseIterGVN::verify_optimize() { // (1) Integer "widen" changes, but the range is the same. // (2) LoadNode performs deep traversals. Load is not notified for changes far away. // (3) CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away. -bool PhaseIterGVN::verify_node_value(Node* n) { +bool PhaseIterGVN::verify_Value_for(Node* n) { // If we assert inside type(n), because the type is still a null, then maybe // the node never went through gvn.transform, which would be a bug. const Type* told = type(n); @@ -1149,15 +1177,873 @@ bool PhaseIterGVN::verify_node_value(Node* n) { // after loop-opts, so that should take care of many of these cases. return false; } - tty->cr(); - tty->print_cr("Missed Value optimization:"); - n->dump_bfs(1, nullptr, ""); - tty->print_cr("Current type:"); - told->dump_on(tty); - tty->cr(); - tty->print_cr("Optimized type:"); - tnew->dump_on(tty); - tty->cr(); + + stringStream ss; // Print as a block without tty lock. + ss.cr(); + ss.print_cr("Missed Value optimization:"); + n->dump_bfs(1, nullptr, "", &ss); + ss.print_cr("Current type:"); + told->dump_on(&ss); + ss.cr(); + ss.print_cr("Optimized type:"); + tnew->dump_on(&ss); + ss.cr(); + tty->print_cr("%s", ss.as_string()); + return true; +} + +// Check that all Ideal optimizations that could be done were done. +// Returns true if it found missed optimization opportunities and +// false otherwise (no missed optimization, or skipped verification). +bool PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) { + // First, we check a list of exceptions, where we skip verification, + // because there are known cases where Ideal can optimize after IGVN. + // Some may be expected and cannot be fixed, and others should be fixed. + switch (n->Opcode()) { + // RangeCheckNode::Ideal looks up the chain for about 999 nodes + // (see "Range-Check scan limit"). So, it is possible that something + // is optimized in that input subgraph, and the RangeCheck was not + // added to the worklist because it would be too expensive to walk + // down the graph for 1000 nodes and put all on the worklist. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xbatch --version + case Op_RangeCheck: + return false; + + // IfNode::Ideal does: + // Node* prev_dom = search_identical(dist, igvn); + // which means we seach up the CFG, traversing at most up to a distance. + // If anything happens rather far away from the If, we may not put the If + // back on the worklist. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_If: + return false; + + // IfNode::simple_subsuming + // Looks for dominating test that subsumes the current test. + // Notification could be difficult because of larger distance. + // + // Found with: + // runtime/exceptionMsgs/ArrayIndexOutOfBoundsException/ArrayIndexOutOfBoundsExceptionTest.java#id1 + // -XX:VerifyIterativeGVN=1110 + case Op_CountedLoopEnd: + return false; + + // LongCountedLoopEndNode::Ideal + // Probably same issue as above. + // + // Found with: + // compiler/predicates/assertion/TestAssertionPredicates.java#NoLoopPredicationXbatch + // -XX:StressLongCountedLoop=2000000 -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_LongCountedLoopEnd: + return false; + + // RegionNode::Ideal does "Skip around the useless IF diamond". + // 245 IfTrue === 244 + // 258 If === 245 257 + // 259 IfTrue === 258 [[ 263 ]] + // 260 IfFalse === 258 [[ 263 ]] + // 263 Region === 263 260 259 [[ 263 268 ]] + // to + // 245 IfTrue === 244 + // 263 Region === 263 245 _ [[ 263 268 ]] + // + // "Useless" means that there is no code in either branch of the If. + // I found a case where this was not done yet during IGVN. + // Why does the Region not get added to IGVN worklist when the If diamond becomes useless? + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_Region: + return false; + + // In AddNode::Ideal, we call "commute", which swaps the inputs so + // that smaller idx are first. Tracking it back, it led me to + // PhaseIdealLoop::remix_address_expressions which swapped the edges. + // + // Example: + // Before PhaseIdealLoop::remix_address_expressions + // 154 AddI === _ 12 144 + // After PhaseIdealLoop::remix_address_expressions + // 154 AddI === _ 144 12 + // After AddNode::Ideal + // 154 AddI === _ 12 144 + // + // I suspect that the node should be added to the IGVN worklist after + // PhaseIdealLoop::remix_address_expressions + // + // This is the only case I looked at, there may be others. Found like this: + // java -XX:VerifyIterativeGVN=0100 -Xbatch --version + // + // The following hit the same logic in PhaseIdealLoop::remix_address_expressions. + // + // Note: currently all of these fail also for other reasons, for example + // because of "commute" doing the reordering with the phi below. Once + // that is resolved, we can come back to this issue here. + // + // case Op_AddD: + // case Op_AddI: + // case Op_AddL: + // case Op_AddF: + // case Op_MulI: + // case Op_MulL: + // case Op_MulF: + // case Op_MulD: + // if (n->in(1)->_idx > n->in(2)->_idx) { + // // Expect "commute" to revert this case. + // return false; + // } + // break; // keep verifying + + // AddFNode::Ideal calls "commute", which can reorder the inputs for this: + // Check for tight loop increments: Loop-phi of Add of loop-phi + // It wants to take the phi into in(1): + // 471 Phi === 435 38 390 + // 390 AddF === _ 471 391 + // + // Other Associative operators are also affected equally. + // + // Investigate why this does not happen earlier during IGVN. + // + // Found with: + // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java + // -XX:VerifyIterativeGVN=1110 + case Op_AddD: + //case Op_AddI: // Also affected for other reasons, see case further down. + //case Op_AddL: // Also affected for other reasons, see case further down. + case Op_AddF: + case Op_MulI: + case Op_MulL: + case Op_MulF: + case Op_MulD: + case Op_MinF: + case Op_MinD: + case Op_MaxF: + case Op_MaxD: + // XorINode::Ideal + // Found with: + // compiler/intrinsics/chacha/TestChaCha20.java + // -XX:VerifyIterativeGVN=1110 + case Op_XorI: + case Op_XorL: + // It seems we may have similar issues with the HF cases. + // Found with aarch64: + // compiler/vectorization/TestFloat16VectorOperations.java + // -XX:VerifyIterativeGVN=1110 + case Op_AddHF: + case Op_MulHF: + case Op_MaxHF: + case Op_MinHF: + return false; + + // In MulNode::Ideal the edges can be swapped to help value numbering: + // + // // We are OK if right is a constant, or right is a load and + // // left is a non-constant. + // if( !(t2->singleton() || + // (in(2)->is_Load() && !(t1->singleton() || in(1)->is_Load())) ) ) { + // if( t1->singleton() || // Left input is a constant? + // // Otherwise, sort inputs (commutativity) to help value numbering. + // (in(1)->_idx > in(2)->_idx) ) { + // swap_edges(1, 2); + // + // Why was this not done earlier during IGVN? + // + // Found with: + // test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java + // -XX:VerifyIterativeGVN=1110 + case Op_AndI: + // Same for AndL. + // Found with: + // compiler/intrinsics/bigInteger/MontgomeryMultiplyTest.java + // -XX:VerifyIterativeGVN=1110 + case Op_AndL: + return false; + + // SubLNode::Ideal does transform like: + // Convert "c1 - (y+c0)" into "(c1-c0) - y" + // + // In IGVN before verification: + // 8423 ConvI2L === _ 3519 [[ 8424 ]] #long:-2 + // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www + // 8424 AddL === _ 8422 8423 [[ 8383 ]] !orig=[8382] + // 8016 ConL === 0 [[ 8383 ]] #long:0 + // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154] + // + // And then in verification: + // 8338 ConL === 0 [[ 8339 8424 ]] #long:-2 <----- Was constant folded. + // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www + // 8424 AddL === _ 8422 8338 [[ 8383 ]] !orig=[8382] + // 8016 ConL === 0 [[ 8383 ]] #long:0 + // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154] + // + // So the form changed from: + // c1 - (y + [8423 ConvI2L]) + // to + // c1 - (y + -2) + // but the SubL was not added to the IGVN worklist. Investigate why. + // There could be other issues too. + // + // There seems to be a related AddL IGVN optimization that triggers + // the same SubL optimization, so investigate that too. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_SubL: + return false; + + // SubINode::Ideal does + // Convert "x - (y+c0)" into "(x-y) - c0" AND + // Convert "c1 - (y+c0)" into "(c1-c0) - y" + // + // Investigate why this does not yet happen during IGVN. + // + // Found with: + // test/hotspot/jtreg/compiler/c2/IVTest.java + // -XX:VerifyIterativeGVN=1110 + case Op_SubI: + return false; + + // AddNode::IdealIL does transform like: + // Convert x + (con - y) into "(x - y) + con" + // + // In IGVN before verification: + // 8382 ConvI2L + // 8381 ConvI2L === _ 791 [[ 8383 ]] #long:0 + // 8383 SubL === _ 8381 8382 + // 8168 ConvI2L + // 8156 AddL === _ 8168 8383 [[ 8158 ]] + // + // And then in verification: + // 8424 AddL + // 8016 ConL === 0 [[ 8383 ]] #long:0 <--- Was constant folded. + // 8383 SubL === _ 8016 8424 + // 8168 ConvI2L + // 8156 AddL === _ 8168 8383 [[ 8158 ]] + // + // So the form changed from: + // x + (ConvI2L(0) - [8382 ConvI2L]) + // to + // x + (0 - [8424 AddL]) + // but the AddL was not added to the IGVN worklist. Investigate why. + // There could be other issues, too. For example with "commute", see above. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_AddL: + return false; + + // SubTypeCheckNode::Ideal calls SubTypeCheckNode::verify_helper, which does + // Node* cmp = phase->transform(new CmpPNode(subklass, in(SuperKlass))); + // record_for_cleanup(cmp, phase); + // This verification code in the Ideal code creates new nodes, and checks + // if they fold in unexpected ways. This means some nodes are created and + // added to the worklist, even if the SubTypeCheck is not optimized. This + // goes agains the assumption of the verification here, which assumes that + // if the node is not optimized, then no new nodes should be created, and + // also no nodes should be added to the worklist. + // I see two options: + // 1) forbid what verify_helper does, because for each Ideal call it + // uses memory and that is suboptimal. But it is not clear how that + // verification can be done otherwise. + // 2) Special case the verification here. Probably the new nodes that + // were just created are dead, i.e. they are not connected down to + // root. We could verify that, and remove those nodes from the graph + // by setting all their inputs to nullptr. And of course we would + // have to remove those nodes from the worklist. + // Maybe there are other options too, I did not dig much deeper yet. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xbatch --version + case Op_SubTypeCheck: + return false; + + // LoopLimitNode::Ideal when stride is constant power-of-2, we can do a lowering + // to other nodes: Conv, Add, Sub, Mul, And ... + // + // 107 ConI === 0 [[ ... ]] #int:2 + // 84 LoadRange === _ 7 83 + // 50 ConI === 0 [[ ... ]] #int:0 + // 549 LoopLimit === _ 50 84 107 + // + // I stepped backward, to see how the node was generated, and I found that it was + // created in PhaseIdealLoop::exact_limit and not changed since. It is added to the + // IGVN worklist. I quickly checked when it goes into LoopLimitNode::Ideal after + // that, and it seems we want to skip lowering it until after loop-opts, but never + // add call record_for_post_loop_opts_igvn. This would be an easy fix, but there + // could be other issues too. + // + // Fond with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_LoopLimit: + return false; + + // PhiNode::Ideal calls split_flow_path, which tries to do this: + // "This optimization tries to find two or more inputs of phi with the same constant + // value. It then splits them into a separate Phi, and according Region." + // + // Example: + // 130 DecodeN === _ 129 + // 50 ConP === 0 [[ 18 91 99 18 ]] #null + // 18 Phi === 14 50 130 50 [[ 133 ]] #java/lang/Object * Oop:java/lang/Object * + // + // turns into: + // + // 50 ConP === 0 [[ 99 91 18 ]] #null + // 130 DecodeN === _ 129 [[ 18 ]] + // 18 Phi === 14 130 50 [[ 133 ]] #java/lang/Object * Oop:java/lang/Object * + // + // We would have to investigate why this optimization does not happen during IGVN. + // There could also be other issues - I did not investigate further yet. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_Phi: + return false; + + // MemBarNode::Ideal does "Eliminate volatile MemBars for scalar replaced objects". + // For examle "The allocated object does not escape". + // + // It seems the difference to earlier calls to MemBarNode::Ideal, is that there + // alloc->as_Allocate()->does_not_escape_thread() returned false, but in verification + // it returned true. Why does the MemBarStoreStore not get added to the IGVN + // worklist when this change happens? + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_MemBarStoreStore: + return false; + + // ConvI2LNode::Ideal converts + // 648 AddI === _ 583 645 [[ 661 ]] + // 661 ConvI2L === _ 648 [[ 664 ]] #long:0..maxint-1:www + // into + // 772 ConvI2L === _ 645 [[ 773 ]] #long:-120..maxint-61:www + // 771 ConvI2L === _ 583 [[ 773 ]] #long:60..120:www + // 773 AddL === _ 771 772 [[ ]] + // + // We have to investigate why this does not happen during IGVN in this case. + // There could also be other issues - I did not investigate further yet. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + case Op_ConvI2L: + return false; + + // AddNode::IdealIL can do this transform (and similar other ones): + // Convert "a*b+a*c into a*(b+c) + // The example had AddI(MulI(a, b), MulI(a, c)). Why did this not happen + // during IGVN? There was a mutation for one of the MulI, and only + // after that the pattern was as needed for the optimization. The MulI + // was added to the IGVN worklist, but not the AddI. This probably + // can be fixed by adding the correct pattern in add_users_of_use_to_worklist. + // + // Found with: + // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java + // -XX:VerifyIterativeGVN=1110 + case Op_AddI: + return false; + + // ArrayCopyNode::Ideal + // calls ArrayCopyNode::prepare_array_copy + // calls Compile::conv_I2X_index -> is called with sizetype = intcon(0), I think that + // is not expected, and we create a range int:0..-1 + // calls Compile::constrained_convI2L -> creates ConvI2L(intcon(1), int:0..-1) + // note: the type is already empty! + // calls PhaseIterGVN::transform + // calls PhaseIterGVN::transform_old + // calls PhaseIterGVN::subsume_node -> subsume ConvI2L with TOP + // calls Unique_Node_List::push -> pushes TOP to worklist + // + // Once we get back to ArrayCopyNode::prepare_array_copy, we get back TOP, and + // return false. This means we eventually return nullptr from ArrayCopyNode::Ideal. + // + // Question: is it ok to push anything to the worklist during ::Ideal, if we will + // return nullptr, indicating nothing happened? + // Is it smart to do transform in Compile::constrained_convI2L, and then + // check for TOP in calls ArrayCopyNode::prepare_array_copy? + // Should we just allow TOP to land on the worklist, as an exception? + // + // Found with: + // compiler/arraycopy/TestArrayCopyAsLoadsStores.java + // -XX:VerifyIterativeGVN=1110 + case Op_ArrayCopy: + return false; + + // CastLLNode::Ideal + // calls ConstraintCastNode::optimize_integer_cast -> pushes CastLL through SubL + // + // Could be a notification issue, where updates inputs of CastLL do not notify + // down through SubL to CastLL. + // + // Found With: + // compiler/c2/TestMergeStoresMemorySegment.java#byte-array + // -XX:VerifyIterativeGVN=1110 + case Op_CastLL: + return false; + + // Similar case happens to CastII + // + // Found With: + // compiler/c2/TestScalarReplacementMaxLiveNodes.java + // -XX:VerifyIterativeGVN=1110 + case Op_CastII: + return false; + + // MaxLNode::Ideal + // calls AddNode::Ideal + // calls commute -> decides to swap edges + // + // Another notification issue, because we check inputs of inputs? + // MaxL -> Phi -> Loop + // MaxL -> Phi -> MaxL + // + // Found with: + // compiler/c2/irTests/TestIfMinMax.java + // -XX:VerifyIterativeGVN=1110 + case Op_MaxL: + case Op_MinL: + return false; + + // OrINode::Ideal + // calls AddNode::Ideal + // calls commute -> left is Load, right not -> commute. + // + // Not sure why notification does not work here, seems like + // the depth is only 1, so it should work. Needs investigation. + // + // Found with: + // compiler/codegen/TestCharVect2.java#id0 + // -XX:VerifyIterativeGVN=1110 + case Op_OrI: + case Op_OrL: + return false; + + // Bool -> constant folded to 1. + // Issue with notification? + // + // Found with: + // compiler/c2/irTests/TestVectorizationMismatchedAccess.java + // -XX:VerifyIterativeGVN=1110 + case Op_Bool: + return false; + + // LShiftLNode::Ideal + // Looks at pattern: "(x + x) << c0", converts it to "x << (c0 + 1)" + // Probably a notification issue. + // + // Found with: + // compiler/conversions/TestMoveConvI2LOrCastIIThruAddIs.java + // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_LShiftL: + return false; + + // LShiftINode::Ideal + // pattern: ((x + con1) << con2) -> x << con2 + con1 << con2 + // Could be issue with notification of inputs of inputs + // + // Side-note: should cases like these not be shared between + // LShiftI and LShiftL? + // + // Found with: + // compiler/escapeAnalysis/Test6689060.java + // -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_LShiftI: + return false; + + // AddPNode::Ideal seems to do set_req without removing lock first. + // Found with various vector tests tier1-tier3. + case Op_AddP: + return false; + + // StrIndexOfNode::Ideal + // Found in tier1-3. + case Op_StrIndexOf: + case Op_StrIndexOfChar: + return false; + + // StrEqualsNode::Identity + // + // Found (linux x64 only?) with: + // serviceability/sa/ClhsdbThreadContext.java + // -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1 -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_StrEquals: + return false; + + // AryEqNode::Ideal + // Not investigated. Reshapes itself and adds lots of nodes to the worklist. + // + // Found with: + // vmTestbase/vm/mlvm/meth/stress/compiler/i2c_c2i/Test.java + // -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation -XX:+StressUnstableIfTraps -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_AryEq: + return false; + + // MergeMemNode::Ideal + // Found in tier1-3. Did not investigate further yet. + case Op_MergeMem: + return false; + + // URShiftINode::Ideal + // Found in tier1-3. Did not investigate further yet. + case Op_URShiftI: + return false; + + // CMoveINode::Ideal + // Found in tier1-3. Did not investigate further yet. + case Op_CMoveI: + return false; + + // CmpPNode::Ideal calls isa_const_java_mirror + // and generates new constant nodes, even if no progress is made. + // We can probably rewrite this so that only types are generated. + // It seems that object types are not hashed, we could investigate + // if that is an option as well. + // + // Found with: + // java -XX:VerifyIterativeGVN=1110 -Xcomp --version + case Op_CmpP: + return false; + + // MinINode::Ideal + // Did not investigate, but there are some patterns that might + // need more notification. + case Op_MinI: + case Op_MaxI: // preemptively removed it as well. + return false; + } + + if (n->is_Load()) { + // LoadNode::Ideal uses tries to find an earlier memory state, and + // checks can_see_stored_value for it. + // + // Investigate why this was not already done during IGVN. + // A similar issue happens with Identity. + // + // There seem to be other cases where loads go up some steps, like + // LoadNode::Ideal going up 10x steps to find dominating load. + // + // Found with: + // test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java + // -XX:VerifyIterativeGVN=1110 + return false; + } + + if (n->is_Store()) { + // StoreNode::Ideal can do this: + // // Capture an unaliased, unconditional, simple store into an initializer. + // // Or, if it is independent of the allocation, hoist it above the allocation. + // That replaces the Store with a MergeMem. + // + // We have to investigate why this does not happen during IGVN in this case. + // There could also be other issues - I did not investigate further yet. + // + // Found with: + // java -XX:VerifyIterativeGVN=0100 -Xcomp --version + return false; + } + + if (n->is_Vector()) { + // VectorNode::Ideal swaps edges, but only for ops + // that are deemed commutable. But swap_edges + // requires the hash to be invariant when the edges + // are swapped, which is not implemented for these + // vector nodes. This seems not to create any trouble + // usually, but we can also get graphs where in the + // end the nodes are not all commuted, so there is + // definitively an issue here. + // + // Probably we have two options: kill the hash, or + // properly make the hash commutation friendly. + // + // Found with: + // compiler/vectorapi/TestMaskedMacroLogicVector.java + // -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -XX:+UseParallelGC -XX:+UseNUMA + return false; + } + + if (n->is_Region()) { + // LoopNode::Ideal calls RegionNode::Ideal. + // CountedLoopNode::Ideal calls RegionNode::Ideal too. + // But I got an issue because RegionNode::optimize_trichotomy + // then modifies another node, and pushes nodes to the worklist + // Not sure if this is ok, modifying another node like that. + // Maybe it is, then we need to look into what to do with + // the nodes that are now on the worklist, maybe just clear + // them out again. But maybe modifying other nodes like that + // is also bad design. In the end, we return nullptr for + // the current CountedLoop. But the extra nodes on the worklist + // trip the asserts later on. + // + // Found with: + // compiler/eliminateAutobox/TestShortBoxing.java + // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + return false; + } + + if (n->is_CallJava()) { + // CallStaticJavaNode::Ideal + // Led to a crash: + // assert((is_CallStaticJava() && cg->is_mh_late_inline()) || (is_CallDynamicJava() && cg->is_virtual_late_inline())) failed: mismatch + // + // Did not investigate yet, could be a bug. + // Or maybe it does not expect to be called during verification. + // + // Found with: + // test/jdk/jdk/incubator/vector/VectorRuns.java + // -XX:VerifyIterativeGVN=1110 + + // CallDynamicJavaNode::Ideal, and I think also for CallStaticJavaNode::Ideal + // and possibly their subclasses. + // During late inlining it can call CallJavaNode::register_for_late_inline + // That means we do more rounds of late inlining, but might fail. + // Then we do IGVN again, and register the node again for late inlining. + // This creates an endless cycle. Everytime we try late inlining, we + // are also creating more nodes, especially SafePoint and MergeMem. + // These nodes are immediately rejected when the inlining fails in the + // do_late_inline_check, but they still grow the memory, until we hit + // the MemLimit and crash. + // The assumption here seems that CallDynamicJavaNode::Ideal does not get + // called repeatedly, and eventually we terminate. I fear this is not + // a great assumption to make. We should investigate more. + // + // Found with: + // compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-U + // -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + return false; + } + + // The number of nodes shoud not increase. + uint old_unique = C->unique(); + + Node* i = n->Ideal(this, can_reshape); + // If there was no new Idealization, we are probably happy. + if (i == nullptr) { + if (old_unique < C->unique()) { + stringStream ss; // Print as a block without tty lock. + ss.cr(); + ss.print_cr("Ideal optimization did not make progress but created new unused nodes."); + ss.print_cr(" old_unique = %d, unique = %d", old_unique, C->unique()); + n->dump_bfs(1, nullptr, "", &ss); + tty->print_cr("%s", ss.as_string()); + return true; + } + + verify_empty_worklist(n); + + // Everything is good. + return false; + } + + // We just saw a new Idealization which was not done during IGVN. + stringStream ss; // Print as a block without tty lock. + ss.cr(); + ss.print_cr("Missed Ideal optimization (can_reshape=%s):", can_reshape ? "true": "false"); + if (i == n) { + ss.print_cr("The node was reshaped by Ideal."); + } else { + ss.print_cr("The node was replaced by Ideal."); + ss.print_cr("Old node:"); + n->dump_bfs(1, nullptr, "", &ss); + } + ss.print_cr("The result after Ideal:"); + i->dump_bfs(1, nullptr, "", &ss); + tty->print_cr("%s", ss.as_string()); + return true; +} + +// Check that all Identity optimizations that could be done were done. +// Returns true if it found missed optimization opportunities and +// false otherwise (no missed optimization, or skipped verification). +bool PhaseIterGVN::verify_Identity_for(Node* n) { + // First, we check a list of exceptions, where we skip verification, + // because there are known cases where Ideal can optimize after IGVN. + // Some may be expected and cannot be fixed, and others should be fixed. + switch (n->Opcode()) { + // SafePointNode::Identity can remove SafePoints, but wants to wait until + // after loopopts: + // // Transforming long counted loops requires a safepoint node. Do not + // // eliminate a safepoint until loop opts are over. + // if (in(0)->is_Proj() && !phase->C->major_progress()) { + // + // I think the check for major_progress does delay it until after loopopts + // but it does not ensure that the node is on the IGVN worklist after + // loopopts. I think we should try to instead check for + // phase->C->post_loop_opts_phase() and call record_for_post_loop_opts_igvn. + // + // Found with: + // java -XX:VerifyIterativeGVN=1000 -Xcomp --version + case Op_SafePoint: + return false; + + // MergeMemNode::Identity replaces the MergeMem with its base_memory if it + // does not record any other memory splits. + // + // I did not deeply investigate, but it looks like MergeMemNode::Identity + // never got called during IGVN for this node, investigate why. + // + // Found with: + // java -XX:VerifyIterativeGVN=1000 -Xcomp --version + case Op_MergeMem: + return false; + + // ConstraintCastNode::Identity finds casts that are the same, except that + // the control is "higher up", i.e. dominates. The call goes via + // ConstraintCastNode::dominating_cast to PhaseGVN::is_dominator_helper, + // which traverses up to 100 idom steps. If anything gets optimized somewhere + // away from the cast, but within 100 idom steps, the cast may not be + // put on the IGVN worklist any more. + // + // Found with: + // java -XX:VerifyIterativeGVN=1000 -Xcomp --version + case Op_CastPP: + case Op_CastII: + case Op_CastLL: + return false; + + // Same issue for CheckCastPP, uses ConstraintCastNode::Identity and + // checks dominator, which may be changed, but too far up for notification + // to work. + // + // Found with: + // compiler/c2/irTests/TestSkeletonPredicates.java + // -XX:VerifyIterativeGVN=1110 + case Op_CheckCastPP: + return false; + + // In SubNode::Identity, we do: + // Convert "(X+Y) - Y" into X and "(X+Y) - X" into Y + // In the example, the AddI had an input replaced, the AddI is + // added to the IGVN worklist, but the SubI is one link further + // down and is not added. I checked add_users_of_use_to_worklist + // where I would expect the SubI would be added, and I cannot + // find the pattern, only this one: + // If changed AddI/SubI inputs, check CmpU for range check optimization. + // + // Fix this "notification" issue and check if there are any other + // issues. + // + // Found with: + // java -XX:VerifyIterativeGVN=1000 -Xcomp --version + case Op_SubI: + case Op_SubL: + return false; + + // PhiNode::Identity checks for patterns like: + // r = (x != con) ? x : con; + // that can be constant folded to "x". + // + // Call goes through PhiNode::is_cmove_id and CMoveNode::is_cmove_id. + // I suspect there was some earlier change to one of the inputs, but + // not all relevant outputs were put on the IGVN worklist. + // + // Found with: + // test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java + // -XX:VerifyIterativeGVN=1110 + case Op_Phi: + return false; + + // ConvI2LNode::Identity does + // convert I2L(L2I(x)) => x + // + // Investigate why this did not already happen during IGVN. + // + // Found with: + // compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-A + // -XX:VerifyIterativeGVN=1110 + case Op_ConvI2L: + return false; + + // MaxNode::find_identity_operation + // Finds patterns like Max(A, Max(A, B)) -> Max(A, B) + // This can be a 2-hop search, so maybe notification is not + // good enough. + // + // Found with: + // compiler/codegen/TestBooleanVect.java + // -XX:VerifyIterativeGVN=1110 + case Op_MaxL: + case Op_MinL: + case Op_MaxI: + case Op_MinI: + case Op_MaxF: + case Op_MinF: + case Op_MaxHF: + case Op_MinHF: + case Op_MaxD: + case Op_MinD: + return false; + + + // AddINode::Identity + // Converts (x-y)+y to x + // Could be issue with notification + // + // Turns out AddL does the same. + // + // Found with: + // compiler/c2/Test6792161.java + // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + case Op_AddI: + case Op_AddL: + return false; + + // AbsINode::Identity + // Not investigated yet. + case Op_AbsI: + return false; + } + + if (n->is_Load()) { + // LoadNode::Identity tries to look for an earlier store value via + // can_see_stored_value. I found an example where this led to + // an Allocation, where we could assume the value was still zero. + // So the LoadN can be replaced with a zerocon. + // + // Investigate why this was not already done during IGVN. + // A similar issue happens with Ideal. + // + // Found with: + // java -XX:VerifyIterativeGVN=1000 -Xcomp --version + return false; + } + + if (n->is_Store()) { + // StoreNode::Identity + // Not investigated, but found missing optimization for StoreI. + // Looks like a StoreI is replaced with an InitializeNode. + // + // Found with: + // applications/ctw/modules/java_base_2.java + // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -Djava.awt.headless=true -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + return false; + } + + if (n->is_Vector()) { + // Found with tier1-3. Not investigated yet. + // The observed issue was with AndVNode::Identity + return false; + } + + Node* i = n->Identity(this); + // If we cannot find any other Identity, we are happy. + if (i == n) { + verify_empty_worklist(n); + return false; + } + + // The verification just found a new Identity that was not found during IGVN. + stringStream ss; // Print as a block without tty lock. + ss.cr(); + ss.print_cr("Missed Identity optimization:"); + ss.print_cr("Old node:"); + n->dump_bfs(1, nullptr, "", &ss); + ss.print_cr("New node:"); + i->dump_bfs(1, nullptr, "", &ss); + tty->print_cr("%s", ss.as_string()); return true; } #endif @@ -1890,12 +2776,12 @@ void PhaseCCP::analyze() { #ifdef ASSERT // For every node n on verify list, check if type(n) == n->Value() -// We have a list of exceptions, see comments in verify_node_value. +// We have a list of exceptions, see comments in verify_Value_for. void PhaseCCP::verify_analyze(Unique_Node_List& worklist_verify) { bool failure = false; while (worklist_verify.size()) { Node* n = worklist_verify.pop(); - failure |= verify_node_value(n); + failure |= verify_Value_for(n); } // If we get this assert, check why the reported nodes were not processed again in CCP. // We should either make sure that these nodes are properly added back to the CCP worklist diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index c2a0f0dbb77..648e911e783 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -494,7 +494,10 @@ public: void optimize(); #ifdef ASSERT void verify_optimize(); - bool verify_node_value(Node* n); + bool verify_Value_for(Node* n); + bool verify_Ideal_for(Node* n, bool can_reshape); + bool verify_Identity_for(Node* n); + void verify_empty_worklist(Node* n); #endif #ifndef PRODUCT @@ -593,6 +596,14 @@ public: // '-XX:VerifyIterativeGVN=10' return ((VerifyIterativeGVN % 100) / 10) == 1; } + static bool is_verify_Ideal() { + // '-XX:VerifyIterativeGVN=100' + return ((VerifyIterativeGVN % 1000) / 100) == 1; + } + static bool is_verify_Identity() { + // '-XX:VerifyIterativeGVN=1000' + return ((VerifyIterativeGVN % 10000) / 1000) == 1; + } protected: // Sub-quadratic implementation of '-XX:VerifyIterativeGVN=1' (Use-Def verification). julong _verify_counter; diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp index d9972b21ea1..18aa4a56d71 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp @@ -299,8 +299,9 @@ JVMFlag::Error TypeProfileLevelConstraintFunc(uint value, bool verbose) { } JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) { + const int max_modes = 4; uint original_value = value; - for (int i = 0; i < 2; i++) { + for (int i = 0; i < max_modes; i++) { if (value % 10 > 1) { JVMFlag::printError(verbose, "Invalid value (" UINT32_FORMAT ") " @@ -312,7 +313,7 @@ JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) { if (value != 0) { JVMFlag::printError(verbose, "Invalid value (" UINT32_FORMAT ") " - "for VerifyIterativeGVN: maximal 2 digits\n", original_value); + "for VerifyIterativeGVN: maximal %d digits\n", original_value, max_modes); return JVMFlag::VIOLATES_CONSTRAINT; } return JVMFlag::SUCCESS; diff --git a/test/hotspot/jtreg/compiler/c2/TestVerifyIterativeGVN.java b/test/hotspot/jtreg/compiler/c2/TestVerifyIterativeGVN.java index f3f589f8fb1..83f3540226f 100644 --- a/test/hotspot/jtreg/compiler/c2/TestVerifyIterativeGVN.java +++ b/test/hotspot/jtreg/compiler/c2/TestVerifyIterativeGVN.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -25,9 +25,9 @@ * @test * @bug 8238756 * @requires vm.debug == true & vm.flavor == "server" - * @summary Run with -Xcomp to test -XX:VerifyIterativeGVN=11 in debug builds. + * @summary Run with -Xcomp to test -XX:VerifyIterativeGVN=1111 in debug builds. * - * @run main/othervm/timeout=300 -Xbatch -Xcomp -XX:VerifyIterativeGVN=11 compiler.c2.TestVerifyIterativeGVN + * @run main/othervm/timeout=300 -Xcomp -XX:VerifyIterativeGVN=1111 compiler.c2.TestVerifyIterativeGVN */ package compiler.c2; From fae9c7a3f04f85aeb9e03c5c9c15fda6405ee989 Mon Sep 17 00:00:00 2001 From: Afshin Zafari Date: Thu, 12 Jun 2025 14:46:39 +0000 Subject: [PATCH 143/143] 8351661: NMT: VMATree should support separate call-stacks for reserve and commit operations Reviewed-by: gziemski, jsjolen --- src/hotspot/share/logging/logTag.hpp | 1 + src/hotspot/share/nmt/memoryFileTracker.cpp | 2 +- .../share/nmt/nmtNativeCallStackStorage.hpp | 5 +- src/hotspot/share/nmt/vmatree.cpp | 753 +++++++-- src/hotspot/share/nmt/vmatree.hpp | 111 +- test/hotspot/gtest/nmt/test_vmatree.cpp | 1430 +++++++++++++++-- 6 files changed, 2033 insertions(+), 269 deletions(-) diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 34927a9b613..9edf81d3f27 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -216,6 +216,7 @@ class outputStream; LOG_TAG(valuebasedclasses) \ LOG_TAG(verification) \ LOG_TAG(verify) \ + LOG_TAG(vmatree) \ LOG_TAG(vmmutex) \ LOG_TAG(vmoperation) \ LOG_TAG(vmthread) \ diff --git a/src/hotspot/share/nmt/memoryFileTracker.cpp b/src/hotspot/share/nmt/memoryFileTracker.cpp index d753a57ede7..4ffcb535e4b 100644 --- a/src/hotspot/share/nmt/memoryFileTracker.cpp +++ b/src/hotspot/share/nmt/memoryFileTracker.cpp @@ -91,7 +91,7 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st NMTUtil::tag_to_name(prev->val().out.mem_tag())); { StreamIndentor si(stream, 4); - _stack_storage.get(prev->val().out.stack()).print_on(stream); + _stack_storage.get(prev->val().out.reserved_stack()).print_on(stream); } stream->cr(); } diff --git a/src/hotspot/share/nmt/nmtNativeCallStackStorage.hpp b/src/hotspot/share/nmt/nmtNativeCallStackStorage.hpp index 258c3284a18..85e044c1a45 100644 --- a/src/hotspot/share/nmt/nmtNativeCallStackStorage.hpp +++ b/src/hotspot/share/nmt/nmtNativeCallStackStorage.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -43,11 +43,8 @@ class NativeCallStackStorage : public CHeapObjBase { public: using StackIndex = int; - -private: constexpr static const StackIndex invalid = std::numeric_limits::max() - 1; -public: static bool equals(const StackIndex a, const StackIndex b) { return a == b; } diff --git a/src/hotspot/share/nmt/vmatree.cpp b/src/hotspot/share/nmt/vmatree.cpp index 3352a6e5cd4..48febdce7c2 100644 --- a/src/hotspot/share/nmt/vmatree.cpp +++ b/src/hotspot/share/nmt/vmatree.cpp @@ -28,21 +28,228 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" -const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::StackIndex{}, mtNone}; + +// Semantics +// This tree is used to store and track the state of virtual memory regions. +// The nodes in the tree are key-value pairs where the key is the memory address and the value is the State of the memory regions. +// The State of a region describes whether the region is released, reserved or committed, which MemTag it has and where in +// Hotspot (using call-stacks) it is reserved or committed. +// Each node holds the State of the regions to its left and right. Each memory region is described by two +// memory addresses for its start and end. +// For example, to describe the region that starts at memory address 0xA000 with size 0x1000, there will be two nodes +// with the keys 0xA000 (node A) and 0xB000 (node B) in the tree. The value of the key-value pairs of node A and +// node B describe the region's State, using right of A and left of B (<--left--A--right-->.....<--left--B--right-->...). +// +// Virtual memory can be reserved, committed, uncommitted and released. For each operation a request +// () is sent to the tree to handle. +// +// The expected changes are described here for each operation: +// +// ### Reserve a region +// When a region is reserved, all the overlapping regions in the tree should: +// - be marked as Reserved +// - take MemTag of the operation +// - store call-stack of the request to the reserve call-stack +// - clear commit call-stack +// +// ### Commit a region +// When a region is committed, all the overlapping regions in the tree should: +// - be marked as Committed +// - take MemTag of the operation or MemTag of the existing region, depends on which-tag-to-use in the request +// - if the region is in Released state +// - mark the region as both Reserved and Committed +// - store the call-stack of the request to the reserve call-stack +// - store the call-stack of the request to the commit call-stack +// +// ### Uncommit a region +// When a region is uncommitted, all the overlapping regions in the tree should: +// - be ignored if the region is in Released state +// - be marked as Reserved +// - not change the MemTag +// - not change the reserve call-stack +// - clear commit call-stack +// +// ### Release a region +// When a region is released, all the overlapping regions in the tree should: +// - be marked as Released +// - set the MemTag to mtNone +// - clear both reserve and commit call-stack +// +// --- Accounting +// After each operation, the tree should be able to report how much memory is reserved or committed per MemTag. +// So for each region that changes to a new State, the report should contain (separately for each tag) the amount +// of reserve and commit that are changed (increased or decreased) due to the operation. + +const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::invalid, mtNone}; const char* VMATree::statetype_strings[3] = { - "reserved", "committed", "released", + "released", "reserved", "committed" }; -VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType state, +VMATree::SIndex VMATree::get_new_reserve_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const { + const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack + const SIndex rq = req.callstack; + const int op = req.op_to_index(); + const Operation oper = req.op(); + assert(op >= 0 && op < 4, "should be"); + assert(op >= 0 && op < 4, "should be"); + // existing state + SIndex result[4][3] = {// Rl Rs C + {ES, ES, ES}, // op == Release + {rq, rq, rq}, // op == Reserve + {es, es, es}, // op == Commit + {es, es, es} // op == Uncommit + }; + // When committing a Released region, the reserve-call-stack of the region should also be as what is in the request + if (oper == Operation::Commit && ex == StateType::Released) { + return rq; + } else { + return result[op][state_to_index(ex)]; + } +} + +VMATree::SIndex VMATree::get_new_commit_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const { + const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack + const SIndex rq = req.callstack; + const int op_index = req.op_to_index(); + const Operation op = req.op(); + assert(op_index >= 0 && op_index < 4, "should be"); + // existing state + SIndex result[4][3] = {// Rl Rs C + {ES, ES, ES}, // op == Release + {ES, ES, ES}, // op == Reserve + {rq, rq, rq}, // op == Commit + {ES, ES, ES} // op == Uncommit + }; + return result[op_index][state_to_index(ex)]; +} + +VMATree::StateType VMATree::get_new_state(const StateType ex, const RequestInfo& req) const { + const StateType Rl = StateType::Released; + const StateType Rs = StateType::Reserved; + const StateType C = StateType::Committed; + const int op = req.op_to_index(); + assert(op >= 0 && op < 4, "should be"); + // existing state + StateType result[4][3] = {// Rl Rs C + {Rl, Rl, Rl}, // op == Release + {Rs, Rs, Rs}, // op == Reserve + { C, C, C}, // op == Commit + {Rl, Rs, Rs} // op == Uncommit + }; + return result[op][state_to_index(ex)]; +} + +MemTag VMATree::get_new_tag(const MemTag ex, const RequestInfo& req) const { + switch(req.op()) { + case Operation::Release: + return mtNone; + case Operation::Reserve: + return req.tag; + case Operation::Commit: + return req.use_tag_inplace ? ex : req.tag; + case Operation::Uncommit: + return ex; + default: + break; + } + return mtNone; +} + +void VMATree::compute_summary_diff(const SingleDiff::delta region_size, + const MemTag current_tag, + const StateType& ex, + const RequestInfo& req, + const MemTag operation_tag, + SummaryDiff& diff) const { + const StateType Rl = StateType::Released; + const StateType Rs = StateType::Reserved; + const StateType C = StateType::Committed; + const int op = req.op_to_index(); + const Operation oper = req.op(); + assert(op >= 0 && op < 4, "should be"); + + SingleDiff::delta a = region_size; + // A region with size `a` has a state as and an operation is requested as in + // The region has tag `current_tag` and the operation has tag `operation_tag`. + // For each state, we decide how much to be added/subtracted from current_tag to operation_tag. Two tables for reserve and commit. + // Each pair of in the table means add `x` to current_tag and add `y` to operation_tag. There are 3 pairs in each row for 3 states. + // For example, `reserve[1][4,5]` says `-a,a` means: + // - we are reserving with operation_tag a region which is already commited with current_tag + // - since we are reserving, then `a` will be added to operation_tag. (`y` is `a`) + // - since we uncommitting (by reserving) then `a` is to be subtracted from current_tag. (`x` is `-a`). + // - amount of uncommitted size is in table `commit[1][4,5]` which is `-a,0` that means subtract `a` from current_tag. + // existing state + SingleDiff::delta reserve[4][3*2] = {// Rl Rs C + {0,0, -a,0, -a,0 }, // op == Release + {0,a, -a,a, -a,a }, // op == Reserve + {0,a, -a,a, -a,a }, // op == Commit + {0,0, 0,0, 0,0 } // op == Uncommit + }; + SingleDiff::delta commit[4][3*2] = {// Rl Rs C + {0,0, 0,0, -a,0 }, // op == Release + {0,0, 0,0, -a,0 }, // op == Reserve + {0,a, 0,a, -a,a }, // op == Commit + {0,0, 0,0, -a,0 } // op == Uncommit + }; + SingleDiff& from_rescom = diff.tag[NMTUtil::tag_to_index(current_tag)]; + SingleDiff& to_rescom = diff.tag[NMTUtil::tag_to_index(operation_tag)]; + int st = state_to_index(ex); + from_rescom.reserve += reserve[op][st * 2 ]; + to_rescom.reserve += reserve[op][st * 2 + 1]; + from_rescom.commit += commit[op][st * 2 ]; + to_rescom.commit += commit[op][st * 2 + 1]; + +} +// update the region state between n1 and n2. Since n1 and n2 are pointers, any update of them will be visible from tree. +// If n1 is noop, it can be removed because its left region (n1->val().in) is already decided and its right state (n1->val().out) is decided here. +// The state of right of n2 (n2->val().out) cannot be decided here yet. +void VMATree::update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff) { + assert(n1 != nullptr,"sanity"); + assert(n2 != nullptr,"sanity"); + //.........n1......n2...... + // ^------^ + // | + IntervalState exSt = n1->val().out; // existing state info + + + StateType existing_state = exSt.type(); + MemTag existing_tag = exSt.mem_tag(); + SIndex existing_reserve_callstack = exSt.reserved_stack(); + SIndex existing_commit_callstack = exSt.committed_stack(); + + StateType new_state = get_new_state(existing_state, req); + MemTag new_tag = get_new_tag(n1->val().out.mem_tag(), req); + SIndex new_reserve_callstack = get_new_reserve_callstack(existing_reserve_callstack, existing_state, req); + SIndex new_commit_callstack = get_new_commit_callstack(existing_commit_callstack, existing_state, req); + + // n1........n2 + // out--> + n1->val().out.set_tag(new_tag); + n1->val().out.set_type(new_state); + n1->val().out.set_reserve_stack(new_reserve_callstack); + n1->val().out.set_commit_stack(new_commit_callstack); + + // n1........n2 + // <--in + n2->val().in.set_tag(new_tag); + n2->val().in.set_type(new_state); + n2->val().in.set_reserve_stack(new_reserve_callstack); + n2->val().in.set_commit_stack(new_commit_callstack); + + SingleDiff::delta region_size = n2->key() - n1->key(); + compute_summary_diff(region_size, existing_tag, existing_state, req, new_tag, diff); +} + +VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateType state, const RegionData& metadata, bool use_tag_inplace) { - assert(!use_tag_inplace || metadata.mem_tag == mtNone, - "If using use_tag_inplace, then the supplied tag should be mtNone, was instead: %s", NMTUtil::tag_to_name(metadata.mem_tag)); - if (A == B) { - // A 0-sized mapping isn't worth recording. + + if (_A == _B) { return SummaryDiff(); } - + assert(_A < _B, "should be"); + SummaryDiff diff; + RequestInfo req{_A, _B, state, metadata.mem_tag, metadata.stack_idx, use_tag_inplace}; IntervalChange stA{ IntervalState{StateType::Released, empty_regiondata}, IntervalState{ state, metadata} @@ -51,176 +258,400 @@ VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType IntervalState{ state, metadata}, IntervalState{StateType::Released, empty_regiondata} }; + stA.out.set_commit_stack(NativeCallStackStorage::invalid); + stB.in.set_commit_stack(NativeCallStackStorage::invalid); + VMATreap::Range rA = _tree.find_enclosing_range(_A); + VMATreap::Range rB = _tree.find_enclosing_range(_B); - // First handle A. - // Find closest node that is LEQ A - bool LEQ_A_found = false; - AddressState LEQ_A; - TreapNode* leqA_n = _tree.closest_leq(A); - if (leqA_n == nullptr) { - assert(!use_tag_inplace, "Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B); - if (use_tag_inplace) { - log_debug(nmt)("Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B); + // nodes: .....X.......Y...Z......W........U + // request: A------------------B + // X,Y = enclosing_nodes(A) + // W,U = enclosing_nodes(B) + // The cases are whether or not X and Y exists and X == A. (A == Y doesn't happen since it is searched by 'lt' predicate) + // The cases are whether or not W and U exists and W == B. (B == U doesn't happen since it is searched by 'lt' predicate) + + // We update regions in 3 sections: 1) X..A..Y, 2) Y....W, 3) W..B..U + // Y: is the closest node greater than A, but less than B + // W: is the closest node less than B, but greater than A + // The regions in [Y,W) are updated in a loop. We update X..A..Y before the loop and W..B..U after the loop. + // The table below summarizes the overlap cases. The overlapping case depends on whether X, Y, W and U exist or not, + // and if they exist whether they are the same or not. + // In the notations here, when there is not dot ('.') between two nodes it meaans that they are the same. For example, + // ...XA....Y.... means X == A. + + + // row 0: .........A..................B..... + // row 1: .........A...YW.............B..... // it is impossible, since it means only one node exists in the tree. + // row 2: .........A...Y..........W...B..... + // row 3: .........A...Y.............WB..... + + // row 4: .....X...A..................B..... + // row 5: .....X...A...YW.............B..... + // row 6: .....X...A...Y..........W...B..... + // row 7: .....X...A...Y.............WB..... + + // row 8: ........XA..................B..... + // row 9: ........XA...YW.............B..... + // row 10: ........XA...Y..........W...B..... + // row 11: ........XA...Y.............WB..... + + // row 12: .........A..................B....U + // row 13: .........A...YW.............B....U + // row 14: .........A...Y..........W...B....U + // row 15: .........A...Y.............WB....U + + // row 16: .....X...A..................B....U + // row 17: .....X...A...YW.............B....U + // row 18: .....X...A...Y..........W...B....U + // row 19: .....X...A...Y.............WB....U + + // row 20: ........XA..................B....U + // row 21: ........XA...YW.............B....U + // row 22: ........XA...Y..........W...B....U + // row 23: ........XA...Y.............WB....U + + + // We intentionally did not summarize/compress the cases to keep them as separate. + // This expanded way of describing the cases helps us to understand/analyze/verify/debug/maintain + // the corresponding code more easily. + // Mapping of table to row, row to switch-case should be consistent. If one changes, the others have + // to be updated accordingly. The sequence of dependecies is: table -> row no -> switch(row)-case -> code. + // Meaning that whenever any of one item in this sequence is changed, the rest of the consequent items to + // be checked/changed. + + TreapNode* X = rA.start; + TreapNode* Y = rA.end; + TreapNode* W = rB.start; + TreapNode* U = rB.end; + TreapNode nA{_A, stA, 0}; // the node that represents A + TreapNode nB{_B, stB, 0}; // the node that represents B + TreapNode* A = &nA; + TreapNode* B = &nB; + auto upsert_if= [&](TreapNode* node) { + if (!node->val().is_noop()) { + _tree.upsert(node->key(), node->val()); } - // No match. We add the A node directly, unless it would have no effect. - if (!stA.is_noop()) { - _tree.upsert(A, stA); + }; + // update region between n1 and n2 + auto update = [&](TreapNode* n1, TreapNode* n2) { + update_region(n1, n2, req, diff); + }; + auto remove_if = [&](TreapNode* node) -> bool{ + if (node->val().is_noop()) { + _tree.remove(node->key()); + return true; } - } else { - LEQ_A_found = true; - LEQ_A = AddressState{leqA_n->key(), leqA_n->val()}; - StateType leqA_state = leqA_n->val().out.type(); - StateType new_state = stA.out.type(); - // If we specify use_tag_inplace then the new region takes over the current tag instead of the tag in metadata. - // This is important because the VirtualMemoryTracker API doesn't require supplying the tag for some operations. - if (use_tag_inplace) { - assert(leqA_n->val().out.type() != StateType::Released, "Should not use inplace the tag of a released region"); - MemTag tag = leqA_n->val().out.mem_tag(); - stA.out.set_tag(tag); - stB.in.set_tag(tag); - } - - // Unless we know better, let B's outgoing state be the outgoing state of the node at or preceding A. - // Consider the case where the found node is the start of a region enclosing [A,B) - stB.out = out_state(leqA_n); - - // Direct address match. - if (leqA_n->key() == A) { - // Take over in state from old address. - stA.in = in_state(leqA_n); - - // We may now be able to merge two regions: - // If the node's old state matches the new, it becomes a noop. That happens, for example, - // when expanding a committed area: commit [x1, A); ... commit [A, x3) - // and the result should be a larger area, [x1, x3). In that case, the middle node (A and le_n) - // is not needed anymore. So we just remove the old node. - stB.in = stA.out; - if (stA.is_noop()) { - // invalidates leqA_n - _tree.remove(leqA_n->key()); - } else { - // If the state is not matching then we have different operations, such as: - // reserve [x1, A); ... commit [A, x2); or - // reserve [x1, A), mem_tag1; ... reserve [A, x2), mem_tag2; or - // reserve [A, x1), mem_tag1; ... reserve [A, x2), mem_tag2; - // then we re-use the existing out node, overwriting its old metadata. - leqA_n->val() = stA; + return false; + }; + GrowableArrayCHeap to_be_removed; + // update regions in [Y,W) + auto update_loop = [&]() { + TreapNode* prev = nullptr; + _tree.visit_range_in_order(_A + 1, _B + 1, [&](TreapNode* curr) { + if (prev != nullptr) { + update_region(prev, curr, req, diff); + // during visit, structure of the tree should not be changed + // keep the keys to be removed, and remove them later + if (prev->val().is_noop()) { + to_be_removed.push(prev->key()); + } } - } else { - // The address must be smaller. - assert(A > leqA_n->key(), "must be"); + prev = curr; + }); + }; + // update region of [A,T) + auto update_A = [&](TreapNode* T) { + A->val().out = A->val().in; + update(A, T); + }; + bool X_exists = X != nullptr; + bool Y_exists = Y != nullptr && Y->key() <= _B; + bool W_exists = W != nullptr && W->key() > _A; + bool U_exists = U != nullptr; + bool X_eq_A = X_exists && X->key() == _A; + bool W_eq_B = W_exists && W->key() == _B; + bool Y_eq_W = Y_exists && W_exists && W->key() == Y->key(); + int row = -1; +#ifdef ASSERT + auto print_case = [&]() { + log_trace(vmatree)(" req: %4d---%4d", (int)_A, (int)_B); + log_trace(vmatree)(" row: %2d", row); + log_trace(vmatree)(" X: %4ld", X_exists ? (long)X->key() : -1); + log_trace(vmatree)(" Y: %4ld", Y_exists ? (long)Y->key() : -1); + log_trace(vmatree)(" W: %4ld", W_exists ? (long)W->key() : -1); + log_trace(vmatree)(" U: %4ld", U_exists ? (long)U->key() : -1); + }; +#endif + // Order of the nodes if they exist are as: X <= A < Y <= W <= B < U + // A---------------------------B + // X Y YW WB U + // XA Y YW WB U + if (!X_exists && !Y_exists && !U_exists) { row = 0; } + if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 1; } + if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 2; } + if (!X_exists && Y_exists && W_eq_B && !U_exists) { row = 3; } - // We add a new node, but only if there would be a state change. If there would not be a - // state change, we just omit the node. - // That happens, for example, when reserving within an already reserved region with identical metadata. - stA.in = out_state(leqA_n); // .. and the region's prior state is the incoming state - if (stA.is_noop()) { - // Nothing to do. - } else { - // Add new node. - _tree.upsert(A, stA); - } + if ( X_exists && !Y_exists && !U_exists) { row = 4; } + if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 5; } + if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 6; } + if ( X_exists && Y_exists && W_eq_B && !U_exists) { row = 7; } + + if ( X_eq_A && !Y_exists && !U_exists) { row = 8; } + if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 9; } + if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 10; } + if ( X_eq_A && Y_exists && W_eq_B && !U_exists) { row = 11; } + + if (!X_exists && !Y_exists && U_exists) { row = 12; } + if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 13; } + if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 14; } + if (!X_exists && Y_exists && W_eq_B && U_exists) { row = 15; } + + if ( X_exists && !Y_exists && U_exists) { row = 16; } + if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 17; } + if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 18; } + if ( X_exists && Y_exists && W_eq_B && U_exists) { row = 19; } + + if ( X_eq_A && !Y_exists && U_exists) { row = 20; } + if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 21; } + if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 22; } + if ( X_eq_A && Y_exists && W_eq_B && U_exists) { row = 23; } + + DEBUG_ONLY(print_case();) + switch(row) { + // row 0: .........A..................B..... + case 0: { + update_A(B); + upsert_if(A); + upsert_if(B); + break; } - } - - // Now we handle B. - // We first search all nodes that are (A, B]. All of these nodes - // need to be deleted and summary accounted for. The last node before B determines B's outgoing state. - // If there is no node between A and B, its A's incoming state. - GrowableArrayCHeap to_be_deleted_inbetween_a_b; - bool B_needs_insert = true; - - // Find all nodes between (A, B] and record their addresses and values. Also update B's - // outgoing state. - _tree.visit_range_in_order(A + 1, B + 1, [&](TreapNode* head) { - int cmp_B = PositionComparator::cmp(head->key(), B); - stB.out = out_state(head); - if (cmp_B < 0) { - // Record all nodes preceding B. - to_be_deleted_inbetween_a_b.push({head->key(), head->val()}); - } else if (cmp_B == 0) { - // Re-purpose B node, unless it would result in a noop node, in - // which case record old node at B for deletion and summary accounting. - if (stB.is_noop()) { - to_be_deleted_inbetween_a_b.push(AddressState{B, head->val()}); - } else { - head->val() = stB; - } - B_needs_insert = false; + // row 1: .........A...YW.............B..... + case 1: { + ShouldNotReachHere(); + break; } - }); - - // Insert B node if needed - if (B_needs_insert && // Was not already inserted - !stB.is_noop()) // The operation is differing - { - _tree.upsert(B, stB); - } - - // We now need to: - // a) Delete all nodes between (A, B]. Including B in the case of a noop. - // b) Perform summary accounting - SummaryDiff diff; - - if (to_be_deleted_inbetween_a_b.length() == 0 && LEQ_A_found) { - // We must have smashed a hole in an existing region (or replaced it entirely). - // LEQ_A < A < B <= C - SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(LEQ_A.out().mem_tag())]; - if (LEQ_A.out().type() == StateType::Reserved) { - rescom.reserve -= B - A; - } else if (LEQ_A.out().type() == StateType::Committed) { - rescom.commit -= B - A; - rescom.reserve -= B - A; + // row 2: .........A...Y..........W...B..... + case 2: { + update_A(Y); + upsert_if(A); + update_loop(); + remove_if(Y); + update(W, B); + remove_if(W); + upsert_if(B); + break; } - } - - // Track the previous node. - AddressState prev{A, stA}; - for (int i = 0; i < to_be_deleted_inbetween_a_b.length(); i++) { - const AddressState delete_me = to_be_deleted_inbetween_a_b.at(i); - _tree.remove(delete_me.address); - - // Perform summary accounting - SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(delete_me.in().mem_tag())]; - if (delete_me.in().type() == StateType::Reserved) { - rescom.reserve -= delete_me.address - prev.address; - } else if (delete_me.in().type() == StateType::Committed) { - rescom.commit -= delete_me.address - prev.address; - rescom.reserve -= delete_me.address - prev.address; + // row 3: .........A...Y.............WB..... + case 3: { + update_A(Y); + upsert_if(A); + update_loop(); + remove_if(W); + break; } - prev = delete_me; - } - - if (prev.address != A && prev.out().type() != StateType::Released) { - // The last node wasn't released, so it must be connected to a node outside of (A, B) - // A - prev - B - (some node >= B) - // It might be that prev.address == B == (some node >= B), this is fine. - if (prev.out().type() == StateType::Reserved) { - SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())]; - rescom.reserve -= B - prev.address; - } else if (prev.out().type() == StateType::Committed) { - SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())]; - rescom.commit -= B - prev.address; - rescom.reserve -= B - prev.address; + // row 4: .....X...A..................B..... + case 4: { + A->val().in = X->val().out; + update_A(B); + upsert_if(A); + upsert_if(B); + break; } + // row 5: .....X...A...YW.............B..... + case 5: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update(Y, B); + remove_if(Y); + upsert_if(B); + break; + } + // row 6: .....X...A...Y..........W...B..... + case 6: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update_loop(); + update(W, B); + remove_if(W); + upsert_if(B); + break; + } + // row 7: .....X...A...Y.............WB..... + case 7: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update_loop(); + remove_if(W); + break; + } + // row 8: ........XA..................B..... + case 8: { + update(X, B); + remove_if(X); + upsert_if(B); + break; + } + // row 9: ........XA...YW.............B..... + case 9: { + update(X, Y); + remove_if(X); + update(W, B); + remove_if(W); + upsert_if(B); + break; + } + // row 10: ........XA...Y..........W...B..... + case 10: { + update(X, Y); + remove_if(X); + update_loop(); + update(W, B); + remove_if(W); + upsert_if(B); + break; + } + // row 11: ........XA...Y.............WB..... + case 11: { + update(X, Y); + remove_if(X); + update_loop(); + remove_if(W); + break; + } + // row 12: .........A..................B....U + case 12: { + update_A(B); + upsert_if(A); + upsert_if(B); + break; + } + // row 13: .........A...YW.............B....U + case 13: { + update_A(Y); + upsert_if(A); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 14: .........A...Y..........W...B....U + case 14: { + update_A(Y); + upsert_if(A); + update_loop(); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 15: .........A...Y.............WB....U + case 15: { + update_A(Y); + upsert_if(A); + update_loop(); + remove_if(W); + break; + } + // row 16: .....X...A..................B....U + case 16: { + A->val().in = X->val().out; + update_A(B); + upsert_if(A); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 17: .....X...A...YW.............B....U + case 17: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 18: .....X...A...Y..........W...B....U + case 18: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update_loop(); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 19: .....X...A...Y.............WB....U + case 19: { + A->val().in = X->val().out; + update_A(Y); + upsert_if(A); + update_loop(); + remove_if(W); + break; + } + // row 20: ........XA..................B....U + case 20: { + update(X, B); + remove_if(X); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 21: ........XA...YW.............B....U + case 21: { + update(X, Y); + remove_if(X); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 22: ........XA...Y..........W...B....U + case 22: { + update(X, Y); + remove_if(X); + update_loop(); + update(W, B); + remove_if(W); + B->val().out = U->val().in; + upsert_if(B); + break; + } + // row 23: ........XA...Y.............WB....U + case 23: { + update(X, Y); + remove_if(X); + update_loop(); + remove_if(W); + break; + } + default: + ShouldNotReachHere(); } - // Finally, we can register the new region [A, B)'s summary data. - SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(stA.out.mem_tag())]; - if (state == StateType::Reserved) { - rescom.reserve += B - A; - } else if (state == StateType::Committed) { - rescom.commit += B - A; - rescom.reserve += B - A; + // Remove the 'noop' nodes that found inside the loop + while(to_be_removed.length() != 0) { + _tree.remove(to_be_removed.pop()); } + return diff; } #ifdef ASSERT void VMATree::print_on(outputStream* out) { visit_in_order([&](TreapNode* current) { - out->print("%zu (%s) - %s - ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()), - statetype_to_string(out_state(current).type())); + out->print("%zu (%s) - %s [%d, %d]-> ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()), + statetype_to_string(out_state(current).type()), current->val().out.reserved_stack(), current->val().out.committed_stack()); }); out->cr(); } @@ -268,7 +699,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con SummaryDiff diff; // Ignore any released ranges, these must be mtNone and have no stack if (type != StateType::Released) { - RegionData new_data = RegionData(out.stack(), tag); + RegionData new_data = RegionData(out.reserved_stack(), tag); SummaryDiff result = register_mapping(from, end, type, new_data); diff.add(result); } @@ -289,7 +720,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con StateType type = out.type(); if (type != StateType::Released) { - RegionData new_data = RegionData(out.stack(), tag); + RegionData new_data = RegionData(out.reserved_stack(), tag); SummaryDiff result = register_mapping(from, end, type, new_data); diff.add(result); } diff --git a/src/hotspot/share/nmt/vmatree.hpp b/src/hotspot/share/nmt/vmatree.hpp index 0c639e929b7..d3bf3724c0e 100644 --- a/src/hotspot/share/nmt/vmatree.hpp +++ b/src/hotspot/share/nmt/vmatree.hpp @@ -44,6 +44,7 @@ class VMATree { public: using position = size_t; using size = size_t; + using SIndex = NativeCallStackStorage::StackIndex; class PositionComparator { public: @@ -55,7 +56,7 @@ public: } }; - enum class StateType : uint8_t { Reserved, Committed, Released, LAST }; + enum class StateType : uint8_t { Released, Reserved, Committed, LAST }; private: static const char* statetype_strings[static_cast(StateType::LAST)]; @@ -70,12 +71,12 @@ public: // Each point has some stack and a tag associated with it. struct RegionData { - const NativeCallStackStorage::StackIndex stack_idx; + const SIndex stack_idx; const MemTag mem_tag; RegionData() : stack_idx(), mem_tag(mtNone) {} - RegionData(NativeCallStackStorage::StackIndex stack_idx, MemTag mem_tag) + RegionData(SIndex stack_idx, MemTag mem_tag) : stack_idx(stack_idx), mem_tag(mem_tag) {} static bool equals(const RegionData& a, const RegionData& b) { @@ -91,15 +92,27 @@ private: private: // Store the type and mem_tag as two bytes uint8_t type_tag[2]; - NativeCallStackStorage::StackIndex sidx; + NativeCallStackStorage::StackIndex _reserved_stack; + NativeCallStackStorage::StackIndex _committed_stack; public: - IntervalState() : type_tag{0,0}, sidx() {} + IntervalState() : type_tag{0,0}, _reserved_stack(NativeCallStackStorage::invalid), _committed_stack(NativeCallStackStorage::invalid) {} + IntervalState(const StateType type, + const MemTag mt, + const NativeCallStackStorage::StackIndex res_stack, + const NativeCallStackStorage::StackIndex com_stack) { + assert(!(type == StateType::Released) || mt == mtNone, "Released state-type must have memory tag mtNone"); + type_tag[0] = static_cast(type); + type_tag[1] = static_cast(mt); + _reserved_stack = res_stack; + _committed_stack = com_stack; + } IntervalState(const StateType type, const RegionData data) { assert(!(type == StateType::Released) || data.mem_tag == mtNone, "Released state-type must have memory tag mtNone"); type_tag[0] = static_cast(type); type_tag[1] = static_cast(data.mem_tag); - sidx = data.stack_idx; + _reserved_stack = data.stack_idx; + _committed_stack = NativeCallStackStorage::invalid; } StateType type() const { @@ -110,16 +123,50 @@ private: return static_cast(type_tag[1]); } - RegionData regiondata() const { - return RegionData{sidx, mem_tag()}; + RegionData reserved_regiondata() const { + return RegionData{_reserved_stack, mem_tag()}; + } + RegionData committed_regiondata() const { + return RegionData{_committed_stack, mem_tag()}; } void set_tag(MemTag tag) { type_tag[1] = static_cast(tag); } - NativeCallStackStorage::StackIndex stack() const { - return sidx; + NativeCallStackStorage::StackIndex reserved_stack() const { + return _reserved_stack; + } + + NativeCallStackStorage::StackIndex committed_stack() const { + return _committed_stack; + } + + void set_reserve_stack(NativeCallStackStorage::StackIndex idx) { + _reserved_stack = idx; + } + + void set_commit_stack(NativeCallStackStorage::StackIndex idx) { + _committed_stack = idx; + } + + bool has_reserved_stack() { + return _reserved_stack != NativeCallStackStorage::invalid; + } + + bool has_committed_stack() { + return _committed_stack != NativeCallStackStorage::invalid; + } + + void set_type(StateType t) { + type_tag[0] = static_cast(t); + } + + bool equals(const IntervalState& other) const { + return mem_tag() == other.mem_tag() && + type() == other.type() && + reserved_stack() == other.reserved_stack() && + committed_stack() == other.committed_stack(); } }; @@ -130,8 +177,14 @@ private: IntervalState out; bool is_noop() { + if (in.type() == StateType::Released && + in.type() == out.type() && + in.mem_tag() == out.mem_tag()) { + return true; + } return in.type() == out.type() && - RegionData::equals(in.regiondata(), out.regiondata()); + RegionData::equals(in.reserved_regiondata(), out.reserved_regiondata()) && + RegionData::equals(in.committed_regiondata(), out.committed_regiondata()); } }; @@ -193,8 +246,44 @@ public: #endif }; + enum Operation {Release, Reserve, Commit, Uncommit}; + struct RequestInfo { + position A, B; + StateType _op; + MemTag tag; + SIndex callstack; + bool use_tag_inplace; + Operation op() const { + return + _op == StateType::Reserved && !use_tag_inplace ? Operation::Reserve : + _op == StateType::Committed ? Operation::Commit : + _op == StateType::Reserved && use_tag_inplace ? Operation::Uncommit : + Operation::Release; + } + + int op_to_index() const { + return + _op == StateType::Reserved && !use_tag_inplace ? 1 : + _op == StateType::Committed ? 2 : + _op == StateType::Reserved && use_tag_inplace ? 3 : + 0; + } + }; + private: SummaryDiff register_mapping(position A, position B, StateType state, const RegionData& metadata, bool use_tag_inplace = false); + StateType get_new_state(const StateType existinting_state, const RequestInfo& req) const; + MemTag get_new_tag(const MemTag existinting_tag, const RequestInfo& req) const; + SIndex get_new_reserve_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const; + SIndex get_new_commit_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const; + void compute_summary_diff(const SingleDiff::delta region_size, const MemTag t1, const StateType& ex, const RequestInfo& req, const MemTag new_tag, SummaryDiff& diff) const; + void update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff); + int state_to_index(const StateType st) const { + return + st == StateType::Released ? 0 : + st == StateType::Reserved ? 1 : + st == StateType::Committed ? 2 : -1; + } public: SummaryDiff reserve_mapping(position from, size size, const RegionData& metadata) { diff --git a/test/hotspot/gtest/nmt/test_vmatree.cpp b/test/hotspot/gtest/nmt/test_vmatree.cpp index 80b5df50062..4d3418cc9fc 100644 --- a/test/hotspot/gtest/nmt/test_vmatree.cpp +++ b/test/hotspot/gtest/nmt/test_vmatree.cpp @@ -36,15 +36,19 @@ using NCS = NativeCallStackStorage; class NMTVMATreeTest : public testing::Test { public: NCS ncs; - constexpr static const int si_len = 2; + constexpr static const int si_len = 4; NCS::StackIndex si[si_len]; NativeCallStack stacks[si_len]; NMTVMATreeTest() : ncs(true) { stacks[0] = make_stack(0xA); stacks[1] = make_stack(0xB); + stacks[2] = make_stack(0xC); + stacks[3] = make_stack(0xD); si[0] = ncs.push(stacks[0]); - si[1] = ncs.push(stacks[0]); + si[1] = ncs.push(stacks[1]); + si[2] = ncs.push(stacks[2]); + si[3] = ncs.push(stacks[3]); } // Utilities @@ -99,6 +103,11 @@ public: // Do it backwards instead. Tree tree2; + // 900---1000 + // 800--900 + // 700--800 + // ... + // 0--100 for (int i = 9; i >= 0; i--) { tree2.reserve_mapping(i * 100, 100, rd); } @@ -167,6 +176,164 @@ public: EXPECT_TRUE(exists(found[2])); EXPECT_TRUE(exists(found[3])); }; + template struct ExpectedTree { + int nodes[NodeCount]; + MemTag tags[NodeCount + 1]; + VMATree::StateType states[NodeCount + 1]; + NativeCallStackStorage::StackIndex res_si[NodeCount + 1]; + NativeCallStackStorage::StackIndex com_si[NodeCount + 1]; + }; + + using State = VMATree::StateType; + using SIndex = VMATree::SIndex; + + struct UpdateCallInfo { + VMATree::IntervalState ex_st; + VMATree::RequestInfo req; + VMATree::IntervalState new_st; + int reserve[2], commit[2]; + }; + + void call_update_region(const UpdateCallInfo upd) { + VMATree::TreapNode n1{upd.req.A, {}, 0}, n2{upd.req.B, {}, 0}; + n1.val().out= upd.ex_st; + n2.val().in = n1.val().out; + Tree tree; + VMATree::SummaryDiff diff; + tree.update_region(&n1, &n2, upd.req, diff); + int from = NMTUtil::tag_to_index(upd.ex_st.mem_tag()); + int to = NMTUtil::tag_to_index(upd.new_st.mem_tag()); + stringStream ss; + ss.print("Ex. State: %d, op: %d, use-tag:%d, from==to: %d", + (int)upd.ex_st.type(), (int)upd.req.op_to_index(), upd.req.use_tag_inplace, from == to); + const char* failed_case = ss.base(); + EXPECT_EQ(n1.val().out.type(), upd.new_st.type()) << failed_case; + EXPECT_EQ(n1.val().out.mem_tag(), upd.new_st.mem_tag()) << failed_case; + EXPECT_EQ(n1.val().out.reserved_stack(), upd.new_st.reserved_stack()) << failed_case; + EXPECT_EQ(n1.val().out.committed_stack(), upd.new_st.committed_stack()) << failed_case; + + if (from == to) { + EXPECT_EQ(diff.tag[from].reserve, upd.reserve[0] + upd.reserve[1]) << failed_case; + EXPECT_EQ(diff.tag[from].commit, upd.commit[0] + upd.commit[1]) << failed_case; + } else { + EXPECT_EQ(diff.tag[from].reserve, upd.reserve[0]) << failed_case; + EXPECT_EQ(diff.tag[from].commit, upd.commit[0]) << failed_case; + EXPECT_EQ(diff.tag[to].reserve, upd.reserve[1]) << failed_case; + EXPECT_EQ(diff.tag[to].commit, upd.commit[1]) << failed_case; + } + } + + template + void create_tree(Tree& tree, ExpectedTree& et, int line_no) { + using SIndex = NativeCallStackStorage::StackIndex; + const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack + VMATree::IntervalChange st; + for (int i = 0; i < N; i++) { + st.in.set_type(et.states[i]); + st.in.set_tag(et.tags[i]); + if (et.res_si[i] >= 0) { + st.in.set_reserve_stack(et.res_si[i]); + } else { + st.in.set_reserve_stack(ES); + } + if (et.com_si[i] >= 0) { + st.in.set_commit_stack(et.com_si[i]); + } else { + st.in.set_commit_stack(ES); + } + + st.out.set_type(et.states[i+1]); + st.out.set_tag(et.tags[i+1]); + if (et.res_si[i+1] >= 0) { + st.out.set_reserve_stack(et.res_si[i+1]); + } else { + st.out.set_reserve_stack(ES); + } + if (et.com_si[i+1] >= 0) { + st.out.set_commit_stack(et.com_si[i+1]); + } else { + st.out.set_commit_stack(ES); + } + tree.tree().upsert((VMATree::position)et.nodes[i], st); + } + print_tree(et, line_no); +} + + template + void check_tree(Tree& tree, const ExpectedTree& et, int line_no) { + using Node = VMATree::TreapNode; + auto left_released = [&](Node n) -> bool { + return n.val().in.type() == VMATree::StateType::Released and + n.val().in.mem_tag() == mtNone; + }; + auto right_released = [&](Node n) -> bool { + return n.val().out.type() == VMATree::StateType::Released and + n.val().out.mem_tag() == mtNone; + }; + for (int i = 0; i < N; i++) { + VMATree::VMATreap::Range r = tree.tree().find_enclosing_range(et.nodes[i]); + ASSERT_TRUE(r.start != nullptr); + Node node = *r.start; + ASSERT_EQ(node.key(), (VMATree::position)et.nodes[i]) << "at line " << line_no; + if (i == (N -1)) { // last node + EXPECT_TRUE(right_released(node)) << "right-of last node is not Released"; + break; + } + if (i == 0) { // first node + EXPECT_TRUE(left_released(node)) << "left-of first node is not Released"; + } + stringStream ss(50); + ss.print("test at line: %d, for node: %d", line_no, et.nodes[i]); + const char* for_this_node = ss.base(); + EXPECT_EQ(node.val().out.type(), et.states[i+1]) << for_this_node; + EXPECT_EQ(node.val().out.mem_tag(), et.tags[i+1]) << for_this_node; + if (et.res_si[i+1] >= 0) { + EXPECT_EQ(node.val().out.reserved_stack(), et.res_si[i+1]) << for_this_node; + EXPECT_EQ(r.end->val().in.reserved_stack(), et.res_si[i+1]) << for_this_node; + } else { + EXPECT_FALSE(node.val().out.has_reserved_stack()) << for_this_node; + EXPECT_FALSE(r.end->val().in.has_reserved_stack()) << for_this_node; + } + if (et.com_si[i+1] >= 0) { + EXPECT_EQ(node.val().out.committed_stack(), et.com_si[i+1]) << for_this_node; + EXPECT_EQ(r.end->val().in.committed_stack(), et.com_si[i+1]) << for_this_node; + } else { + EXPECT_FALSE(node.val().out.has_committed_stack()) << for_this_node; + EXPECT_FALSE(r.end->val().in.has_committed_stack()) << for_this_node; + } + } + print_tree(et, line_no); + } + + template + void print_tree(const ExpectedTree& et, int line_no) { + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + stringStream ss; + ss.print_cr("Tree nodes for line %d", line_no); + ss.print_cr(" // 1 2 3 4 5"); + ss.print_cr(" // 012345678901234567890123456789012345678901234567890"); + ss.print (" // "); + int j = 0; + for (int i = 0; i < N; i++) { + char state_char = et.states[i+1] == Rl ? '.' : + et.states[i+1] == Rs ? 'r' : + et.states[i+1] == C ? 'C' : ' '; + if (i == 0 && et.nodes[i] != 0) { + for (j = 0; j < et.nodes[i]; j++) { + ss.put('.'); + } + } + for (j = et.nodes[i]; i < (N - 1) && j < et.nodes[i + 1]; j++) { + ss.put(state_char); + } + } + for (; j <= 50; j++) { + ss.put('.'); + } + tty->print_cr("%s", ss.base()); + } }; @@ -179,13 +346,17 @@ TEST_VM_F(NMTVMATreeTest, OverlappingReservationsResultInTwoNodes) { EXPECT_EQ(2, count_nodes(tree)); } -TEST_VM_F(NMTVMATreeTest, UseFlagInplace) { +TEST_VM_F(NMTVMATreeTest, UseTagInplace) { Tree tree; - VMATree::RegionData rd1(si[0], mtTest); - VMATree::RegionData rd2(si[1], mtNone); - tree.reserve_mapping(0, 100, rd1); - tree.commit_mapping(20, 50, rd2, true); - tree.uncommit_mapping(30, 10, rd2); + VMATree::RegionData rd_Test_cs0(si[0], mtTest); + VMATree::RegionData rd_None_cs1(si[1], mtNone); + tree.reserve_mapping(0, 100, rd_Test_cs0); + // reserve: 0---------------------100 + // commit: 20**********70 + // uncommit: 30--40 + // post-cond: 0---20**30--40**70----100 + tree.commit_mapping(20, 50, rd_None_cs1, true); + tree.uncommit_mapping(30, 10, rd_None_cs1); tree.visit_in_order([&](TNode* node) { if (node->key() != 100) { EXPECT_EQ(mtTest, node->val().out.mem_tag()) << "failed at: " << node->key(); @@ -211,10 +382,10 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { { // Identical operation but different metadata should not merge Tree tree; - VMATree::RegionData rd{si[0], mtTest }; - VMATree::RegionData rd2{si[1], mtNMT }; - tree.reserve_mapping(0, 100, rd); - tree.reserve_mapping(100, 100, rd2); + VMATree::RegionData rd_Test_cs0{si[0], mtTest}; + VMATree::RegionData rd_NMT_cs1{si[1], mtNMT}; + tree.reserve_mapping(0, 100, rd_Test_cs0); + tree.reserve_mapping(100, 100, rd_NMT_cs1); EXPECT_EQ(3, count_nodes(tree)); int found_nodes = 0; @@ -222,14 +393,14 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { { // Reserving after commit should overwrite commit Tree tree; - VMATree::RegionData rd{si[0], mtTest }; - VMATree::RegionData rd2{si[1], mtNMT }; - tree.commit_mapping(50, 50, rd2); - tree.reserve_mapping(0, 100, rd); + VMATree::RegionData rd_Test_cs0{si[0], mtTest}; + VMATree::RegionData rd_NMT_cs1{si[1], mtNMT}; + tree.commit_mapping(50, 50, rd_NMT_cs1); + tree.reserve_mapping(0, 100, rd_Test_cs0); treap(tree).visit_in_order([&](TNode* x) { EXPECT_TRUE(x->key() == 0 || x->key() == 100); if (x->key() == 0) { - EXPECT_EQ(x->val().out.regiondata().mem_tag, mtTest); + EXPECT_EQ(x->val().out.reserved_regiondata().mem_tag, mtTest); } }); @@ -238,19 +409,19 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { { // Split a reserved region into two different reserved regions Tree tree; - VMATree::RegionData rd{si[0], mtTest }; - VMATree::RegionData rd2{si[1], mtNMT }; - VMATree::RegionData rd3{si[0], mtNone }; - tree.reserve_mapping(0, 100, rd); - tree.reserve_mapping(0, 50, rd2); - tree.reserve_mapping(50, 50, rd3); + VMATree::RegionData rd_Test_cs0{si[0], mtTest}; + VMATree::RegionData rd_NMT_cs1{si[1], mtNMT}; + VMATree::RegionData rd_None_cs0{si[0], mtNone}; + tree.reserve_mapping(0, 100, rd_Test_cs0); + tree.reserve_mapping(0, 50, rd_NMT_cs1); + tree.reserve_mapping(50, 50, rd_None_cs0); EXPECT_EQ(3, count_nodes(tree)); } { // One big reserve + release leaves an empty tree - Tree::RegionData rd{si[0], mtNMT}; + VMATree::RegionData rd_NMT_cs0{si[0], mtNMT}; Tree tree; - tree.reserve_mapping(0, 500000, rd); + tree.reserve_mapping(0, 500000, rd_NMT_cs0); tree.release_mapping(0, 500000); EXPECT_EQ(nullptr, treap_root(tree)); @@ -258,27 +429,27 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { { // A committed region inside of/replacing a reserved region // should replace the reserved region's metadata. - Tree::RegionData rd{si[0], mtNMT}; - VMATree::RegionData rd2{si[1], mtTest}; + VMATree::RegionData rd_NMT_cs0{si[0], mtNMT}; + VMATree::RegionData rd_Test_cs1{si[1], mtTest}; Tree tree; - tree.reserve_mapping(0, 100, rd); - tree.commit_mapping(0, 100, rd2); + tree.reserve_mapping(0, 100, rd_NMT_cs0); + tree.commit_mapping(0, 100, rd_Test_cs1); treap(tree).visit_range_in_order(0, 99999, [&](TNode* x) { if (x->key() == 0) { - EXPECT_EQ(mtTest, x->val().out.regiondata().mem_tag); + EXPECT_EQ(mtTest, x->val().out.reserved_regiondata().mem_tag); } if (x->key() == 100) { - EXPECT_EQ(mtTest, x->val().in.regiondata().mem_tag); + EXPECT_EQ(mtTest, x->val().in.reserved_regiondata().mem_tag); } }); } { // Attempting to reserve or commit an empty region should not change the tree. Tree tree; - Tree::RegionData rd{si[0], mtNMT}; - tree.reserve_mapping(0, 0, rd); + VMATree::RegionData rd_NMT_cs0{si[0], mtNMT}; + tree.reserve_mapping(0, 0, rd_NMT_cs0); EXPECT_EQ(nullptr, treap_root(tree)); - tree.commit_mapping(0, 0, rd); + tree.commit_mapping(0, 0, rd_NMT_cs0); EXPECT_EQ(nullptr, treap_root(tree)); } } @@ -289,12 +460,12 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { VMATree::position from; VMATree::position to; MemTag tag; - NCS::StackIndex stack; + NCS::StackIndex reserve_stack; State state; }; // Take a sorted list of testranges and check that those and only those are found in the tree. - auto expect_equivalent_form = [&](auto& expected, VMATree& tree) { + auto expect_equivalent_form = [&](auto& expected, VMATree& tree, int line_no) { // With auto& our arrays do not deteriorate to pointers but are kept as testrange[N] // so this actually works! int len = sizeof(expected) / sizeof(testrange); @@ -311,11 +482,11 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { EXPECT_EQ(expect.from, found.start->key()); EXPECT_EQ(expect.to, found.end->key()); // Same tag - EXPECT_EQ(expect.tag, found.start->val().out.mem_tag()); - EXPECT_EQ(expect.tag, found.end->val().in.mem_tag()); + EXPECT_EQ(expect.tag, found.start->val().out.mem_tag()) << " and at test-line: " << line_no; + EXPECT_EQ(expect.tag, found.end->val().in.mem_tag()) << " and at test-line: " << line_no; // Same stack - EXPECT_EQ(expect.stack, found.start->val().out.stack()); - EXPECT_EQ(expect.stack, found.end->val().in.stack()); + EXPECT_EQ(expect.reserve_stack, found.start->val().out.reserved_stack()) << "Unexpected stack at region: " << i << " and at test-line: " << line_no; + EXPECT_EQ(expect.reserve_stack, found.end->val().in.reserved_stack()) << "Unexpected stack at region: " << i << " and at test-line: " << line_no; // Same state EXPECT_EQ(expect.state, found.start->val().out.type()); EXPECT_EQ(expect.state, found.end->val().in.type()); @@ -324,6 +495,8 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { EXPECT_EQ(len+1, tree.tree().size()); }; NCS::StackIndex si = NCS::StackIndex(); + NCS::StackIndex es = NCS::invalid; // empty or no stack is stored + Tree::RegionData rd(si, mtNone); { // The gc/cds case with only reserved data @@ -337,7 +510,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.set_tag(0, 500, mtGC); tree.set_tag(500, 100, mtClassShared); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Now let's add in some committed data @@ -353,6 +526,13 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { }; VMATree tree; + // 0---------------------------------------------------600 + // 100****225 + // 550***560 + // 565***575 + // 0------100****225---------550***560---565***575-----600 + // 0------100****225---500---550***560---565***575-----600 + // <-------mtGC---------><-----------mtClassShared-------> tree.reserve_mapping(0, 600, rd); // The committed areas tree.commit_mapping(100, 125, rd); @@ -361,7 +541,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { // OK, set tag tree.set_tag(0, 500, mtGC); tree.set_tag(500, 100, mtClassShared); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Setting the tag for adjacent regions with same stacks should merge the regions @@ -374,7 +554,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.reserve_mapping(0, 100, gc); tree.reserve_mapping(100, 100, compiler); tree.set_tag(0, 200, mtGC); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Setting the tag for adjacent regions with different stacks should NOT merge the regions @@ -390,7 +570,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.reserve_mapping(0, 100, gc); tree.reserve_mapping(100, 100, compiler); tree.set_tag(0, 200, mtGC); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Setting the tag in the middle of a range causes a split @@ -403,7 +583,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { Tree::RegionData compiler(si, mtCompiler); tree.reserve_mapping(0, 200, compiler); tree.set_tag(100, 50, mtGC); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Setting the tag in between two ranges causes a split @@ -418,13 +598,13 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.reserve_mapping(0, 100, gc); tree.reserve_mapping(100, 100, compiler); tree.set_tag(75, 50, mtClass); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Holes in the address range are acceptable and untouched testrange expected[]{ { 0, 50, mtGC, si, State::Reserved}, - {50, 75, mtNone, si, State::Released}, + {50, 75, mtNone, es, State::Released}, {75, 80, mtGC, si, State::Reserved}, {80, 100, mtClassShared, si, State::Reserved} }; @@ -433,7 +613,7 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.reserve_mapping(0, 50, class_shared); tree.reserve_mapping(75, 25, class_shared); tree.set_tag(0, 80, mtGC); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Check that setting tag with 'hole' not consisting of any regions work @@ -444,15 +624,15 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { Tree::RegionData class_shared(si, mtClassShared); tree.reserve_mapping(10, 10, class_shared); tree.set_tag(0, 100, mtCompiler); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } { // Check that multiple holes still work testrange expected[]{ { 0, 1, mtGC, si, State::Reserved}, - { 1, 50, mtNone, si, State::Released}, + { 1, 50, mtNone, es, State::Released}, {50, 75, mtGC, si, State::Reserved}, - {75, 99, mtNone, si, State::Released}, + {75, 99, mtNone, es, State::Released}, {99, 100, mtGC, si, State::Reserved} }; VMATree tree; @@ -461,79 +641,158 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { tree.release_mapping(1, 49); tree.release_mapping(75, 24); tree.set_tag(0, 100, mtGC); - expect_equivalent_form(expected, tree); + expect_equivalent_form(expected, tree, __LINE__); } } // Tests for summary accounting TEST_VM_F(NMTVMATreeTest, SummaryAccounting) { { // Fully enclosed re-reserving works correctly. - Tree::RegionData rd(NCS::StackIndex(), mtTest); - Tree::RegionData rd2(NCS::StackIndex(), mtNMT); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); + Tree::RegionData rd_NMT_cs0(NCS::StackIndex(), mtNMT); Tree tree; - VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); + VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd_Test_cs0); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(100, diff.reserve); - all_diff = tree.reserve_mapping(50, 25, rd2); + all_diff = tree.reserve_mapping(50, 25, rd_NMT_cs0); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCC.......... +// Legend: +// A - Test (reserved) +// B - Native Memory Tracking (reserved) +// C - Test (reserved) +// . - free diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; VMATree::SingleDiff diff2 = all_diff.tag[NMTUtil::tag_to_index(mtNMT)]; EXPECT_EQ(-25, diff.reserve); EXPECT_EQ(25, diff2.reserve); } { // Fully release reserved mapping - Tree::RegionData rd(NCS::StackIndex(), mtTest); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); Tree tree; - VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); + VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd_Test_cs0); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(100, diff.reserve); all_diff = tree.release_mapping(0, 100); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// .............................................................................................................. +// Legend: diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(-100, diff.reserve); } { // Convert some of a released mapping to a committed one - Tree::RegionData rd(NCS::StackIndex(), mtTest); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); Tree tree; - VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); + VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd_Test_cs0); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(diff.reserve, 100); - all_diff = tree.commit_mapping(0, 100, rd); + all_diff = tree.commit_mapping(0, 100, rd_Test_cs0); +// 1 2 3 4 5 6 7 8 9 10 11 +// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.......... +// Legend: +// a - Test (committed) +// . - free diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(0, diff.reserve); EXPECT_EQ(100, diff.commit); } { // Adjacent reserved mappings with same type - Tree::RegionData rd(NCS::StackIndex(), mtTest); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); Tree tree; - VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); + VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 10, rd_Test_cs0); +// 1 2 +// 01234567890123456789 +// AAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; - EXPECT_EQ(diff.reserve, 100); - all_diff = tree.reserve_mapping(100, 100, rd); + EXPECT_EQ(diff.reserve, 10); + all_diff = tree.reserve_mapping(10, 10, rd_Test_cs0); +// 1 2 3 +// 012345678901234567890123456789 +// AAAAAAAAAAAAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; - EXPECT_EQ(100, diff.reserve); + EXPECT_EQ(10, diff.reserve); } { // Adjacent reserved mappings with different tags - Tree::RegionData rd(NCS::StackIndex(), mtTest); - Tree::RegionData rd2(NCS::StackIndex(), mtNMT); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); + Tree::RegionData rd_NMT_cs0(NCS::StackIndex(), mtNMT); Tree tree; - VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); + VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 10, rd_Test_cs0); +// 1 2 +// 01234567890123456789 +// AAAAAAAAAA.......... +// Legend: +// A - Test (reserved) +// . - free VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; - EXPECT_EQ(diff.reserve, 100); - all_diff = tree.reserve_mapping(100, 100, rd2); + EXPECT_EQ(diff.reserve, 10); + all_diff = tree.reserve_mapping(10, 10, rd_NMT_cs0); +// 1 2 3 +// 012345678901234567890123456789 +// AAAAAAAAAABBBBBBBBBB.......... +// Legend: +// A - Test (reserved) +// B - Native Memory Tracking (reserved) +// . - free diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; EXPECT_EQ(0, diff.reserve); diff = all_diff.tag[NMTUtil::tag_to_index(mtNMT)]; - EXPECT_EQ(100, diff.reserve); + EXPECT_EQ(10, diff.reserve); } { // A commit with two previous commits inside of it should only register // the new memory in the commit diff. Tree tree; - Tree::RegionData rd(NCS::StackIndex(), mtTest); - tree.commit_mapping(128, 128, rd); - tree.commit_mapping(512, 128, rd); - VMATree::SummaryDiff diff = tree.commit_mapping(0, 1024, rd); - EXPECT_EQ(768, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); - EXPECT_EQ(768, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); + Tree::RegionData rd_Test_cs0(NCS::StackIndex(), mtTest); + tree.commit_mapping(16, 16, rd_Test_cs0); +// 1 2 3 4 +// 0123456789012345678901234567890123456789 +// ................aaaaaaaaaaaaaaaa.......... +// Legend: +// a - Test (committed) +// . - free + tree.commit_mapping(32, 32, rd_Test_cs0); +// 1 2 3 4 5 6 7 +// 0123456789012345678901234567890123456789012345678901234567890123456789 +// ................aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.......... +// Legend: +// a - Test (committed) +// . - free + VMATree::SummaryDiff diff = tree.commit_mapping(0, 64, rd_Test_cs0); +// 1 2 3 4 5 6 7 +// 0123456789012345678901234567890123456789012345678901234567890123456789 +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.......... +// Legend: +// a - Test (committed) +// . - free + EXPECT_EQ(16, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); + EXPECT_EQ(16, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); } } @@ -713,10 +972,21 @@ TEST_VM_F(NMTVMATreeTest, TestConsistencyWithSimpleTracker) { VMATree::TreapNode* endn = find(treap, (end * page_size) + page_size); ASSERT_NE(nullptr, endn); - const NativeCallStack& start_stack = ncss.get(startn->val().out.stack()); - const NativeCallStack& end_stack = ncss.get(endn->val().in.stack()); - ASSERT_TRUE(starti.stack.equals(start_stack)); - ASSERT_TRUE(endi.stack.equals(end_stack)); + const NativeCallStack& start_stack = ncss.get(startn->val().out.reserved_stack()); + const NativeCallStack& end_stack = ncss.get(endn->val().in.reserved_stack()); + // If start-node of a reserved region is committed, the stack is stored in the second_stack of the node. + if (startn->val().out.has_committed_stack()) { + const NativeCallStack& start_second_stack = ncss.get(startn->val().out.committed_stack()); + ASSERT_TRUE(starti.stack.equals(start_stack) || starti.stack.equals(start_second_stack)); + } else { + ASSERT_TRUE(starti.stack.equals(start_stack)); + } + if (endn->val().in.has_committed_stack()) { + const NativeCallStack& end_second_stack = ncss.get(endn->val().in.committed_stack()); + ASSERT_TRUE(endi.stack.equals(end_stack) || endi.stack.equals(end_second_stack)); + } else { + ASSERT_TRUE(endi.stack.equals(end_stack)); + } ASSERT_EQ(starti.mem_tag, startn->val().out.mem_tag()); ASSERT_EQ(endi.mem_tag, endn->val().in.mem_tag()); @@ -725,18 +995,994 @@ TEST_VM_F(NMTVMATreeTest, TestConsistencyWithSimpleTracker) { } } -TEST_VM_F(NMTVMATreeTest, SummaryAccountingWhenUseFlagInplace) { +TEST_VM_F(NMTVMATreeTest, SummaryAccountingWhenUseTagInplace) { Tree tree; - VMATree::RegionData rd1(si[0], mtTest); - VMATree::RegionData rd2(si[1], mtNone); - tree.reserve_mapping(0, 100, rd1); - VMATree::SummaryDiff diff = tree.commit_mapping(0, 50, rd2, true); + VMATree::RegionData rd_Test_cs0(si[0], mtTest); + VMATree::RegionData rd_None_cs1(si[1], mtNone); +// 1 2 3 4 5 +// 012345678901234567890123456789012345678901234567890 +// .................................................. + tree.reserve_mapping(0, 50, rd_Test_cs0); +// 1 2 3 4 5 +// 012345678901234567890123456789012345678901234567890 +// rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr +VMATree::SummaryDiff diff = tree.commit_mapping(0, 25, rd_None_cs1, true); +// 1 2 3 4 5 +// 012345678901234567890123456789012345678901234567890 +// CCCCCCCCCCCCCCCCCCCCCCCCCrrrrrrrrrrrrrrrrrrrrrrrrr EXPECT_EQ(0, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); - EXPECT_EQ(50, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); - diff = tree.commit_mapping(60, 10, rd2, true); + EXPECT_EQ(25, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); + + diff = tree.commit_mapping(30, 5, rd_None_cs1, true); +// 1 2 3 4 5 +// 012345678901234567890123456789012345678901234567890 +// CCCCCCCCCCCCCCCCCCCCCCCCCrrrrrCCCCCrrrrrrrrrrrrrrr EXPECT_EQ(0, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); - EXPECT_EQ(10, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); - diff = tree.uncommit_mapping(0, 50, rd2); + EXPECT_EQ(5, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); + + diff = tree.uncommit_mapping(0, 25, rd_None_cs1); +// 1 2 3 4 5 +// 012345678901234567890123456789012345678901234567890 +// rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrCCCCCrrrrrrrrrrrrrrr EXPECT_EQ(0, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); - EXPECT_EQ(-50, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); + EXPECT_EQ(-25, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); +} + +// How the memory regions are visualized: +// 1 2 3 4 5 6 7 | +// 0123456789012345678901234567890123456789012345678901234567890123456789 |_> memory address +// aaaaaaBBBBBBBcccccccDDDDDDDeeeeeeeFFFFFFFF........................... |->some letters showing the state of the memory +// Legend: +// . - None (free/released) +// r - MemTag (reserved) +// C - MemTag (committed) +// MemTag is Test if omitted. + +TEST_VM_F(NMTVMATreeTest, SeparateStacksForCommitAndReserve) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + + {// Check committing into a reserved region inherits the call stacks + Tree tree; + tree.reserve_mapping(0, 50, rd_Test_cs1); // reserve in an empty tree + // Pre: empty tree. + // Post: + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr. + ExpectedTree<2> et1 = {{ 0, 50 }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 }}; + check_tree(tree, et1, __LINE__); + tree.commit_mapping(25, 10, rd_None_cs2, true); // commit at the middle of the region + // Post: + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrrrrrrrrrrrrrrrrCCCCCCCCCCrrrrrrrrrrrrrrr. + ExpectedTree<4> et2 = {{ 0, 25, 35, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_2 , -1 , -1 }}; + check_tree(tree, et2, __LINE__); + + tree.commit_mapping(0, 20, rd_None_cs2, true); // commit at the beginning of the region + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // CCCCCCCCCCCCCCCCCCCCrrrrrCCCCCCCCCCrrrrrrrrrrrrrrr. + ExpectedTree<5> et3 = {{ 0, 20, 25, 35, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , Rs , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , si_2 , -1 , si_2 , -1 , -1 }}; + check_tree(tree, et3, __LINE__); + + tree.commit_mapping(40, 10, rd_None_cs2, true); // commit at the end of the region + // Post: + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // CCCCCCCCCCCCCCCCCCCCrrrrrCCCCCCCCCCrrrrrCCCCCCCCCC. + ExpectedTree<6> et4 = {{ 0, 20, 25, 35, 40, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , Rs , C , Rs , C , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , si_2 , -1 , si_2 , -1 , si_2 , -1 }}; + check_tree(tree, et4, __LINE__); + } + {// committing overlapped regions does not destroy the old call-stacks + Tree tree; + tree.reserve_mapping(0, 50, rd_Test_cs1); // reserving in an empty tree + // Pre: empty tree. + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<2> et1 = {{ 0 , 50 }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 }}; + check_tree(tree, et1, __LINE__); + + tree.commit_mapping(10, 10, rd_None_cs2, true); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrCCCCCCCCCCrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<4> et2 = {{ 0, 10, 20, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_2 , -1 , -1 }}; + check_tree(tree, et2, __LINE__); + + SIndex si_3 = si[2]; + VMATree::RegionData rd_Test_cs3(si_3, mtTest); + // commit with overlap at the region's start + tree.commit_mapping(5, 10, rd_Test_cs3); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrCCCCCCCCCCCCCCCrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<5> et3 = {{ 0, 5, 15, 20, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_3 , si_2 , -1 , -1 }}; + check_tree(tree, et3, __LINE__); + + SIndex si_4 = si[3]; + VMATree::RegionData call_stack_4(si_4, mtTest); + // commit with overlap at the region's end + tree.commit_mapping(15, 10, call_stack_4); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrCCCCCCCCCCCCCCCCCCCCrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<5> et4 = {{ 0, 5, 15, 25, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_3 , si_4 , -1 , -1 }}; + check_tree(tree, et4, __LINE__); + } + {// uncommit should not store any call-stack + Tree tree; + tree.reserve_mapping(0, 50, rd_Test_cs1); + + tree.commit_mapping(10, 10, rd_None_cs2, true); + + tree.commit_mapping(0, 5, rd_None_cs2, true); + + tree.uncommit_mapping(0, 3, rd_None_cs2); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrCCrrrrrCCCCCCCCCCrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<6> et1 = {{ 0, 3, 5, 10, 20, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , Rs , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_2 , -1 , si_2 , -1 , -1 }}; + check_tree(tree, et1, __LINE__); + + tree.uncommit_mapping(5, 10, rd_None_cs2); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrCCrrrrrrrrrrCCCCCrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr. + ExpectedTree<6> et2 = {{ 0, 3, 5, 15, 20, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , Rs , C , Rs , Rl }, + {-1 , si_1 , si_1 , si_1 , si_1 , si_1 , -1 }, + {-1 , -1 , si_2 , -1 , si_2 , -1 , -1 }}; + check_tree(tree, et2, __LINE__); + } + {// reserve after reserve, but only different call-stacks + SIndex si_4 = si[3]; + VMATree::RegionData call_stack_4(si_4, mtTest); + + Tree tree; + tree.reserve_mapping(0, 50, rd_Test_cs1); + tree.reserve_mapping(10, 10, call_stack_4); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<4> et1 = {{ 0, 10, 20, 50 }, + {mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rl }, + {-1 , si_1 , si_4 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 }}; + check_tree(tree, et1, __LINE__); + } + {// commit without reserve + Tree tree; + tree.commit_mapping(0, 50, rd_Test_cs1); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC + ExpectedTree<2> et = {{ 0, 50 }, + {mtNone, mtTest, mtNone}, + {Rl , C , Rl }, + {-1 , si_1 , -1 }, + {-1 , si_1 , -1 }}; + check_tree(tree, et, __LINE__); + } + {// reserve after commit + Tree tree; + tree.commit_mapping(0, 50, rd_None_cs2); + tree.reserve_mapping(0, 50, rd_Test_cs1); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr + ExpectedTree<2> et = {{ 0, 50 }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 }}; + check_tree(tree, et, __LINE__); + } +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows0To3) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + + // row 0: .........A..................B..... + // case of empty tree is already covered in other tests. + // row 1 is impossible. See the implementation. + { + // row 2: .........A...Y.......................W.....B.......... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr......................................... + Tree tree; + ExpectedTree<5> pre = {{ 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCCCCCCC.......................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10); + ExpectedTree<6> et = {{ 5, 10, 12, 14, 16, 25 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 3: .........A...Y.......................WB..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // ..........rrrrrrrrrr............................... + Tree tree; + ExpectedTree<5> pre = {{ 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCC............................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 5); + ExpectedTree<6> et = {{ 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows4to7) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + + { + // row 4: .....X...A..................B..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr......................................... + Tree tree; + ExpectedTree<2> pre = {{ 0, 10, }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(20, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr..........CCCCCCCCCCCCCCCCCCCC........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20); + ExpectedTree<4> et = {{ 0, 10, 20, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , C , Rl }, + {-1 , si_1 , -1 , si_2 , -1 }, + {-1 , -1 , -1 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 5: .....X...A...YW.............B..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....rrrrrrrrrr.................................... + Tree tree; + ExpectedTree<2> pre = {{ 5, 15, }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(10, 10, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....rrrrrCCCCCCCCCC............................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 10); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20 - 15); + ExpectedTree<4> et = {{ 5, 10, 15, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , C , Rl }, + {-1 , si_1 , si_1 , si_2 , -1 }, + {-1 , -1 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 6: .....X...A.....Y.......................W.....B... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr............................... + Tree tree; + ExpectedTree<7> pre = {{ 0, 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(7, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr..CCCCCCCCCCCCCCCCCCCC........................ + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10); + ExpectedTree<8> et = {{ 0, 5, 7, 10, 12, 14, 16, 27 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rl , C , C , C , C , C , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 7: .....X...A...Y.......................WB..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr............................... + Tree tree; + ExpectedTree<7> pre = {{ 0, 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(7, 13, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr..CCCCCCCCCCCCC............................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 13); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 3); + ExpectedTree<8> et = {{ 0, 5, 7, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rl , C , C , C , C , C , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows8to11) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + { + // row 8: ........XA..................B..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr......................................... + // nodes: 0--------50........................... + // si1 + // - + // request: 50*****************250 + // post: 0--------50*****************250 + // si1 si2 + // - si2 + Tree tree; + ExpectedTree<2> pre = {{ 0, 10, }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(10, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrCCCCCCCCCCCCCCCCCCCC..................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20); + ExpectedTree<3> et = {{ 0, 10, 30 }, + {mtNone, mtTest, mtTest, mtNone}, + {Rl , Rs , C , Rl }, + {-1 , si_1 , si_2 , -1 }, + {-1 , -1 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 9: ........XA....YW.............B..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr......................................... + Tree tree; + ExpectedTree<2> pre = {{ 0, 10, }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(0, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // CCCCCCCCCCCCCCCCCCCC............................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10); + ExpectedTree<3> et = {{ 0, 10, 20 }, + {mtNone, mtTest, mtTest, mtNone}, + {Rl , C , C , Rl }, + {-1 , si_1 , si_2 , -1 }, + {-1 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 10: ........XA...Y.......................W.....B... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....rrrrrrrrrrrrrrr............................... + Tree tree; + ExpectedTree<6> pre = {{ 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCCCCCCC.......................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 25 - 20); + ExpectedTree<6> et = {{ 5, 10, 12, 14, 16, 25 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 11: ........XA...Y.......................WB..... + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....rrrrrrrrrrrrrrr............................... + Tree tree; + ExpectedTree<6> pre = {{ 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rs , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCC............................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 0); + ExpectedTree<6> et = {{ 5, 10, 12, 14, 16, 20 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 } + }; + check_tree(tree, et, __LINE__); + } + +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows12to15) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + + { + // row 12: .........A..................B.....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // ..............................rrrrrrrrrr........... + Tree tree; + ExpectedTree<2> pre = {{ 30, 40 }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCCCCCCC.....rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20); + ExpectedTree<4> et = {{ 5, 25, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , C , Rl , Rs , Rl }, + {-1 , si_2 , -1 , si_1 , -1 }, + {-1 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 13: .........A...YW.............B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // ..........rrrrrrrrrrrrrrrrrrrr..................... + Tree tree; + ExpectedTree<2> pre = {{ 10, 30 }, + {mtNone, mtTest, mtNone}, + {Rl , Rs , Rl }, + {-1 , si_1 , -1 }, + {-1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCCCCCCCrrrrr..................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 30 - 25); + ExpectedTree<4> et = {{ 5, 10, 25, 30 }, + {mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , C , C , Rs , Rl }, + {-1 , si_2 , si_1 , si_1 , -1 }, + {-1 , si_2 , si_2 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 14: .........A...Y.......................W....B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // ..........rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<7> pre = {{ 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCCCCCCC.....rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, (10 - 5) + ( 25 - 20)); + ExpectedTree<8> et = {{ 5, 10, 12, 14, 16, 25, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 15: .........A...Y.......................WB....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // ..........rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<7> pre = {{ 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // .....CCCCCCCCCCCCCCC..........rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10 - 5); + ExpectedTree<8> et = {{ 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows16to19) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + { + // row 16: .....X...A..................B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr....................rrrrrrrrrr........... + Tree tree; + ExpectedTree<4> pre = {{ 0, 10, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(15, 10, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr.....CCCCCCCCCC.....rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 10); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10); + ExpectedTree<6> et = {{ 0, 10, 15, 25, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , C , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 17: .....X...A...YW.............B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr..........rrrrrrrrrr..................... + Tree tree; + ExpectedTree<4> pre = {{ 0, 10, 20, 30 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(15, 10, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr.....CCCCCCCCCCrrrrr..................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 10); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20 - 15); + ExpectedTree<6> et = {{ 0, 10, 15, 20, 25, 30 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , Rl , C , C , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_1 , -1 }, + {-1 , -1 , -1 , si_2 , si_2 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 18: ....X....A...Y.......................W....B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<9> pre = {{ 0, 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(7, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr..CCCCCCCCCCCCCCCCCCCC...rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, (10 - 7) + (27 - 20)); + ExpectedTree<10> et = {{ 0, 5, 7, 12, 14, 16, 20, 27, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 19: .....X...A...Y.......................WB....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<9> pre = {{ 0, 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(7, 13, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr..CCCCCCCCCCCCC..........rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 13); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10 - 7); + ExpectedTree<10> et = {{ 0, 5, 7, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + +} + +TEST_VM_F(NMTVMATreeTest, OverlapTableRows20to23) { + using SIndex = NativeCallStackStorage::StackIndex; + using State = VMATree::StateType; + SIndex si_1 = si[0]; + SIndex si_2 = si[1]; + SIndex si_3 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + VMATree::RegionData rd_Test_cs1(si_1, mtTest); + VMATree::RegionData rd_None_cs1(si_1, mtNone); + VMATree::RegionData rd_Test_cs2(si_2, mtTest); + VMATree::RegionData rd_None_cs2(si_2, mtNone); + VMATree::RegionData rd_None_cs3(si_3, mtNone); + + { + // row 20: ........XA..................B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr....................rrrrrrrrrr........... + Tree tree; + ExpectedTree<4> pre = {{ 0, 10, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(10, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrCCCCCCCCCCCCCCC.....rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 15); + ExpectedTree<5> et = {{ 0, 10, 25, 30, 40 }, + {mtNone, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , C , Rl , Rs , Rl }, + {-1 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 21: ........XA...YW.............B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrr..........rrrrrrrrrr..................... + Tree tree; + ExpectedTree<4> pre = {{ 0, 10, 20, 30 }, + {mtNone, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(10, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrrrrrrCCCCCCCCCCCCCCCrrrrr..................... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 20 - 10); + ExpectedTree<5> et = {{ 0, 10, 20, 25, 30 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtNone}, + {Rl , Rs , C , C , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_1 , -1 }, + {-1 , -1 , si_2 , si_2 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 22: ........XA...Y.......................W....B....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<9> pre = {{ 0, 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_2 , si_1 , si_2 , si_1 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 20, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrCCCCCCCCCCCCCCCCCCCC.....rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 20); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, (10 - 5) + (25 - 20)); + ExpectedTree<9> et = {{ 0, 5, 12, 14, 16, 20, 25, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + { + // row 23: ........XA...Y.......................WB....U + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrr.....rrrrrrrrrr..........rrrrrrrrrr........... + Tree tree; + ExpectedTree<9> pre = {{ 0, 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtNone, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , Rl , Rs , Rs , Rs , Rs , Rl , Rs , Rl }, + {-1 , si_1 , -1 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 } + }; + create_tree(tree, pre, __LINE__); + VMATree::SummaryDiff diff = tree.commit_mapping(5, 15, rd_Test_cs2, false); + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890 + // rrrrrCCCCCCCCCCCCCCC..........rrrrrrrrrr........... + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].commit, 15); + EXPECT_EQ(diff.tag[NMTUtil::tag_to_index(mtTest)].reserve, 10 - 5); + ExpectedTree<9> et = {{ 0, 5, 10, 12, 14, 16, 20, 30, 40 }, + {mtNone, mtTest, mtTest, mtTest, mtTest, mtTest, mtTest, mtNone, mtTest, mtNone}, + {Rl , Rs , C , C , C , C , C , Rl , Rs , Rl }, + {-1 , si_1 , si_2 , si_1 , si_2 , si_1 , si_2 , -1 , si_1 , -1 }, + {-1 , -1 , si_2 , si_2 , si_2 , si_2 , si_2 , -1 , -1 , -1 } + }; + check_tree(tree, et, __LINE__); + } + +} + +TEST_VM_F(NMTVMATreeTest, UpdateRegionTest) { + using State = VMATree::StateType; + using SIndex = VMATree::SIndex; + SIndex ES = NativeCallStackStorage::invalid; + SIndex s0 = si[0]; + SIndex s1 = si[1]; + SIndex s2 = si[2]; + + const State Rs = State::Reserved; + const State Rl = State::Released; + const State C = State::Committed; + const int a = 100; + const MemTag ReqTag = mtTest; + const VMATree::RequestInfo ReleaseRequest{0, a, Rl, mtNone, ES, false}; + const VMATree::RequestInfo ReserveRequest{0, a, Rs, ReqTag, s2, false}; + const VMATree::RequestInfo CommitRequest{0, a, C, ReqTag, s2, false}; + const VMATree::RequestInfo UncommitRequest{0, a, Rs, mtNone, ES, true}; + const VMATree::RequestInfo CopyTagCommitRequest{0, a, C, ReqTag, s2, true}; + // existing state request expected state expected diff + // st tag stacks st tag stacks reserve commit + // -- ------ ------ ---------------------- -- ------ ------ ------- ------- + UpdateCallInfo call_info[]={{{Rl, mtNone, ES, ES}, ReleaseRequest, {Rl, mtNone, ES, ES}, {0, 0}, {0, 0}}, + {{Rl, mtNone, ES, ES}, ReserveRequest, {Rs, ReqTag, s2, ES}, {0, a}, {0, 0}}, + {{Rl, mtNone, ES, ES}, CommitRequest, { C, ReqTag, s2, s2}, {0, a}, {0, a}}, + {{Rl, mtNone, ES, ES}, CopyTagCommitRequest, { C, mtNone, s2, s2}, {0, a}, {0, a}}, + {{Rl, mtNone, ES, ES}, UncommitRequest, {Rl, mtNone, ES, ES}, {0, 0}, {0, 0}}, + {{Rs, mtGC, s0, ES}, ReleaseRequest, {Rl, mtNone, ES, ES}, {-a, 0}, {0, 0}}, + {{Rs, mtGC, s0, ES}, ReserveRequest, {Rs, ReqTag, s2, ES}, {-a, a}, {0, 0}}, // diff tag + {{Rs, mtTest, s0, ES}, ReserveRequest, {Rs, ReqTag, s2, ES}, {0, 0}, {0, 0}}, // same tag + {{Rs, mtGC, s0, ES}, CommitRequest, { C, ReqTag, s0, s2}, {-a, a}, {0, a}}, + {{Rs, mtGC, s0, ES}, CopyTagCommitRequest, { C, mtGC, s0, s2}, {0, 0}, {0, a}}, + {{Rs, mtGC, s0, ES}, UncommitRequest, {Rs, mtGC, s0, ES}, {0, 0}, {0, 0}}, + {{ C, mtGC, s0, s1}, ReleaseRequest, {Rl, mtNone, ES, ES}, {-a, 0}, {-a, 0}}, + {{ C, mtGC, s0, s1}, ReserveRequest, {Rs, ReqTag, s2, ES}, {-a, a}, {-a, 0}}, // diff tag + {{ C, mtTest, s0, s1}, ReserveRequest, {Rs, ReqTag, s2, ES}, {0, 0}, {-a, 0}}, // same tag + {{ C, mtGC, s0, s1}, CommitRequest, { C, ReqTag, s0, s2}, {-a, a}, {-a, a}}, + {{ C, mtGC, s0, s1}, CopyTagCommitRequest, { C, mtGC, s0, s2}, {0, 0}, {-a, a}}, + {{ C, mtGC, s0, s1}, UncommitRequest, {Rs, mtGC, s0, ES}, {0, 0}, {-a, 0}} + }; + for (auto ci : call_info) { + call_update_region(ci); + } } \ No newline at end of file