From 81c502660b744eae1138788052198c3345838bcf Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Mon, 16 Nov 2015 10:14:46 -0800 Subject: [PATCH 01/12] 8029574: TreeMap: optimization of method computeRedLevel() Reviewed-by: martin, shade --- .../share/classes/java/util/TreeMap.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/jdk/src/java.base/share/classes/java/util/TreeMap.java b/jdk/src/java.base/share/classes/java/util/TreeMap.java index a5ed0f5e91c..248ef934369 100644 --- a/jdk/src/java.base/share/classes/java/util/TreeMap.java +++ b/jdk/src/java.base/share/classes/java/util/TreeMap.java @@ -2581,19 +2581,17 @@ public class TreeMap } /** - * Find the level down to which to assign all nodes BLACK. This is the - * last `full' level of the complete binary tree produced by - * buildTree. The remaining nodes are colored RED. (This makes a `nice' - * set of color assignments wrt future insertions.) This level number is + * Finds the level down to which to assign all nodes BLACK. This is the + * last `full' level of the complete binary tree produced by buildTree. + * The remaining nodes are colored RED. (This makes a `nice' set of + * color assignments wrt future insertions.) This level number is * computed by finding the number of splits needed to reach the zeroeth - * node. (The answer is ~lg(N), but in any case must be computed by same - * quick O(lg(N)) loop.) + * node. + * + * @param size the (non-negative) number of keys in the tree to be built */ - private static int computeRedLevel(int sz) { - int level = 0; - for (int m = sz - 1; m >= 0; m = m / 2 - 1) - level++; - return level; + private static int computeRedLevel(int size) { + return 31 - Integer.numberOfLeadingZeros(size + 1); } /** From cd715bbcb222bd0656368adbaa872637a7c11a57 Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Wed, 18 Nov 2015 08:43:52 +0800 Subject: [PATCH 02/12] 8139436: sun.security.mscapi.KeyStore might load incomplete data Reviewed-by: vinnie, weijun --- .../classes/sun/security/mscapi/KeyStore.java | 6 +- .../sun/security/mscapi/AccessKeyStore.java | 17 +-- .../sun/security/mscapi/AccessKeyStore.sh | 3 +- .../security/mscapi/IsSunMSCAPIAvailable.java | 13 +- .../security/mscapi/IsSunMSCAPIAvailable.sh | 3 +- .../mscapi/IterateWindowsRootStore.java | 130 ++++++++++++++++++ .../mscapi/KeyStoreCompatibilityMode.java | 13 +- .../mscapi/KeyStoreCompatibilityMode.sh | 4 +- .../sun/security/mscapi/KeytoolChangeAlias.sh | 3 +- jdk/test/sun/security/mscapi/PrngSlow.java | 29 ++-- .../sun/security/mscapi/PublicKeyInterop.java | 4 +- .../sun/security/mscapi/PublicKeyInterop.sh | 3 +- .../sun/security/mscapi/RSAEncryptDecrypt.sh | 3 +- .../sun/security/mscapi/ShortRSAKey1024.sh | 3 +- .../security/mscapi/ShortRSAKeyWithinTLS.java | 5 +- .../security/mscapi/SignUsingNONEwithRSA.java | 6 +- .../security/mscapi/SignUsingNONEwithRSA.sh | 3 +- .../security/mscapi/SignUsingSHA2withRSA.java | 6 +- .../security/mscapi/SignUsingSHA2withRSA.sh | 3 +- .../sun/security/mscapi/SignatureOffsets.java | 1 + .../security/mscapi/SignedObjectChain.java | 1 + .../security/mscapi/SmallPrimeExponentP.java | 3 +- 22 files changed, 178 insertions(+), 84 deletions(-) create mode 100644 jdk/test/sun/security/mscapi/IterateWindowsRootStore.java diff --git a/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java b/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java index 7c080d14a4a..00fdf8c2ba8 100644 --- a/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java +++ b/jdk/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/KeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -310,7 +310,7 @@ abstract class KeyStore extends KeyStoreSpi { if (alias.equals(entry.getAlias())) { X509Certificate[] certChain = entry.getCertificateChain(); - return certChain[0]; + return certChain.length == 0 ? null : certChain[0]; } } @@ -840,7 +840,7 @@ abstract class KeyStore extends KeyStoreSpi { // Obtain certificate factory if (certificateFactory == null) { - certificateFactory = CertificateFactory.getInstance("X.509"); + certificateFactory = CertificateFactory.getInstance("X.509", "SUN"); } // Generate certificate diff --git a/jdk/test/sun/security/mscapi/AccessKeyStore.java b/jdk/test/sun/security/mscapi/AccessKeyStore.java index 357984b8aef..81f0dbcb2ca 100644 --- a/jdk/test/sun/security/mscapi/AccessKeyStore.java +++ b/jdk/test/sun/security/mscapi/AccessKeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -36,17 +36,6 @@ public class AccessKeyStore { public static void main(String[] args) throws Exception { - // Check if the provider is available - try { - Class.forName("sun.security.mscapi.SunMSCAPI"); - - } catch (Exception e) { - System.out.println( - "The SunMSCAPI provider is not available on this platform: " + - e); - return; - } - // Check that a security manager has been installed if (System.getSecurityManager() == null) { throw new Exception("A security manager has not been installed"); @@ -86,8 +75,8 @@ public class AccessKeyStore { } int i = 0; - for (Enumeration e = keyStore.aliases(); e.hasMoreElements(); ) { - String alias = (String) e.nextElement(); + for (Enumeration e = keyStore.aliases(); e.hasMoreElements(); ) { + String alias = e.nextElement(); displayEntry(keyStore, alias, i++); } } diff --git a/jdk/test/sun/security/mscapi/AccessKeyStore.sh b/jdk/test/sun/security/mscapi/AccessKeyStore.sh index 0998de4821f..e8250027d43 100644 --- a/jdk/test/sun/security/mscapi/AccessKeyStore.sh +++ b/jdk/test/sun/security/mscapi/AccessKeyStore.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 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 @@ -26,6 +26,7 @@ # @test # @bug 6324295 6931562 +# @requires os.family == "windows" # @run shell AccessKeyStore.sh # @summary Confirm that permission must be granted to access keystores. diff --git a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java index d48f7855c36..ac3c2ffcf37 100644 --- a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java +++ b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -33,16 +33,6 @@ public class IsSunMSCAPIAvailable { public static void main(String[] args) throws Exception { - // Check if the provider is available - try { - Class.forName("sun.security.mscapi.SunMSCAPI"); - - } catch (Exception e) { - System.out.println( - "The SunMSCAPI provider is not available on this platform"); - return; - } - // Dynamically register the SunMSCAPI provider Security.addProvider(new sun.security.mscapi.SunMSCAPI()); @@ -58,7 +48,6 @@ public class IsSunMSCAPIAvailable { /* * Secure Random */ - SecureRandom random = SecureRandom.getInstance("Windows-PRNG", p); System.out.println(" Windows-PRNG is implemented by: " + random.getClass().getName()); diff --git a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh index 6e7ffa302b0..accf1bf4e43 100644 --- a/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh +++ b/jdk/test/sun/security/mscapi/IsSunMSCAPIAvailable.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 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 @@ -26,6 +26,7 @@ # @test # @bug 6318171 6931562 +# @requires os.family == "windows" # @run shell IsSunMSCAPIAvailable.sh # @summary Basic test of the Microsoft CryptoAPI provider. diff --git a/jdk/test/sun/security/mscapi/IterateWindowsRootStore.java b/jdk/test/sun/security/mscapi/IterateWindowsRootStore.java new file mode 100644 index 00000000000..aea35817371 --- /dev/null +++ b/jdk/test/sun/security/mscapi/IterateWindowsRootStore.java @@ -0,0 +1,130 @@ +/* + * 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. + * + * 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.InputStream; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.security.cert.CRL; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactorySpi; +import java.util.Collection; +import java.util.Enumeration; + +/* + * @test + * @bug 8139436 + * @summary This test validates an iteration over the Windows-ROOT certificate store + * and retrieving all certificates. + * Bug 8139436 reports an issue when 3rd party JCE providers would throw exceptions + * upon creating Certificate objects. + * This would for instance happen when using IAIK 3.15 and Elliptic Curve certificates + * are contained in the Windows-ROOT certificate store. + * The test uses a simple dummy provider which just throws Exceptions in its CertificateFactory. + * To test an external provider, you can use property sun.security.mscapi.testprovider and + * set it to the provider class name which has to be constructible by a constructor without + * arguments. The provider jar has to be added to the classpath. + * E.g. run jtreg with -javaoption:-Dsun.security.mscapi.testprovider=iaik.security.provider.IAIK and + * -cpa: + * + * @requires os.family == "windows" + * @author Christoph Langer + * @run main IterateWindowsRootStore + */ +public class IterateWindowsRootStore { + public static class TestFactory extends CertificateFactorySpi { + @Override + public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException { + throw new CertificateException("unimplemented"); + } + + @Override + public Collection engineGenerateCertificates(InputStream inStream) throws CertificateException { + throw new CertificateException("unimplemented"); + } + + @Override + public CRL engineGenerateCRL(InputStream inStream) throws CRLException { + throw new CRLException("unimplemented"); + } + + @Override + public Collection engineGenerateCRLs(InputStream inStream) throws CRLException { + throw new CRLException("unimplemented"); + } + } + + public static class TestProvider extends Provider { + private static final long serialVersionUID = 1L; + + public TestProvider() { + super("TestProvider", 0.1, "Test provider for IterateWindowsRootStore"); + + /* + * Certificates + */ + this.put("CertificateFactory.X.509", "IterateWindowsRootStore$TestFactory"); + this.put("Alg.Alias.CertificateFactory.X509", "X.509"); + } + } + + public static void main(String[] args) throws Exception { + // Try to register a JCE provider from property sun.security.mscapi.testprovider in the first slot + // otherwise register a dummy provider which would provoke the issue of bug 8139436 + boolean providerPrepended = false; + String testprovider = System.getProperty("sun.security.mscapi.testprovider"); + if (testprovider != null && !testprovider.isEmpty()) { + try { + System.out.println("Trying to prepend external JCE provider " + testprovider); + Class providerclass = Class.forName(testprovider); + Object provider = providerclass.newInstance(); + Security.insertProviderAt((Provider)provider, 1); + } catch (Exception e) { + System.out.println("Could not load JCE provider " + testprovider +". Exception is:"); + e.printStackTrace(System.out); + } + providerPrepended = true; + System.out.println("Sucessfully prepended JCE provider " + testprovider); + } + if (!providerPrepended) { + System.out.println("Trying to prepend dummy JCE provider"); + Security.insertProviderAt(new TestProvider(), 1); + System.out.println("Sucessfully prepended dummy JCE provider"); + } + + // load Windows-ROOT KeyStore + KeyStore keyStore = KeyStore.getInstance("Windows-ROOT", "SunMSCAPI"); + keyStore.load(null, null); + + // iterate KeyStore + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + System.out.print("Reading certificate for alias: " + alias + "..."); + keyStore.getCertificate(alias); + System.out.println(" done."); + } + } +} diff --git a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java index 04ce94c782d..b18abca674f 100644 --- a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java +++ b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -38,17 +38,6 @@ public class KeyStoreCompatibilityMode { public static void main(String[] args) throws Exception { - // Check if the provider is available - try { - Class.forName("sun.security.mscapi.SunMSCAPI"); - - } catch (Exception e) { - System.out.println( - "The SunMSCAPI provider is not available on this platform: " + - e); - return; - } - if (args.length > 0 && "-disable".equals(args[0])) { mode = false; } else { diff --git a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh index b6ca1395d76..80a9d565420 100644 --- a/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh +++ b/jdk/test/sun/security/mscapi/KeyStoreCompatibilityMode.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 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 @@ -23,9 +23,9 @@ # questions. # - # @test # @bug 6324294 6931562 +# @requires os.family == "windows" # @run shell KeyStoreCompatibilityMode.sh # @summary Confirm that a null stream or password is not permitted when # compatibility mode is enabled (and vice versa). diff --git a/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh b/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh index 03e296d3652..1996490109c 100644 --- a/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh +++ b/jdk/test/sun/security/mscapi/KeytoolChangeAlias.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 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 @@ -26,6 +26,7 @@ # @test # @bug 6415696 6931562 +# @requires os.family == "windows" # @run shell KeytoolChangeAlias.sh # @summary Test "keytool -changealias" using the Microsoft CryptoAPI provider. diff --git a/jdk/test/sun/security/mscapi/PrngSlow.java b/jdk/test/sun/security/mscapi/PrngSlow.java index 3420cc6b2a1..b8abd93a834 100644 --- a/jdk/test/sun/security/mscapi/PrngSlow.java +++ b/jdk/test/sun/security/mscapi/PrngSlow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -24,6 +24,7 @@ /** * @test * @bug 6449335 + * @requires os.family == "windows" * @summary MSCAPI's PRNG is too slow * @key randomness */ @@ -34,23 +35,15 @@ public class PrngSlow { public static void main(String[] args) throws Exception { double t = 0.0; - try { - SecureRandom sr = null; - sr = SecureRandom.getInstance("PRNG", "SunMSCAPI"); - long start = System.nanoTime(); - int x = 0; - for(int i = 0; i < 10000; i++) { - if (i % 100 == 0) System.err.print("."); - if (sr.nextBoolean()) x++; - }; - t = (System.nanoTime() - start) / 1000000000.0; - System.err.println("\nSpend " + t + " seconds"); - } catch (Exception e) { - // Not supported here, maybe not a Win32 - System.err.println("Cannot find PRNG for SunMSCAPI or other mysterious bugs"); - e.printStackTrace(); - return; - } + SecureRandom sr = null; + sr = SecureRandom.getInstance("Windows-PRNG", "SunMSCAPI"); + long start = System.nanoTime(); + for (int i = 0; i < 10000; i++) { + if (i % 100 == 0) System.err.print("."); + sr.nextBoolean(); + }; + t = (System.nanoTime() - start) / 1000000000.0; + System.err.println("\nSpend " + t + " seconds"); if (t > 5) throw new RuntimeException("Still too slow"); } diff --git a/jdk/test/sun/security/mscapi/PublicKeyInterop.java b/jdk/test/sun/security/mscapi/PublicKeyInterop.java index 53d5b094681..a7570cdd818 100644 --- a/jdk/test/sun/security/mscapi/PublicKeyInterop.java +++ b/jdk/test/sun/security/mscapi/PublicKeyInterop.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -38,8 +38,6 @@ import sun.misc.HexDumpEncoder; public class PublicKeyInterop { public static void main(String[] arg) throws Exception { - PrivateKey privKey = null; - Certificate cert = null; KeyStore ks = KeyStore.getInstance("Windows-MY"); ks.load(null, null); System.out.println("Loaded keystore: Windows-MY"); diff --git a/jdk/test/sun/security/mscapi/PublicKeyInterop.sh b/jdk/test/sun/security/mscapi/PublicKeyInterop.sh index 73f0e6e45fe..f5b6c913142 100644 --- a/jdk/test/sun/security/mscapi/PublicKeyInterop.sh +++ b/jdk/test/sun/security/mscapi/PublicKeyInterop.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 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 @@ -25,6 +25,7 @@ # @test # @bug 6888925 +# @requires os.family == "windows" # @run shell PublicKeyInterop.sh # @summary SunMSCAPI's Cipher can't use RSA public keys obtained from other # sources. diff --git a/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh b/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh index ed17bd1159e..9c5efb656b8 100644 --- a/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh +++ b/jdk/test/sun/security/mscapi/RSAEncryptDecrypt.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 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 @@ -25,6 +25,7 @@ # @test # @bug 6457422 6931562 +# @requires os.family == "windows" # @run shell RSAEncryptDecrypt.sh # @summary Confirm that plaintext can be encrypted and then decrypted using the # RSA cipher in the SunMSCAPI crypto provider. NOTE: The RSA cipher is diff --git a/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh b/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh index f7094f210af..41ae34f9e8d 100644 --- a/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh +++ b/jdk/test/sun/security/mscapi/ShortRSAKey1024.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 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 @@ -27,6 +27,7 @@ # @test # @bug 7106773 # @summary 512 bits RSA key cannot work with SHA384 and SHA512 +# @requires os.family == "windows" # @run shell ShortRSAKey1024.sh 1024 # @run shell ShortRSAKey1024.sh 768 # @run shell ShortRSAKey1024.sh 512 diff --git a/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java b/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java index 8958718ae7a..f2a752599eb 100644 --- a/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java +++ b/jdk/test/sun/security/mscapi/ShortRSAKeyWithinTLS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -22,12 +22,9 @@ */ import java.io.*; -import java.net.*; -import java.util.*; import java.security.*; import javax.net.*; import javax.net.ssl.*; -import java.lang.reflect.*; import sun.security.util.KeyUtil; diff --git a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java index 02cf4f67c80..ec8c0c5f9e1 100644 --- a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java +++ b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -118,12 +118,12 @@ public class SignUsingNONEwithRSA { ks.load(null, null); System.out.println("Loaded keystore: Windows-MY"); - Enumeration e = ks.aliases(); + Enumeration e = ks.aliases(); PrivateKey privateKey = null; PublicKey publicKey = null; while (e.hasMoreElements()) { - String alias = (String) e.nextElement(); + String alias = e.nextElement(); if (alias.equals("6578658")) { System.out.println("Loaded entry: " + alias); privateKey = (PrivateKey) ks.getKey(alias, null); diff --git a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh index e8c93a75781..cbd7629f73d 100644 --- a/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh +++ b/jdk/test/sun/security/mscapi/SignUsingNONEwithRSA.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 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 @@ -26,6 +26,7 @@ # @test # @bug 6578658 +# @requires os.family == "windows" # @run shell SignUsingNONEwithRSA.sh # @summary Sign using the NONEwithRSA signature algorithm from SunMSCAPI # @key intermittent diff --git a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java index 6835bd3f959..90973ecce4d 100644 --- a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java +++ b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -58,12 +58,12 @@ public class SignUsingSHA2withRSA { ks.load(null, null); System.out.println("Loaded keystore: Windows-MY"); - Enumeration e = ks.aliases(); + Enumeration e = ks.aliases(); PrivateKey privateKey = null; PublicKey publicKey = null; while (e.hasMoreElements()) { - String alias = (String) e.nextElement(); + String alias = e.nextElement(); if (alias.equals("6753664")) { System.out.println("Loaded entry: " + alias); privateKey = (PrivateKey) ks.getKey(alias, null); diff --git a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh index d70a4e1c973..6c1685ff153 100644 --- a/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh +++ b/jdk/test/sun/security/mscapi/SignUsingSHA2withRSA.sh @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 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 @@ -26,6 +26,7 @@ # @test # @bug 6753664 +# @requires os.family == "windows" # @run shell SignUsingSHA2withRSA.sh # @summary Support SHA256 (and higher) in SunMSCAPI # @key intermittent diff --git a/jdk/test/sun/security/mscapi/SignatureOffsets.java b/jdk/test/sun/security/mscapi/SignatureOffsets.java index f9f8e2c6133..4c710396840 100644 --- a/jdk/test/sun/security/mscapi/SignatureOffsets.java +++ b/jdk/test/sun/security/mscapi/SignatureOffsets.java @@ -36,6 +36,7 @@ import java.security.SignatureException; * and passing in different signature offset (0, 33, 66, 99). * @library /lib/testlibrary * @compile ../../../java/security/Signature/Offsets.java + * @requires os.family == "windows" * @run main SignatureOffsets SunMSCAPI NONEwithRSA * @run main SignatureOffsets SunMSCAPI MD2withRSA * @run main SignatureOffsets SunMSCAPI MD5withRSA diff --git a/jdk/test/sun/security/mscapi/SignedObjectChain.java b/jdk/test/sun/security/mscapi/SignedObjectChain.java index 9790daa919b..d436612798f 100644 --- a/jdk/test/sun/security/mscapi/SignedObjectChain.java +++ b/jdk/test/sun/security/mscapi/SignedObjectChain.java @@ -25,6 +25,7 @@ * @test * @bug 8050374 * @compile ../../../java/security/SignedObject/Chain.java + * @requires os.family == "windows" * @summary Verify a chain of signed objects */ public class SignedObjectChain { diff --git a/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java b/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java index 00c3bec673a..1ee0f5bb069 100644 --- a/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java +++ b/jdk/test/sun/security/mscapi/SmallPrimeExponentP.java @@ -28,8 +28,6 @@ import java.security.KeyStore; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateCrtKey; -import java.util.HashSet; -import java.util.Set; /* * @test @@ -37,6 +35,7 @@ import java.util.Set; * @modules java.base/sun.security.x509 * java.base/sun.security.tools.keytool * @summary sun/security/mscapi/ShortRSAKey1024.sh fails intermittently + * @requires os.family == "windows" */ public class SmallPrimeExponentP { From e9b75962b352e24fd6361fcbb2648be726fabaf0 Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Wed, 18 Nov 2015 17:39:40 +0100 Subject: [PATCH 03/12] 8143232: Fix java.lang.invoke bootstrap when specifying COMPILE_THRESHOLD Reviewed-by: vlivanov --- .../classes/java/lang/invoke/LambdaForm.java | 8 +++- .../invoke/CompileThresholdBootstrapTest.java | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java index 1ab16ac5c94..2023bfabfde 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java @@ -1781,23 +1781,27 @@ class LambdaForm { NamedFunction idFun; LambdaForm zeForm; NamedFunction zeFun; + + // Create the LFs and NamedFunctions. Precompiling LFs to byte code is needed to break circular + // bootstrap dependency on this method in case we're interpreting LFs if (isVoid) { Name[] idNames = new Name[] { argument(0, L_TYPE) }; idForm = new LambdaForm(idMem.getName(), 1, idNames, VOID_RESULT); + idForm.compileToBytecode(); idFun = new NamedFunction(idMem, SimpleMethodHandle.make(idMem.getInvocationType(), idForm)); - assert(zeMem == null); zeForm = idForm; zeFun = idFun; } else { Name[] idNames = new Name[] { argument(0, L_TYPE), argument(1, type) }; idForm = new LambdaForm(idMem.getName(), 2, idNames, 1); + idForm.compileToBytecode(); idFun = new NamedFunction(idMem, SimpleMethodHandle.make(idMem.getInvocationType(), idForm)); - assert(zeMem != null); Object zeValue = Wrapper.forBasicType(btChar).zero(); Name[] zeNames = new Name[] { argument(0, L_TYPE), new Name(idFun, zeValue) }; zeForm = new LambdaForm(zeMem.getName(), 1, zeNames, 1); + zeForm.compileToBytecode(); zeFun = new NamedFunction(zeMem, SimpleMethodHandle.make(zeMem.getInvocationType(), zeForm)); } diff --git a/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java b/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java new file mode 100644 index 00000000000..8acbe9cde59 --- /dev/null +++ b/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java @@ -0,0 +1,48 @@ +/* + * 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. + * + * 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 8143232 + * @summary Test verifies that LF bootstraps properly when run with COMPILE_THRESHOLD set + * @compile CompileThresholdBootstrapTest.java + * @run testng/othervm -Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=30 test.java.lang.invoke.CompileThresholdBootstrapTest + */ +package test.java.lang.invoke; + +import java.lang.invoke.MethodHandles; +import org.testng.*; +import org.testng.annotations.*; + +public final class CompileThresholdBootstrapTest { + + @Test + public void testBootstrap() throws Throwable { + Assert.assertEquals(0, (int)MethodHandles.constant(int.class, (int)0).invokeExact()); + } + + public static void main(String ... args) { + CompileThresholdBootstrapTest test = CompileThresholdBootstrapTest(); + test.testBootstrap(); + } +} From 756306661310b3f6e64315ada8eecaca1f66d23f Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Wed, 18 Nov 2015 20:56:00 +0100 Subject: [PATCH 04/12] 8143253: java/lang/invoke/CompileThresholdBootstrapTest.java failing on mach5 Reviewed-by: lancea --- .../java/lang/invoke/CompileThresholdBootstrapTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java b/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java index 8acbe9cde59..f7e0e0004e8 100644 --- a/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java +++ b/jdk/test/java/lang/invoke/CompileThresholdBootstrapTest.java @@ -42,7 +42,11 @@ public final class CompileThresholdBootstrapTest { } public static void main(String ... args) { - CompileThresholdBootstrapTest test = CompileThresholdBootstrapTest(); - test.testBootstrap(); + try { + CompileThresholdBootstrapTest test = new CompileThresholdBootstrapTest(); + test.testBootstrap(); + } catch (Throwable t) { + t.printStackTrace(); + } } } From 969487d3806473ce7d4be31ef300b376eeb9e3e3 Mon Sep 17 00:00:00 2001 From: Xueming Shen Date: Thu, 19 Nov 2015 12:57:59 -0800 Subject: [PATCH 05/12] 8143330: Two implementation methods of AbstractStringBuilder are mistakenly declared as "protected" in JDK9b93 Reviewed-by: darcy, alanb --- .../share/classes/java/lang/AbstractStringBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index 65f3c497b18..3cd12eeac15 100644 --- a/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/jdk/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1584,7 +1584,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence { * @param dstBegin the char index, not offset of byte[] * @param coder the coder of dst[] */ - protected void getBytes(byte dst[], int dstBegin, byte coder) { + void getBytes(byte dst[], int dstBegin, byte coder) { if (this.coder == coder) { System.arraycopy(value, 0, dst, dstBegin << coder, count << coder); } else { // this.coder == LATIN && coder == UTF16 @@ -1593,7 +1593,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence { } /* for readObject() */ - protected void initBytes(char[] value, int off, int len) { + void initBytes(char[] value, int off, int len) { if (String.COMPACT_STRINGS) { this.value = StringUTF16.compress(value, off, len); if (this.value != null) { From a7d92d59f9f19a2aefdd063c3c4b654ec8e87c61 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Fri, 20 Nov 2015 08:34:04 +0800 Subject: [PATCH 06/12] 8056174: New APIs for jar signing Reviewed-by: mullan --- .../sun/security/x509/AlgorithmId.java | 65 + .../jdk/security/jarsigner/JarSigner.java | 1286 +++++++++++++++++ .../jarsigner/JarSignerException.java | 56 + .../sun/security/tools/jarsigner/Main.java | 1016 ++----------- jdk/test/jdk/security/jarsigner/Function.java | 182 +++ jdk/test/jdk/security/jarsigner/Spec.java | 251 ++++ .../sun/security/tools/jarsigner/Options.java | 137 ++ 7 files changed, 2065 insertions(+), 928 deletions(-) create mode 100644 jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java create mode 100644 jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java create mode 100644 jdk/test/jdk/security/jarsigner/Function.java create mode 100644 jdk/test/jdk/security/jarsigner/Spec.java create mode 100644 jdk/test/sun/security/tools/jarsigner/Options.java diff --git a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java index 530dc167c00..f84371da80e 100644 --- a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java +++ b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java @@ -977,4 +977,69 @@ public class AlgorithmId implements Serializable, DerEncoder { } return null; } + + /** + * Checks if a signature algorithm matches a key algorithm, i.e. a + * signature can be initialized with a key. + * + * @param kAlg must not be null + * @param sAlg must not be null + * @throws IllegalArgumentException if they do not match + */ + public static void checkKeyAndSigAlgMatch(String kAlg, String sAlg) { + String sAlgUp = sAlg.toUpperCase(Locale.US); + if ((sAlgUp.endsWith("WITHRSA") && !kAlg.equalsIgnoreCase("RSA")) || + (sAlgUp.endsWith("WITHECDSA") && !kAlg.equalsIgnoreCase("EC")) || + (sAlgUp.endsWith("WITHDSA") && !kAlg.equalsIgnoreCase("DSA"))) { + throw new IllegalArgumentException( + "key algorithm not compatible with signature algorithm"); + } + } + + /** + * Returns the default signature algorithm for a private key. The digest + * part might evolve with time. Remember to update the spec of + * {@link jdk.security.jarsigner.JarSigner.Builder#getDefaultSignatureAlgorithm(PrivateKey)} + * if updated. + * + * @param k cannot be null + * @return the default alg, might be null if unsupported + */ + public static String getDefaultSigAlgForKey(PrivateKey k) { + switch (k.getAlgorithm().toUpperCase()) { + case "EC": + return ecStrength(KeyUtil.getKeySize(k)) + + "withECDSA"; + case "DSA": + return ifcFfcStrength(KeyUtil.getKeySize(k)) + + "withDSA"; + case "RSA": + return ifcFfcStrength(KeyUtil.getKeySize(k)) + + "withRSA"; + default: + return null; + } + } + + // Values from SP800-57 part 1 rev 3 tables 2 and three + private static String ecStrength (int bitLength) { + if (bitLength >= 512) { // 256 bits of strength + return "SHA512"; + } else if (bitLength >= 384) { // 192 bits of strength + return "SHA384"; + } else { // 128 bits of strength and less + return "SHA256"; + } + } + + // same values for RSA and DSA + private static String ifcFfcStrength (int bitLength) { + if (bitLength > 7680) { // 256 bits + return "SHA512"; + } else if (bitLength > 3072) { // 192 bits + return "SHA384"; + } else { // 128 bits and less + return "SHA256"; + } + } } diff --git a/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java new file mode 100644 index 00000000000..b9ce4e30225 --- /dev/null +++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java @@ -0,0 +1,1286 @@ +/* + * 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 jdk.security.jarsigner; + +import com.sun.jarsigner.ContentSigner; +import com.sun.jarsigner.ContentSignerParameters; +import sun.security.tools.PathList; +import sun.security.tools.jarsigner.TimestampedSigner; +import sun.security.util.ManifestDigester; +import sun.security.util.SignatureFileVerifier; +import sun.security.x509.AlgorithmId; + +import java.io.*; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.*; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * An immutable utility class to sign a jar file. + *

+ * A caller creates a {@code JarSigner.Builder} object, (optionally) sets + * some parameters, and calls {@link JarSigner.Builder#build build} to create + * a {@code JarSigner} object. This {@code JarSigner} object can then + * be used to sign a jar file. + *

+ * Unless otherwise stated, calling a method of {@code JarSigner} or + * {@code JarSigner.Builder} with a null argument will throw + * a {@link NullPointerException}. + *

+ * Example: + *

+ * JarSigner signer = new JarSigner.Builder(key, certPath)
+ *         .digestAlgorithm("SHA-1")
+ *         .signatureAlgorithm("SHA1withDSA")
+ *         .build();
+ * try (ZipFile in = new ZipFile(inputFile);
+ *         FileOutputStream out = new FileOutputStream(outputFile)) {
+ *     signer.sign(in, out);
+ * }
+ * 
+ * + * @since 1.9 + */ +@jdk.Exported +public final class JarSigner { + + /** + * A mutable builder class that can create an immutable {@code JarSigner} + * from various signing-related parameters. + * + * @since 1.9 + */ + @jdk.Exported + public static class Builder { + + // Signer materials: + final PrivateKey privateKey; + final X509Certificate[] certChain; + + // JarSigner options: + // Support multiple digestalg internally. Can be null, but not empty + String[] digestalg; + String sigalg; + // Precisely should be one provider for each digestalg, maybe later + Provider digestProvider; + Provider sigProvider; + URI tsaUrl; + String signerName; + BiConsumer handler; + + // Implementation-specific properties: + String tSAPolicyID; + String tSADigestAlg; + boolean signManifest = true; + boolean externalSF = true; + String altSignerPath; + String altSigner; + + /** + * Creates a {@code JarSigner.Builder} object with + * a {@link KeyStore.PrivateKeyEntry} object. + * + * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer. + */ + public Builder(KeyStore.PrivateKeyEntry entry) { + this.privateKey = entry.getPrivateKey(); + try { + // called internally, no need to clone + Certificate[] certs = entry.getCertificateChain(); + this.certChain = Arrays.copyOf(certs, certs.length, + X509Certificate[].class); + } catch (ArrayStoreException ase) { + // Wrong type, not X509Certificate. Won't document. + throw new IllegalArgumentException( + "Entry does not contain X509Certificate"); + } + } + + /** + * Creates a {@code JarSigner.Builder} object with a private key and + * a certification path. + * + * @param privateKey the private key of the signer. + * @param certPath the certification path of the signer. + * @throws IllegalArgumentException if {@code certPath} is empty, or + * the {@code privateKey} algorithm does not match the algorithm + * of the {@code PublicKey} in the end entity certificate + * (the first certificate in {@code certPath}). + */ + public Builder(PrivateKey privateKey, CertPath certPath) { + List certs = certPath.getCertificates(); + if (certs.isEmpty()) { + throw new IllegalArgumentException("certPath cannot be empty"); + } + if (!privateKey.getAlgorithm().equals + (certs.get(0).getPublicKey().getAlgorithm())) { + throw new IllegalArgumentException + ("private key algorithm does not match " + + "algorithm of public key in end entity " + + "certificate (the 1st in certPath)"); + } + this.privateKey = privateKey; + try { + this.certChain = certs.toArray(new X509Certificate[certs.size()]); + } catch (ArrayStoreException ase) { + // Wrong type, not X509Certificate. + throw new IllegalArgumentException( + "Entry does not contain X509Certificate"); + } + } + + /** + * Sets the digest algorithm. If no digest algorithm is specified, + * the default algorithm returned by {@link #getDefaultDigestAlgorithm} + * will be used. + * + * @param algorithm the standard name of the algorithm. See + * the {@code MessageDigest} section in the + * Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm names. + * @return the {@code JarSigner.Builder} itself. + * @throws NoSuchAlgorithmException if {@code algorithm} is not available. + */ + public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException { + MessageDigest.getInstance(Objects.requireNonNull(algorithm)); + this.digestalg = new String[]{algorithm}; + this.digestProvider = null; + return this; + } + + /** + * Sets the digest algorithm from the specified provider. + * If no digest algorithm is specified, the default algorithm + * returned by {@link #getDefaultDigestAlgorithm} will be used. + * + * @param algorithm the standard name of the algorithm. See + * the {@code MessageDigest} section in the + * Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm names. + * @param provider the provider. + * @return the {@code JarSigner.Builder} itself. + * @throws NoSuchAlgorithmException if {@code algorithm} is not + * available in the specified provider. + */ + public Builder digestAlgorithm(String algorithm, Provider provider) + throws NoSuchAlgorithmException { + MessageDigest.getInstance( + Objects.requireNonNull(algorithm), + Objects.requireNonNull(provider)); + this.digestalg = new String[]{algorithm}; + this.digestProvider = provider; + return this; + } + + /** + * Sets the signature algorithm. If no signature algorithm + * is specified, the default signature algorithm returned by + * {@link #getDefaultSignatureAlgorithm} for the private key + * will be used. + * + * @param algorithm the standard name of the algorithm. See + * the {@code Signature} section in the + * Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm names. + * @return the {@code JarSigner.Builder} itself. + * @throws NoSuchAlgorithmException if {@code algorithm} is not available. + * @throws IllegalArgumentException if {@code algorithm} is not + * compatible with the algorithm of the signer's private key. + */ + public Builder signatureAlgorithm(String algorithm) + throws NoSuchAlgorithmException { + // Check availability + Signature.getInstance(Objects.requireNonNull(algorithm)); + AlgorithmId.checkKeyAndSigAlgMatch( + privateKey.getAlgorithm(), algorithm); + this.sigalg = algorithm; + this.sigProvider = null; + return this; + } + + /** + * Sets the signature algorithm from the specified provider. If no + * signature algorithm is specified, the default signature algorithm + * returned by {@link #getDefaultSignatureAlgorithm} for the private + * key will be used. + * + * @param algorithm the standard name of the algorithm. See + * the {@code Signature} section in the + * Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm names. + * @param provider the provider. + * @return the {@code JarSigner.Builder} itself. + * @throws NoSuchAlgorithmException if {@code algorithm} is not + * available in the specified provider. + * @throws IllegalArgumentException if {@code algorithm} is not + * compatible with the algorithm of the signer's private key. + */ + public Builder signatureAlgorithm(String algorithm, Provider provider) + throws NoSuchAlgorithmException { + // Check availability + Signature.getInstance( + Objects.requireNonNull(algorithm), + Objects.requireNonNull(provider)); + AlgorithmId.checkKeyAndSigAlgMatch( + privateKey.getAlgorithm(), algorithm); + this.sigalg = algorithm; + this.sigProvider = provider; + return this; + } + + /** + * Sets the URI of the Time Stamping Authority (TSA). + * + * @param uri the URI. + * @return the {@code JarSigner.Builder} itself. + */ + public Builder tsa(URI uri) { + this.tsaUrl = Objects.requireNonNull(uri); + return this; + } + + /** + * Sets the signer name. The name will be used as the base name for + * the signature files. All lowercase characters will be converted to + * uppercase for signature file names. If a signer name is not + * specified, the string "SIGNER" will be used. + * + * @param name the signer name. + * @return the {@code JarSigner.Builder} itself. + * @throws IllegalArgumentException if {@code name} is empty or has + * a size bigger than 8, or it contains characters not from the + * set "a-zA-Z0-9_-". + */ + public Builder signerName(String name) { + if (name.isEmpty() || name.length() > 8) { + throw new IllegalArgumentException("Name too long"); + } + + name = name.toUpperCase(Locale.ENGLISH); + + for (int j = 0; j < name.length(); j++) { + char c = name.charAt(j); + if (! + ((c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + (c == '-') || + (c == '_'))) { + throw new IllegalArgumentException( + "Invalid characters in name"); + } + } + this.signerName = name; + return this; + } + + /** + * Sets en event handler that will be triggered when a {@link JarEntry} + * is to be added, signed, or updated during the signing process. + *

+ * The handler can be used to display signing progress. The first + * argument of the handler can be "adding", "signing", or "updating", + * and the second argument is the name of the {@link JarEntry} + * being processed. + * + * @param handler the event handler. + * @return the {@code JarSigner.Builder} itself. + */ + public Builder eventHandler(BiConsumer handler) { + this.handler = Objects.requireNonNull(handler); + return this; + } + + /** + * Sets an additional implementation-specific property indicated by + * the specified key. + * + * @implNote This implementation supports the following properties: + *

    + *
  • "tsaDigestAlg": algorithm of digest data in the timestamping + * request. The default value is the same as the result of + * {@link #getDefaultDigestAlgorithm}. + *
  • "tsaPolicyId": TSAPolicyID for Timestamping Authority. + * No default value. + *
  • "internalsf": "true" if the .SF file is included inside the + * signature block, "false" otherwise. Default "false". + *
  • "sectionsonly": "true" if the .SF file only contains the hash + * value for each section of the manifest and not for the whole + * manifest, "false" otherwise. Default "false". + *
+ * All property names are case-insensitive. + * + * @param key the name of the property. + * @param value the value of the property. + * @return the {@code JarSigner.Builder} itself. + * @throws UnsupportedOperationException if the key is not supported + * by this implementation. + * @throws IllegalArgumentException if the value is not accepted as + * a legal value for this key. + */ + public Builder setProperty(String key, String value) { + Objects.requireNonNull(key); + Objects.requireNonNull(value); + switch (key.toLowerCase(Locale.US)) { + case "tsadigestalg": + try { + MessageDigest.getInstance(value); + } catch (NoSuchAlgorithmException nsae) { + throw new IllegalArgumentException( + "Invalid tsadigestalg", nsae); + } + this.tSADigestAlg = value; + break; + case "tsapolicyid": + this.tSAPolicyID = value; + break; + case "internalsf": + switch (value) { + case "true": + externalSF = false; + break; + case "false": + externalSF = true; + break; + default: + throw new IllegalArgumentException( + "Invalid internalsf value"); + } + break; + case "sectionsonly": + switch (value) { + case "true": + signManifest = false; + break; + case "false": + signManifest = true; + break; + default: + throw new IllegalArgumentException( + "Invalid signManifest value"); + } + break; + case "altsignerpath": + altSignerPath = value; + break; + case "altsigner": + altSigner = value; + break; + default: + throw new UnsupportedOperationException( + "Unsupported key " + key); + } + return this; + } + + /** + * Gets the default digest algorithm. + * + * @implNote This implementation returns "SHA-256". The value may + * change in the future. + * + * @return the default digest algorithm. + */ + public static String getDefaultDigestAlgorithm() { + return "SHA-256"; + } + + /** + * Gets the default signature algorithm for a private key. + * For example, SHA256withRSA for a 2048-bit RSA key, and + * SHA384withECDSA for a 384-bit EC key. + * + * @implNote This implementation makes use of comparable strengths + * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.3. + * Specifically, if a DSA or RSA key with a key size greater than 7680 + * bits, or an EC key with a key size greater than or equal to 512 bits, + * SHA-512 will be used as the hash function for the signature. + * If a DSA or RSA key has a key size greater than 3072 bits, or an + * EC key has a key size greater than or equal to 384 bits, SHA-384 will + * be used. Otherwise, SHA-256 will be used. The value may + * change in the future. + * + * @param key the private key. + * @return the default signature algorithm. Returns null if a default + * signature algorithm cannot be found. In this case, + * {@link #signatureAlgorithm} must be called to specify a + * signature algorithm. Otherwise, the {@link #build} method + * will throw an {@link IllegalArgumentException}. + */ + public static String getDefaultSignatureAlgorithm(PrivateKey key) { + return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key)); + } + + /** + * Builds a {@code JarSigner} object from the parameters set by the + * setter methods. + *

+ * This method does not modify internal state of this {@code Builder} + * object and can be called multiple times to generate multiple + * {@code JarSigner} objects. After this method is called, calling + * any method on this {@code Builder} will have no effect on + * the newly built {@code JarSigner} object. + * + * @return the {@code JarSigner} object. + * @throws IllegalArgumentException if a signature algorithm is not + * set and cannot be derived from the private key using the + * {@link #getDefaultSignatureAlgorithm} method. + */ + public JarSigner build() { + return new JarSigner(this); + } + } + + private static final String META_INF = "META-INF/"; + + // All fields in Builder are duplicated here as final. Those not + // provided but has a default value will be filled with default value. + + // Precisely, a final array field can still be modified if only + // reference is copied, no clone is done because we are concerned about + // casual change instead of malicious attack. + + // Signer materials: + private final PrivateKey privateKey; + private final X509Certificate[] certChain; + + // JarSigner options: + private final String[] digestalg; + private final String sigalg; + private final Provider digestProvider; + private final Provider sigProvider; + private final URI tsaUrl; + private final String signerName; + private final BiConsumer handler; + + // Implementation-specific properties: + private final String tSAPolicyID; + private final String tSADigestAlg; + private final boolean signManifest; // "sign" the whole manifest + private final boolean externalSF; // leave the .SF out of the PKCS7 block + private final String altSignerPath; + private final String altSigner; + + private JarSigner(JarSigner.Builder builder) { + + this.privateKey = builder.privateKey; + this.certChain = builder.certChain; + if (builder.digestalg != null) { + // No need to clone because builder only accepts one alg now + this.digestalg = builder.digestalg; + } else { + this.digestalg = new String[] { + Builder.getDefaultDigestAlgorithm() }; + } + this.digestProvider = builder.digestProvider; + if (builder.sigalg != null) { + this.sigalg = builder.sigalg; + } else { + this.sigalg = JarSigner.Builder + .getDefaultSignatureAlgorithm(privateKey); + if (this.sigalg == null) { + throw new IllegalArgumentException( + "No signature alg for " + privateKey.getAlgorithm()); + } + } + this.sigProvider = builder.sigProvider; + this.tsaUrl = builder.tsaUrl; + + if (builder.signerName == null) { + this.signerName = "SIGNER"; + } else { + this.signerName = builder.signerName; + } + this.handler = builder.handler; + + if (builder.tSADigestAlg != null) { + this.tSADigestAlg = builder.tSADigestAlg; + } else { + this.tSADigestAlg = Builder.getDefaultDigestAlgorithm(); + } + this.tSAPolicyID = builder.tSAPolicyID; + this.signManifest = builder.signManifest; + this.externalSF = builder.externalSF; + this.altSigner = builder.altSigner; + this.altSignerPath = builder.altSignerPath; + } + + /** + * Signs a file into an {@link OutputStream}. This method will not close + * {@code file} or {@code os}. + * + * @param file the file to sign. + * @param os the output stream. + * @throws JarSignerException if the signing fails. + */ + public void sign(ZipFile file, OutputStream os) { + try { + sign0(Objects.requireNonNull(file), + Objects.requireNonNull(os)); + } catch (SocketTimeoutException | CertificateException e) { + // CertificateException is thrown when the received cert from TSA + // has no id-kp-timeStamping in its Extended Key Usages extension. + throw new JarSignerException("Error applying timestamp", e); + } catch (IOException ioe) { + throw new JarSignerException("I/O error", ioe); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new JarSignerException("Error in signer materials", e); + } catch (SignatureException se) { + throw new JarSignerException("Error creating signature", se); + } + } + + /** + * Returns the digest algorithm for this {@code JarSigner}. + *

+ * The return value is never null. + * + * @return the digest algorithm. + */ + public String getDigestAlgorithm() { + return digestalg[0]; + } + + /** + * Returns the signature algorithm for this {@code JarSigner}. + *

+ * The return value is never null. + * + * @return the signature algorithm. + */ + public String getSignatureAlgorithm() { + return sigalg; + } + + /** + * Returns the URI of the Time Stamping Authority (TSA). + * + * @return the URI of the TSA. + */ + public URI getTsa() { + return tsaUrl; + } + + /** + * Returns the signer name of this {@code JarSigner}. + *

+ * The return value is never null. + * + * @return the signer name. + */ + public String getSignerName() { + return signerName; + } + + /** + * Returns the value of an additional implementation-specific property + * indicated by the specified key. If a property is not set but has a + * default value, the default value will be returned. + * + * @implNote See {@link JarSigner.Builder#setProperty} for a list of + * properties this implementation supports. All property names are + * case-insensitive. + * + * @param key the name of the property. + * @return the value for the property. + * @throws UnsupportedOperationException if the key is not supported + * by this implementation. + */ + public String getProperty(String key) { + Objects.requireNonNull(key); + switch (key.toLowerCase(Locale.US)) { + case "tsadigestalg": + return tSADigestAlg; + case "tsapolicyid": + return tSAPolicyID; + case "internalsf": + return Boolean.toString(!externalSF); + case "sectionsonly": + return Boolean.toString(!signManifest); + case "altsignerpath": + return altSignerPath; + case "altsigner": + return altSigner; + default: + throw new UnsupportedOperationException( + "Unsupported key " + key); + } + } + + private void sign0(ZipFile zipFile, OutputStream os) + throws IOException, CertificateException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + MessageDigest[] digests; + try { + digests = new MessageDigest[digestalg.length]; + for (int i = 0; i < digestalg.length; i++) { + if (digestProvider == null) { + digests[i] = MessageDigest.getInstance(digestalg[i]); + } else { + digests[i] = MessageDigest.getInstance( + digestalg[i], digestProvider); + } + } + } catch (NoSuchAlgorithmException asae) { + // Should not happen. User provided alg were checked, and default + // alg should always be available. + throw new AssertionError(asae); + } + + PrintStream ps = new PrintStream(os); + ZipOutputStream zos = new ZipOutputStream(ps); + + Manifest manifest = new Manifest(); + Map mfEntries = manifest.getEntries(); + + // The Attributes of manifest before updating + Attributes oldAttr = null; + + boolean mfModified = false; + boolean mfCreated = false; + byte[] mfRawBytes = null; + + // Check if manifest exists + ZipEntry mfFile; + if ((mfFile = getManifestFile(zipFile)) != null) { + // Manifest exists. Read its raw bytes. + mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes(); + manifest.read(new ByteArrayInputStream(mfRawBytes)); + oldAttr = (Attributes) (manifest.getMainAttributes().clone()); + } else { + // Create new manifest + Attributes mattr = manifest.getMainAttributes(); + mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(), + "1.0"); + String javaVendor = System.getProperty("java.vendor"); + String jdkVersion = System.getProperty("java.version"); + mattr.putValue("Created-By", jdkVersion + " (" + javaVendor + + ")"); + mfFile = new ZipEntry(JarFile.MANIFEST_NAME); + mfCreated = true; + } + + /* + * For each entry in jar + * (except for signature-related META-INF entries), + * do the following: + * + * - if entry is not contained in manifest, add it to manifest; + * - if entry is contained in manifest, calculate its hash and + * compare it with the one in the manifest; if they are + * different, replace the hash in the manifest with the newly + * generated one. (This may invalidate existing signatures!) + */ + Vector mfFiles = new Vector<>(); + + boolean wasSigned = false; + + for (Enumeration enum_ = zipFile.entries(); + enum_.hasMoreElements(); ) { + ZipEntry ze = enum_.nextElement(); + + if (ze.getName().startsWith(META_INF)) { + // Store META-INF files in vector, so they can be written + // out first + mfFiles.addElement(ze); + + if (SignatureFileVerifier.isBlockOrSF( + ze.getName().toUpperCase(Locale.ENGLISH))) { + wasSigned = true; + } + + if (SignatureFileVerifier.isSigningRelated(ze.getName())) { + // ignore signature-related and manifest files + continue; + } + } + + if (manifest.getAttributes(ze.getName()) != null) { + // jar entry is contained in manifest, check and + // possibly update its digest attributes + if (updateDigests(ze, zipFile, digests, + manifest)) { + mfModified = true; + } + } else if (!ze.isDirectory()) { + // Add entry to manifest + Attributes attrs = getDigestAttributes(ze, zipFile, digests); + mfEntries.put(ze.getName(), attrs); + mfModified = true; + } + } + + // Recalculate the manifest raw bytes if necessary + if (mfModified) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + manifest.write(baos); + if (wasSigned) { + byte[] newBytes = baos.toByteArray(); + if (mfRawBytes != null + && oldAttr.equals(manifest.getMainAttributes())) { + + /* + * Note: + * + * The Attributes object is based on HashMap and can handle + * continuation columns. Therefore, even if the contents are + * not changed (in a Map view), the bytes that it write() + * may be different from the original bytes that it read() + * from. Since the signature on the main attributes is based + * on raw bytes, we must retain the exact bytes. + */ + + int newPos = findHeaderEnd(newBytes); + int oldPos = findHeaderEnd(mfRawBytes); + + if (newPos == oldPos) { + System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos); + } else { + // cat oldHead newTail > newBytes + byte[] lastBytes = new byte[oldPos + + newBytes.length - newPos]; + System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos); + System.arraycopy(newBytes, newPos, lastBytes, oldPos, + newBytes.length - newPos); + newBytes = lastBytes; + } + } + mfRawBytes = newBytes; + } else { + mfRawBytes = baos.toByteArray(); + } + } + + // Write out the manifest + if (mfModified) { + // manifest file has new length + mfFile = new ZipEntry(JarFile.MANIFEST_NAME); + } + if (handler != null) { + if (mfCreated) { + handler.accept("adding", mfFile.getName()); + } else if (mfModified) { + handler.accept("updating", mfFile.getName()); + } + } + + zos.putNextEntry(mfFile); + zos.write(mfRawBytes); + + // Calculate SignatureFile (".SF") and SignatureBlockFile + ManifestDigester manDig = new ManifestDigester(mfRawBytes); + SignatureFile sf = new SignatureFile(digests, manifest, manDig, + signerName, signManifest); + + byte[] block; + + Signature signer; + if (sigProvider == null ) { + signer = Signature.getInstance(sigalg); + } else { + signer = Signature.getInstance(sigalg, sigProvider); + } + signer.initSign(privateKey); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + sf.write(baos); + + byte[] content = baos.toByteArray(); + + signer.update(content); + byte[] signature = signer.sign(); + + @SuppressWarnings("deprecation") + ContentSigner signingMechanism = null; + if (altSigner != null) { + signingMechanism = loadSigningMechanism(altSigner, + altSignerPath); + } + + @SuppressWarnings("deprecation") + ContentSignerParameters params = + new JarSignerParameters(null, tsaUrl, tSAPolicyID, + tSADigestAlg, signature, + signer.getAlgorithm(), certChain, content, zipFile); + block = sf.generateBlock(params, externalSF, signingMechanism); + + String sfFilename = sf.getMetaName(); + String bkFilename = sf.getBlockName(privateKey); + + ZipEntry sfFile = new ZipEntry(sfFilename); + ZipEntry bkFile = new ZipEntry(bkFilename); + + long time = System.currentTimeMillis(); + sfFile.setTime(time); + bkFile.setTime(time); + + // signature file + zos.putNextEntry(sfFile); + sf.write(zos); + + if (handler != null) { + if (zipFile.getEntry(sfFilename) != null) { + handler.accept("updating", sfFilename); + } else { + handler.accept("adding", sfFilename); + } + } + + // signature block file + zos.putNextEntry(bkFile); + zos.write(block); + + if (handler != null) { + if (zipFile.getEntry(bkFilename) != null) { + handler.accept("updating", bkFilename); + } else { + handler.accept("adding", bkFilename); + } + } + + // Write out all other META-INF files that we stored in the + // vector + for (int i = 0; i < mfFiles.size(); i++) { + ZipEntry ze = mfFiles.elementAt(i); + if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME) + && !ze.getName().equalsIgnoreCase(sfFilename) + && !ze.getName().equalsIgnoreCase(bkFilename)) { + if (handler != null) { + if (manifest.getAttributes(ze.getName()) != null) { + handler.accept("signing", ze.getName()); + } else if (!ze.isDirectory()) { + handler.accept("adding", ze.getName()); + } + } + writeEntry(zipFile, zos, ze); + } + } + + // Write out all other files + for (Enumeration enum_ = zipFile.entries(); + enum_.hasMoreElements(); ) { + ZipEntry ze = enum_.nextElement(); + + if (!ze.getName().startsWith(META_INF)) { + if (handler != null) { + if (manifest.getAttributes(ze.getName()) != null) { + handler.accept("signing", ze.getName()); + } else { + handler.accept("adding", ze.getName()); + } + } + writeEntry(zipFile, zos, ze); + } + } + zipFile.close(); + zos.close(); + } + + private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze) + throws IOException { + ZipEntry ze2 = new ZipEntry(ze.getName()); + ze2.setMethod(ze.getMethod()); + ze2.setTime(ze.getTime()); + ze2.setComment(ze.getComment()); + ze2.setExtra(ze.getExtra()); + if (ze.getMethod() == ZipEntry.STORED) { + ze2.setSize(ze.getSize()); + ze2.setCrc(ze.getCrc()); + } + os.putNextEntry(ze2); + writeBytes(zf, ze, os); + } + + private void writeBytes + (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException { + try (InputStream is = zf.getInputStream(ze)) { + is.transferTo(os); + } + } + + private boolean updateDigests(ZipEntry ze, ZipFile zf, + MessageDigest[] digests, + Manifest mf) throws IOException { + boolean update = false; + + Attributes attrs = mf.getAttributes(ze.getName()); + String[] base64Digests = getDigests(ze, zf, digests); + + for (int i = 0; i < digests.length; i++) { + // The entry name to be written into attrs + String name = null; + try { + // Find if the digest already exists. An algorithm could have + // different names. For example, last time it was SHA, and this + // time it's SHA-1. + AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm()); + for (Object key : attrs.keySet()) { + if (key instanceof Attributes.Name) { + String n = key.toString(); + if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { + String tmp = n.substring(0, n.length() - 7); + if (AlgorithmId.get(tmp).equals(aid)) { + name = n; + break; + } + } + } + } + } catch (NoSuchAlgorithmException nsae) { + // Ignored. Writing new digest entry. + } + + if (name == null) { + name = digests[i].getAlgorithm() + "-Digest"; + attrs.putValue(name, base64Digests[i]); + update = true; + } else { + // compare digests, and replace the one in the manifest + // if they are different + String mfDigest = attrs.getValue(name); + if (!mfDigest.equalsIgnoreCase(base64Digests[i])) { + attrs.putValue(name, base64Digests[i]); + update = true; + } + } + } + return update; + } + + private Attributes getDigestAttributes( + ZipEntry ze, ZipFile zf, MessageDigest[] digests) + throws IOException { + + String[] base64Digests = getDigests(ze, zf, digests); + Attributes attrs = new Attributes(); + + for (int i = 0; i < digests.length; i++) { + attrs.putValue(digests[i].getAlgorithm() + "-Digest", + base64Digests[i]); + } + return attrs; + } + + /* + * Returns manifest entry from given jar file, or null if given jar file + * does not have a manifest entry. + */ + private ZipEntry getManifestFile(ZipFile zf) { + ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME); + if (ze == null) { + // Check all entries for matching name + Enumeration enum_ = zf.entries(); + while (enum_.hasMoreElements() && ze == null) { + ze = enum_.nextElement(); + if (!JarFile.MANIFEST_NAME.equalsIgnoreCase + (ze.getName())) { + ze = null; + } + } + } + return ze; + } + + private String[] getDigests( + ZipEntry ze, ZipFile zf, MessageDigest[] digests) + throws IOException { + + int n, i; + try (InputStream is = zf.getInputStream(ze)) { + long left = ze.getSize(); + byte[] buffer = new byte[8192]; + while ((left > 0) + && (n = is.read(buffer, 0, buffer.length)) != -1) { + for (i = 0; i < digests.length; i++) { + digests[i].update(buffer, 0, n); + } + left -= n; + } + } + + // complete the digests + String[] base64Digests = new String[digests.length]; + for (i = 0; i < digests.length; i++) { + base64Digests[i] = Base64.getEncoder() + .encodeToString(digests[i].digest()); + } + return base64Digests; + } + + @SuppressWarnings("fallthrough") + private int findHeaderEnd(byte[] bs) { + // Initial state true to deal with empty header + boolean newline = true; // just met a newline + int len = bs.length; + for (int i = 0; i < len; i++) { + switch (bs[i]) { + case '\r': + if (i < len - 1 && bs[i + 1] == '\n') i++; + // fallthrough + case '\n': + if (newline) return i + 1; //+1 to get length + newline = true; + break; + default: + newline = false; + } + } + // If header end is not found, it means the MANIFEST.MF has only + // the main attributes section and it does not end with 2 newlines. + // Returns the whole length so that it can be completely replaced. + return len; + } + + /* + * Try to load the specified signing mechanism. + * The URL class loader is used. + */ + @SuppressWarnings("deprecation") + private ContentSigner loadSigningMechanism(String signerClassName, + String signerClassPath) { + + // construct class loader + String cpString; // make sure env.class.path defaults to dot + + // do prepends to get correct ordering + cpString = PathList.appendPath( + System.getProperty("env.class.path"), null); + cpString = PathList.appendPath( + System.getProperty("java.class.path"), cpString); + cpString = PathList.appendPath(signerClassPath, cpString); + URL[] urls = PathList.pathToURLs(cpString); + ClassLoader appClassLoader = new URLClassLoader(urls); + + try { + // attempt to find signer + Class signerClass = appClassLoader.loadClass(signerClassName); + Object signer = signerClass.newInstance(); + return (ContentSigner) signer; + } catch (ClassNotFoundException|InstantiationException| + IllegalAccessException|ClassCastException e) { + throw new IllegalArgumentException( + "Invalid altSigner or altSignerPath", e); + } + } + + static class SignatureFile { + + /** + * SignatureFile + */ + Manifest sf; + + /** + * .SF base name + */ + String baseName; + + public SignatureFile(MessageDigest digests[], + Manifest mf, + ManifestDigester md, + String baseName, + boolean signManifest) { + + this.baseName = baseName; + + String version = System.getProperty("java.version"); + String javaVendor = System.getProperty("java.vendor"); + + sf = new Manifest(); + Attributes mattr = sf.getMainAttributes(); + + mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0"); + mattr.putValue("Created-By", version + " (" + javaVendor + ")"); + + if (signManifest) { + for (MessageDigest digest: digests) { + mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest", + Base64.getEncoder().encodeToString( + md.manifestDigest(digest))); + } + } + + // create digest of the manifest main attributes + ManifestDigester.Entry mde = + md.get(ManifestDigester.MF_MAIN_ATTRS, false); + if (mde != null) { + for (MessageDigest digest: digests) { + mattr.putValue(digest.getAlgorithm() + + "-Digest-" + ManifestDigester.MF_MAIN_ATTRS, + Base64.getEncoder().encodeToString( + mde.digest(digest))); + } + } else { + throw new IllegalStateException + ("ManifestDigester failed to create " + + "Manifest-Main-Attribute entry"); + } + + // go through the manifest entries and create the digests + Map entries = sf.getEntries(); + for (String name: mf.getEntries().keySet()) { + mde = md.get(name, false); + if (mde != null) { + Attributes attr = new Attributes(); + for (MessageDigest digest: digests) { + attr.putValue(digest.getAlgorithm() + "-Digest", + Base64.getEncoder().encodeToString( + mde.digest(digest))); + } + entries.put(name, attr); + } + } + } + + // Write .SF file + public void write(OutputStream out) throws IOException { + sf.write(out); + } + + // get .SF file name + public String getMetaName() { + return "META-INF/" + baseName + ".SF"; + } + + // get .DSA (or .DSA, .EC) file name + public String getBlockName(PrivateKey privateKey) { + String keyAlgorithm = privateKey.getAlgorithm(); + return "META-INF/" + baseName + "." + keyAlgorithm; + } + + // Generates the PKCS#7 content of block file + @SuppressWarnings("deprecation") + public byte[] generateBlock(ContentSignerParameters params, + boolean externalSF, + ContentSigner signingMechanism) + throws NoSuchAlgorithmException, + IOException, CertificateException { + + if (signingMechanism == null) { + signingMechanism = new TimestampedSigner(); + } + return signingMechanism.generateSignedData( + params, + externalSF, + params.getTimestampingAuthority() != null + || params.getTimestampingAuthorityCertificate() != null); + } + } + + @SuppressWarnings("deprecation") + class JarSignerParameters implements ContentSignerParameters { + + private String[] args; + private URI tsa; + private byte[] signature; + private String signatureAlgorithm; + private X509Certificate[] signerCertificateChain; + private byte[] content; + private ZipFile source; + private String tSAPolicyID; + private String tSADigestAlg; + + JarSignerParameters(String[] args, URI tsa, + String tSAPolicyID, String tSADigestAlg, + byte[] signature, String signatureAlgorithm, + X509Certificate[] signerCertificateChain, + byte[] content, ZipFile source) { + + Objects.requireNonNull(signature); + Objects.requireNonNull(signatureAlgorithm); + Objects.requireNonNull(signerCertificateChain); + + this.args = args; + this.tsa = tsa; + this.tSAPolicyID = tSAPolicyID; + this.tSADigestAlg = tSADigestAlg; + this.signature = signature; + this.signatureAlgorithm = signatureAlgorithm; + this.signerCertificateChain = signerCertificateChain; + this.content = content; + this.source = source; + } + + public String[] getCommandLine() { + return args; + } + + public URI getTimestampingAuthority() { + return tsa; + } + + public X509Certificate getTimestampingAuthorityCertificate() { + // We don't use this param. Always provide tsaURI. + return null; + } + + public String getTSAPolicyID() { + return tSAPolicyID; + } + + public String getTSADigestAlg() { + return tSADigestAlg; + } + + public byte[] getSignature() { + return signature; + } + + public String getSignatureAlgorithm() { + return signatureAlgorithm; + } + + public X509Certificate[] getSignerCertificateChain() { + return signerCertificateChain; + } + + public byte[] getContent() { + return content; + } + + public ZipFile getSource() { + return source; + } + } +} diff --git a/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java new file mode 100644 index 00000000000..a6c73a68af4 --- /dev/null +++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java @@ -0,0 +1,56 @@ +/* + * 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 jdk.security.jarsigner; + +/** + * This exception is thrown when {@link JarSigner#sign} fails. + * + * @since 1.9 + */ +@jdk.Exported +public class JarSignerException extends RuntimeException { + + private static final long serialVersionUID = -4732217075689309530L; + + /** + * Constructs a new {@code JarSignerException} with the specified detail + * message and cause. + *

+ * Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this {@code JarSignerException}'s detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + */ + public JarSignerException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java index 1417800400e..9d2932e8880 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java +++ b/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java @@ -29,22 +29,16 @@ import java.io.*; import java.util.*; import java.util.zip.*; import java.util.jar.*; -import java.math.BigInteger; import java.net.URI; -import java.net.URISyntaxException; import java.text.Collator; import java.text.MessageFormat; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; import java.security.*; -import java.lang.reflect.Constructor; -import com.sun.jarsigner.ContentSigner; -import com.sun.jarsigner.ContentSignerParameters; import java.net.SocketTimeoutException; import java.net.URL; -import java.net.URLClassLoader; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertificateExpiredException; @@ -53,11 +47,12 @@ import java.security.cert.CertificateNotYetValidException; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.util.Map.Entry; + +import jdk.security.jarsigner.JarSigner; +import jdk.security.jarsigner.JarSignerException; import sun.security.tools.KeyStoreUtil; -import sun.security.tools.PathList; import sun.security.x509.*; import sun.security.util.*; -import java.util.Base64; /** @@ -88,10 +83,6 @@ public class Main { collator.setStrength(Collator.PRIMARY); } - private static final String META_INF = "META-INF/"; - - private static final Class[] PARAM_STRING = { String.class }; - private static final String NONE = "NONE"; private static final String P11KEYSTORE = "PKCS11"; @@ -133,13 +124,13 @@ public class Main { char[] keypass; // private key password String sigfile; // name of .SF file String sigalg; // name of signature algorithm - String digestalg = "SHA-256"; // name of digest algorithm + String digestalg; // name of digest algorithm String signedjar; // output filename String tsaUrl; // location of the Timestamping Authority String tsaAlias; // alias for the Timestamping Authority's certificate String altCertChain; // file to read alternative cert chain from String tSAPolicyID; - String tSADigestAlg = "SHA-256"; + String tSADigestAlg; boolean verify = false; // verify the jar String verbose = null; // verbose output when signing/verifying boolean showcerts = false; // show certs when verifying @@ -149,9 +140,6 @@ public class Main { boolean strict = false; // treat warnings as error // read zip entry raw bytes - private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); - private byte[] buffer = new byte[8192]; - private ContentSigner signingMechanism = null; private String altSignerClass = null; private String altSignerClasspath = null; private ZipFile zipFile = null; @@ -216,6 +204,9 @@ public class Main { if ((keystore != null) || (storepass != null)) { System.out.println(rb.getString("jarsigner.error.") + e.getMessage()); + if (debug) { + e.printStackTrace(); + } System.exit(1); } } @@ -229,12 +220,7 @@ public class Main { loadKeyStore(keystore, true); getAliasInfo(alias); - // load the alternative signing mechanism - if (altSignerClass != null) { - signingMechanism = loadSigningMechanism(altSignerClass, - altSignerClasspath); - } - signJar(jarfile, alias, args); + signJar(jarfile, alias); } } catch (Exception e) { System.out.println(rb.getString("jarsigner.error.") + e); @@ -626,8 +612,7 @@ public class Main { InputStream is = null; try { is = jf.getInputStream(je); - int n; - while ((n = is.read(buffer, 0, buffer.length)) != -1) { + while (is.read(buffer, 0, buffer.length) != -1) { // we just read. this will throw a SecurityException // if a signature/digest check fails. } @@ -1035,7 +1020,6 @@ public class Main { return cacheForInKS.get(signer); } - boolean found = false; int result = 0; List certs = signer.getSignerCertPath().getCertificates(); for (Certificate c : certs) { @@ -1058,7 +1042,6 @@ public class Main { } if (alias != null) { storeHash.put(c, "(" + alias + ")"); - found = true; result |= IN_KEYSTORE; } } @@ -1090,7 +1073,7 @@ public class Main { return output; } - void signJar(String jarName, String alias, String[] args) + void signJar(String jarName, String alias) throws Exception { boolean aliasUsed = false; X509Certificate tsaCert = null; @@ -1110,17 +1093,17 @@ public class Main { for (int j = 0; j < sigfile.length(); j++) { char c = sigfile.charAt(j); if (! - ((c>= 'A' && c<= 'Z') || - (c>= '0' && c<= '9') || - (c == '-') || - (c == '_'))) { + ((c>= 'A' && c<= 'Z') || + (c>= '0' && c<= '9') || + (c == '-') || + (c == '_'))) { if (aliasUsed) { // convert illegal characters from the alias to be _'s c = '_'; } else { - throw new - RuntimeException(rb.getString - ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or.")); + throw new + RuntimeException(rb.getString + ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or.")); } } tmpSigFile.append(c); @@ -1149,275 +1132,88 @@ public class Main { error(rb.getString("unable.to.create.")+tmpJarName, ioe); } - PrintStream ps = new PrintStream(fos); - ZipOutputStream zos = new ZipOutputStream(ps); + CertPath cp = CertificateFactory.getInstance("X.509") + .generateCertPath(Arrays.asList(certChain)); + JarSigner.Builder builder = new JarSigner.Builder(privateKey, cp); - /* First guess at what they might be - we don't xclude RSA ones. */ - String sfFilename = (META_INF + sigfile + ".SF").toUpperCase(Locale.ENGLISH); - String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(Locale.ENGLISH); + if (verbose != null) { + builder.eventHandler((action, file) -> { + System.out.println(rb.getString("." + action + ".") + file); + }); + } - Manifest manifest = new Manifest(); - Map mfEntries = manifest.getEntries(); + if (digestalg != null) { + builder.digestAlgorithm(digestalg); + } + if (sigalg != null) { + builder.signatureAlgorithm(sigalg); + } - // The Attributes of manifest before updating - Attributes oldAttr = null; + URI tsaURI = null; - boolean mfModified = false; - boolean mfCreated = false; - byte[] mfRawBytes = null; + if (tsaUrl != null) { + tsaURI = new URI(tsaUrl); + } else if (tsaAlias != null) { + tsaCert = getTsaCert(tsaAlias); + tsaURI = TimestampedSigner.getTimestampingURI(tsaCert); + } - try { - MessageDigest digests[] = { MessageDigest.getInstance(digestalg) }; - - // Check if manifest exists - ZipEntry mfFile; - if ((mfFile = getManifestFile(zipFile)) != null) { - // Manifest exists. Read its raw bytes. - mfRawBytes = getBytes(zipFile, mfFile); - manifest.read(new ByteArrayInputStream(mfRawBytes)); - oldAttr = (Attributes)(manifest.getMainAttributes().clone()); - } else { - // Create new manifest - Attributes mattr = manifest.getMainAttributes(); - mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(), - "1.0"); - String javaVendor = System.getProperty("java.vendor"); - String jdkVersion = System.getProperty("java.version"); - mattr.putValue("Created-By", jdkVersion + " (" +javaVendor - + ")"); - mfFile = new ZipEntry(JarFile.MANIFEST_NAME); - mfCreated = true; - } - - /* - * For each entry in jar - * (except for signature-related META-INF entries), - * do the following: - * - * - if entry is not contained in manifest, add it to manifest; - * - if entry is contained in manifest, calculate its hash and - * compare it with the one in the manifest; if they are - * different, replace the hash in the manifest with the newly - * generated one. (This may invalidate existing signatures!) - */ - Vector mfFiles = new Vector<>(); - - boolean wasSigned = false; - - for (Enumeration enum_=zipFile.entries(); - enum_.hasMoreElements();) { - ZipEntry ze = enum_.nextElement(); - - if (ze.getName().startsWith(META_INF)) { - // Store META-INF files in vector, so they can be written - // out first - mfFiles.addElement(ze); - - if (SignatureFileVerifier.isBlockOrSF( - ze.getName().toUpperCase(Locale.ENGLISH))) { - wasSigned = true; - } - - if (signatureRelated(ze.getName())) { - // ignore signature-related and manifest files - continue; - } - } - - if (manifest.getAttributes(ze.getName()) != null) { - // jar entry is contained in manifest, check and - // possibly update its digest attributes - if (updateDigests(ze, zipFile, digests, - manifest) == true) { - mfModified = true; - } - } else if (!ze.isDirectory()) { - // Add entry to manifest - Attributes attrs = getDigestAttributes(ze, zipFile, - digests); - mfEntries.put(ze.getName(), attrs); - mfModified = true; - } - } - - // Recalculate the manifest raw bytes if necessary - if (mfModified) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - manifest.write(baos); - if (wasSigned) { - byte[] newBytes = baos.toByteArray(); - if (mfRawBytes != null - && oldAttr.equals(manifest.getMainAttributes())) { - - /* - * Note: - * - * The Attributes object is based on HashMap and can handle - * continuation columns. Therefore, even if the contents are - * not changed (in a Map view), the bytes that it write() - * may be different from the original bytes that it read() - * from. Since the signature on the main attributes is based - * on raw bytes, we must retain the exact bytes. - */ - - int newPos = findHeaderEnd(newBytes); - int oldPos = findHeaderEnd(mfRawBytes); - - if (newPos == oldPos) { - System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos); - } else { - // cat oldHead newTail > newBytes - byte[] lastBytes = new byte[oldPos + - newBytes.length - newPos]; - System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos); - System.arraycopy(newBytes, newPos, lastBytes, oldPos, - newBytes.length - newPos); - newBytes = lastBytes; - } - } - mfRawBytes = newBytes; - } else { - mfRawBytes = baos.toByteArray(); - } - } - - // Write out the manifest - if (mfModified) { - // manifest file has new length - mfFile = new ZipEntry(JarFile.MANIFEST_NAME); - } + if (tsaURI != null) { if (verbose != null) { - if (mfCreated) { - System.out.println(rb.getString(".adding.") + - mfFile.getName()); - } else if (mfModified) { - System.out.println(rb.getString(".updating.") + - mfFile.getName()); - } - } - zos.putNextEntry(mfFile); - zos.write(mfRawBytes); - - // Calculate SignatureFile (".SF") and SignatureBlockFile - ManifestDigester manDig = new ManifestDigester(mfRawBytes); - SignatureFile sf = new SignatureFile(digests, manifest, manDig, - sigfile, signManifest); - - if (tsaAlias != null) { - tsaCert = getTsaCert(tsaAlias); - } - - if (tsaUrl == null && tsaCert == null) { - noTimestamp = true; - } - - SignatureFile.Block block = null; - - try { - block = - sf.generateBlock(privateKey, sigalg, certChain, - externalSF, tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg, - signingMechanism, args, zipFile); - } catch (SocketTimeoutException e) { - // Provide a helpful message when TSA is beyond a firewall - error(rb.getString("unable.to.sign.jar.") + - rb.getString("no.response.from.the.Timestamping.Authority.") + - "\n -J-Dhttp.proxyHost=" + - "\n -J-Dhttp.proxyPort=\n" + - rb.getString("or") + - "\n -J-Dhttps.proxyHost= " + - "\n -J-Dhttps.proxyPort= ", e); - } - - sfFilename = sf.getMetaName(); - bkFilename = block.getMetaName(); - - ZipEntry sfFile = new ZipEntry(sfFilename); - ZipEntry bkFile = new ZipEntry(bkFilename); - - long time = System.currentTimeMillis(); - sfFile.setTime(time); - bkFile.setTime(time); - - // signature file - zos.putNextEntry(sfFile); - sf.write(zos); - if (verbose != null) { - if (zipFile.getEntry(sfFilename) != null) { - System.out.println(rb.getString(".updating.") + - sfFilename); - } else { - System.out.println(rb.getString(".adding.") + - sfFilename); - } - } - - if (verbose != null) { - if (tsaUrl != null || tsaCert != null) { - System.out.println( + System.out.println( rb.getString("requesting.a.signature.timestamp")); - } if (tsaUrl != null) { System.out.println(rb.getString("TSA.location.") + tsaUrl); - } - if (tsaCert != null) { - URI tsaURI = TimestampedSigner.getTimestampingURI(tsaCert); - if (tsaURI != null) { - System.out.println(rb.getString("TSA.location.") + - tsaURI); - } + } else if (tsaCert != null) { System.out.println(rb.getString("TSA.certificate.") + - printCert("", tsaCert, false, null, false)); - } - if (signingMechanism != null) { - System.out.println( - rb.getString("using.an.alternative.signing.mechanism")); + printCert("", tsaCert, false, null, false)); } } + builder.tsa(tsaURI); + if (tSADigestAlg != null) { + builder.setProperty("tsaDigestAlg", tSADigestAlg); + } - // signature block file - zos.putNextEntry(bkFile); - block.write(zos); + if (tSAPolicyID != null) { + builder.setProperty("tsaPolicyId", tSAPolicyID); + } + } else { + noTimestamp = true; + } + + if (altSignerClass != null) { + builder.setProperty("altSigner", altSignerClass); if (verbose != null) { - if (zipFile.getEntry(bkFilename) != null) { - System.out.println(rb.getString(".updating.") + - bkFilename); - } else { - System.out.println(rb.getString(".adding.") + - bkFilename); - } + System.out.println( + rb.getString("using.an.alternative.signing.mechanism")); } + } - // Write out all other META-INF files that we stored in the - // vector - for (int i=0; i" + + "\n -J-Dhttp.proxyPort=\n" + + rb.getString("or") + + "\n -J-Dhttps.proxyHost= " + + "\n -J-Dhttps.proxyPort= ", e); + } else { + error(rb.getString("unable.to.sign.jar.")+e.getCause(), e.getCause()); } - - // Write out all other files - for (Enumeration enum_=zipFile.entries(); - enum_.hasMoreElements();) { - ZipEntry ze = enum_.nextElement(); - - if (!ze.getName().startsWith(META_INF)) { - if (verbose != null) { - if (manifest.getAttributes(ze.getName()) != null) - System.out.println(rb.getString(".signing.") + - ze.getName()); - else - System.out.println(rb.getString(".adding.") + - ze.getName()); - } - writeEntry(zipFile, zos, ze); - } - } - } catch(IOException ioe) { - error(rb.getString("unable.to.sign.jar.")+ioe, ioe); } finally { // close the resouces if (zipFile != null) { @@ -1425,8 +1221,8 @@ public class Main { zipFile = null; } - if (zos != null) { - zos.close(); + if (fos != null) { + fos.close(); } } @@ -1526,35 +1322,6 @@ public class Main { // } } - /** - * Find the length of header inside bs. The header is a multiple (>=0) - * lines of attributes plus an empty line. The empty line is included - * in the header. - */ - @SuppressWarnings("fallthrough") - private int findHeaderEnd(byte[] bs) { - // Initial state true to deal with empty header - boolean newline = true; // just met a newline - int len = bs.length; - for (int i=0; i 0) && (n = is.read(buffer, 0, buffer.length)) != -1) { - os.write(buffer, 0, n); - left -= n; - } - } finally { - if (is != null) { - is.close(); - } - } - } - void loadKeyStore(String keyStoreName, boolean prompt) { if (!nullStream && keyStoreName == null) { @@ -1958,15 +1686,13 @@ public class Main { } } - void error(String message) - { + void error(String message) { System.out.println(rb.getString("jarsigner.")+message); System.exit(1); } - void error(String message, Exception e) - { + void error(String message, Throwable e) { System.out.println(rb.getString("jarsigner.")+message); if (debug) { e.printStackTrace(); @@ -1990,8 +1716,7 @@ public class Main { } } - char[] getPass(String prompt) - { + char[] getPass(String prompt) { System.err.print(prompt); System.err.flush(); try { @@ -2008,569 +1733,4 @@ public class Main { // this shouldn't happen return null; } - - /* - * Reads all the bytes for a given zip entry. - */ - private synchronized byte[] getBytes(ZipFile zf, - ZipEntry ze) throws IOException { - int n; - - InputStream is = null; - try { - is = zf.getInputStream(ze); - baos.reset(); - long left = ze.getSize(); - - while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) { - baos.write(buffer, 0, n); - left -= n; - } - } finally { - if (is != null) { - is.close(); - } - } - - return baos.toByteArray(); - } - - /* - * Returns manifest entry from given jar file, or null if given jar file - * does not have a manifest entry. - */ - private ZipEntry getManifestFile(ZipFile zf) { - ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME); - if (ze == null) { - // Check all entries for matching name - Enumeration enum_ = zf.entries(); - while (enum_.hasMoreElements() && ze == null) { - ze = enum_.nextElement(); - if (!JarFile.MANIFEST_NAME.equalsIgnoreCase - (ze.getName())) { - ze = null; - } - } - } - return ze; - } - - /* - * Computes the digests of a zip entry, and returns them as an array - * of base64-encoded strings. - */ - private synchronized String[] getDigests(ZipEntry ze, ZipFile zf, - MessageDigest[] digests) - throws IOException { - - int n, i; - InputStream is = null; - try { - is = zf.getInputStream(ze); - long left = ze.getSize(); - while((left > 0) - && (n = is.read(buffer, 0, buffer.length)) != -1) { - for (i=0; i signerClass = appClassLoader.loadClass(signerClassName); - - // Check that it implements ContentSigner - Object signer = signerClass.newInstance(); - if (!(signer instanceof ContentSigner)) { - MessageFormat form = new MessageFormat( - rb.getString("signerClass.is.not.a.signing.mechanism")); - Object[] source = {signerClass.getName()}; - throw new IllegalArgumentException(form.format(source)); - } - return (ContentSigner)signer; - } -} - -class SignatureFile { - - /** SignatureFile */ - Manifest sf; - - /** .SF base name */ - String baseName; - - public SignatureFile(MessageDigest digests[], - Manifest mf, - ManifestDigester md, - String baseName, - boolean signManifest) - - { - this.baseName = baseName; - - String version = System.getProperty("java.version"); - String javaVendor = System.getProperty("java.vendor"); - - sf = new Manifest(); - Attributes mattr = sf.getMainAttributes(); - - mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0"); - mattr.putValue("Created-By", version + " (" + javaVendor + ")"); - - if (signManifest) { - // sign the whole manifest - for (int i=0; i < digests.length; i++) { - mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest", - Base64.getEncoder().encodeToString(md.manifestDigest(digests[i]))); - } - } - - // create digest of the manifest main attributes - ManifestDigester.Entry mde = - md.get(ManifestDigester.MF_MAIN_ATTRS, false); - if (mde != null) { - for (int i=0; i < digests.length; i++) { - mattr.putValue(digests[i].getAlgorithm() + - "-Digest-" + ManifestDigester.MF_MAIN_ATTRS, - Base64.getEncoder().encodeToString(mde.digest(digests[i]))); - } - } else { - throw new IllegalStateException - ("ManifestDigester failed to create " + - "Manifest-Main-Attribute entry"); - } - - /* go through the manifest entries and create the digests */ - - Map entries = sf.getEntries(); - Iterator> mit = - mf.getEntries().entrySet().iterator(); - while(mit.hasNext()) { - Map.Entry e = mit.next(); - String name = e.getKey(); - mde = md.get(name, false); - if (mde != null) { - Attributes attr = new Attributes(); - for (int i=0; i < digests.length; i++) { - attr.putValue(digests[i].getAlgorithm()+"-Digest", - Base64.getEncoder().encodeToString(mde.digest(digests[i]))); - } - entries.put(name, attr); - } - } - } - - /** - * Writes the SignatureFile to the specified OutputStream. - * - * @param out the output stream - * @exception IOException if an I/O error has occurred - */ - - public void write(OutputStream out) throws IOException - { - sf.write(out); - } - - /** - * get .SF file name - */ - public String getMetaName() - { - return "META-INF/"+ baseName + ".SF"; - } - - /** - * get base file name - */ - public String getBaseName() - { - return baseName; - } - - /* - * Generate a signed data block. - * If a URL or a certificate (containing a URL) for a Timestamping - * Authority is supplied then a signature timestamp is generated and - * inserted into the signed data block. - * - * @param sigalg signature algorithm to use, or null to use default - * @param tsaUrl The location of the Timestamping Authority. If null - * then no timestamp is requested. - * @param tsaCert The certificate for the Timestamping Authority. If null - * then no timestamp is requested. - * @param signingMechanism The signing mechanism to use. - * @param args The command-line arguments to jarsigner. - * @param zipFile The original source Zip file. - */ - @SuppressWarnings("deprecation") - public Block generateBlock(PrivateKey privateKey, - String sigalg, - X509Certificate[] certChain, - boolean externalSF, String tsaUrl, - X509Certificate tsaCert, - String tSAPolicyID, - String tSADigestAlg, - ContentSigner signingMechanism, - String[] args, ZipFile zipFile) - throws NoSuchAlgorithmException, InvalidKeyException, IOException, - SignatureException, CertificateException - { - return new Block(this, privateKey, sigalg, certChain, externalSF, - tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg, signingMechanism, args, zipFile); - } - - - public static class Block { - - private byte[] block; - private String blockFileName; - - /* - * Construct a new signature block. - */ - @SuppressWarnings("deprecation") - Block(SignatureFile sfg, PrivateKey privateKey, String sigalg, - X509Certificate[] certChain, boolean externalSF, String tsaUrl, - X509Certificate tsaCert, String tSAPolicyID, String tSADigestAlg, - ContentSigner signingMechanism, String[] args, ZipFile zipFile) - throws NoSuchAlgorithmException, InvalidKeyException, IOException, - SignatureException, CertificateException { - - Principal issuerName = certChain[0].getIssuerDN(); - if (!(issuerName instanceof X500Name)) { - // must extract the original encoded form of DN for subsequent - // name comparison checks (converting to a String and back to - // an encoded DN could cause the types of String attribute - // values to be changed) - X509CertInfo tbsCert = new - X509CertInfo(certChain[0].getTBSCertificate()); - issuerName = (Principal) - tbsCert.get(X509CertInfo.ISSUER + "." + - X509CertInfo.DN_NAME); - } - BigInteger serial = certChain[0].getSerialNumber(); - - String signatureAlgorithm; - String keyAlgorithm = privateKey.getAlgorithm(); - /* - * If no signature algorithm was specified, we choose a - * default that is compatible with the private key algorithm. - */ - if (sigalg == null) { - - if (keyAlgorithm.equalsIgnoreCase("DSA")) - signatureAlgorithm = "SHA256withDSA"; - else if (keyAlgorithm.equalsIgnoreCase("RSA")) - signatureAlgorithm = "SHA256withRSA"; - else if (keyAlgorithm.equalsIgnoreCase("EC")) - signatureAlgorithm = "SHA256withECDSA"; - else - throw new RuntimeException("private key is not a DSA or " - + "RSA key"); - } else { - signatureAlgorithm = sigalg; - } - - // check common invalid key/signature algorithm combinations - String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.ENGLISH); - if ((sigAlgUpperCase.endsWith("WITHRSA") && - !keyAlgorithm.equalsIgnoreCase("RSA")) || - (sigAlgUpperCase.endsWith("WITHECDSA") && - !keyAlgorithm.equalsIgnoreCase("EC")) || - (sigAlgUpperCase.endsWith("WITHDSA") && - !keyAlgorithm.equalsIgnoreCase("DSA"))) { - throw new SignatureException - ("private key algorithm is not compatible with signature algorithm"); - } - - blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm; - - AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm); - AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm); - - Signature sig = Signature.getInstance(signatureAlgorithm); - sig.initSign(privateKey); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - sfg.write(baos); - - byte[] content = baos.toByteArray(); - - sig.update(content); - byte[] signature = sig.sign(); - - // Timestamp the signature and generate the signature block file - if (signingMechanism == null) { - signingMechanism = new TimestampedSigner(); - } - URI tsaUri = null; - try { - if (tsaUrl != null) { - tsaUri = new URI(tsaUrl); - } - } catch (URISyntaxException e) { - throw new IOException(e); - } - - // Assemble parameters for the signing mechanism - ContentSignerParameters params = - new JarSignerParameters(args, tsaUri, tsaCert, tSAPolicyID, - tSADigestAlg, signature, - signatureAlgorithm, certChain, content, zipFile); - - // Generate the signature block - block = signingMechanism.generateSignedData( - params, externalSF, (tsaUrl != null || tsaCert != null)); - } - - /* - * get block file name. - */ - public String getMetaName() - { - return blockFileName; - } - - /** - * Writes the block file to the specified OutputStream. - * - * @param out the output stream - * @exception IOException if an I/O error has occurred - */ - - public void write(OutputStream out) throws IOException - { - out.write(block); - } - } -} - - -/* - * This object encapsulates the parameters used to perform content signing. - */ -@SuppressWarnings("deprecation") -class JarSignerParameters implements ContentSignerParameters { - - private String[] args; - private URI tsa; - private X509Certificate tsaCertificate; - private byte[] signature; - private String signatureAlgorithm; - private X509Certificate[] signerCertificateChain; - private byte[] content; - private ZipFile source; - private String tSAPolicyID; - private String tSADigestAlg; - - /** - * Create a new object. - */ - JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate, - String tSAPolicyID, String tSADigestAlg, - byte[] signature, String signatureAlgorithm, - X509Certificate[] signerCertificateChain, byte[] content, - ZipFile source) { - - if (signature == null || signatureAlgorithm == null || - signerCertificateChain == null || tSADigestAlg == null) { - throw new NullPointerException(); - } - this.args = args; - this.tsa = tsa; - this.tsaCertificate = tsaCertificate; - this.tSAPolicyID = tSAPolicyID; - this.tSADigestAlg = tSADigestAlg; - this.signature = signature; - this.signatureAlgorithm = signatureAlgorithm; - this.signerCertificateChain = signerCertificateChain; - this.content = content; - this.source = source; - } - - /** - * Retrieves the command-line arguments. - * - * @return The command-line arguments. May be null. - */ - public String[] getCommandLine() { - return args; - } - - /** - * Retrieves the identifier for a Timestamping Authority (TSA). - * - * @return The TSA identifier. May be null. - */ - public URI getTimestampingAuthority() { - return tsa; - } - - /** - * Retrieves the certificate for a Timestamping Authority (TSA). - * - * @return The TSA certificate. May be null. - */ - public X509Certificate getTimestampingAuthorityCertificate() { - return tsaCertificate; - } - - public String getTSAPolicyID() { - return tSAPolicyID; - } - - public String getTSADigestAlg() { - return tSADigestAlg; - } - - /** - * Retrieves the signature. - * - * @return The non-null signature bytes. - */ - public byte[] getSignature() { - return signature; - } - - /** - * Retrieves the name of the signature algorithm. - * - * @return The non-null string name of the signature algorithm. - */ - public String getSignatureAlgorithm() { - return signatureAlgorithm; - } - - /** - * Retrieves the signer's X.509 certificate chain. - * - * @return The non-null array of X.509 public-key certificates. - */ - public X509Certificate[] getSignerCertificateChain() { - return signerCertificateChain; - } - - /** - * Retrieves the content that was signed. - * - * @return The content bytes. May be null. - */ - public byte[] getContent() { - return content; - } - - /** - * Retrieves the original source ZIP file before it was signed. - * - * @return The original ZIP file. May be null. - */ - public ZipFile getSource() { - return source; - } } diff --git a/jdk/test/jdk/security/jarsigner/Function.java b/jdk/test/jdk/security/jarsigner/Function.java new file mode 100644 index 00000000000..eead632be87 --- /dev/null +++ b/jdk/test/jdk/security/jarsigner/Function.java @@ -0,0 +1,182 @@ +/* + * 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. + * + * 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 8056174 + * @summary test the functions of JarSigner API + * @modules java.base/sun.security.tools.keytool + * jdk.jartool + */ + +import jdk.security.jarsigner.JarSigner; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class Function { + public static void main(String[] args) throws Exception { + + try (FileOutputStream fout =new FileOutputStream("src.zip"); + ZipOutputStream zout = new ZipOutputStream(fout)) { + zout.putNextEntry(new ZipEntry("x")); + zout.write(new byte[10]); + zout.closeEntry(); + } + + sun.security.tools.keytool.Main.main( + ("-storetype jks -keystore ks -storepass changeit" + + " -keypass changeit -dname" + + " CN=RSA -alias r -genkeypair -keyalg rsa").split(" ")); + + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(new FileInputStream("ks"), "changeit".toCharArray()); + PrivateKey key = (PrivateKey)ks.getKey("r", "changeit".toCharArray()); + Certificate cert = ks.getCertificate("r"); + JarSigner.Builder jsb = new JarSigner.Builder(key, + CertificateFactory.getInstance("X.509").generateCertPath( + Collections.singletonList(cert))); + + jsb.digestAlgorithm("SHA1"); + jsb.signatureAlgorithm("SHA1withRSA"); + + AtomicInteger counter = new AtomicInteger(0); + StringBuilder sb = new StringBuilder(); + jsb.eventHandler( + (a, f)->{ + counter.incrementAndGet(); + sb.append(a).append(' ').append(f).append('\n'); + }); + + OutputStream blackHole = new OutputStream() { + @Override + public void write(int b) throws IOException { } + }; + + try (ZipFile src = new ZipFile("src.zip")) { + jsb.build().sign(src, blackHole); + } + + if (counter.get() != 4) { + throw new Exception("Event number is " + counter.get() + + ":\n" + sb.toString()); + } + + // Provider test. + Provider p = new MyProvider(); + jsb.digestAlgorithm("Five", p); + jsb.signatureAlgorithm("SHA1WithRSA", p); + try (ZipFile src = new ZipFile("src.zip"); + FileOutputStream out = new FileOutputStream("out.jar")) { + jsb.build().sign(src, out); + } + + try (JarFile signed = new JarFile("out.jar")) { + Manifest man = signed.getManifest(); + assertTrue(man.getAttributes("x").getValue("Five-Digest").equals("FAKE")); + + Manifest sf = new Manifest(signed.getInputStream( + signed.getJarEntry("META-INF/SIGNER.SF"))); + assertTrue(sf.getMainAttributes().getValue("Five-Digest-Manifest") + .equals("FAKE")); + assertTrue(sf.getAttributes("x").getValue("Five-Digest").equals("FAKE")); + + try (InputStream sig = signed.getInputStream( + signed.getJarEntry("META-INF/SIGNER.RSA"))) { + byte[] data = sig.readAllBytes(); + assertTrue(Arrays.equals( + Arrays.copyOfRange(data, data.length-8, data.length), + "FAKEFAKE".getBytes())); + } + } + } + + private static void assertTrue(boolean v) { + if (!v) { + throw new AssertionError(); + } + } + + public static class MyProvider extends Provider { + MyProvider() { + super("MY", 1.0d, null); + put("MessageDigest.Five", Five.class.getName()); + put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName()); + } + } + + // "Five" is a MessageDigest always returns the same value + public static class Five extends MessageDigest { + static final byte[] dig = {0x14, 0x02, (byte)0x84}; //base64 -> FAKE + public Five() { super("Five"); } + protected void engineUpdate(byte input) { } + protected void engineUpdate(byte[] input, int offset, int len) { } + protected byte[] engineDigest() { return dig; } + protected void engineReset() { } + } + + // This fake "SHA1withRSA" is a Signature always returns the same value. + // An existing name must be used otherwise PKCS7 does not which OID to use. + public static class SHA1WithRSA extends Signature { + static final byte[] sig = "FAKEFAKE".getBytes(); + public SHA1WithRSA() { super("SHA1WithRSA"); } + protected void engineInitVerify(PublicKey publicKey) + throws InvalidKeyException { } + protected void engineInitSign(PrivateKey privateKey) + throws InvalidKeyException { } + protected void engineUpdate(byte b) throws SignatureException { } + protected void engineUpdate(byte[] b, int off, int len) + throws SignatureException { } + protected byte[] engineSign() throws SignatureException { return sig; } + protected boolean engineVerify(byte[] sigBytes) + throws SignatureException { + return Arrays.equals(sigBytes, sig); + } + protected void engineSetParameter(String param, Object value) + throws InvalidParameterException { } + protected Object engineGetParameter(String param) + throws InvalidParameterException { return null; } + } +} diff --git a/jdk/test/jdk/security/jarsigner/Spec.java b/jdk/test/jdk/security/jarsigner/Spec.java new file mode 100644 index 00000000000..a4853bf08c2 --- /dev/null +++ b/jdk/test/jdk/security/jarsigner/Spec.java @@ -0,0 +1,251 @@ +/* + * 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. + * + * 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 8056174 + * @summary Make sure JarSigner impl conforms to spec + * @library /lib/testlibrary + * @modules java.base/sun.security.tools.keytool + * java.base/sun.security.provider.certpath + * jdk.jartool + */ + +import com.sun.jarsigner.ContentSigner; +import com.sun.jarsigner.ContentSignerParameters; +import jdk.security.jarsigner.JarSigner; +import jdk.testlibrary.JarUtils; +import sun.security.provider.certpath.X509CertPath; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.CertPath; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.Collections; +import java.util.function.BiConsumer; + +public class Spec { + + public static void main(String[] args) throws Exception { + + // Prepares raw file + Files.write(Paths.get("a"), "a".getBytes()); + + // Pack + JarUtils.createJar("a.jar", "a"); + + // Prepare a keystore + sun.security.tools.keytool.Main.main( + ("-keystore ks -storepass changeit -keypass changeit -dname" + + " CN=RSA -alias r -genkeypair -keyalg rsa").split(" ")); + sun.security.tools.keytool.Main.main( + ("-keystore ks -storepass changeit -keypass changeit -dname" + + " CN=DSA -alias d -genkeypair -keyalg dsa").split(" ")); + + char[] pass = "changeit".toCharArray(); + + KeyStore ks = KeyStore.getInstance( + new File("ks"), pass); + PrivateKey pkr = (PrivateKey)ks.getKey("r", pass); + PrivateKey pkd = (PrivateKey)ks.getKey("d", pass); + CertPath cp = CertificateFactory.getInstance("X.509") + .generateCertPath(Arrays.asList(ks.getCertificateChain("r"))); + + Provider sun = Security.getProvider("SUN"); + + // throws + npe(()->new JarSigner.Builder(null)); + npe(()->new JarSigner.Builder(null, cp)); + iae(()->new JarSigner.Builder( + pkr, new X509CertPath(Collections.emptyList()))); + iae(()->new JarSigner.Builder(pkd, cp)); // unmatched certs alg + + JarSigner.Builder b1 = new JarSigner.Builder(pkr, cp); + + npe(()->b1.digestAlgorithm(null)); + nsae(()->b1.digestAlgorithm("HAHA")); + b1.digestAlgorithm("SHA-256"); + + npe(()->b1.digestAlgorithm("SHA-256", null)); + npe(()->b1.digestAlgorithm(null, sun)); + nsae(()->b1.digestAlgorithm("HAHA", sun)); + b1.digestAlgorithm("SHA-256", sun); + + npe(()->b1.signatureAlgorithm(null)); + nsae(()->b1.signatureAlgorithm("HAHAwithHEHE")); + iae(()->b1.signatureAlgorithm("SHA256withECDSA")); + + npe(()->b1.signatureAlgorithm(null, sun)); + npe(()->b1.signatureAlgorithm("SHA256withRSA", null)); + nsae(()->b1.signatureAlgorithm("HAHAwithHEHE", sun)); + iae(()->b1.signatureAlgorithm("SHA256withDSA", sun)); + + npe(()->b1.tsa(null)); + + npe(()->b1.signerName(null)); + iae(()->b1.signerName("")); + iae(()->b1.signerName("123456789")); + iae(()->b1.signerName("a+b")); + + npe(()->b1.setProperty(null, "")); + uoe(()->b1.setProperty("what", "")); + npe(()->b1.setProperty("tsadigestalg", null)); + iae(()->b1.setProperty("tsadigestalg", "HAHA")); + npe(()->b1.setProperty("tsapolicyid", null)); + npe(()->b1.setProperty("internalsf", null)); + iae(()->b1.setProperty("internalsf", "Hello")); + npe(()->b1.setProperty("sectionsonly", null)); + iae(()->b1.setProperty("sectionsonly", "OK")); + npe(()->b1.setProperty("altsigner", null)); + npe(()->b1.eventHandler(null)); + + // default values + JarSigner.Builder b2 = new JarSigner.Builder(pkr, cp); + JarSigner js2 = b2.build(); + + assertTrue(js2.getDigestAlgorithm().equals( + JarSigner.Builder.getDefaultDigestAlgorithm())); + assertTrue(js2.getSignatureAlgorithm().equals( + JarSigner.Builder.getDefaultSignatureAlgorithm(pkr))); + assertTrue(js2.getTsa() == null); + assertTrue(js2.getSignerName().equals("SIGNER")); + assertTrue(js2.getProperty("tsadigestalg").equals( + JarSigner.Builder.getDefaultDigestAlgorithm())); + assertTrue(js2.getProperty("tsapolicyid") == null); + assertTrue(js2.getProperty("internalsf").equals("false")); + assertTrue(js2.getProperty("sectionsonly").equals("false")); + assertTrue(js2.getProperty("altsigner") == null); + uoe(()->js2.getProperty("invalid")); + + // default values + BiConsumer myeh = (a,s)->{}; + URI tsa = new URI("https://tsa.com"); + + JarSigner.Builder b3 = new JarSigner.Builder(pkr, cp) + .digestAlgorithm("SHA-1") + .signatureAlgorithm("SHA1withRSA") + .signerName("Duke") + .tsa(tsa) + .setProperty("tsadigestalg", "SHA-512") + .setProperty("tsapolicyid", "1.2.3.4") + .setProperty("internalsf", "true") + .setProperty("sectionsonly", "true") + .setProperty("altsigner", "MyContentSigner") + .eventHandler(myeh); + JarSigner js3 = b3.build(); + + assertTrue(js3.getDigestAlgorithm().equals("SHA-1")); + assertTrue(js3.getSignatureAlgorithm().equals("SHA1withRSA")); + assertTrue(js3.getTsa().equals(tsa)); + assertTrue(js3.getSignerName().equals("DUKE")); + assertTrue(js3.getProperty("tsadigestalg").equals("SHA-512")); + assertTrue(js3.getProperty("tsapolicyid").equals("1.2.3.4")); + assertTrue(js3.getProperty("internalsf").equals("true")); + assertTrue(js3.getProperty("sectionsonly").equals("true")); + assertTrue(js3.getProperty("altsigner").equals("MyContentSigner")); + assertTrue(js3.getProperty("altsignerpath") == null); + + assertTrue(JarSigner.Builder.getDefaultDigestAlgorithm().equals("SHA-256")); + + // Calculating large DSA and RSA keys are too slow. + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(1024); + assertTrue(JarSigner.Builder + .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate()) + .equals("SHA256withRSA")); + + kpg = KeyPairGenerator.getInstance("DSA"); + kpg.initialize(1024); + assertTrue(JarSigner.Builder + .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate()) + .equals("SHA256withDSA")); + + kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(192); + assertTrue(JarSigner.Builder + .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate()) + .equals("SHA256withECDSA")); + kpg.initialize(384); + assertTrue(JarSigner.Builder + .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate()) + .equals("SHA384withECDSA")); + kpg.initialize(571); + assertTrue(JarSigner.Builder + .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate()) + .equals("SHA512withECDSA")); + } + + interface RunnableWithException { + void run() throws Exception; + } + + static void uoe(RunnableWithException r) throws Exception { + checkException(r, UnsupportedOperationException.class); + } + + static void nsae(RunnableWithException r) throws Exception { + checkException(r, NoSuchAlgorithmException.class); + } + + static void npe(RunnableWithException r) throws Exception { + checkException(r, NullPointerException.class); + } + + static void iae(RunnableWithException r) throws Exception { + checkException(r, IllegalArgumentException.class); + } + + static void checkException(RunnableWithException r, Class ex) + throws Exception { + try { + r.run(); + } catch (Exception e) { + if (ex.isAssignableFrom(e.getClass())) { + return; + } + throw e; + } + throw new Exception("No exception thrown"); + } + + static void assertTrue(boolean x) throws Exception { + if (!x) throw new Exception("Not true"); + } + + static class MyContentSigner extends ContentSigner { + @Override + public byte[] generateSignedData( + ContentSignerParameters parameters, + boolean omitContent, + boolean applyTimestamp) throws NoSuchAlgorithmException, + CertificateException, IOException { + return new byte[0]; + } + } +} diff --git a/jdk/test/sun/security/tools/jarsigner/Options.java b/jdk/test/sun/security/tools/jarsigner/Options.java new file mode 100644 index 00000000000..e70903d06e3 --- /dev/null +++ b/jdk/test/sun/security/tools/jarsigner/Options.java @@ -0,0 +1,137 @@ +/* + * 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. + * + * 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 8056174 + * @summary Make sure the jarsigner tool still works after it's modified to + * be based on JarSigner API + * @library /lib/testlibrary + * @modules java.base/sun.security.tools.keytool + * jdk.jartool/sun.security.tools.jarsigner + * java.base/sun.security.pkcs + * java.base/sun.security.x509 + */ + +import com.sun.jarsigner.ContentSigner; +import com.sun.jarsigner.ContentSignerParameters; +import jdk.testlibrary.JarUtils; +import sun.security.pkcs.PKCS7; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class Options { + + public static void main(String[] args) throws Exception { + + // Prepares raw file + Files.write(Paths.get("a"), "a".getBytes()); + + // Pack + JarUtils.createJar("a.jar", "a"); + + // Prepare a keystore + sun.security.tools.keytool.Main.main( + ("-keystore jks -storepass changeit -keypass changeit -dname" + + " CN=A -alias a -genkeypair -keyalg rsa").split(" ")); + + // -altsign + sun.security.tools.jarsigner.Main.main( + ("-debug -signedjar altsign.jar -keystore jks -storepass changeit" + + " -altsigner Options$X a.jar a").split(" ")); + + try (JarFile jf = new JarFile("altsign.jar")) { + JarEntry je = jf.getJarEntry("META-INF/A.RSA"); + try (InputStream is = jf.getInputStream(je)) { + if (!Arrays.equals(is.readAllBytes(), "1234".getBytes())) { + throw new Exception("altsign go wrong"); + } + } + } + + // -sigfile, -digestalg, -sigalg, -internalsf, -sectionsonly + sun.security.tools.jarsigner.Main.main( + ("-debug -signedjar new.jar -keystore jks -storepass changeit" + + " -sigfile olala -digestalg SHA1 -sigalg SHA224withRSA" + + " -internalsf -sectionsonly a.jar a").split(" ")); + + try (JarFile jf = new JarFile("new.jar")) { + JarEntry je = jf.getJarEntry("META-INF/OLALA.SF"); + Objects.requireNonNull(je); // check -sigfile + byte[] sf = null; // content of .SF + try (InputStream is = jf.getInputStream(je)) { + sf = is.readAllBytes(); // save for later comparison + Attributes attrs = new Manifest(new ByteArrayInputStream(sf)) + .getMainAttributes(); + // check -digestalg + if (!attrs.containsKey(new Attributes.Name( + "SHA1-Digest-Manifest-Main-Attributes"))) { + throw new Exception("digestalg incorrect"); + } + // check -sectionsonly + if (attrs.containsKey(new Attributes.Name( + "SHA1-Digest-Manifest"))) { + throw new Exception("SF should not have file digest"); + } + } + + je = jf.getJarEntry("META-INF/OLALA.RSA"); + try (InputStream is = jf.getInputStream(je)) { + PKCS7 p7 = new PKCS7(is.readAllBytes()); + String alg = p7.getSignerInfos()[0] + .getDigestAlgorithmId().getName(); + if (!alg.equals("SHA-224")) { // check -sigalg + throw new Exception("PKCS7 signing is using " + alg); + } + // check -internalsf + if (!Arrays.equals(sf, p7.getContentInfo().getData())) { + throw new Exception("SF not in RSA"); + } + } + + } + + // TSA-related ones are checked in ts.sh + } + + public static class X extends ContentSigner { + @Override + public byte[] generateSignedData(ContentSignerParameters parameters, + boolean omitContent, boolean applyTimestamp) + throws NoSuchAlgorithmException, CertificateException, + IOException { + return "1234".getBytes(); + } + } +} From cb8623ad3ba1ad5a81ee21af1087ff976872b57c Mon Sep 17 00:00:00 2001 From: Amanda Jiang Date: Thu, 19 Nov 2015 19:46:46 -0800 Subject: [PATCH 07/12] 8048357: PKCS basic tests Reviewed-by: weijun --- .../pkcs/pkcs10/PKCS10AttrEncoding.java | 144 +++++++++ .../pkcs/pkcs10/PKCS10AttributeReader.java | 131 ++++++++ .../security/pkcs/pkcs7/PKCS7VerifyTest.java | 117 +++++++ .../sun/security/pkcs/pkcs7/SignerOrder.java | 274 ++++++++++++++++ .../pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF | 82 +++++ .../jarsigner/META-INF/PKCS7TEST.DSA.base64 | 60 ++++ .../pkcs7/jarsigner/META-INF/PKCS7TEST.SF | 82 +++++ .../sun/security/pkcs/pkcs8/PKCS8Test.java | 294 ++++++++++++++++++ 8 files changed, 1184 insertions(+) create mode 100644 jdk/test/sun/security/pkcs/pkcs10/PKCS10AttrEncoding.java create mode 100644 jdk/test/sun/security/pkcs/pkcs10/PKCS10AttributeReader.java create mode 100644 jdk/test/sun/security/pkcs/pkcs7/PKCS7VerifyTest.java create mode 100644 jdk/test/sun/security/pkcs/pkcs7/SignerOrder.java create mode 100644 jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF create mode 100644 jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.DSA.base64 create mode 100644 jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.SF create mode 100644 jdk/test/sun/security/pkcs/pkcs8/PKCS8Test.java diff --git a/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttrEncoding.java b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttrEncoding.java new file mode 100644 index 00000000000..d9ecc2bf435 --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttrEncoding.java @@ -0,0 +1,144 @@ +/* + * 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. + * + * 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 8048357 + * @summary test DER encoding of PKCS10 attributes + * @modules java.base/sun.security.pkcs + * java.base/sun.security.pkcs10 + * java.base/sun.security.util + * java.base/sun.security.x509 + * @compile -XDignore.symbol.file PKCS10AttrEncoding.java + * @run main PKCS10AttrEncoding + */ +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Signature; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.HashMap; +import sun.security.pkcs.PKCS9Attribute; +import sun.security.pkcs10.PKCS10; +import sun.security.pkcs10.PKCS10Attribute; +import sun.security.pkcs10.PKCS10Attributes; +import sun.security.util.ObjectIdentifier; +import sun.security.x509.X500Name; +import sun.security.x509.X509Key; + +public class PKCS10AttrEncoding { + + static final ObjectIdentifier[] ids = { + PKCS9Attribute.CONTENT_TYPE_OID, // ContentType + PKCS9Attribute.SIGNING_TIME_OID, // SigningTime + PKCS9Attribute.CHALLENGE_PASSWORD_OID // ChallengePassword + }; + static int failedCount = 0; + static HashMap constructedMap = new HashMap<>(); + + public static void main(String[] args) throws Exception { + + // initializations + int len = ids.length; + Object[] values = { + new ObjectIdentifier("1.2.3.4"), + new GregorianCalendar(1970, 1, 25, 8, 56, 7).getTime(), + "challenging" + }; + for (int j = 0; j < len; j++) { + constructedMap.put(ids[j], values[j]); + } + + X500Name subject = new X500Name("cn=Test"); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); + String sigAlg = "DSA"; + + keyGen.initialize(512); + + KeyPair pair = keyGen.generateKeyPair(); + X509Key publicKey = (X509Key) pair.getPublic(); + PrivateKey privateKey = pair.getPrivate(); + + Signature signature = Signature.getInstance(sigAlg); + signature.initSign(privateKey); + + // Create the PKCS10 request + PKCS10Attribute[] attrs = new PKCS10Attribute[len]; + for (int j = 0; j < len; j++) { + attrs[j] = new PKCS10Attribute(ids[j], values[j]); + } + PKCS10 req = new PKCS10(publicKey, new PKCS10Attributes(attrs)); + System.out.println("List of attributes in constructed PKCS10 " + + "request: "); + checkAttributes(req.getAttributes().getElements()); + + // Encode the PKCS10 request and generate another PKCS10 request from + // the encoded byte array + req.encodeAndSign(subject, signature); + PKCS10 resp = new PKCS10(req.getEncoded()); + System.out.println("List of attributes in DER encoded PKCS10 Request:"); + checkAttributes(resp.getAttributes().getElements()); + + if (failedCount > 0) { + throw new RuntimeException("Attributes Compared : Failed"); + } + System.out.println("Attributes Compared : Pass"); + } + + static void checkAttributes(Enumeration attrs) { + int numOfAttrs = 0; + while (attrs.hasMoreElements()) { + numOfAttrs ++; + PKCS10Attribute attr = (PKCS10Attribute) attrs.nextElement(); + + if (constructedMap.containsKey(attr.getAttributeId())) { + if (constructedMap.get(attr.getAttributeId()). + equals(attr.getAttributeValue())) { + System.out.print("AttributeId: " + attr.getAttributeId()); + System.out.println(" AttributeValue: " + + attr.getAttributeValue()); + } else { + failedCount++; + System.out.print("< AttributeId: " + attr.getAttributeId()); + System.out.println(" AttributeValue: " + constructedMap. + get(attr.getAttributeId())); + System.out.print("< AttributeId: " + attr.getAttributeId()); + System.out.println(" AttributeValue: " + + attr.getAttributeValue()); + } + } else { + failedCount++; + System.out.println("No " + attr.getAttributeId() + + " in DER encoded PKCS10 Request"); + } + } + if(numOfAttrs != constructedMap.size()){ + failedCount++; + System.out.println("Incorrect number of attributes."); + + } + System.out.println(); + } + +} diff --git a/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttributeReader.java b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttributeReader.java new file mode 100644 index 00000000000..aef650c68a5 --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs10/PKCS10AttributeReader.java @@ -0,0 +1,131 @@ +/* + * 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. + * + * 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 8048357 + * @summary Read in a file containing a DER encoded PKCS10 certificate request, + * flanked with "begin" and "end" lines. + * @modules java.base/sun.security.pkcs + * java.base/sun.security.pkcs10 + * java.base/sun.security.util + * @compile -XDignore.symbol.file PKCS10AttributeReader.java + * @run main PKCS10AttributeReader + */ +import java.util.Base64; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Date; +import sun.security.pkcs.PKCS9Attribute; +import sun.security.pkcs10.PKCS10Attribute; +import sun.security.pkcs10.PKCS10Attributes; +import sun.security.util.DerInputStream; +import sun.security.util.ObjectIdentifier; + +/* + Tests only reads DER encoding files, contents of corresponding asn.1 files + are copied below for reference. + + # An attribute set for testing with PKCS10. + + {A0 # implicit tag + {SEQ # Content Type + {OID 1.2.840.113549.1.9.3} + {SET + {OID "1234"} + } + } + {SEQ # Challenge Password + {OID 1.2.840.113549.1.9.7} + {SET + {T61String "GuessWhoAmI"} + } + } + {SEQ # Signing Time + {OID 1.2.840.113549.1.9.5} + {SET + {UTCTime "970422145010Z"} + } + } + } + */ +public class PKCS10AttributeReader { + // DER encoded files are binary files, to avoid attaching binary files, + // DER files were encoded in base64 + static final String ATTRIBS = "oE8wEwYJKoZIhvcNAQkDMQYGBDEyMzQwGgYJKoZIhv" + + "cNAQkHMQ0UC0d1ZXNzV2hv\nQW1JMBwGCSqGSIb3DQEJBTEPFw05NzA0MjIxND" + + "UwMTBa"; + + public static void main(String[] args) throws Exception { + + // Decode base64 encoded DER file + byte[] pkcs10Bytes = Base64.getMimeDecoder().decode(ATTRIBS.getBytes()); + + HashMap RequestStander = new HashMap() { + { + put(PKCS9Attribute.CHALLENGE_PASSWORD_OID, "GuessWhoAmI"); + put(PKCS9Attribute.SIGNING_TIME_OID, new Date(861720610000L)); + put(PKCS9Attribute.CONTENT_TYPE_OID, + new ObjectIdentifier("1.9.50.51.52")); + } + }; + + int invalidNum = 0; + PKCS10Attributes resp = new PKCS10Attributes( + new DerInputStream(pkcs10Bytes)); + Enumeration eReq = resp.getElements(); + int numOfAttrs = 0; + while (eReq.hasMoreElements()) { + numOfAttrs++; + PKCS10Attribute attr = (PKCS10Attribute) eReq.nextElement(); + if (RequestStander.containsKey(attr.getAttributeId())) { + if (RequestStander.get(attr.getAttributeId()) + .equals(attr.getAttributeValue())) { + System.out.println(attr.getAttributeId() + " " + + attr.getAttributeValue()); + } else { + invalidNum++; + System.out.println("< " + attr.getAttributeId() + " " + + attr.getAttributeValue()); + System.out.println("< " + attr.getAttributeId() + " " + + RequestStander.get(attr.getAttributeId())); + } + } else { + invalidNum++; + System.out.println("No" + attr.getAttributeId() + + "in Certificate Request list"); + } + } + if (numOfAttrs != RequestStander.size()) { + invalidNum++; + System.out.println("Incorrect number of attributes."); + } + System.out.println(); + if (invalidNum > 0) { + throw new RuntimeException( + "Attributes Compared with Stander :" + " Failed"); + } + System.out.println("Attributes Compared with Stander: Pass"); + } + +} diff --git a/jdk/test/sun/security/pkcs/pkcs7/PKCS7VerifyTest.java b/jdk/test/sun/security/pkcs/pkcs7/PKCS7VerifyTest.java new file mode 100644 index 00000000000..868bdc7bc51 --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs7/PKCS7VerifyTest.java @@ -0,0 +1,117 @@ +/* + * 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. + * + * 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 8048357 + * @summary Read signed data in one or more PKCS7 objects from individual files, + * verify SignerInfos and certificate chain. + * @modules java.base/sun.security.pkcs + * @run main PKCS7VerifyTest PKCS7TEST.DSA.base64 + * @run main PKCS7VerifyTest PKCS7TEST.DSA.base64 PKCS7TEST.SF + */ +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import sun.security.pkcs.PKCS7; +import sun.security.pkcs.SignerInfo; + +public class PKCS7VerifyTest { + + static final String TESTSRC = System.getProperty("test.src", "."); + static final String FS = File.separator; + static final String FILEPATH = TESTSRC + FS + "jarsigner" + FS + "META-INF" + + FS; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + throw new RuntimeException("usage: java JarVerify "); + } + + // The command " java PKCS7VerifyTest file1 [file2] " + // treats file1 as containing the DER encoding of a PKCS7 signed data + // object. If file2 is absent, the program verifies that some signature + // (SignerInfo) file1 correctly signs the data contained in the + // ContentInfo component of the PKCS7 object encoded by file1. If file2 + // is present, the program verifies file1 contains a correct signature + // for the contents of file2. + + PKCS7 pkcs7; + byte[] data; + + // to avoid attaching binary DSA file, the DSA file was encoded + // in Base64, decode encoded Base64 DSA file below + byte[] base64Bytes = Files.readAllBytes(Paths.get(FILEPATH + args[0])); + pkcs7 = new PKCS7(new ByteArrayInputStream( + Base64.getMimeDecoder().decode(base64Bytes))); + if (args.length < 2) { + data = null; + } else { + data = Files.readAllBytes(Paths.get(FILEPATH + args[1])); + + } + + SignerInfo[] signerInfos = pkcs7.verify(data); + + if (signerInfos == null) { + throw new RuntimeException("no signers verify"); + } + System.out.println("Verifying SignerInfos:"); + for (SignerInfo signerInfo : signerInfos) { + System.out.println(signerInfo.toString()); + } + + X509Certificate certs[] = pkcs7.getCertificates(); + + HashMap certTable = new HashMap(certs.length); + for (X509Certificate cert : certs) { + certTable.put(cert.getSubjectDN().toString(), cert); + } + + // try to verify all the certs + for (Map.Entry entry : certTable.entrySet()) { + + X509Certificate cert = entry.getValue(); + X509Certificate issuerCert = certTable + .get(cert.getIssuerDN().toString()); + + System.out.println("Subject: " + cert.getSubjectDN()); + if (issuerCert == null) { + System.out.println("Issuer certificate not found"); + } else { + System.out.println("Issuer: " + cert.getIssuerDN()); + cert.verify(issuerCert.getPublicKey()); + System.out.println("Cert verifies."); + } + System.out.println(); + } + } + +} diff --git a/jdk/test/sun/security/pkcs/pkcs7/SignerOrder.java b/jdk/test/sun/security/pkcs/pkcs7/SignerOrder.java new file mode 100644 index 00000000000..e80a76d4f9b --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs7/SignerOrder.java @@ -0,0 +1,274 @@ +/* + * 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. + * + * 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 8048357 + * @summary test PKCS7 data signing, encoding and verification + * @modules java.base/sun.security.pkcs + * java.base/sun.security.util + * java.base/sun.security.x509 + * @run main SignerOrder + */ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.X509Certificate; +import java.util.Date; +import sun.misc.HexDumpEncoder; +import sun.security.pkcs.ContentInfo; +import sun.security.pkcs.PKCS7; +import sun.security.pkcs.SignerInfo; +import sun.security.util.DerOutputStream; +import sun.security.x509.AlgorithmId; +import sun.security.x509.CertificateAlgorithmId; +import sun.security.x509.CertificateSerialNumber; +import sun.security.x509.CertificateValidity; +import sun.security.x509.CertificateVersion; +import sun.security.x509.CertificateX509Key; +import sun.security.x509.X500Name; +import sun.security.x509.X509CertImpl; +import sun.security.x509.X509CertInfo; +import sun.security.x509.X509Key; + +public class SignerOrder { + + static final HexDumpEncoder hexDump = new HexDumpEncoder(); + + //signer infos + static final byte[] data1 = "12345".getBytes(); + static final byte[] data2 = "abcde".getBytes(); + + public static void main(String[] argv) throws Exception { + + SignerInfo[] signerInfos = new SignerInfo[9]; + SimpleSigner signer1 = new SimpleSigner(null, null, null, null); + signerInfos[8] = signer1.genSignerInfo(data1); + signerInfos[7] = signer1.genSignerInfo(new byte[]{}); + signerInfos[6] = signer1.genSignerInfo(data2); + + SimpleSigner signer2 = new SimpleSigner(null, null, null, null); + signerInfos[5] = signer2.genSignerInfo(data1); + signerInfos[4] = signer2.genSignerInfo(new byte[]{}); + signerInfos[3] = signer2.genSignerInfo(data2); + + SimpleSigner signer3 = new SimpleSigner(null, null, null, null); + signerInfos[2] = signer3.genSignerInfo(data1); + signerInfos[1] = signer3.genSignerInfo(new byte[]{}); + signerInfos[0] = signer3.genSignerInfo(data2); + + ContentInfo contentInfo = new ContentInfo(data1); + + AlgorithmId[] algIds = {new AlgorithmId(AlgorithmId.SHA256_oid)}; + + X509Certificate[] certs = {signer3.getCert(), signer2.getCert(), + signer1.getCert()}; + + PKCS7 pkcs71 = new PKCS7(algIds, contentInfo, + certs, + signerInfos); + + System.out.println("SignerInfos in original."); + printSignerInfos(pkcs71.getSignerInfos()); + + DerOutputStream out = new DerOutputStream(); + pkcs71.encodeSignedData(out); + + PKCS7 pkcs72 = new PKCS7(out.toByteArray()); + System.out.println("\nSignerInfos read back in:"); + printSignerInfos(pkcs72.getSignerInfos()); + + System.out.println("Verified signers of original:"); + SignerInfo[] verifs1 = pkcs71.verify(); + + System.out.println("Verified signers of after read-in:"); + SignerInfo[] verifs2 = pkcs72.verify(); + + if (verifs1.length != verifs2.length) { + throw new RuntimeException("Length or Original vs read-in " + + "should be same"); + } + } + + static void printSignerInfos(SignerInfo signerInfo) throws IOException { + ByteArrayOutputStream strm = new ByteArrayOutputStream(); + signerInfo.derEncode(strm); + System.out.println("SignerInfo, length: " + + strm.toByteArray().length); + System.out.println(hexDump.encode(strm.toByteArray())); + System.out.println("\n"); + strm.reset(); + } + + static void printSignerInfos(SignerInfo[] signerInfos) throws IOException { + ByteArrayOutputStream strm = new ByteArrayOutputStream(); + for (int i = 0; i < signerInfos.length; i++) { + signerInfos[i].derEncode(strm); + System.out.println("SignerInfo[" + i + "], length: " + + strm.toByteArray().length); + System.out.println(hexDump.encode(strm.toByteArray())); + System.out.println("\n"); + strm.reset(); + } + } + +} + +/** + * A simple extension of sun.security.x509.X500Signer that adds a no-fuss + * signing algorithm. + */ +class SimpleSigner { + + private final Signature sig; + private final X500Name agent; + private final AlgorithmId digestAlgId; + private final AlgorithmId encryptionAlgId; + private final AlgorithmId algId; // signature algid; + //combines digest + encryption + private final X509Key publicKey; + private final PrivateKey privateKey; + private final X509Certificate cert; + + public SimpleSigner(String digestAlg, + String encryptionAlg, + KeyPair keyPair, + X500Name agent) throws Exception { + + if (agent == null) { + agent = new X500Name("cn=test"); + } + if (digestAlg == null) { + digestAlg = "SHA"; + } + if (encryptionAlg == null) { + encryptionAlg = "DSA"; + } + if (keyPair == null) { + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance(encryptionAlg); + keyGen.initialize(1024); + keyPair = keyGen.generateKeyPair(); + } + publicKey = (X509Key) keyPair.getPublic(); + privateKey = keyPair.getPrivate(); + + if ("DSA".equals(encryptionAlg)) { + this.sig = Signature.getInstance(encryptionAlg); + } else { // RSA + this.sig = Signature.getInstance(digestAlg + "/" + encryptionAlg); + } + this.sig.initSign(privateKey); + + this.agent = agent; + this.digestAlgId = AlgorithmId.get(digestAlg); + this.encryptionAlgId = AlgorithmId.get(encryptionAlg); + this.algId = AlgorithmId.get(this.sig.getAlgorithm()); + + this.cert = getSelfCert(); + } + + /** + * Take the data and sign it. + * + * @param buf buffer holding the next chunk of the data to be signed + * @param offset starting point of to-be-signed data + * @param len how many bytes of data are to be signed + * @return the signature for the input data. + * @exception SignatureException on errors. + */ + public byte[] simpleSign(byte[] buf, int offset, int len) + throws SignatureException { + sig.update(buf, offset, len); + return sig.sign(); + } + + /** + * Returns the digest algorithm used to sign. + */ + public AlgorithmId getDigestAlgId() { + return digestAlgId; + } + + /** + * Returns the encryption algorithm used to sign. + */ + public AlgorithmId getEncryptionAlgId() { + return encryptionAlgId; + } + + /** + * Returns the name of the signing agent. + */ + public X500Name getSigner() { + return agent; + } + + public X509Certificate getCert() { + return cert; + } + + private X509Certificate getSelfCert() throws Exception { + long validity = 1000; + X509CertImpl certLocal; + Date firstDate, lastDate; + + firstDate = new Date(); + lastDate = new Date(); + lastDate.setTime(lastDate.getTime() + validity + 1000); + + CertificateValidity interval = new CertificateValidity(firstDate, + lastDate); + + X509CertInfo info = new X509CertInfo(); + // Add all mandatory attributes + info.set(X509CertInfo.VERSION, + new CertificateVersion(CertificateVersion.V1)); + info.set(X509CertInfo.SERIAL_NUMBER, + new CertificateSerialNumber( + (int) (firstDate.getTime() / 1000))); + info.set(X509CertInfo.ALGORITHM_ID, + new CertificateAlgorithmId(algId)); + info.set(X509CertInfo.SUBJECT, agent); + info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey)); + info.set(X509CertInfo.VALIDITY, interval); + info.set(X509CertInfo.ISSUER, agent); + + certLocal = new X509CertImpl(info); + certLocal.sign(privateKey, algId.getName()); + + return certLocal; + } + + public SignerInfo genSignerInfo(byte[] data) throws SignatureException { + return new SignerInfo((X500Name) cert.getIssuerDN(), + new BigInteger("" + cert.getSerialNumber()), + getDigestAlgId(), algId, + simpleSign(data, 0, data.length)); + } +} diff --git a/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..6be546d4daf --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/MANIFEST.MF @@ -0,0 +1,82 @@ +Manifest-Version: 1.0 + +Name: CheckCerts.class +Digest-Algorithms: SHA +SHA-Digest: xLygljhRro6990piIVEilVI8szQ= + +Name: ContentInfoTest.class +Digest-Algorithms: SHA +SHA-Digest: TSVdEMQW2gdFi6qeba+UixdHSdo= + +Name: JarVerify.class +Digest-Algorithms: SHA +SHA-Digest: Wg+PiDzunNGH4KrWAp00/okp39s= + +Name: JarVerify2.class +Digest-Algorithms: SHA +SHA-Digest: 5uYBQxwGWgYmNBwhnWRbymeXmWM= + +Name: PKCS7Read.class +Digest-Algorithms: SHA +SHA-Digest: JPIxttHBfRpQaFyiQJ2Wfkvj/ls= + +Name: PKCS7Test.class +Digest-Algorithms: SHA +SHA-Digest: R64SXXgZrOvGiO/eMsfG/T1Vn30= + +Name: PKCS7Test10.class +Digest-Algorithms: SHA +SHA-Digest: 2R0yxuxRHTPqdAzJJcrvqkpbQgo= + +Name: PKCS7Test11.class +Digest-Algorithms: SHA +SHA-Digest: /0HcwnpQi0hwJsJtvt5peWFGvtc= + +Name: PKCS7Test12.class +Digest-Algorithms: SHA +SHA-Digest: s5CcqimfRqR9CW25tFBY0JK3RVU= + +Name: PKCS7Test2.class +Digest-Algorithms: SHA +SHA-Digest: 71VkFEMUle5sjXNFbSW31F1ZJ58= + +Name: PKCS7Test3.class +Digest-Algorithms: SHA +SHA-Digest: mU/D5C6SgPRmwoLQzwF5VnN3aqM= + +Name: PKCS7Test4.class +Digest-Algorithms: SHA +SHA-Digest: ss9NFvxF8emaEjdKdvtzWXfs0/E= + +Name: PKCS7Test5.class +Digest-Algorithms: SHA +SHA-Digest: DHvQ20UAXoYgfCPAOeCOrglsJwU= + +Name: PKCS7Test6.class +Digest-Algorithms: SHA +SHA-Digest: aiCb8chroH7XDaNfAz6wr57lXsA= + +Name: PKCS7Test7.class +Digest-Algorithms: SHA +SHA-Digest: UoieXLC68alFgfD/Q1NW9/r2kaY= + +Name: PKCS7Test8.class +Digest-Algorithms: SHA +SHA-Digest: eMW7mq5b/KVB1M5L76wcV1+uFQs= + +Name: PKCS7Test9.class +Digest-Algorithms: SHA +SHA-Digest: EEWCZG1creWjqVZVIEgr0on3y6A= + +Name: SignerInfoTest.class +Digest-Algorithms: SHA +SHA-Digest: l6SNfpnFipGg8gy4XqY3HhA0RrY= + +Name: SignerInfoTest2.class +Digest-Algorithms: SHA +SHA-Digest: 5jbzlkZqXKNmmmE+pcjQka8D6WE= + +Name: SimpleSigner.class +Digest-Algorithms: SHA +SHA-Digest: l9ODQHY4wxhIvLw4/B0qe9NjwxQ= + diff --git a/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.DSA.base64 b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.DSA.base64 new file mode 100644 index 00000000000..f084beb89b6 --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.DSA.base64 @@ -0,0 +1,60 @@ +MIILKAYJKoZIhvcNAQcCoIILGTCCCxUCAQExCzAJBgUrDgMCGgUAMIIHbQYJKoZI +hvcNAQcBoIIHXgSCB1pTaWduYXR1cmUtVmVyc2lvbjogMS4wDQoNCk5hbWU6IENo +ZWNrQ2VydHMuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdl +c3Q6IHlhMXh3dnNRTytEUnBRYnczRmgyblJCMkpRYz0NCg0KTmFtZTogQ29udGVu +dEluZm9UZXN0LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGln +ZXN0OiBDYStFSmFrVTZ6dzRLQWhvcWNuQ3BOcWsyTEk9DQoNCk5hbWU6IEphclZl +cmlmeS5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDog +K0RHYVdXa25md2U0Wk9wc29NVEZ6ZldSdmhRPQ0KDQpOYW1lOiBKYXJWZXJpZnky +LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiBHcUR6 +WXlZNFAvV0g1SEt2aVdxWHR0UGc1ckU9DQoNCk5hbWU6IFBLQ1M3UmVhZC5jbGFz +cw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogUW1mOEs5aFhW +bHdJZFBZNm52MmpGUGZHcWtBPQ0KDQpOYW1lOiBQS0NTN1Rlc3QuY2xhc3MNCkRp +Z2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IEdiZS9nenl2MkY1OGY2 +RUVoU1oxQnFHWHRsbz0NCg0KTmFtZTogUEtDUzdUZXN0MTAuY2xhc3MNCkRpZ2Vz +dC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IDh3QnFXLy9lVzJzTlJJOTFi +TFlFT29kY2dhRT0NCg0KTmFtZTogUEtDUzdUZXN0MTEuY2xhc3MNCkRpZ2VzdC1B +bGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IGJYaExLRXNsY3VFWGk0dS9haGdU +MnE2dGNFVT0NCg0KTmFtZTogUEtDUzdUZXN0MTIuY2xhc3MNCkRpZ2VzdC1BbGdv +cml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IDlLRVkxYjUyUUxtTjBxei81ejB3QkZy +T216MD0NCg0KTmFtZTogUEtDUzdUZXN0Mi5jbGFzcw0KRGlnZXN0LUFsZ29yaXRo +bXM6IFNIQQ0KU0hBLURpZ2VzdDogK1VhMzIvMlE4RjJiclFRbVNYWCtYUytNL2g0 +PQ0KDQpOYW1lOiBQS0NTN1Rlc3QzLmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczog +U0hBDQpTSEEtRGlnZXN0OiAwSFhVWnlhU2ZkZUtlZThuWnpFalJTeXJldTQ9DQoN +Ck5hbWU6IFBLQ1M3VGVzdDQuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEEN +ClNIQS1EaWdlc3Q6IEo3eXJTMjRvS3VTZ2F1dHZkemhxQmo3ZGJjUT0NCg0KTmFt +ZTogUEtDUzdUZXN0NS5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hB +LURpZ2VzdDogSlR2OVdTb3gxTEVTUjJMcTdzMFVxU2x0RFNRPQ0KDQpOYW1lOiBQ +S0NTN1Rlc3Q2LmNsYXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGln +ZXN0OiBnR3Yra05oK3UzSFExdHp4bGNBVzdTcEZUS2s9DQoNCk5hbWU6IFBLQ1M3 +VGVzdDcuY2xhc3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6 +IGZpSEYxYUExYWN6czFPd0V5OEc3VkMrcjdMST0NCg0KTmFtZTogUEtDUzdUZXN0 +OC5jbGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogNzRU +VzdJOVZPdzVWZ0x2aFJtRGZxRVd2ZkFRPQ0KDQpOYW1lOiBQS0NTN1Rlc3Q5LmNs +YXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiAxY0JJbkdU +Y08xQVFaKy8wdmhGa2laV3dsQTA9DQoNCk5hbWU6IFNpZ25lckluZm9UZXN0LmNs +YXNzDQpEaWdlc3QtQWxnb3JpdGhtczogU0hBDQpTSEEtRGlnZXN0OiBjRlk0Q3RT +anphMUErV2pBS05TVnF1cGpSWUU9DQoNCk5hbWU6IFNpZ25lckluZm9UZXN0Mi5j +bGFzcw0KRGlnZXN0LUFsZ29yaXRobXM6IFNIQQ0KU0hBLURpZ2VzdDogYU5NMEZQ +MHpFelF6eGxYeDZxQ0J4dWtta0hRPQ0KDQpOYW1lOiBTaW1wbGVTaWduZXIuY2xh +c3MNCkRpZ2VzdC1BbGdvcml0aG1zOiBTSEENClNIQS1EaWdlc3Q6IC9MV0NzbkM3 +TVpNUjZHb3czeTJjdnA3STBTTT0NCg0KoIICvzCCArswggJ3AgUA59UzNDALBgcq +hkjOOAQDBQAwdTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlD +dXBlcnRpbm8xGTAXBgNVBAoTEFN1biBNaWNyb3N5c3RlbXMxETAPBgNVBAsTCEph +dmFTb2Z0MRcwFQYDVQQDEw5Eb3VnbGFzIEhvb3ZlcjAeFw05NzEwMDIxODEyMDda +Fw05NzEyMzExNzEyMDdaMHUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAG +A1UEBxMJQ3VwZXJ0aW5vMRkwFwYDVQQKExBTdW4gTWljcm9zeXN0ZW1zMREwDwYD +VQQLEwhKYXZhU29mdDEXMBUGA1UEAxMORG91Z2xhcyBIb292ZXIwggFRMIHoBgcq +hkjOOAQBMIHcAmEA6eZCWZ01XzfJf/01ZxILjiXJzUPpJ7OpZw++xdiQFBki0sOz +rSSACTeZhp0ehGqrSfqwrSbSzmoiIZ1HC859d31KIfvpwnC1f2BwAvPO+Dk2lM9F +7jaIwRqMVqsSej2vAhUAnNvYTJ8awvOND4D0KrlS5zOL9RECYDBHCtWgBfsUzi2d +zYfji8fRscX6y67L6V8ZCqejHSPE27y+BhdFREAaWywCCWXYwr0hcdNmhEV3H3S6 +CE0gKdg8HBWFR/Op8aJxW+I9Ua5NPlofanBk8xaTOjRtP1KSUgNkAAJhAMN5uB+B +ZJ0W2UjXMyKoFUFXRYiLpnaSw63kl9tKnR9R5rEreiyHQ5IelPxjwCHGgTbYK0y+ +xKTGHVWiQN/YJmHLbSrcSSM/d89aR/sVbGoAwQOyYraFGUNIOTQjjXcXCjALBgcq +hkjOOAQDBQADMQAwLgIVAJxmL029GLXDJVbk72d4cSPQ4/rvAhUAll9UPl8aOMEg +V4egANhwbynMGSgxgc4wgcsCAQEwfjB1MQswCQYDVQQGEwJVUzELMAkGA1UECBMC +Q0ExEjAQBgNVBAcTCUN1cGVydGlubzEZMBcGA1UEChMQU3VuIE1pY3Jvc3lzdGVt +czERMA8GA1UECxMISmF2YVNvZnQxFzAVBgNVBAMTDkRvdWdsYXMgSG9vdmVyAgUA +59UzNDAJBgUrDgMCGgUAMAsGByqGSM44BAMFAAQuMCwCFDmry17kzDD6Y5X1BqIS +lq6swckPAhRtiXvBHa5CRGjbwk8yqf9hGgZfFA== diff --git a/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.SF b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.SF new file mode 100644 index 00000000000..05a79382189 --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs7/jarsigner/META-INF/PKCS7TEST.SF @@ -0,0 +1,82 @@ +Signature-Version: 1.0 + +Name: CheckCerts.class +Digest-Algorithms: SHA +SHA-Digest: ya1xwvsQO+DRpQbw3Fh2nRB2JQc= + +Name: ContentInfoTest.class +Digest-Algorithms: SHA +SHA-Digest: Ca+EJakU6zw4KAhoqcnCpNqk2LI= + +Name: JarVerify.class +Digest-Algorithms: SHA +SHA-Digest: +DGaWWknfwe4ZOpsoMTFzfWRvhQ= + +Name: JarVerify2.class +Digest-Algorithms: SHA +SHA-Digest: GqDzYyY4P/WH5HKviWqXttPg5rE= + +Name: PKCS7Read.class +Digest-Algorithms: SHA +SHA-Digest: Qmf8K9hXVlwIdPY6nv2jFPfGqkA= + +Name: PKCS7Test.class +Digest-Algorithms: SHA +SHA-Digest: Gbe/gzyv2F58f6EEhSZ1BqGXtlo= + +Name: PKCS7Test10.class +Digest-Algorithms: SHA +SHA-Digest: 8wBqW//eW2sNRI91bLYEOodcgaE= + +Name: PKCS7Test11.class +Digest-Algorithms: SHA +SHA-Digest: bXhLKEslcuEXi4u/ahgT2q6tcEU= + +Name: PKCS7Test12.class +Digest-Algorithms: SHA +SHA-Digest: 9KEY1b52QLmN0qz/5z0wBFrOmz0= + +Name: PKCS7Test2.class +Digest-Algorithms: SHA +SHA-Digest: +Ua32/2Q8F2brQQmSXX+XS+M/h4= + +Name: PKCS7Test3.class +Digest-Algorithms: SHA +SHA-Digest: 0HXUZyaSfdeKee8nZzEjRSyreu4= + +Name: PKCS7Test4.class +Digest-Algorithms: SHA +SHA-Digest: J7yrS24oKuSgautvdzhqBj7dbcQ= + +Name: PKCS7Test5.class +Digest-Algorithms: SHA +SHA-Digest: JTv9WSox1LESR2Lq7s0UqSltDSQ= + +Name: PKCS7Test6.class +Digest-Algorithms: SHA +SHA-Digest: gGv+kNh+u3HQ1tzxlcAW7SpFTKk= + +Name: PKCS7Test7.class +Digest-Algorithms: SHA +SHA-Digest: fiHF1aA1aczs1OwEy8G7VC+r7LI= + +Name: PKCS7Test8.class +Digest-Algorithms: SHA +SHA-Digest: 74TW7I9VOw5VgLvhRmDfqEWvfAQ= + +Name: PKCS7Test9.class +Digest-Algorithms: SHA +SHA-Digest: 1cBInGTcO1AQZ+/0vhFkiZWwlA0= + +Name: SignerInfoTest.class +Digest-Algorithms: SHA +SHA-Digest: cFY4CtSjza1A+WjAKNSVqupjRYE= + +Name: SignerInfoTest2.class +Digest-Algorithms: SHA +SHA-Digest: aNM0FP0zEzQzxlXx6qCBxukmkHQ= + +Name: SimpleSigner.class +Digest-Algorithms: SHA +SHA-Digest: /LWCsnC7MZMR6Gow3y2cvp7I0SM= + diff --git a/jdk/test/sun/security/pkcs/pkcs8/PKCS8Test.java b/jdk/test/sun/security/pkcs/pkcs8/PKCS8Test.java new file mode 100644 index 00000000000..25396734e5d --- /dev/null +++ b/jdk/test/sun/security/pkcs/pkcs8/PKCS8Test.java @@ -0,0 +1,294 @@ +/* + * 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. + * + * 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 8048357 + * @summary PKCS8 Standards Conformance Tests + * @modules java.base/sun.security.pkcs + * java.base/sun.security.util + * java.base/sun.security.provider + * java.base/sun.security.x509 + * java.base/sun.misc + * @compile -XDignore.symbol.file PKCS8Test.java + * @run main PKCS8Test + */ +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.util.Arrays; +import sun.misc.HexDumpEncoder; +import sun.security.pkcs.PKCS8Key; +import sun.security.provider.DSAPrivateKey; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import sun.security.x509.AlgorithmId; + +import static java.lang.System.out; + +public class PKCS8Test { + + static final HexDumpEncoder hexDump = new HexDumpEncoder(); + + static final DerOutputStream derOutput = new DerOutputStream(); + + static final String FORMAT = "PKCS#8"; + static final String EXPECTED_ALG_ID_CHRS = "DSA\n\tp: 02\n\tq: 03\n" + + "\tg: 04\n"; + static final String ALGORITHM = "DSA"; + static final String EXCEPTION_MESSAGE = "version mismatch: (supported: " + + "00, parsed: 01"; + + // test second branch in byte[] encode() + // DER encoding,include (empty) set of attributes + static final int[] NEW_ENCODED_KEY_INTS = { 0x30, + // length 30 = 0x1e + 0x1e, + // first element + // version Version (= INTEGER) + 0x02, + // length 1 + 0x01, + // value 0 + 0x00, + // second element + // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier + // (sequence) + // (an object identifier?) + 0x30, + // length 18 + 0x12, + // contents + // object identifier, 5 bytes + 0x06, 0x05, + // { 1 3 14 3 2 12 } + 0x2b, 0x0e, 0x03, 0x02, 0x0c, + // sequence, 9 bytes + 0x30, 0x09, + // integer 2 + 0x02, 0x01, 0x02, + // integer 3 + 0x02, 0x01, 0x03, + // integer 4 + 0x02, 0x01, 0x04, + // third element + // privateKey PrivateKey (= OCTET STRING) + 0x04, + // length + 0x03, + // privateKey contents + 0x02, 0x01, 0x01, + // 4th (optional) element -- attributes [0] IMPLICIT Attributes + // OPTIONAL + // (Attributes = SET OF Attribute) Here, it will be empty. + 0xA0, + // length + 0x00 }; + + // encoding originally created, but with the version changed + static final int[] NEW_ENCODED_KEY_INTS_2 = { + // sequence + 0x30, + // length 28 = 0x1c + 0x1c, + // first element + // version Version (= INTEGER) + 0x02, + // length 1 + 0x01, + // value 1 (illegal) + 0x01, + // second element + // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier + // (sequence) + // (an object identifier?) + 0x30, + // length 18 + 0x12, + // contents + // object identifier, 5 bytes + 0x06, 0x05, + // { 1 3 14 3 2 12 } + 0x2b, 0x0e, 0x03, 0x02, 0x0c, + // sequence, 9 bytes + 0x30, 0x09, + // integer 2 + 0x02, 0x01, 0x02, + // integer 3 + 0x02, 0x01, 0x03, + // integer 4 + 0x02, 0x01, 0x04, + // third element + // privateKey PrivateKey (= OCTET STRING) + 0x04, + // length + 0x03, + // privateKey contents + 0x02, 0x01, 0x01 }; + + // 0000: 30 1E 02 01 00 30 14 06 07 2A 86 48 CE 38 04 01 0....0...*.H.8.. + // 0010: 30 09 02 01 02 02 01 03 02 01 04 04 03 02 01 01 0............... + static final int[] EXPECTED = { 0x30, + // length 30 = 0x1e + 0x1e, + // first element + // version Version (= INTEGER) + 0x02, + // length 1 + 0x01, + // value 0 + 0x00, + // second element + // privateKeyAlgorithmIdentifier PrivateKeyAlgorithmIdentifier + // (sequence) + // (an object identifier?) + 0x30, 0x14, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x01, + // integer 2 + 0x30, 0x09, 0x02, + // integer 3 + 0x01, 0x02, 0x02, + // integer 4 + 0x01, 0x03, 0x02, + // third element + // privateKey PrivateKey (= OCTET STRING) + 0x01, + // length + 0x04, + // privateKey contents + 0x04, 0x03, 0x02, + // 4th (optional) element -- attributes [0] IMPLICIT Attributes + // OPTIONAL + // (Attributes = SET OF Attribute) Here, it will be empty. + 0x01, + // length + 0x01 }; + + static void raiseException(String expected, String received) { + throw new RuntimeException( + "Expected " + expected + "; Received " + received); + } + + public static void main(String[] args) + throws IOException, InvalidKeyException { + + byte[] encodedKey = getEncodedKey(); + byte[] expectedBytes = new byte[EXPECTED.length]; + for (int i = 0; i < EXPECTED.length; i++) { + expectedBytes[i] = (byte) EXPECTED[i]; + } + + dumpByteArray("encodedKey :", encodedKey); + if (!Arrays.equals(encodedKey, expectedBytes)) { + raiseException(new String(expectedBytes), new String(encodedKey)); + } + + PKCS8Key decodedKey = PKCS8Key.parse(new DerValue(encodedKey)); + String alg = decodedKey.getAlgorithm(); + AlgorithmId algId = decodedKey.getAlgorithmId(); + out.println("Algorithm :" + alg); + out.println("AlgorithmId: " + algId); + + if (!ALGORITHM.equals(alg)) { + raiseException(ALGORITHM, alg); + } + if (!EXPECTED_ALG_ID_CHRS.equalsIgnoreCase(algId.toString())) { + raiseException(EXPECTED_ALG_ID_CHRS, algId.toString()); + } + + decodedKey.encode(derOutput); + dumpByteArray("Stream encode: ", derOutput.toByteArray()); + if (!Arrays.equals(derOutput.toByteArray(), expectedBytes)) { + raiseException(new String(expectedBytes), derOutput.toString()); + } + + dumpByteArray("byte[] encoding: ", decodedKey.getEncoded()); + if (!Arrays.equals(decodedKey.getEncoded(), expectedBytes)) { + raiseException(new String(expectedBytes), + new String(decodedKey.getEncoded())); + } + + if (!FORMAT.equals(decodedKey.getFormat())) { + raiseException(FORMAT, decodedKey.getFormat()); + } + + try { + byte[] newEncodedKey = new byte[NEW_ENCODED_KEY_INTS.length]; + for (int i = 0; i < newEncodedKey.length; i++) { + newEncodedKey[i] = (byte) NEW_ENCODED_KEY_INTS[i]; + } + PKCS8Key newDecodedKey = PKCS8Key + .parse(new DerValue(newEncodedKey)); + + throw new RuntimeException( + "key1: Expected an IOException during " + "parsing"); + } catch (IOException e) { + System.out.println("newEncodedKey: should have excess data due to " + + "attributes, which are not supported"); + } + + try { + byte[] newEncodedKey2 = new byte[NEW_ENCODED_KEY_INTS_2.length]; + for (int i = 0; i < newEncodedKey2.length; i++) { + newEncodedKey2[i] = (byte) NEW_ENCODED_KEY_INTS_2[i]; + } + + PKCS8Key newDecodedKey2 = PKCS8Key + .parse(new DerValue(newEncodedKey2)); + + throw new RuntimeException( + "key2: Expected an IOException during " + "parsing"); + } catch (IOException e) { + out.println("Key 2: should be illegal version"); + out.println(e.getMessage()); + if (!EXCEPTION_MESSAGE.equals(e.getMessage())) { + throw new RuntimeException("Key2: expected: " + + EXCEPTION_MESSAGE + " get: " + e.getMessage()); + } + } + + } + + // get a byte array from somewhere + static byte[] getEncodedKey() throws InvalidKeyException { + BigInteger p = BigInteger.valueOf(1); + BigInteger q = BigInteger.valueOf(2); + BigInteger g = BigInteger.valueOf(3); + BigInteger x = BigInteger.valueOf(4); + + DSAPrivateKey priv = new DSAPrivateKey(p, q, g, x); + return priv.getEncoded(); + } + + static void dumpByteArray(String nm, byte[] bytes) throws IOException { + out.println(nm + " length: " + bytes.length); + hexDump.encodeBuffer(bytes, out); + } + + static String toString(PKCS8Key key) { + StringBuilder builder = new StringBuilder(key.getAlgorithm()); + builder.append('\n').append("parameters:") + .append(key.getAlgorithmId().toString()); + return builder.toString(); + } + +} From 5b2c88e28a2d8d2debf051ff74829f4661b83f8f Mon Sep 17 00:00:00 2001 From: Michael Haupt Date: Fri, 20 Nov 2015 15:34:12 +0100 Subject: [PATCH 08/12] 8139885: implement JEP 274: enhanced method handles Reviewed-by: jrose, psandoz, vlivanov --- .../java/lang/invoke/MethodHandle.java | 125 +- .../java/lang/invoke/MethodHandleImpl.java | 272 ++++- .../java/lang/invoke/MethodHandles.java | 1025 +++++++++++++++- .../classes/java/lang/invoke/MethodType.java | 21 +- .../java/lang/invoke/AccessControlTest.java | 43 +- jdk/test/java/lang/invoke/BigArityTest.java | 322 ++++- .../lang/invoke/FindClassSecurityManager.java | 44 + .../java/lang/invoke/MethodHandlesTest.java | 485 +++++++- jdk/test/java/lang/invoke/T8139885.java | 1082 +++++++++++++++++ .../lang/invoke/findclass.security.policy | 9 + 10 files changed, 3297 insertions(+), 131 deletions(-) create mode 100644 jdk/test/java/lang/invoke/FindClassSecurityManager.java create mode 100644 jdk/test/java/lang/invoke/T8139885.java create mode 100644 jdk/test/java/lang/invoke/findclass.security.policy diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java index 67017b6b02a..3be1b7ea7df 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandle.java @@ -872,13 +872,54 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray * @see #asCollector */ public MethodHandle asSpreader(Class arrayType, int arrayLength) { - MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength); - int arity = type().parameterCount(); - int spreadArgPos = arity - arrayLength; + return asSpreader(type().parameterCount() - arrayLength, arrayType, arrayLength); + } + + /** + * Makes an array-spreading method handle, which accepts an array argument at a given position and spreads + * its elements as positional arguments in place of the array. The new method handle adapts, as its target, + * the current method handle. The type of the adapter will be the same as the type of the target, except that the + * {@code arrayLength} parameters of the target's type, starting at the zero-based position {@code spreadArgPos}, + * are replaced by a single array parameter of type {@code arrayType}. + *

+ * This method behaves very much like {@link #asSpreader(Class, int)}, but accepts an additional {@code spreadArgPos} + * argument to indicate at which position in the parameter list the spreading should take place. + *

+ * @apiNote Example: + *

{@code
+    MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class));
+    MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2);
+    Object[] ints = new Object[]{3, 9, 7, 7};
+    Comparator cmp = (a, b) -> a - b;
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0);
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0);
+    assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0);
+     * }
+ * @param spreadArgPos the position (zero-based index) in the argument list at which spreading should start. + * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments + * @param arrayLength the number of arguments to spread from an incoming array argument + * @return a new method handle which spreads an array argument at a given position, + * before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type, + * or if target does not have at least + * {@code arrayLength} parameter types, + * or if {@code arrayLength} is negative, + * or if {@code spreadArgPos} has an illegal value (negative, or together with arrayLength exceeding the + * number of arguments), + * or if the resulting method handle's type would have + * too many parameters + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * + * @see #asSpreader(Class, int) + * @since 9 + */ + public MethodHandle asSpreader(int spreadArgPos, Class arrayType, int arrayLength) { + MethodType postSpreadType = asSpreaderChecks(arrayType, spreadArgPos, arrayLength); MethodHandle afterSpread = this.asType(postSpreadType); BoundMethodHandle mh = afterSpread.rebind(); LambdaForm lform = mh.editor().spreadArgumentsForm(1 + spreadArgPos, arrayType, arrayLength); - MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, arity, arrayType); + MethodType preSpreadType = postSpreadType.replaceParameterTypes(spreadArgPos, spreadArgPos + arrayLength, arrayType); return mh.copyWith(preSpreadType, lform); } @@ -886,15 +927,18 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray * See if {@code asSpreader} can be validly called with the given arguments. * Return the type of the method handle call after spreading but before conversions. */ - private MethodType asSpreaderChecks(Class arrayType, int arrayLength) { + private MethodType asSpreaderChecks(Class arrayType, int pos, int arrayLength) { spreadArrayChecks(arrayType, arrayLength); int nargs = type().parameterCount(); if (nargs < arrayLength || arrayLength < 0) throw newIllegalArgumentException("bad spread array length"); + if (pos < 0 || pos + arrayLength > nargs) { + throw newIllegalArgumentException("bad spread position"); + } Class arrayElement = arrayType.getComponentType(); MethodType mtype = type(); boolean match = true, fail = false; - for (int i = nargs - arrayLength; i < nargs; i++) { + for (int i = pos; i < arrayLength; i++) { Class ptype = mtype.parameterType(i); if (ptype != arrayElement) { match = false; @@ -905,7 +949,7 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray } } if (match) return mtype; - MethodType needType = mtype.asSpreaderType(arrayType, arrayLength); + MethodType needType = mtype.asSpreaderType(arrayType, pos, arrayLength); if (!fail) return needType; // elicit an error: this.asType(needType); @@ -998,10 +1042,53 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123)); * @see #asVarargsCollector */ public MethodHandle asCollector(Class arrayType, int arrayLength) { - asCollectorChecks(arrayType, arrayLength); - int collectArgPos = type().parameterCount() - 1; + return asCollector(type().parameterCount() - 1, arrayType, arrayLength); + } + + /** + * Makes an array-collecting method handle, which accepts a given number of positional arguments starting + * at a given position, and collects them into an array argument. The new method handle adapts, as its + * target, the current method handle. The type of the adapter will be the same as the type of the target, + * except that the parameter at the position indicated by {@code collectArgPos} (usually of type {@code arrayType}) + * is replaced by {@code arrayLength} parameters whose type is element type of {@code arrayType}. + *

+ * This method behaves very much like {@link #asCollector(Class, int)}, but differs in that its {@code + * collectArgPos} argument indicates at which position in the parameter list arguments should be collected. This + * index is zero-based. + *

+ * @apiNote Examples: + *

{@code
+    StringWriter swr = new StringWriter();
+    MethodHandle swWrite = LOOKUP.findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)).bindTo(swr);
+    MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4);
+    swWrite4.invoke('A', 'B', 'C', 'D', 1, 2);
+    assertEquals("BC", swr.toString());
+    swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4);
+    assertEquals("BCPQRS", swr.toString());
+    swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1);
+    assertEquals("BCPQRSZ", swr.toString());
+     * }
+ * @param collectArgPos the zero-based position in the parameter list at which to start collecting. + * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments + * @param arrayLength the number of arguments to collect into a new array argument + * @return a new method handle which collects some arguments + * into an array, before calling the original method handle + * @throws NullPointerException if {@code arrayType} is a null reference + * @throws IllegalArgumentException if {@code arrayType} is not an array type + * or {@code arrayType} is not assignable to this method handle's array parameter type, + * or {@code arrayLength} is not a legal array size, + * or {@code collectArgPos} has an illegal value (negative, or greater than the number of arguments), + * or the resulting method handle's type would have + * too many parameters + * @throws WrongMethodTypeException if the implied {@code asType} call fails + * + * @see #asCollector(Class, int) + * @since 9 + */ + public MethodHandle asCollector(int collectArgPos, Class arrayType, int arrayLength) { + asCollectorChecks(arrayType, collectArgPos, arrayLength); BoundMethodHandle mh = rebind(); - MethodType resultType = type().asCollectorType(arrayType, arrayLength); + MethodType resultType = type().asCollectorType(arrayType, collectArgPos, arrayLength); MethodHandle newArray = MethodHandleImpl.varargsArray(arrayType, arrayLength); LambdaForm lform = mh.editor().collectArgumentArrayForm(1 + collectArgPos, newArray); if (lform != null) { @@ -1015,15 +1102,18 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123)); * See if {@code asCollector} can be validly called with the given arguments. * Return false if the last parameter is not an exact match to arrayType. */ - /*non-public*/ boolean asCollectorChecks(Class arrayType, int arrayLength) { + /*non-public*/ boolean asCollectorChecks(Class arrayType, int pos, int arrayLength) { spreadArrayChecks(arrayType, arrayLength); int nargs = type().parameterCount(); - if (nargs != 0) { - Class lastParam = type().parameterType(nargs-1); - if (lastParam == arrayType) return true; - if (lastParam.isAssignableFrom(arrayType)) return false; + if (pos < 0 || pos >= nargs) { + throw newIllegalArgumentException("bad collect position"); } - throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType); + if (nargs != 0) { + Class param = type().parameterType(pos); + if (param == arrayType) return true; + if (param.isAssignableFrom(arrayType)) return false; + } + throw newIllegalArgumentException("array type not assignable to argument", this, arrayType); } /** @@ -1178,7 +1268,7 @@ assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0))); */ public MethodHandle asVarargsCollector(Class arrayType) { Objects.requireNonNull(arrayType); - boolean lastMatch = asCollectorChecks(arrayType, 0); + boolean lastMatch = asCollectorChecks(arrayType, type().parameterCount() - 1, 0); if (isVarargsCollector() && lastMatch) return this; return MethodHandleImpl.makeVarargsCollector(this, arrayType); @@ -1341,7 +1431,6 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString()); // cannot be cracked into MethodHandleInfo. assert viewAsTypeChecks(newType, strict); BoundMethodHandle mh = rebind(); - assert(!((MethodHandle)mh instanceof DirectMethodHandle)); return mh.copyWith(newType, mh.form); } diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index a07896b7752..7ad49cd38aa 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -27,16 +27,17 @@ package java.lang.invoke; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; +import java.util.List; import java.util.function.Function; +import java.util.stream.Collectors; import sun.invoke.empty.Empty; import sun.invoke.util.ValueConversions; import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; -import jdk.internal.HotSpotIntrinsicCandidate; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import static java.lang.invoke.LambdaForm.*; @@ -1297,7 +1298,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; @Override public MethodHandle asCollector(Class arrayType, int arrayLength) { if (intrinsicName == Intrinsic.IDENTITY) { - MethodType resultType = type().asCollectorType(arrayType, arrayLength); + MethodType resultType = type().asCollectorType(arrayType, type().parameterCount() - 1, arrayLength); MethodHandle newArray = MethodHandleImpl.varargsArray(arrayType, arrayLength); return newArray.asType(resultType); } @@ -1619,17 +1620,251 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; } } + /** + * Assembles a loop method handle from the given handles and type information. This works by binding and configuring + * the {@linkplain #looper(MethodHandle[], MethodHandle[], MethodHandle[], MethodHandle[], int, int, Object[]) "most + * generic loop"}. + * + * @param tloop the return type of the loop. + * @param targs types of the arguments to be passed to the loop. + * @param tvars types of loop-local variables. + * @param init sanitized array of initializers for loop-local variables. + * @param step sanitited array of loop bodies. + * @param pred sanitized array of predicates. + * @param fini sanitized array of loop finalizers. + * + * @return a handle that, when invoked, will execute the loop. + */ + static MethodHandle makeLoop(Class tloop, List> targs, List> tvars, List init, + List step, List pred, List fini) { + MethodHandle[] ainit = toArrayArgs(init); + MethodHandle[] astep = toArrayArgs(step); + MethodHandle[] apred = toArrayArgs(pred); + MethodHandle[] afini = toArrayArgs(fini); + + MethodHandle l = getConstantHandle(MH_looper); + + // Bind the statically known arguments. + l = MethodHandles.insertArguments(l, 0, ainit, astep, apred, afini, tvars.size(), targs.size()); + + // Turn the args array into an argument list. + l = l.asCollector(Object[].class, targs.size()); + + // Finally, make loop type. + MethodType loopType = MethodType.methodType(tloop, targs); + l = l.asType(loopType); + + return l; + } + + /** + * Converts all handles in the {@code hs} array to handles that accept an array of arguments. + * + * @param hs method handles to be converted. + * + * @return the {@code hs} array, with all method handles therein converted. + */ + static MethodHandle[] toArrayArgs(List hs) { + return hs.stream().map(h -> h.asSpreader(Object[].class, h.type().parameterCount())).toArray(MethodHandle[]::new); + } + + /** + * This method embodies the most generic loop for use by {@link MethodHandles#loop(MethodHandle[][])}. A handle on + * it will be transformed into a handle on a concrete loop instantiation by {@link #makeLoop}. + * + * @param init loop-local variable initializers. + * @param step bodies. + * @param pred predicates. + * @param fini finalizers. + * @param varSize number of loop-local variables. + * @param nArgs number of arguments passed to the loop. + * @param args arguments to the loop invocation. + * + * @return the result of executing the loop. + */ + static Object looper(MethodHandle[] init, MethodHandle[] step, MethodHandle[] pred, MethodHandle[] fini, + int varSize, int nArgs, Object[] args) throws Throwable { + Object[] varsAndArgs = new Object[varSize + nArgs]; + for (int i = 0, v = 0; i < init.length; ++i) { + if (init[i].type().returnType() == void.class) { + init[i].invoke(args); + } else { + varsAndArgs[v++] = init[i].invoke(args); + } + } + System.arraycopy(args, 0, varsAndArgs, varSize, nArgs); + final int nSteps = step.length; + for (; ; ) { + for (int i = 0, v = 0; i < nSteps; ++i) { + MethodHandle p = pred[i]; + MethodHandle s = step[i]; + MethodHandle f = fini[i]; + if (s.type().returnType() == void.class) { + s.invoke(varsAndArgs); + } else { + varsAndArgs[v++] = s.invoke(varsAndArgs); + } + if (!(boolean) p.invoke(varsAndArgs)) { + return f.invoke(varsAndArgs); + } + } + } + } + + /** + * This method is bound as the predicate in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle, + * MethodHandle) counting loops}. + * + * @param counter the counter parameter, passed in during loop execution. + * @param limit the upper bound of the parameter, statically bound at loop creation time. + * + * @return whether the counter has reached the limit. + */ + static boolean countedLoopPredicate(int counter, int limit) { + return counter <= limit; + } + + /** + * This method is bound as the step function in {@linkplain MethodHandles#countedLoop(MethodHandle, MethodHandle, + * MethodHandle) counting loops} to increment the counter. + * + * @param counter the loop counter. + * + * @return the loop counter incremented by 1. + */ + static int countedLoopStep(int counter, int limit) { + return counter + 1; + } + + /** + * This is bound to initialize the loop-local iterator in {@linkplain MethodHandles#iteratedLoop iterating loops}. + * + * @param it the {@link Iterable} over which the loop iterates. + * + * @return an {@link Iterator} over the argument's elements. + */ + static Iterator initIterator(Iterable it) { + return it.iterator(); + } + + /** + * This method is bound as the predicate in {@linkplain MethodHandles#iteratedLoop iterating loops}. + * + * @param it the iterator to be checked. + * + * @return {@code true} iff there are more elements to iterate over. + */ + static boolean iteratePredicate(Iterator it) { + return it.hasNext(); + } + + /** + * This method is bound as the step for retrieving the current value from the iterator in {@linkplain + * MethodHandles#iteratedLoop iterating loops}. + * + * @param it the iterator. + * + * @return the next element from the iterator. + */ + static Object iterateNext(Iterator it) { + return it.next(); + } + + /** + * Makes a {@code try-finally} handle that conforms to the type constraints. + * + * @param target the target to execute in a {@code try-finally} block. + * @param cleanup the cleanup to execute in the {@code finally} block. + * @param type the result type of the entire construct. + * @param argTypes the types of the arguments. + * + * @return a handle on the constructed {@code try-finally} block. + */ + static MethodHandle makeTryFinally(MethodHandle target, MethodHandle cleanup, Class type, List> argTypes) { + MethodHandle tf = getConstantHandle(type == void.class ? MH_tryFinallyVoidExec : MH_tryFinallyExec); + + // Bind the statically known arguments. + tf = MethodHandles.insertArguments(tf, 0, target, cleanup); + + // Turn the args array into an argument list. + tf = tf.asCollector(Object[].class, argTypes.size()); + + // Finally, make try-finally type. + MethodType tfType = MethodType.methodType(type, argTypes); + tf = tf.asType(tfType); + + return tf; + } + + /** + * A method that will be bound during construction of a {@code try-finally} handle with non-{@code void} return type + * by {@link MethodHandles#tryFinally(MethodHandle, MethodHandle)}. + * + * @param target the handle to wrap in a {@code try-finally} block. This will be bound. + * @param cleanup the handle to run in any case before returning. This will be bound. + * @param args the arguments to the call. These will remain as the argument list. + * + * @return whatever the execution of the {@code target} returned (it may have been modified by the execution of + * {@code cleanup}). + * @throws Throwable in case anything is thrown by the execution of {@code target}, the {@link Throwable} will be + * passed to the {@code cleanup} handle, which may decide to throw any exception it sees fit. + */ + static Object tryFinallyExecutor(MethodHandle target, MethodHandle cleanup, Object[] args) throws Throwable { + Throwable t = null; + Object r = null; + try { + r = target.invoke(args); + } catch (Throwable thrown) { + t = thrown; + throw t; + } finally { + r = cleanup.invoke(t, r, args); + } + return r; + } + + /** + * A method that will be bound during construction of a {@code try-finally} handle with {@code void} return type by + * {@link MethodHandles#tryFinally(MethodHandle, MethodHandle)}. + * + * @param target the handle to wrap in a {@code try-finally} block. This will be bound. + * @param cleanup the handle to run in any case before returning. This will be bound. + * @param args the arguments to the call. These will remain as the argument list. + * + * @throws Throwable in case anything is thrown by the execution of {@code target}, the {@link Throwable} will be + * passed to the {@code cleanup} handle, which may decide to throw any exception it sees fit. + */ + static void tryFinallyVoidExecutor(MethodHandle target, MethodHandle cleanup, Object[] args) throws Throwable { + Throwable t = null; + try { + target.invoke(args); + } catch (Throwable thrown) { + t = thrown; + throw t; + } finally { + cleanup.invoke(t, args); + } + } + // Indexes into constant method handles: - private static final int + static final int MH_cast = 0, MH_selectAlternative = 1, MH_copyAsPrimitiveArray = 2, MH_fillNewTypedArray = 3, MH_fillNewArray = 4, MH_arrayIdentity = 5, - MH_LIMIT = 6; + MH_looper = 6, + MH_countedLoopPred = 7, + MH_countedLoopStep = 8, + MH_iteratePred = 9, + MH_initIterator = 10, + MH_iterateNext = 11, + MH_tryFinallyExec = 12, + MH_tryFinallyVoidExec = 13, + MH_LIMIT = 14; - private static MethodHandle getConstantHandle(int idx) { + static MethodHandle getConstantHandle(int idx) { MethodHandle handle = HANDLES[idx]; if (handle != null) { return handle; @@ -1672,6 +1907,31 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; return makeIntrinsic(IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "selectAlternative", MethodType.methodType(MethodHandle.class, boolean.class, MethodHandle.class, MethodHandle.class)), Intrinsic.SELECT_ALTERNATIVE); + case MH_looper: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "looper", MethodType.methodType(Object.class, + MethodHandle[].class, MethodHandle[].class, MethodHandle[].class, MethodHandle[].class, + int.class, int.class, Object[].class)); + case MH_countedLoopPred: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "countedLoopPredicate", + MethodType.methodType(boolean.class, int.class, int.class)); + case MH_countedLoopStep: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "countedLoopStep", + MethodType.methodType(int.class, int.class, int.class)); + case MH_iteratePred: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iteratePredicate", + MethodType.methodType(boolean.class, Iterator.class)); + case MH_initIterator: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "initIterator", + MethodType.methodType(Iterator.class, Iterable.class)); + case MH_iterateNext: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "iterateNext", + MethodType.methodType(Object.class, Iterator.class)); + case MH_tryFinallyExec: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "tryFinallyExecutor", + MethodType.methodType(Object.class, MethodHandle.class, MethodHandle.class, Object[].class)); + case MH_tryFinallyVoidExec: + return IMPL_LOOKUP.findStatic(MethodHandleImpl.class, "tryFinallyVoidExecutor", + MethodType.methodType(void.class, MethodHandle.class, MethodHandle.class, Object[].class)); } } catch (ReflectiveOperationException ex) { throw newInternalError(ex); diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 13520e45f1c..8378ab9e1c3 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -26,10 +26,7 @@ package java.lang.invoke; import java.lang.reflect.*; -import java.util.BitSet; -import java.util.List; -import java.util.Arrays; -import java.util.Objects; +import java.util.*; import sun.invoke.util.ValueConversions; import sun.invoke.util.VerifyAccess; @@ -39,11 +36,13 @@ import sun.reflect.Reflection; import sun.reflect.misc.ReflectUtil; import sun.security.util.SecurityConstants; import java.lang.invoke.LambdaForm.BasicType; -import static java.lang.invoke.LambdaForm.BasicType.*; + import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandleImpl.Intrinsic; import static java.lang.invoke.MethodHandleNatives.Constants.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * This class consists exclusively of static methods that operate on or return @@ -176,7 +175,7 @@ public class MethodHandles { * equivalent of a particular bytecode behavior. * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.) * Here is a summary of the correspondence between these factory methods and - * the behavior the resulting method handles: + * the behavior of the resulting method handles: * * * @@ -235,6 +234,10 @@ public class MethodHandles { * * * + * + * + * + * *
lookup expression{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}({@code static})?
{@code T m(A*);}
{@code (T) aMethod.invoke(thisOrNull, arg*);}
{@link java.lang.invoke.MethodHandles.Lookup#findClass lookup.findClass("C")}{@code class C { ... }}{@code C.class;}
* * Here, the type {@code C} is the class or interface being searched for a member, @@ -255,6 +258,10 @@ public class MethodHandles { * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand * for reflective objects corresponding to the given members. *

+ * The bytecode behavior for a {@code findClass} operation is a load of a constant class, + * as if by {@code ldc CONSTANT_Class}. + * The behavior is represented, not as a method handle, but directly as a {@code Class} constant. + *

* In cases where the given member is of variable arity (i.e., a method or constructor) * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}. * In all other cases, the returned method handle will be of fixed arity. @@ -423,7 +430,7 @@ public class MethodHandles { * and the Core Reflection API * (as found on {@link java.lang.Class Class}). *

- * If a security manager is present, member lookups are subject to + * If a security manager is present, member and class lookups are subject to * additional checks. * From one to three calls are made to the security manager. * Any of these calls can refuse access by throwing a @@ -433,6 +440,8 @@ public class MethodHandles { * {@code refc} as the containing class in which the member * is being sought, and {@code defc} as the class in which the * member is actually defined. + * (If a class or other type is being accessed, + * the {@code refc} and {@code defc} values are the class itself.) * The value {@code lookc} is defined as not present * if the current lookup object does not have * private access. @@ -444,11 +453,16 @@ public class MethodHandles { * then {@link SecurityManager#checkPackageAccess * smgr.checkPackageAccess(refcPkg)} is called, * where {@code refcPkg} is the package of {@code refc}. - *

  • Step 2: + *
  • Step 2a: * If the retrieved member is not public and * {@code lookc} is not present, then * {@link SecurityManager#checkPermission smgr.checkPermission} * with {@code RuntimePermission("accessDeclaredMembers")} is called. + *
  • Step 2b: + * If the retrieved class has a {@code null} class loader, + * and {@code lookc} is not present, then + * {@link SecurityManager#checkPermission smgr.checkPermission} + * with {@code RuntimePermission("getClassLoader")} is called. *
  • Step 3: * If the retrieved member is not public, * and if {@code lookc} is not present, @@ -458,9 +472,9 @@ public class MethodHandles { * where {@code defcPkg} is the package of {@code defc}. * * Security checks are performed after other access checks have passed. - * Therefore, the above rules presuppose a member that is public, + * Therefore, the above rules presuppose a member or class that is public, * or else that is being accessed from a lookup class that has - * rights to access the member. + * rights to access the member or class. * *

    Caller sensitive methods

    * A small number of Java methods have a special property called caller sensitivity. @@ -921,6 +935,49 @@ assertEquals("[x, y, z]", pb.command().toString()); return getDirectConstructor(refc, ctor); } + /** + * Looks up a class by name from the lookup context defined by this {@code Lookup} object. The static + * initializer of the class is not run. + * + * @param targetName the fully qualified name of the class to be looked up. + * @return the requested class. + * @exception SecurityException if a security manager is present and it + * refuses access + * @throws LinkageError if the linkage fails + * @throws ClassNotFoundException if the class does not exist. + * @throws IllegalAccessException if the class is not accessible, using the allowed access + * modes. + * @exception SecurityException if a security manager is present and it + * refuses access + * @since 9 + */ + public Class findClass(String targetName) throws ClassNotFoundException, IllegalAccessException { + Class targetClass = Class.forName(targetName, false, lookupClass.getClassLoader()); + return accessClass(targetClass); + } + + /** + * Determines if a class can be accessed from the lookup context defined by this {@code Lookup} object. The + * static initializer of the class is not run. + * + * @param targetClass the class to be access-checked + * + * @return the class that has been access-checked + * + * @throws IllegalAccessException if the class is not accessible from the lookup class, using the allowed access + * modes. + * @exception SecurityException if a security manager is present and it + * refuses access + * @since 9 + */ + public Class accessClass(Class targetClass) throws IllegalAccessException { + if (!VerifyAccess.isClassAccessible(targetClass, lookupClass, allowedModes)) { + throw new MemberName(targetClass).makeAccessException("access violation", this); + } + checkSecurityManager(targetClass, null); + return targetClass; + } + /** * Produces an early-bound method handle for a virtual method. * It will bypass checks for overriding methods on the receiver, @@ -995,7 +1052,7 @@ assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method */ public MethodHandle findSpecial(Class refc, String name, MethodType type, Class specialCaller) throws NoSuchMethodException, IllegalAccessException { - checkSpecialCaller(specialCaller); + checkSpecialCaller(specialCaller, refc); Lookup specialLookup = this.in(specialCaller); MemberName method = specialLookup.resolveOrFail(REF_invokeSpecial, refc, name, type); return specialLookup.getDirectMethod(REF_invokeSpecial, refc, method, findBoundCallerClass(method)); @@ -1224,7 +1281,7 @@ return mh1; * @throws NullPointerException if any argument is null */ public MethodHandle unreflectSpecial(Method m, Class specialCaller) throws IllegalAccessException { - checkSpecialCaller(specialCaller); + checkSpecialCaller(specialCaller, null); Lookup specialLookup = this.in(specialCaller); MemberName method = new MemberName(m, true); assert(method.isMethod()); @@ -1444,7 +1501,15 @@ return mh1; ReflectUtil.checkPackageAccess(refc); } - // Step 2: + if (m == null) { // findClass or accessClass + // Step 2b: + if (!fullPowerLookup) { + smgr.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); + } + return; + } + + // Step 2a: if (m.isPublic()) return; if (!fullPowerLookup) { smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); @@ -1557,11 +1622,13 @@ return mh1; private static final boolean ALLOW_NESTMATE_ACCESS = false; - private void checkSpecialCaller(Class specialCaller) throws IllegalAccessException { + private void checkSpecialCaller(Class specialCaller, Class refc) throws IllegalAccessException { int allowedModes = this.allowedModes; if (allowedModes == TRUSTED) return; if (!hasPrivateAccess() || (specialCaller != lookupClass() + // ensure non-abstract methods in superinterfaces can be special-invoked + && !(refc != null && refc.isInterface() && refc.isAssignableFrom(specialCaller)) && !(ALLOW_NESTMATE_ACCESS && VerifyAccess.isSamePackageMember(specialCaller, lookupClass())))) throw new MemberName(specialCaller). @@ -1888,7 +1955,7 @@ return invoker; MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { if (leadingArgCount < 0 || leadingArgCount > type.parameterCount()) throw newIllegalArgumentException("bad argument count", leadingArgCount); - type = type.asSpreaderType(Object[].class, type.parameterCount() - leadingArgCount); + type = type.asSpreaderType(Object[].class, leadingArgCount, type.parameterCount() - leadingArgCount); return type.invokers().spreadInvoker(leadingArgCount); } @@ -2924,19 +2991,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); */ public static MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { - int foldPos = 0; - MethodType targetType = target.type(); - MethodType combinerType = combiner.type(); - Class rtype = foldArgumentChecks(foldPos, targetType, combinerType); - BoundMethodHandle result = target.rebind(); - boolean dropResult = (rtype == void.class); - // Note: This may cache too many distinct LFs. Consider backing off to varargs code. - LambdaForm lform = result.editor().foldArgumentsForm(1 + foldPos, dropResult, combinerType.basicType()); - MethodType newType = targetType; - if (!dropResult) - newType = newType.dropParameterTypes(foldPos, foldPos + 1); - result = result.copyWithExtendL(newType, lform, combiner); - return result; + return foldArguments(target, 0, combiner); } private static Class foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) { @@ -2949,7 +3004,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); .equals(targetType.parameterList().subList(afterInsertPos, afterInsertPos + foldArgs)))) ok = false; - if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0)) + if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(foldPos)) ok = false; if (!ok) throw misMatchedTypes("target and combiner types", targetType, combinerType); @@ -3011,7 +3066,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); return MethodHandleImpl.makeGuardWithTest(test, target, fallback); } - static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) { + static RuntimeException misMatchedTypes(String what, T t1, T t2) { return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2); } @@ -3057,6 +3112,7 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); * the given exception type, or if the method handle types do * not match in their return types and their * corresponding parameters + * @see MethodHandles#tryFinally(MethodHandle, MethodHandle) */ public static MethodHandle catchException(MethodHandle target, @@ -3100,4 +3156,913 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); throw new ClassCastException(exType.getName()); return MethodHandleImpl.throwException(MethodType.methodType(returnType, exType)); } + + /** + * Constructs a method handle representing a loop with several loop variables that are updated and checked upon each + * iteration. Upon termination of the loop due to one of the predicates, a corresponding finalizer is run and + * delivers the loop's result, which is the return value of the resulting handle. + *

    + * Intuitively, every loop is formed by one or more "clauses", each specifying a local iteration value and/or a loop + * exit. Each iteration of the loop executes each clause in order. A clause can optionally update its iteration + * variable; it can also optionally perform a test and conditional loop exit. In order to express this logic in + * terms of method handles, each clause will determine four actions:

      + *
    • Before the loop executes, the initialization of an iteration variable or loop invariant local. + *
    • When a clause executes, an update step for the iteration variable. + *
    • When a clause executes, a predicate execution to test for loop exit. + *
    • If a clause causes a loop exit, a finalizer execution to compute the loop's return value. + *
    + *

    + * Some of these clause parts may be omitted according to certain rules, and useful default behavior is provided in + * this case. See below for a detailed description. + *

    + * Each clause function, with the exception of clause initializers, is able to observe the entire loop state, + * because it will be passed all current iteration variable values, as well as all incoming loop + * parameters. Most clause functions will not need all of this information, but they will be formally connected as + * if by {@link #dropArguments}. + *

    + * Given a set of clauses, there is a number of checks and adjustments performed to connect all the parts of the + * loop. They are spelled out in detail in the steps below. In these steps, every occurrence of the word "must" + * corresponds to a place where {@link IllegalArgumentException} may be thrown if the required constraint is not met + * by the inputs to the loop combinator. The term "effectively identical", applied to parameter type lists, means + * that they must be identical, or else one list must be a proper prefix of the other. + *

    + * Step 0: Determine clause structure.

      + *
    1. The clause array (of type {@code MethodHandle[][]} must be non-{@code null} and contain at least one element. + *
    2. The clause array may not contain {@code null}s or sub-arrays longer than four elements. + *
    3. Clauses shorter than four elements are treated as if they were padded by {@code null} elements to length + * four. Padding takes place by appending elements to the array. + *
    4. Clauses with all {@code null}s are disregarded. + *
    5. Each clause is treated as a four-tuple of functions, called "init", "step", "pred", and "fini". + *
    + *

    + * Step 1A: Determine iteration variables.

      + *
    1. Examine init and step function return types, pairwise, to determine each clause's iteration variable type. + *
    2. If both functions are omitted, use {@code void}; else if one is omitted, use the other's return type; else + * use the common return type (they must be identical). + *
    3. Form the list of return types (in clause order), omitting all occurrences of {@code void}. + *
    4. This list of types is called the "common prefix". + *
    + *

    + * Step 1B: Determine loop parameters.

      + *
    1. Examine init function parameter lists. + *
    2. Omitted init functions are deemed to have {@code null} parameter lists. + *
    3. All init function parameter lists must be effectively identical. + *
    4. The longest parameter list (which is necessarily unique) is called the "common suffix". + *
    + *

    + * Step 1C: Determine loop return type.

      + *
    1. Examine fini function return types, disregarding omitted fini functions. + *
    2. If there are no fini functions, use {@code void} as the loop return type. + *
    3. Otherwise, use the common return type of the fini functions; they must all be identical. + *
    + *

    + * Step 1D: Check other types.

      + *
    1. There must be at least one non-omitted pred function. + *
    2. Every non-omitted pred function must have a {@code boolean} return type. + *
    + *

    + * (Implementation Note: Steps 1A, 1B, 1C, 1D are logically independent of each other, and may be performed in any + * order.) + *

    + * Step 2: Determine parameter lists.

      + *
    1. The parameter list for the resulting loop handle will be the "common suffix". + *
    2. The parameter list for init functions will be adjusted to the "common suffix". (Note that their parameter + * lists are already effectively identical to the common suffix.) + *
    3. The parameter list for non-init (step, pred, and fini) functions will be adjusted to the common prefix + * followed by the common suffix, called the "common parameter sequence". + *
    4. Every non-init, non-omitted function parameter list must be effectively identical to the common parameter + * sequence. + *
    + *

    + * Step 3: Fill in omitted functions.

      + *
    1. If an init function is omitted, use a {@linkplain #constant constant function} of the appropriate + * {@code null}/zero/{@code false}/{@code void} type. (For this purpose, a constant {@code void} is simply a + * function which does nothing and returns {@code void}; it can be obtained from another constant function by + * {@linkplain MethodHandle#asType type conversion}.) + *
    2. If a step function is omitted, use an {@linkplain #identity identity function} of the clause's iteration + * variable type; insert dropped argument parameters before the identity function parameter for the non-{@code void} + * iteration variables of preceding clauses. (This will turn the loop variable into a local loop invariant.) + *
    3. If a pred function is omitted, the corresponding fini function must also be omitted. + *
    4. If a pred function is omitted, use a constant {@code true} function. (This will keep the loop going, as far + * as this clause is concerned.) + *
    5. If a fini function is omitted, use a constant {@code null}/zero/{@code false}/{@code void} function of the + * loop return type. + *
    + *

    + * Step 4: Fill in missing parameter types.

      + *
    1. At this point, every init function parameter list is effectively identical to the common suffix, but some + * lists may be shorter. For every init function with a short parameter list, pad out the end of the list by + * {@linkplain #dropArguments dropping arguments}. + *
    2. At this point, every non-init function parameter list is effectively identical to the common parameter + * sequence, but some lists may be shorter. For every non-init function with a short parameter list, pad out the end + * of the list by {@linkplain #dropArguments dropping arguments}. + *
    + *

    + * Final observations.

      + *
    1. After these steps, all clauses have been adjusted by supplying omitted functions and arguments. + *
    2. All init functions have a common parameter type list, which the final loop handle will also have. + *
    3. All fini functions have a common return type, which the final loop handle will also have. + *
    4. All non-init functions have a common parameter type list, which is the common parameter sequence, of + * (non-{@code void}) iteration variables followed by loop parameters. + *
    5. Each pair of init and step functions agrees in their return types. + *
    6. Each non-init function will be able to observe the current values of all iteration variables, by means of the + * common prefix. + *
    + *

    + * Loop execution.

      + *
    1. When the loop is called, the loop input values are saved in locals, to be passed (as the common suffix) to + * every clause function. These locals are loop invariant. + *
    2. Each init function is executed in clause order (passing the common suffix) and the non-{@code void} values + * are saved (as the common prefix) into locals. These locals are loop varying (unless their steps are identity + * functions, as noted above). + *
    3. All function executions (except init functions) will be passed the common parameter sequence, consisting of + * the non-{@code void} iteration values (in clause order) and then the loop inputs (in argument order). + *
    4. The step and pred functions are then executed, in clause order (step before pred), until a pred function + * returns {@code false}. + *
    5. The non-{@code void} result from a step function call is used to update the corresponding loop variable. The + * updated value is immediately visible to all subsequent function calls. + *
    6. If a pred function returns {@code false}, the corresponding fini function is called, and the resulting value + * is returned from the loop as a whole. + *
    + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the types / values + * of loop variables; {@code A}/{@code a}, those of arguments passed to the resulting loop; and {@code R}, the + * result types of finalizers as well as of the resulting loop. + *

    {@code
    +     * V... init...(A...);
    +     * boolean pred...(V..., A...);
    +     * V... step...(V..., A...);
    +     * R fini...(V..., A...);
    +     * R loop(A... a) {
    +     *   V... v... = init...(a...);
    +     *   for (;;) {
    +     *     for ((v, p, s, f) in (v..., pred..., step..., fini...)) {
    +     *       v = s(v..., a...);
    +     *       if (!p(v..., a...)) {
    +     *         return f(v..., a...);
    +     *       }
    +     *     }
    +     *   }
    +     * }
    +     * }
    + *

    + * @apiNote Example: + *

    {@code
    +     * // iterative implementation of the factorial function as a loop handle
    +     * static int one(int k) { return 1; }
    +     * int inc(int i, int acc, int k) { return i + 1; }
    +     * int mult(int i, int acc, int k) { return i * acc; }
    +     * boolean pred(int i, int acc, int k) { return i < k; }
    +     * int fin(int i, int acc, int k) { return acc; }
    +     * // assume MH_one, MH_inc, MH_mult, MH_pred, and MH_fin are handles to the above methods
    +     * // null initializer for counter, should initialize to 0
    +     * MethodHandle[] counterClause = new MethodHandle[]{null, MH_inc};
    +     * MethodHandle[] accumulatorClause = new MethodHandle[]{MH_one, MH_mult, MH_pred, MH_fin};
    +     * MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause);
    +     * assertEquals(120, loop.invoke(5));
    +     * }
    + * + * @param clauses an array of arrays (4-tuples) of {@link MethodHandle}s adhering to the rules described above. + * + * @return a method handle embodying the looping behavior as defined by the arguments. + * + * @throws IllegalArgumentException in case any of the constraints described above is violated. + * + * @see MethodHandles#whileLoop(MethodHandle, MethodHandle, MethodHandle) + * @see MethodHandles#doWhileLoop(MethodHandle, MethodHandle, MethodHandle) + * @see MethodHandles#countedLoop(MethodHandle, MethodHandle, MethodHandle) + * @see MethodHandles#iteratedLoop(MethodHandle, MethodHandle, MethodHandle) + * @since 9 + */ + public static MethodHandle loop(MethodHandle[]... clauses) { + // Step 0: determine clause structure. + checkLoop0(clauses); + + List init = new ArrayList<>(); + List step = new ArrayList<>(); + List pred = new ArrayList<>(); + List fini = new ArrayList<>(); + + Stream.of(clauses).filter(c -> Stream.of(c).anyMatch(Objects::nonNull)).forEach(clause -> { + init.add(clause[0]); // all clauses have at least length 1 + step.add(clause.length <= 1 ? null : clause[1]); + pred.add(clause.length <= 2 ? null : clause[2]); + fini.add(clause.length <= 3 ? null : clause[3]); + }); + + assert Stream.of(init, step, pred, fini).map(List::size).distinct().count() == 1; + final int nclauses = init.size(); + + // Step 1A: determine iteration variables. + final List> iterationVariableTypes = new ArrayList<>(); + for (int i = 0; i < nclauses; ++i) { + MethodHandle in = init.get(i); + MethodHandle st = step.get(i); + if (in == null && st == null) { + iterationVariableTypes.add(void.class); + } else if (in != null && st != null) { + checkLoop1a(i, in, st); + iterationVariableTypes.add(in.type().returnType()); + } else { + iterationVariableTypes.add(in == null ? st.type().returnType() : in.type().returnType()); + } + } + final List> commonPrefix = iterationVariableTypes.stream().filter(t -> t != void.class). + collect(Collectors.toList()); + + // Step 1B: determine loop parameters. + final List> empty = new ArrayList<>(); + final List> commonSuffix = init.stream().filter(Objects::nonNull).map(MethodHandle::type). + map(MethodType::parameterList).reduce((p, q) -> p.size() >= q.size() ? p : q).orElse(empty); + checkLoop1b(init, commonSuffix); + + // Step 1C: determine loop return type. + // Step 1D: check other types. + final Class loopReturnType = fini.stream().filter(Objects::nonNull).map(MethodHandle::type). + map(MethodType::returnType).findFirst().orElse(void.class); + checkLoop1cd(pred, fini, loopReturnType); + + // Step 2: determine parameter lists. + final List> commonParameterSequence = new ArrayList<>(commonPrefix); + commonParameterSequence.addAll(commonSuffix); + checkLoop2(step, pred, fini, commonParameterSequence); + + // Step 3: fill in omitted functions. + for (int i = 0; i < nclauses; ++i) { + Class t = iterationVariableTypes.get(i); + if (init.get(i) == null) { + init.set(i, zeroHandle(t)); + } + if (step.get(i) == null) { + step.set(i, dropArguments(t == void.class ? zeroHandle(t) : identity(t), 0, commonPrefix.subList(0, i))); + } + if (pred.get(i) == null) { + pred.set(i, constant(boolean.class, true)); + } + if (fini.get(i) == null) { + fini.set(i, zeroHandle(t)); + } + } + + // Step 4: fill in missing parameter types. + List finit = fillParameterTypes(init, commonSuffix); + List fstep = fillParameterTypes(step, commonParameterSequence); + List fpred = fillParameterTypes(pred, commonParameterSequence); + List ffini = fillParameterTypes(fini, commonParameterSequence); + + assert finit.stream().map(MethodHandle::type).map(MethodType::parameterList). + allMatch(pl -> pl.equals(commonSuffix)); + assert Stream.of(fstep, fpred, ffini).flatMap(List::stream).map(MethodHandle::type).map(MethodType::parameterList). + allMatch(pl -> pl.equals(commonParameterSequence)); + + return MethodHandleImpl.makeLoop(loopReturnType, commonSuffix, commonPrefix, finit, fstep, fpred, ffini); + } + + private static List fillParameterTypes(List hs, final List> targetParams) { + return hs.stream().map(h -> { + int pc = h.type().parameterCount(); + int tpsize = targetParams.size(); + return pc < tpsize ? dropArguments(h, pc, targetParams.subList(pc, tpsize)) : h; + }).collect(Collectors.toList()); + } + + /** + * Constructs a {@code while} loop from an initializer, a body, and a predicate. This is a convenience wrapper for + * the {@linkplain #loop(MethodHandle[][]) generic loop combinator}. + *

    + * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}. + * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle + * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code + * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][]) + * generic loop combinator}. + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of + * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument + * passed to the loop. + *

    {@code
    +     * V init(A);
    +     * boolean pred(V, A);
    +     * V body(V, A);
    +     * V whileLoop(A a) {
    +     *   V v = init(a);
    +     *   while (pred(v, a)) {
    +     *     v = body(v, a);
    +     *   }
    +     *   return v;
    +     * }
    +     * }
    + *

    + * @apiNote Example: + *

    {@code
    +     * // implement the zip function for lists as a loop handle
    +     * List initZip(Iterator a, Iterator b) { return new ArrayList<>(); }
    +     * boolean zipPred(List zip, Iterator a, Iterator b) { return a.hasNext() && b.hasNext(); }
    +     * List zipStep(List zip, Iterator a, Iterator b) {
    +     *   zip.add(a.next());
    +     *   zip.add(b.next());
    +     *   return zip;
    +     * }
    +     * // assume MH_initZip, MH_zipPred, and MH_zipStep are handles to the above methods
    +     * MethodHandle loop = MethodHandles.doWhileLoop(MH_initZip, MH_zipPred, MH_zipStep);
    +     * List a = Arrays.asList("a", "b", "c", "d");
    +     * List b = Arrays.asList("e", "f", "g", "h");
    +     * List zipped = Arrays.asList("a", "e", "b", "f", "c", "g", "d", "h");
    +     * assertEquals(zipped, (List) loop.invoke(a.iterator(), b.iterator()));
    +     * }
    + * + *

    + * @implSpec The implementation of this method is equivalent to: + *

    {@code
    +     * MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) {
    +     *     MethodHandle[]
    +     *         checkExit = {null, null, pred, identity(init.type().returnType())},
    +     *         varBody = {init, body};
    +     *     return loop(checkExit, varBody);
    +     * }
    +     * }
    + * + * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's + * result type. Passing {@code null} or a {@code void} init function will make the loop's result type + * {@code void}. + * @param pred condition for the loop, which may not be {@code null}. + * @param body body of the loop, which may not be {@code null}. + * + * @return the value of the loop variable as the loop terminates. + * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure + * + * @see MethodHandles#loop(MethodHandle[][]) + * @since 9 + */ + public static MethodHandle whileLoop(MethodHandle init, MethodHandle pred, MethodHandle body) { + MethodHandle fin = init == null ? zeroHandle(void.class) : identity(init.type().returnType()); + MethodHandle[] checkExit = {null, null, pred, fin}; + MethodHandle[] varBody = {init, body}; + return loop(checkExit, varBody); + } + + /** + * Constructs a {@code do-while} loop from an initializer, a body, and a predicate. This is a convenience wrapper + * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}. + *

    + * The loop handle's result type is the same as the sole loop variable's, i.e., the result type of {@code init}. + * The parameter type list of {@code init} also determines that of the resulting handle. The {@code pred} handle + * must have an additional leading parameter of the same type as {@code init}'s result, and so must the {@code + * body}. These constraints follow directly from those described for the {@linkplain MethodHandles#loop(MethodHandle[][]) + * generic loop combinator}. + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of + * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument + * passed to the loop. + *

    {@code
    +     * V init(A);
    +     * boolean pred(V, A);
    +     * V body(V, A);
    +     * V doWhileLoop(A a) {
    +     *   V v = init(a);
    +     *   do {
    +     *     v = body(v, a);
    +     *   } while (pred(v, a));
    +     *   return v;
    +     * }
    +     * }
    + *

    + * @apiNote Example: + *

    {@code
    +     * // int i = 0; while (i < limit) { ++i; } return i; => limit
    +     * int zero(int limit) { return 0; }
    +     * int step(int i, int limit) { return i + 1; }
    +     * boolean pred(int i, int limit) { return i < limit; }
    +     * // assume MH_zero, MH_step, and MH_pred are handles to the above methods
    +     * MethodHandle loop = MethodHandles.doWhileLoop(MH_zero, MH_step, MH_pred);
    +     * assertEquals(23, loop.invoke(23));
    +     * }
    + * + *

    + * @implSpec The implementation of this method is equivalent to: + *

    {@code
    +     * MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) {
    +     *     MethodHandle[] clause = { init, body, pred, identity(init.type().returnType()) };
    +     *     return loop(clause);
    +     * }
    +     * }
    + * + * + * @param init initializer: it should provide the initial value of the loop variable. This controls the loop's + * result type. Passing {@code null} or a {@code void} init function will make the loop's result type + * {@code void}. + * @param pred condition for the loop, which may not be {@code null}. + * @param body body of the loop, which may not be {@code null}. + * + * @return the value of the loop variable as the loop terminates. + * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure + * + * @see MethodHandles#loop(MethodHandle[][]) + * @since 9 + */ + public static MethodHandle doWhileLoop(MethodHandle init, MethodHandle body, MethodHandle pred) { + MethodHandle fin = init == null ? zeroHandle(void.class) : identity(init.type().returnType()); + MethodHandle[] clause = {init, body, pred, fin}; + return loop(clause); + } + + /** + * Constructs a loop that runs a given number of iterations. The loop counter is an {@code int} initialized from the + * {@code iterations} handle evaluation result. The counter is passed to the {@code body} function, so that must + * accept an initial {@code int} argument. The result of the loop execution is the final value of the additional + * local state. This is a convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop + * combinator}. + *

    + * The result type and parameter type list of {@code init} determine those of the resulting handle. The {@code + * iterations} handle must accept the same parameter types as {@code init} but return an {@code int}. The {@code + * body} handle must accept the same parameter types as well, preceded by an {@code int} parameter for the counter, + * and a parameter of the same type as {@code init}'s result. These constraints follow directly from those described + * for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}. + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of + * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument + * passed to the loop. + *

    {@code
    +     * int iterations(A);
    +     * V init(A);
    +     * V body(int, V, A);
    +     * V countedLoop(A a) {
    +     *   int end = iterations(a);
    +     *   V v = init(a);
    +     *   for (int i = 0; i < end; ++i) {
    +     *     v = body(i, v, a);
    +     *   }
    +     *   return v;
    +     * }
    +     * }
    + *

    + * @apiNote Example: + *

    {@code
    +     * // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s;
    +     * // => a variation on a well known theme
    +     * String start(String arg) { return arg; }
    +     * String step(int counter, String v, String arg) { return "na " + v; }
    +     * // assume MH_start and MH_step are handles to the two methods above
    +     * MethodHandle loop = MethodHandles.countedLoop(13, MH_start, MH_step);
    +     * assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!"));
    +     * }
    + * + *

    + * @implSpec The implementation of this method is equivalent to: + *

    {@code
    +     * MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) {
    +     *     return countedLoop(null, iterations, init, body);  // null => constant zero
    +     * }
    +     * }
    + * + * @param iterations a handle to return the number of iterations this loop should run. + * @param init initializer for additional loop state. This determines the loop's result type. + * Passing {@code null} or a {@code void} init function will make the loop's result type + * {@code void}. + * @param body the body of the loop, which must not be {@code null}. + * It must accept an initial {@code int} parameter (for the counter), and then any + * additional loop-local variable plus loop parameters. + * + * @return a method handle representing the loop. + * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure + * + * @since 9 + */ + public static MethodHandle countedLoop(MethodHandle iterations, MethodHandle init, MethodHandle body) { + return countedLoop(null, iterations, init, body); + } + + /** + * Constructs a loop that counts over a range of numbers. The loop counter is an {@code int} that will be + * initialized to the {@code int} value returned from the evaluation of the {@code start} handle and run to the + * value returned from {@code end} (exclusively) with a step width of 1. The counter value is passed to the {@code + * body} function in each iteration; it has to accept an initial {@code int} parameter + * for that. The result of the loop execution is the final value of the additional local state + * obtained by running {@code init}. + * This is a + * convenience wrapper for the {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}. + *

    + * The constraints for the {@code init} and {@code body} handles are the same as for {@link + * #countedLoop(MethodHandle, MethodHandle, MethodHandle)}. Additionally, the {@code start} and {@code end} handles + * must return an {@code int} and accept the same parameters as {@code init}. + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of + * the sole loop variable as well as the result type of the loop; and {@code A}/{@code a}, that of the argument + * passed to the loop. + *

    {@code
    +     * int start(A);
    +     * int end(A);
    +     * V init(A);
    +     * V body(int, V, A);
    +     * V countedLoop(A a) {
    +     *   int s = start(a);
    +     *   int e = end(a);
    +     *   V v = init(a);
    +     *   for (int i = s; i < e; ++i) {
    +     *     v = body(i, v, a);
    +     *   }
    +     *   return v;
    +     * }
    +     * }
    + * + *

    + * @implSpec The implementation of this method is equivalent to: + *

    {@code
    +     * MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) {
    +     *     MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, int.class, int.class);
    +     *     // assume MH_increment and MH_lessThan are handles to x+1 and x
    + * + * @param start a handle to return the start value of the loop counter. + * If it is {@code null}, a constant zero is assumed. + * @param end a non-{@code null} handle to return the end value of the loop counter (the loop will run to {@code end-1}). + * @param init initializer for additional loop state. This determines the loop's result type. + * Passing {@code null} or a {@code void} init function will make the loop's result type + * {@code void}. + * @param body the body of the loop, which must not be {@code null}. + * It must accept an initial {@code int} parameter (for the counter), and then any + * additional loop-local variable plus loop parameters. + * + * @return a method handle representing the loop. + * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure + * + * @since 9 + */ + public static MethodHandle countedLoop(MethodHandle start, MethodHandle end, MethodHandle init, MethodHandle body) { + MethodHandle returnVar = dropArguments(init == null ? zeroHandle(void.class) : identity(init.type().returnType()), + 0, int.class, int.class); + MethodHandle[] indexVar = {start, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopStep)}; + MethodHandle[] loopLimit = {end, null, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_countedLoopPred), returnVar}; + MethodHandle[] bodyClause = {init, dropArguments(body, 1, int.class)}; + return loop(indexVar, loopLimit, bodyClause); + } + + /** + * Constructs a loop that ranges over the elements produced by an {@code Iterator}. + * The iterator will be produced by the evaluation of the {@code iterator} handle. + * If this handle is passed as {@code null} the method {@link Iterable#iterator} will be used instead, + * and will be applied to a leading argument of the loop handle. + * Each value produced by the iterator is passed to the {@code body}, which must accept an initial {@code T} parameter. + * The result of the loop execution is the final value of the additional local state + * obtained by running {@code init}. + *

    + * This is a convenience wrapper for the + * {@linkplain MethodHandles#loop(MethodHandle[][]) generic loop combinator}, and the constraints imposed on the {@code body} + * handle follow directly from those described for the latter. + *

    + * Here is pseudocode for the resulting loop handle. In the code, {@code V}/{@code v} represent the type / value of + * the loop variable as well as the result type of the loop; {@code T}/{@code t}, that of the elements of the + * structure the loop iterates over, and {@code A}/{@code a}, that of the argument passed to the loop. + *

    {@code
    +     * Iterator iterator(A);  // defaults to Iterable::iterator
    +     * V init(A);
    +     * V body(T,V,A);
    +     * V iteratedLoop(A a) {
    +     *   Iterator it = iterator(a);
    +     *   V v = init(a);
    +     *   for (T t : it) {
    +     *     v = body(t, v, a);
    +     *   }
    +     *   return v;
    +     * }
    +     * }
    + *

    + * The type {@code T} may be either a primitive or reference. + * Since type {@code Iterator} is erased in the method handle representation to the raw type + * {@code Iterator}, the {@code iteratedLoop} combinator adjusts the leading argument type for {@code body} + * to {@code Object} as if by the {@link MethodHandle#asType asType} conversion method. + * Therefore, if an iterator of the wrong type appears as the loop is executed, + * runtime exceptions may occur as the result of dynamic conversions performed by {@code asType}. + *

    + * @apiNote Example: + *

    {@code
    +     * // reverse a list
    +     * List reverseStep(String e, List r) {
    +     *   r.add(0, e);
    +     *   return r;
    +     * }
    +     * List newArrayList() { return new ArrayList<>(); }
    +     * // assume MH_reverseStep, MH_newArrayList are handles to the above methods
    +     * MethodHandle loop = MethodHandles.iteratedLoop(null, MH_newArrayList, MH_reverseStep);
    +     * List list = Arrays.asList("a", "b", "c", "d", "e");
    +     * List reversedList = Arrays.asList("e", "d", "c", "b", "a");
    +     * assertEquals(reversedList, (List) loop.invoke(list));
    +     * }
    + *

    + * @implSpec The implementation of this method is equivalent to: + *

    {@code
    +     * MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) {
    +     *     // assume MH_next and MH_hasNext are handles to methods of Iterator
    +     *     Class itype = iterator.type().returnType();
    +     *     Class ttype = body.type().parameterType(0);
    +     *     MethodHandle returnVar = dropArguments(identity(init.type().returnType()), 0, itype);
    +     *     MethodHandle nextVal = MH_next.asType(MH_next.type().changeReturnType(ttype));
    +     *     MethodHandle[]
    +     *         iterVar = {iterator, null, MH_hasNext, returnVar}, // it = iterator(); while (it.hasNext)
    +     *         bodyClause = {init, filterArgument(body, 0, nextVal)};  // v = body(t, v, a);
    +     *     return loop(iterVar, bodyClause);
    +     * }
    +     * }
    + * + * @param iterator a handle to return the iterator to start the loop. + * Passing {@code null} will make the loop call {@link Iterable#iterator()} on the first + * incoming value. + * @param init initializer for additional loop state. This determines the loop's result type. + * Passing {@code null} or a {@code void} init function will make the loop's result type + * {@code void}. + * @param body the body of the loop, which must not be {@code null}. + * It must accept an initial {@code T} parameter (for the iterated values), and then any + * additional loop-local variable plus loop parameters. + * + * @return a method handle embodying the iteration loop functionality. + * @throws IllegalArgumentException if any argument has a type inconsistent with the loop structure + * + * @since 9 + */ + public static MethodHandle iteratedLoop(MethodHandle iterator, MethodHandle init, MethodHandle body) { + checkIteratedLoop(body); + + MethodHandle initit = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_initIterator); + MethodHandle initIterator = iterator == null ? + initit.asType(initit.type().changeParameterType(0, body.type().parameterType(init == null ? 1 : 2))) : + iterator; + Class itype = initIterator.type().returnType(); + Class ttype = body.type().parameterType(0); + + MethodHandle returnVar = + dropArguments(init == null ? zeroHandle(void.class) : identity(init.type().returnType()), 0, itype); + MethodHandle initnx = MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iterateNext); + MethodHandle nextVal = initnx.asType(initnx.type().changeReturnType(ttype)); + + MethodHandle[] iterVar = {initIterator, null, MethodHandleImpl.getConstantHandle(MethodHandleImpl.MH_iteratePred), returnVar}; + MethodHandle[] bodyClause = {init, filterArgument(body, 0, nextVal)}; + + return loop(iterVar, bodyClause); + } + + /** + * Makes a method handle that adapts a {@code target} method handle by wrapping it in a {@code try-finally} block. + * Another method handle, {@code cleanup}, represents the functionality of the {@code finally} block. Any exception + * thrown during the execution of the {@code target} handle will be passed to the {@code cleanup} handle. The + * exception will be rethrown, unless {@code cleanup} handle throws an exception first. The + * value returned from the {@code cleanup} handle's execution will be the result of the execution of the + * {@code try-finally} handle. + *

    + * The {@code cleanup} handle will be passed one or two additional leading arguments. + * The first is the exception thrown during the + * execution of the {@code target} handle, or {@code null} if no exception was thrown. + * The second is the result of the execution of the {@code target} handle, or, if it throws an exception, + * a {@code null}, zero, or {@code false} value of the required type is supplied as a placeholder. + * The second argument is not present if the {@code target} handle has a {@code void} return type. + * (Note that, except for argument type conversions, combinators represent {@code void} values in parameter lists + * by omitting the corresponding paradoxical arguments, not by inserting {@code null} or zero values.) + *

    + * The {@code target} and {@code cleanup} handles' return types must be the same. Their parameter type lists also + * must be the same, but the {@code cleanup} handle must accept one or two more leading parameters:

      + *
    • a {@code Throwable}, which will carry the exception thrown by the {@code target} handle (if any); and + *
    • a parameter of the same type as the return type of both {@code target} and {@code cleanup}, which will carry + * the result from the execution of the {@code target} handle. + * This parameter is not present if the {@code target} returns {@code void}. + *
    + *

    + * The pseudocode for the resulting adapter looks as follows. In the code, {@code V} represents the result type of + * the {@code try/finally} construct; {@code A}/{@code a}, the types and values of arguments to the resulting + * handle consumed by the cleanup; and {@code B}/{@code b}, those of arguments to the resulting handle discarded by + * the cleanup. + *

    {@code
    +     * V target(A..., B...);
    +     * V cleanup(Throwable, V, A...);
    +     * V adapter(A... a, B... b) {
    +     *   V result = (zero value for V);
    +     *   Throwable throwable = null;
    +     *   try {
    +     *     result = target(a..., b...);
    +     *   } catch (Throwable t) {
    +     *     throwable = t;
    +     *     throw t;
    +     *   } finally {
    +     *     result = cleanup(throwable, result, a...);
    +     *   }
    +     *   return result;
    +     * }
    +     * }
    + *

    + * Note that the saved arguments ({@code a...} in the pseudocode) cannot + * be modified by execution of the target, and so are passed unchanged + * from the caller to the cleanup, if it is invoked. + *

    + * The target and cleanup must return the same type, even if the cleanup + * always throws. + * To create such a throwing cleanup, compose the cleanup logic + * with {@link #throwException throwException}, + * in order to create a method handle of the correct return type. + *

    + * Note that {@code tryFinally} never converts exceptions into normal returns. + * In rare cases where exceptions must be converted in that way, first wrap + * the target with {@link #catchException(MethodHandle, Class, MethodHandle)} + * to capture an outgoing exception, and then wrap with {@code tryFinally}. + * + * @param target the handle whose execution is to be wrapped in a {@code try} block. + * @param cleanup the handle that is invoked in the finally block. + * + * @return a method handle embodying the {@code try-finally} block composed of the two arguments. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if {@code cleanup} does not accept + * the required leading arguments, or if the method handle types do + * not match in their return types and their + * corresponding trailing parameters + * + * @see MethodHandles#catchException(MethodHandle, Class, MethodHandle) + * @since 9 + */ + public static MethodHandle tryFinally(MethodHandle target, MethodHandle cleanup) { + List> targetParamTypes = target.type().parameterList(); + List> cleanupParamTypes = cleanup.type().parameterList(); + Class rtype = target.type().returnType(); + + checkTryFinally(target, cleanup); + + // Match parameter lists: if the cleanup has a shorter parameter list than the target, add ignored arguments. + int tpSize = targetParamTypes.size(); + int cpPrefixLength = rtype == void.class ? 1 : 2; + int cpSize = cleanupParamTypes.size(); + MethodHandle aCleanup = cpSize - cpPrefixLength < tpSize ? + dropArguments(cleanup, cpSize, targetParamTypes.subList(tpSize - (cpSize - cpPrefixLength), tpSize)) : + cleanup; + + MethodHandle aTarget = target.asSpreader(Object[].class, target.type().parameterCount()); + aCleanup = aCleanup.asSpreader(Object[].class, tpSize); + + return MethodHandleImpl.makeTryFinally(aTarget, aCleanup, rtype, targetParamTypes); + } + + /** + * Adapts a target method handle by pre-processing some of its arguments, starting at a given position, and then + * calling the target with the result of the pre-processing, inserted into the original sequence of arguments just + * before the folded arguments. + *

    + * This method is closely related to {@link #foldArguments(MethodHandle, MethodHandle)}, but allows to control the + * position in the parameter list at which folding takes place. The argument controlling this, {@code pos}, is a + * zero-based index. The aforementioned method {@link #foldArguments(MethodHandle, MethodHandle)} assumes position + * 0. + *

    + * @apiNote Example: + *

    {@code
    +    import static java.lang.invoke.MethodHandles.*;
    +    import static java.lang.invoke.MethodType.*;
    +    ...
    +    MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
    +    "println", methodType(void.class, String.class))
    +    .bindTo(System.out);
    +    MethodHandle cat = lookup().findVirtual(String.class,
    +    "concat", methodType(String.class, String.class));
    +    assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
    +    MethodHandle catTrace = foldArguments(cat, 1, trace);
    +    // also prints "jum":
    +    assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
    +     * }
    + *

    Here is pseudocode for the resulting adapter: + *

    {@code
    +     * // there are N arguments in A...
    +     * T target(Z..., V, A[N]..., B...);
    +     * V combiner(A...);
    +     * T adapter(Z... z, A... a, B... b) {
    +     *   V v = combiner(a...);
    +     *   return target(z..., v, a..., b...);
    +     * }
    +     * // and if the combiner has a void return:
    +     * T target2(Z..., A[N]..., B...);
    +     * void combiner2(A...);
    +     * T adapter2(Z... z, A... a, B... b) {
    +     *   combiner2(a...);
    +     *   return target2(z..., a..., b...);
    +     * }
    +     * }
    + * + * @param target the method handle to invoke after arguments are combined + * @param pos the position at which to start folding and at which to insert the folding result; if this is {@code + * 0}, the effect is the same as for {@link #foldArguments(MethodHandle, MethodHandle)}. + * @param combiner method handle to call initially on the incoming arguments + * @return method handle which incorporates the specified argument folding logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if {@code combiner}'s return type + * is non-void and not the same as the argument type at position {@code pos} of + * the target signature, or if the {@code N} argument types at position {@code pos} + * of the target signature + * (skipping one matching the {@code combiner}'s return type) + * are not identical with the argument types of {@code combiner} + * + * @see #foldArguments(MethodHandle, MethodHandle) + * @since 9 + */ + public static MethodHandle foldArguments(MethodHandle target, int pos, MethodHandle combiner) { + MethodType targetType = target.type(); + MethodType combinerType = combiner.type(); + Class rtype = foldArgumentChecks(pos, targetType, combinerType); + BoundMethodHandle result = target.rebind(); + boolean dropResult = rtype == void.class; + LambdaForm lform = result.editor().foldArgumentsForm(1 + pos, dropResult, combinerType.basicType()); + MethodType newType = targetType; + if (!dropResult) { + newType = newType.dropParameterTypes(pos, pos + 1); + } + result = result.copyWithExtendL(newType, lform, combiner); + return result; + } + + /** + * Wrap creation of a proper zero handle for a given type. + * + * @param type the type. + * + * @return a zero value for the given type. + */ + static MethodHandle zeroHandle(Class type) { + return type.isPrimitive() ? zero(Wrapper.forPrimitiveType(type), type) : zero(Wrapper.OBJECT, type); + } + + private static void checkLoop0(MethodHandle[][] clauses) { + if (clauses == null || clauses.length == 0) { + throw newIllegalArgumentException("null or no clauses passed"); + } + if (Stream.of(clauses).anyMatch(Objects::isNull)) { + throw newIllegalArgumentException("null clauses are not allowed"); + } + if (Stream.of(clauses).anyMatch(c -> c.length > 4)) { + throw newIllegalArgumentException("All loop clauses must be represented as MethodHandle arrays with at most 4 elements."); + } + } + + private static void checkLoop1a(int i, MethodHandle in, MethodHandle st) { + if (in.type().returnType() != st.type().returnType()) { + throw misMatchedTypes("clause " + i + ": init and step return types", in.type().returnType(), + st.type().returnType()); + } + } + + private static void checkLoop1b(List init, List> commonSuffix) { + if (init.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::parameterList). + anyMatch(pl -> !pl.equals(commonSuffix.subList(0, pl.size())))) { + throw newIllegalArgumentException("found non-effectively identical init parameter type lists: " + init + + " (common suffix: " + commonSuffix + ")"); + } + } + + private static void checkLoop1cd(List pred, List fini, Class loopReturnType) { + if (fini.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType). + anyMatch(t -> t != loopReturnType)) { + throw newIllegalArgumentException("found non-identical finalizer return types: " + fini + " (return type: " + + loopReturnType + ")"); + } + + if (!pred.stream().filter(Objects::nonNull).findFirst().isPresent()) { + throw newIllegalArgumentException("no predicate found", pred); + } + if (pred.stream().filter(Objects::nonNull).map(MethodHandle::type).map(MethodType::returnType). + anyMatch(t -> t != boolean.class)) { + throw newIllegalArgumentException("predicates must have boolean return type", pred); + } + } + + private static void checkLoop2(List step, List pred, List fini, List> commonParameterSequence) { + if (Stream.of(step, pred, fini).flatMap(List::stream).filter(Objects::nonNull).map(MethodHandle::type). + map(MethodType::parameterList).anyMatch(pl -> !pl.equals(commonParameterSequence.subList(0, pl.size())))) { + throw newIllegalArgumentException("found non-effectively identical parameter type lists:\nstep: " + step + + "\npred: " + pred + "\nfini: " + fini + " (common parameter sequence: " + commonParameterSequence + ")"); + } + } + + private static void checkIteratedLoop(MethodHandle body) { + if (null == body) { + throw newIllegalArgumentException("iterated loop body must not be null"); + } + } + + private static void checkTryFinally(MethodHandle target, MethodHandle cleanup) { + Class rtype = target.type().returnType(); + if (rtype != cleanup.type().returnType()) { + throw misMatchedTypes("target and return types", cleanup.type().returnType(), rtype); + } + List> cleanupParamTypes = cleanup.type().parameterList(); + if (!Throwable.class.isAssignableFrom(cleanupParamTypes.get(0))) { + throw misMatchedTypes("cleanup first argument and Throwable", cleanup.type(), Throwable.class); + } + if (rtype != void.class && cleanupParamTypes.get(1) != rtype) { + throw misMatchedTypes("cleanup second argument and target return type", cleanup.type(), rtype); + } + // The cleanup parameter list (minus the leading Throwable and result parameters) must be a sublist of the + // target parameter list. + int cleanupArgIndex = rtype == void.class ? 1 : 2; + if (!cleanupParamTypes.subList(cleanupArgIndex, cleanupParamTypes.size()). + equals(target.type().parameterList().subList(0, cleanupParamTypes.size() - cleanupArgIndex))) { + throw misMatchedTypes("cleanup parameters after (Throwable,result) and target parameter list prefix", + cleanup.type(), target.type()); + } + } + } diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java index 7f77c5e84ad..be3090a4451 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -469,12 +469,13 @@ class MethodType implements java.io.Serializable { /** Replace the last arrayLength parameter types with the component type of arrayType. * @param arrayType any array type + * @param pos position at which to spread * @param arrayLength the number of parameter types to change * @return the resulting type */ - /*non-public*/ MethodType asSpreaderType(Class arrayType, int arrayLength) { + /*non-public*/ MethodType asSpreaderType(Class arrayType, int pos, int arrayLength) { assert(parameterCount() >= arrayLength); - int spreadPos = ptypes.length - arrayLength; + int spreadPos = pos; if (arrayLength == 0) return this; // nothing to change if (arrayType == Object[].class) { if (isGeneric()) return this; // nothing to change @@ -489,10 +490,10 @@ class MethodType implements java.io.Serializable { } Class elemType = arrayType.getComponentType(); assert(elemType != null); - for (int i = spreadPos; i < ptypes.length; i++) { + for (int i = spreadPos; i < spreadPos + arrayLength; i++) { if (ptypes[i] != elemType) { Class[] fixedPtypes = ptypes.clone(); - Arrays.fill(fixedPtypes, i, ptypes.length, elemType); + Arrays.fill(fixedPtypes, i, spreadPos + arrayLength, elemType); return methodType(rtype, fixedPtypes); } } @@ -512,12 +513,14 @@ class MethodType implements java.io.Serializable { /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType. * @param arrayType any array type + * @param pos position at which to insert parameters * @param arrayLength the number of parameter types to insert * @return the resulting type */ - /*non-public*/ MethodType asCollectorType(Class arrayType, int arrayLength) { + /*non-public*/ MethodType asCollectorType(Class arrayType, int pos, int arrayLength) { assert(parameterCount() >= 1); - assert(lastParameterType().isAssignableFrom(arrayType)); + assert(pos < ptypes.length); + assert(ptypes[pos].isAssignableFrom(arrayType)); MethodType res; if (arrayType == Object[].class) { res = genericMethodType(arrayLength); @@ -532,7 +535,11 @@ class MethodType implements java.io.Serializable { if (ptypes.length == 1) { return res; } else { - return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1)); + // insert after (if need be), then before + if (pos < parameterList().size() - 1) { + res = res.insertParameterTypes(arrayLength, parameterList().subList(pos + 1, parameterList().size())); + } + return res.insertParameterTypes(0, parameterList().subList(0, pos)); } } diff --git a/jdk/test/java/lang/invoke/AccessControlTest.java b/jdk/test/java/lang/invoke/AccessControlTest.java index e6c38804867..f10a2300745 100644 --- a/jdk/test/java/lang/invoke/AccessControlTest.java +++ b/jdk/test/java/lang/invoke/AccessControlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -225,6 +225,31 @@ public class AccessControlTest { System.out.println(this+" willAccess "+lc+" m1="+m1+" m2="+m2+" => "+((m2 & m1) != 0)); return (m2 & m1) != 0; } + + /** Predict the success or failure of accessing this class. */ + public boolean willAccessClass(Class c2, boolean load) { + Class c1 = lookupClass(); + if (load && c1.getClassLoader() == null) { + return false; + } + LookupCase lc = this.in(c2); + int m1 = lc.lookupModes(); + boolean r = false; + if (m1 == 0) { + r = false; + } else { + int m2 = fixMods(c2.getModifiers()); + if ((m2 & PUBLIC) != 0) { + r = true; + } else if ((m1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage()) { + r = true; + } + } + if (verbosity >= 2) { + System.out.println(this+" willAccessClass "+lc+" c1="+c1+" c2="+c2+" => "+r); + } + return r; + } } private static Class topLevelClass(Class cls) { @@ -342,6 +367,8 @@ public class AccessControlTest { Method method = targetMethod(targetClass, targetAccess, methodType); // Try to access target method from various contexts. for (LookupCase sourceCase : CASES) { + testOneAccess(sourceCase, method, "findClass"); + testOneAccess(sourceCase, method, "accessClass"); testOneAccess(sourceCase, method, "find"); testOneAccess(sourceCase, method, "unreflect"); } @@ -356,11 +383,19 @@ public class AccessControlTest { Class targetClass = method.getDeclaringClass(); String methodName = method.getName(); MethodType methodType = methodType(method.getReturnType(), method.getParameterTypes()); - boolean willAccess = sourceCase.willAccess(method); + boolean isFindOrAccessClass = "findClass".equals(kind) || "accessClass".equals(kind); + boolean willAccess = isFindOrAccessClass ? + sourceCase.willAccessClass(targetClass, "findClass".equals(kind)) : sourceCase.willAccess(method); boolean didAccess = false; ReflectiveOperationException accessError = null; try { switch (kind) { + case "accessClass": + sourceCase.lookup().accessClass(targetClass); + break; + case "findClass": + sourceCase.lookup().findClass(targetClass.getName()); + break; case "find": if ((method.getModifiers() & Modifier.STATIC) != 0) sourceCase.lookup().findStatic(targetClass, methodName, methodType); @@ -378,8 +413,8 @@ public class AccessControlTest { accessError = ex; } if (willAccess != didAccess) { - System.out.println(sourceCase+" => "+targetClass.getSimpleName()+"."+methodName+methodType); - System.out.println("fail on "+method+" ex="+accessError); + System.out.println(sourceCase+" => "+targetClass.getSimpleName()+(isFindOrAccessClass?"":"."+methodName+methodType)); + System.out.println("fail "+(isFindOrAccessClass?kind:"on "+method)+" ex="+accessError); assertEquals(willAccess, didAccess); } testCount++; diff --git a/jdk/test/java/lang/invoke/BigArityTest.java b/jdk/test/java/lang/invoke/BigArityTest.java index 2a767b05f98..3ee9401c6ff 100644 --- a/jdk/test/java/lang/invoke/BigArityTest.java +++ b/jdk/test/java/lang/invoke/BigArityTest.java @@ -58,6 +58,8 @@ public class BigArityTest { return x == null ? dflt : x; } + static final MethodType MT_A = MethodType.methodType(Object.class, Object.class, Object[].class, Object.class); + static Object hashArguments(Object... args) { return Objects.hash(args); } @@ -108,9 +110,36 @@ public class BigArityTest { } } // Sizes not in the above array are good: - target.asCollector(Object[].class, minbig-1); + target.asCollector(Object[].class, minbig - 1); for (int i = 2; i <= 10; i++) - target.asCollector(Object[].class, minbig-i); + target.asCollector(Object[].class, minbig - i); + } + + static void asciae02target(Object[] a, Object b) { + // naught + } + + @Test + public void asCollectorIAE02() throws ReflectiveOperationException { + final int[] INVALID_ARRAY_LENGTHS = { + Integer.MIN_VALUE, Integer.MIN_VALUE + 1, -2, -1, 254, 255, Integer.MAX_VALUE - 1, Integer.MAX_VALUE + }; + MethodHandle target = MethodHandles.lookup().findStatic(BigArityTest.class, "asciae02target", + MethodType.methodType(void.class, Object[].class, Object.class)); + int minbig = Integer.MAX_VALUE; + for (int invalidLength : INVALID_ARRAY_LENGTHS) { + if (minbig > invalidLength && invalidLength > 100) minbig = invalidLength; + try { + target.asCollector(0, Object[].class, invalidLength); + assert(false) : invalidLength; + } catch (IllegalArgumentException ex) { + System.out.println("OK: "+ex); + } + } + // Sizes not in the above array are good: + for (int i = 1; i <= 10; ++i) { + target.asCollector(0, Object[].class, minbig - i); + } } @Test @@ -216,51 +245,86 @@ public class BigArityTest { Class cls = (Class) cls0; //Class cls = Object[].class.asSubclass(cls0); int nargs = args.length, skip; + Object hr; MethodHandle smh = mh.asSpreader(cls, nargs - (skip = 0)); + MethodHandle hsmh = mh.asSpreader(0, cls, nargs - skip); Object[] tail = Arrays.copyOfRange(args, skip, nargs, cls); - if (cls == Object[].class) + Object[] head = Arrays.copyOfRange(args, 0, nargs - skip, cls); + if (cls == Object[].class) { r = smh.invokeExact(tail); - else if (cls == Integer[].class) + hr = hsmh.invokeExact(head); + } else if (cls == Integer[].class) { r = smh.invokeExact((Integer[]) tail); //warning OK, see 8019340 - else + hr = hsmh.invokeExact((Integer[]) head); + } else { r = smh.invoke(tail); + hr = hsmh.invoke(head); + } assertEquals(r0, r); + assertEquals(r0, hr); smh = mh.asSpreader(cls, nargs - (skip = 1)); + hsmh = mh.asSpreader(0, cls, nargs - skip); tail = Arrays.copyOfRange(args, skip, nargs, cls); - if (cls == Object[].class) + head = Arrays.copyOfRange(args, 0, nargs - skip, cls); + if (cls == Object[].class) { r = smh.invokeExact(args[0], tail); - else if (cls == Integer[].class) + hr = hsmh.invokeExact(head, args[2]); + } else if (cls == Integer[].class) { r = smh.invokeExact(args[0], (Integer[]) tail); - else + hr = hsmh.invokeExact((Integer[]) head, args[2]); + } else { r = smh.invoke(args[0], tail); + hr = hsmh.invoke(head, args[2]); + } assertEquals(r0, r); + assertEquals(r0, hr); smh = mh.asSpreader(cls, nargs - (skip = 2)); + hsmh = mh.asSpreader(0, cls, nargs - skip); tail = Arrays.copyOfRange(args, skip, nargs, cls); - if (cls == Object[].class) + head = Arrays.copyOfRange(args, 0, nargs - skip, cls); + if (cls == Object[].class) { r = smh.invokeExact(args[0], args[1], tail); - else if (cls == Integer[].class) + hr = hsmh.invokeExact(head, args[1], args[2]); + } else if (cls == Integer[].class) { r = smh.invokeExact(args[0], args[1], (Integer[]) tail); - else + hr = hsmh.invokeExact((Integer[]) head, args[1], args[2]); + } else { r = smh.invoke(args[0], args[1], tail); + hr = hsmh.invoke(head, args[1], args[2]); + } assertEquals(r0, r); + assertEquals(r0, hr); smh = mh.asSpreader(cls, nargs - (skip = 3)); + hsmh = mh.asSpreader(0, cls, nargs - skip); tail = Arrays.copyOfRange(args, skip, nargs, cls); - if (cls == Object[].class) + head = Arrays.copyOfRange(args, 0, nargs - skip, cls); + if (cls == Object[].class) { r = smh.invokeExact(args[0], args[1], args[2], tail); - else if (cls == Integer[].class) + hr = hsmh.invokeExact(head, args[0], args[1], args[2]); + } else if (cls == Integer[].class) { r = smh.invokeExact(args[0], args[1], args[2], (Integer[]) tail); - else + hr = hsmh.invokeExact((Integer[]) head, args[0], args[1], args[2]); + } else { r = smh.invoke(args[0], args[1], args[2], tail); + hr = hsmh.invoke(head, args[0], args[1], args[2]); + } assertEquals(r0, r); + assertEquals(r0, hr); // Try null array in addition to zero-length array: tail = null; - if (cls == Object[].class) + head = null; + if (cls == Object[].class) { r = smh.invokeExact(args[0], args[1], args[2], tail); - else if (cls == Integer[].class) + hr = hsmh.invokeExact(head, args[0], args[1], args[2]); + } else if (cls == Integer[].class) { r = smh.invokeExact(args[0], args[1], args[2], (Integer[]) tail); - else + hr = hsmh.invokeExact((Integer[]) head, args[0], args[1], args[2]); + } else { r = smh.invoke(args[0], args[1], args[2], tail); + hr = hsmh.invoke(head, args[0], args[1], args[2]); + } assertEquals(r0, r); + assertEquals(r0, hr); } } @@ -292,7 +356,7 @@ public class BigArityTest { @Test public void testArities() throws Throwable { System.out.println("testing spreaders and collectors on high arities..."); - int iterations = ITERATION_COUNT; + int iterations = ITERATION_COUNT; testArities(Object[].class, MIN_ARITY-10, MIN_ARITY-1, iterations / 1000); testArities(Object[].class, MIN_ARITY, SLOW_ARITY-1, iterations); testArities(Object[].class, SLOW_ARITY, MAX_ARITY, iterations / 1000); @@ -307,8 +371,13 @@ public class BigArityTest { Class cls = (Class) cls0; System.out.println("array class: "+cls.getSimpleName()); int iterations = ITERATION_COUNT / 1000; - testArities(cls, MIN_ARITY, SLOW_ARITY-1, iterations); - testArities(cls, SLOW_ARITY, MAX_ARITY, iterations / 100); + try { + testArities(cls, MIN_ARITY, SLOW_ARITY - 1, iterations); + testArities(cls, SLOW_ARITY, MAX_ARITY, iterations / 100); + } catch (Throwable t) { + t.printStackTrace(); + throw t; + } } } @@ -321,11 +390,14 @@ public class BigArityTest { if (verbose) System.out.println("arity="+arity); MethodHandle mh = MH_hashArguments(cls, arity); MethodHandle mh_VA = mh.asSpreader(cls, arity); + MethodHandle mh_VA_h = mh.asSpreader(0, cls, arity-1); assert(mh_VA.type().parameterType(0) == cls); - testArities(cls, arity, iterations, verbose, mh, mh_VA); + assert(mh_VA_h.type().parameterType(0) == cls); + testArities(cls, arity, iterations, verbose, mh, mh_VA, mh_VA_h); // mh_CA will collect arguments of a particular type and pass them to mh_VA MethodHandle mh_CA = mh_VA.asCollector(cls, arity); MethodHandle mh_VA2 = mh_CA.asSpreader(cls, arity); + MethodHandle mh_VA2_h = mh_CA.asSpreader(0, cls, arity-1); assert(mh_CA.type().equals(mh.type())); assert(mh_VA2.type().equals(mh_VA.type())); if (cls != Object[].class) { @@ -336,7 +408,7 @@ public class BigArityTest { } } int iterations_VA = iterations / 100; - testArities(cls, arity, iterations_VA, false, mh_CA, mh_VA2); + testArities(cls, arity, iterations_VA, false, mh_CA, mh_VA2, mh_VA2_h); } } @@ -357,13 +429,16 @@ public class BigArityTest { * @param verbose are we printing extra output? * @param mh a fixed-arity version of {@code hashArguments} * @param mh_VA a variable-arity version of {@code hashArguments}, accepting the given array type {@code cls} + * @param mh_VA_h a version of {@code hashArguments} that has a leading {@code cls} array and one final {@code cls} + * argument */ private void testArities(Class cls, int arity, int iterations, boolean verbose, MethodHandle mh, - MethodHandle mh_VA + MethodHandle mh_VA, + MethodHandle mh_VA_h ) throws Throwable { if (iterations < 4) iterations = 4; final int MAX_MH_ARITY = MAX_JVM_ARITY - 1; // mh.invoke(arg*[N]) @@ -373,6 +448,7 @@ public class BigArityTest { args = Arrays.copyOf(args, arity, cls); Object r0 = Objects.hash(args); Object r; + Object hr; MethodHandle ximh = null; MethodHandle gimh = null; if (arity <= MAX_INVOKER_ARITY) { @@ -397,13 +473,18 @@ public class BigArityTest { Object[] mh_args = cat(mh, args); assert(arity <= MAX_MH_ARITY); for (int i = 0; i < iterations; ++i) { - if (cls == Object[].class) + if (cls == Object[].class) { r = mh_VA.invokeExact(args); - else if (cls == Integer[].class) - r = mh_VA.invokeExact((Integer[])args); //warning OK, see 8019340 - else + hr = mh_VA_h.invokeExact(Arrays.copyOfRange(args, 0, arity - 1), args[arity - 1]); + } else if (cls == Integer[].class) { + r = mh_VA.invokeExact((Integer[]) args); //warning OK, see 8019340 + hr = mh_VA_h.invokeExact((Integer[]) Arrays.copyOfRange(args, 0, arity - 1), (Integer) args[arity - 1]); + } else { r = mh_VA.invoke(args); + hr = mh_VA_h.invoke(Arrays.copyOfRange(args, 0, arity - 1), args[arity - 1]); + } assertEquals(r0, r); + assertEquals(r0, hr); r = mh.invokeWithArguments(args); assertEquals(r0, r); if (ximh != null) { @@ -473,6 +554,43 @@ public class BigArityTest { // xF8, xF9, xFA, xFB); } + static Object hashArguments_252_a(Object x00, Object[] x01_FA, Object xFB) { + return Objects.hash( + // + x00, x01_FA[0], x01_FA[1], x01_FA[2], x01_FA[3], x01_FA[4], x01_FA[5], x01_FA[6], x01_FA[7], x01_FA[8], + x01_FA[9], x01_FA[10], x01_FA[11], x01_FA[12], x01_FA[13], x01_FA[14], x01_FA[15], x01_FA[16], + x01_FA[17], x01_FA[18], x01_FA[19], x01_FA[20], x01_FA[21], x01_FA[22], x01_FA[23], x01_FA[24], + x01_FA[25], x01_FA[26], x01_FA[27], x01_FA[28], x01_FA[29], x01_FA[30], x01_FA[31], x01_FA[32], + x01_FA[33], x01_FA[34], x01_FA[35], x01_FA[36], x01_FA[37], x01_FA[38], x01_FA[39], x01_FA[40], + x01_FA[41], x01_FA[42], x01_FA[43], x01_FA[44], x01_FA[45], x01_FA[46], x01_FA[47], x01_FA[48], + x01_FA[49], x01_FA[50], x01_FA[51], x01_FA[52], x01_FA[53], x01_FA[54], x01_FA[55], x01_FA[56], + x01_FA[57], x01_FA[58], x01_FA[59], x01_FA[60], x01_FA[61], x01_FA[62], x01_FA[63], x01_FA[64], + x01_FA[65], x01_FA[66], x01_FA[67], x01_FA[68], x01_FA[69], x01_FA[70], x01_FA[71], x01_FA[72], + x01_FA[73], x01_FA[74], x01_FA[75], x01_FA[76], x01_FA[77], x01_FA[78], x01_FA[79], x01_FA[80], + x01_FA[81], x01_FA[82], x01_FA[83], x01_FA[84], x01_FA[85], x01_FA[86], x01_FA[87], x01_FA[88], + x01_FA[89], x01_FA[90], x01_FA[91], x01_FA[92], x01_FA[93], x01_FA[94], x01_FA[95], x01_FA[96], + x01_FA[97], x01_FA[98], x01_FA[99], x01_FA[100], x01_FA[101], x01_FA[102], x01_FA[103], x01_FA[104], + x01_FA[105], x01_FA[106], x01_FA[107], x01_FA[108], x01_FA[109], x01_FA[110], x01_FA[111], x01_FA[112], + x01_FA[113], x01_FA[114], x01_FA[115], x01_FA[116], x01_FA[117], x01_FA[118], x01_FA[119], x01_FA[120], + x01_FA[121], x01_FA[122], x01_FA[123], x01_FA[124], x01_FA[125], x01_FA[126], x01_FA[127], x01_FA[128], + x01_FA[129], x01_FA[130], x01_FA[131], x01_FA[132], x01_FA[133], x01_FA[134], x01_FA[135], x01_FA[136], + x01_FA[137], x01_FA[138], x01_FA[139], x01_FA[140], x01_FA[141], x01_FA[142], x01_FA[143], x01_FA[144], + x01_FA[145], x01_FA[146], x01_FA[147], x01_FA[148], x01_FA[149], x01_FA[150], x01_FA[151], x01_FA[152], + x01_FA[153], x01_FA[154], x01_FA[155], x01_FA[156], x01_FA[157], x01_FA[158], x01_FA[159], x01_FA[160], + x01_FA[161], x01_FA[162], x01_FA[163], x01_FA[164], x01_FA[165], x01_FA[166], x01_FA[167], x01_FA[168], + x01_FA[169], x01_FA[170], x01_FA[171], x01_FA[172], x01_FA[173], x01_FA[174], x01_FA[175], x01_FA[176], + x01_FA[177], x01_FA[178], x01_FA[179], x01_FA[180], x01_FA[181], x01_FA[182], x01_FA[183], x01_FA[184], + x01_FA[185], x01_FA[186], x01_FA[187], x01_FA[188], x01_FA[189], x01_FA[190], x01_FA[191], x01_FA[192], + x01_FA[193], x01_FA[194], x01_FA[195], x01_FA[196], x01_FA[197], x01_FA[198], x01_FA[199], x01_FA[200], + x01_FA[201], x01_FA[202], x01_FA[203], x01_FA[204], x01_FA[205], x01_FA[206], x01_FA[207], x01_FA[208], + x01_FA[209], x01_FA[210], x01_FA[211], x01_FA[212], x01_FA[213], x01_FA[214], x01_FA[215], x01_FA[216], + x01_FA[217], x01_FA[218], x01_FA[219], x01_FA[220], x01_FA[221], x01_FA[222], x01_FA[223], x01_FA[224], + x01_FA[225], x01_FA[226], x01_FA[227], x01_FA[228], x01_FA[229], x01_FA[230], x01_FA[231], x01_FA[232], + x01_FA[233], x01_FA[234], x01_FA[235], x01_FA[236], x01_FA[237], x01_FA[238], x01_FA[239], x01_FA[240], + x01_FA[241], x01_FA[242], x01_FA[243], x01_FA[244], x01_FA[245], x01_FA[246], x01_FA[247], x01_FA[248], + // + x01_FA[249], xFB); + } @Test public void test252() throws Throwable { @@ -507,6 +625,8 @@ public class BigArityTest { test252(mh, a, r0); MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY); test252(mh_CA, a, r0); + MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2); + test252(mh_a, a, r0); } public void test252(MethodHandle mh, Object[] a, Object r0) throws Throwable { Object r; @@ -686,6 +806,43 @@ public class BigArityTest { // xF8, xF9, xFA, xFB, xFC); } + static Object hashArguments_253_a(Object x00, Object[] x01_FB, Object xFC) { + return Objects.hash( + // + x00, x01_FB[0], x01_FB[1], x01_FB[2], x01_FB[3], x01_FB[4], x01_FB[5], x01_FB[6], x01_FB[7], x01_FB[8], + x01_FB[9], x01_FB[10], x01_FB[11], x01_FB[12], x01_FB[13], x01_FB[14], x01_FB[15], x01_FB[16], + x01_FB[17], x01_FB[18], x01_FB[19], x01_FB[20], x01_FB[21], x01_FB[22], x01_FB[23], x01_FB[24], + x01_FB[25], x01_FB[26], x01_FB[27], x01_FB[28], x01_FB[29], x01_FB[30], x01_FB[31], x01_FB[32], + x01_FB[33], x01_FB[34], x01_FB[35], x01_FB[36], x01_FB[37], x01_FB[38], x01_FB[39], x01_FB[40], + x01_FB[41], x01_FB[42], x01_FB[43], x01_FB[44], x01_FB[45], x01_FB[46], x01_FB[47], x01_FB[48], + x01_FB[49], x01_FB[50], x01_FB[51], x01_FB[52], x01_FB[53], x01_FB[54], x01_FB[55], x01_FB[56], + x01_FB[57], x01_FB[58], x01_FB[59], x01_FB[60], x01_FB[61], x01_FB[62], x01_FB[63], x01_FB[64], + x01_FB[65], x01_FB[66], x01_FB[67], x01_FB[68], x01_FB[69], x01_FB[70], x01_FB[71], x01_FB[72], + x01_FB[73], x01_FB[74], x01_FB[75], x01_FB[76], x01_FB[77], x01_FB[78], x01_FB[79], x01_FB[80], + x01_FB[81], x01_FB[82], x01_FB[83], x01_FB[84], x01_FB[85], x01_FB[86], x01_FB[87], x01_FB[88], + x01_FB[89], x01_FB[90], x01_FB[91], x01_FB[92], x01_FB[93], x01_FB[94], x01_FB[95], x01_FB[96], + x01_FB[97], x01_FB[98], x01_FB[99], x01_FB[100], x01_FB[101], x01_FB[102], x01_FB[103], x01_FB[104], + x01_FB[105], x01_FB[106], x01_FB[107], x01_FB[108], x01_FB[109], x01_FB[110], x01_FB[111], x01_FB[112], + x01_FB[113], x01_FB[114], x01_FB[115], x01_FB[116], x01_FB[117], x01_FB[118], x01_FB[119], x01_FB[120], + x01_FB[121], x01_FB[122], x01_FB[123], x01_FB[124], x01_FB[125], x01_FB[126], x01_FB[127], x01_FB[128], + x01_FB[129], x01_FB[130], x01_FB[131], x01_FB[132], x01_FB[133], x01_FB[134], x01_FB[135], x01_FB[136], + x01_FB[137], x01_FB[138], x01_FB[139], x01_FB[140], x01_FB[141], x01_FB[142], x01_FB[143], x01_FB[144], + x01_FB[145], x01_FB[146], x01_FB[147], x01_FB[148], x01_FB[149], x01_FB[150], x01_FB[151], x01_FB[152], + x01_FB[153], x01_FB[154], x01_FB[155], x01_FB[156], x01_FB[157], x01_FB[158], x01_FB[159], x01_FB[160], + x01_FB[161], x01_FB[162], x01_FB[163], x01_FB[164], x01_FB[165], x01_FB[166], x01_FB[167], x01_FB[168], + x01_FB[169], x01_FB[170], x01_FB[171], x01_FB[172], x01_FB[173], x01_FB[174], x01_FB[175], x01_FB[176], + x01_FB[177], x01_FB[178], x01_FB[179], x01_FB[180], x01_FB[181], x01_FB[182], x01_FB[183], x01_FB[184], + x01_FB[185], x01_FB[186], x01_FB[187], x01_FB[188], x01_FB[189], x01_FB[190], x01_FB[191], x01_FB[192], + x01_FB[193], x01_FB[194], x01_FB[195], x01_FB[196], x01_FB[197], x01_FB[198], x01_FB[199], x01_FB[200], + x01_FB[201], x01_FB[202], x01_FB[203], x01_FB[204], x01_FB[205], x01_FB[206], x01_FB[207], x01_FB[208], + x01_FB[209], x01_FB[210], x01_FB[211], x01_FB[212], x01_FB[213], x01_FB[214], x01_FB[215], x01_FB[216], + x01_FB[217], x01_FB[218], x01_FB[219], x01_FB[220], x01_FB[221], x01_FB[222], x01_FB[223], x01_FB[224], + x01_FB[225], x01_FB[226], x01_FB[227], x01_FB[228], x01_FB[229], x01_FB[230], x01_FB[231], x01_FB[232], + x01_FB[233], x01_FB[234], x01_FB[235], x01_FB[236], x01_FB[237], x01_FB[238], x01_FB[239], x01_FB[240], + x01_FB[241], x01_FB[242], x01_FB[243], x01_FB[244], x01_FB[245], x01_FB[246], x01_FB[247], x01_FB[248], + // + x01_FB[249], x01_FB[250], xFC); + } @Test public void test253() throws Throwable { @@ -720,6 +877,8 @@ public class BigArityTest { test253(mh, a, r0); MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY); test253(mh_CA, a, r0); + MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2); + test253(mh_a, a, r0); } public void test253(MethodHandle mh, Object[] a, Object r0) throws Throwable { Object r; @@ -899,6 +1058,43 @@ public class BigArityTest { // xF8, xF9, xFA, xFB, xFC, xFD); } + static Object hashArguments_254_a(Object x00, Object[] x01_FC, Object xFD) { + return Objects.hash( + // + x00, x01_FC[0], x01_FC[1], x01_FC[2], x01_FC[3], x01_FC[4], x01_FC[5], x01_FC[6], x01_FC[7], x01_FC[8], + x01_FC[9], x01_FC[10], x01_FC[11], x01_FC[12], x01_FC[13], x01_FC[14], x01_FC[15], x01_FC[16], + x01_FC[17], x01_FC[18], x01_FC[19], x01_FC[20], x01_FC[21], x01_FC[22], x01_FC[23], x01_FC[24], + x01_FC[25], x01_FC[26], x01_FC[27], x01_FC[28], x01_FC[29], x01_FC[30], x01_FC[31], x01_FC[32], + x01_FC[33], x01_FC[34], x01_FC[35], x01_FC[36], x01_FC[37], x01_FC[38], x01_FC[39], x01_FC[40], + x01_FC[41], x01_FC[42], x01_FC[43], x01_FC[44], x01_FC[45], x01_FC[46], x01_FC[47], x01_FC[48], + x01_FC[49], x01_FC[50], x01_FC[51], x01_FC[52], x01_FC[53], x01_FC[54], x01_FC[55], x01_FC[56], + x01_FC[57], x01_FC[58], x01_FC[59], x01_FC[60], x01_FC[61], x01_FC[62], x01_FC[63], x01_FC[64], + x01_FC[65], x01_FC[66], x01_FC[67], x01_FC[68], x01_FC[69], x01_FC[70], x01_FC[71], x01_FC[72], + x01_FC[73], x01_FC[74], x01_FC[75], x01_FC[76], x01_FC[77], x01_FC[78], x01_FC[79], x01_FC[80], + x01_FC[81], x01_FC[82], x01_FC[83], x01_FC[84], x01_FC[85], x01_FC[86], x01_FC[87], x01_FC[88], + x01_FC[89], x01_FC[90], x01_FC[91], x01_FC[92], x01_FC[93], x01_FC[94], x01_FC[95], x01_FC[96], + x01_FC[97], x01_FC[98], x01_FC[99], x01_FC[100], x01_FC[101], x01_FC[102], x01_FC[103], x01_FC[104], + x01_FC[105], x01_FC[106], x01_FC[107], x01_FC[108], x01_FC[109], x01_FC[110], x01_FC[111], x01_FC[112], + x01_FC[113], x01_FC[114], x01_FC[115], x01_FC[116], x01_FC[117], x01_FC[118], x01_FC[119], x01_FC[120], + x01_FC[121], x01_FC[122], x01_FC[123], x01_FC[124], x01_FC[125], x01_FC[126], x01_FC[127], x01_FC[128], + x01_FC[129], x01_FC[130], x01_FC[131], x01_FC[132], x01_FC[133], x01_FC[134], x01_FC[135], x01_FC[136], + x01_FC[137], x01_FC[138], x01_FC[139], x01_FC[140], x01_FC[141], x01_FC[142], x01_FC[143], x01_FC[144], + x01_FC[145], x01_FC[146], x01_FC[147], x01_FC[148], x01_FC[149], x01_FC[150], x01_FC[151], x01_FC[152], + x01_FC[153], x01_FC[154], x01_FC[155], x01_FC[156], x01_FC[157], x01_FC[158], x01_FC[159], x01_FC[160], + x01_FC[161], x01_FC[162], x01_FC[163], x01_FC[164], x01_FC[165], x01_FC[166], x01_FC[167], x01_FC[168], + x01_FC[169], x01_FC[170], x01_FC[171], x01_FC[172], x01_FC[173], x01_FC[174], x01_FC[175], x01_FC[176], + x01_FC[177], x01_FC[178], x01_FC[179], x01_FC[180], x01_FC[181], x01_FC[182], x01_FC[183], x01_FC[184], + x01_FC[185], x01_FC[186], x01_FC[187], x01_FC[188], x01_FC[189], x01_FC[190], x01_FC[191], x01_FC[192], + x01_FC[193], x01_FC[194], x01_FC[195], x01_FC[196], x01_FC[197], x01_FC[198], x01_FC[199], x01_FC[200], + x01_FC[201], x01_FC[202], x01_FC[203], x01_FC[204], x01_FC[205], x01_FC[206], x01_FC[207], x01_FC[208], + x01_FC[209], x01_FC[210], x01_FC[211], x01_FC[212], x01_FC[213], x01_FC[214], x01_FC[215], x01_FC[216], + x01_FC[217], x01_FC[218], x01_FC[219], x01_FC[220], x01_FC[221], x01_FC[222], x01_FC[223], x01_FC[224], + x01_FC[225], x01_FC[226], x01_FC[227], x01_FC[228], x01_FC[229], x01_FC[230], x01_FC[231], x01_FC[232], + x01_FC[233], x01_FC[234], x01_FC[235], x01_FC[236], x01_FC[237], x01_FC[238], x01_FC[239], x01_FC[240], + x01_FC[241], x01_FC[242], x01_FC[243], x01_FC[244], x01_FC[245], x01_FC[246], x01_FC[247], x01_FC[248], + // + x01_FC[249], x01_FC[250], x01_FC[251], xFD); + } @Test public void test254() throws Throwable { @@ -933,6 +1129,8 @@ public class BigArityTest { test254(mh, a, r0); MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY); test254(mh_CA, a, r0); + MethodHandle mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2); + test254(mh_a, a, r0); } public void test254(MethodHandle mh, Object[] a, Object r0) throws Throwable { Object r; @@ -1094,6 +1292,43 @@ public class BigArityTest { // xF8, xF9, xFA, xFB, xFC, xFD, xFE); } + static Object hashArguments_255_a(Object x00, Object[] x01_FD, Object xFE) { + return Objects.hash( + // + x00, x01_FD[0], x01_FD[1], x01_FD[2], x01_FD[3], x01_FD[4], x01_FD[5], x01_FD[6], x01_FD[7], x01_FD[8], + x01_FD[9], x01_FD[10], x01_FD[11], x01_FD[12], x01_FD[13], x01_FD[14], x01_FD[15], x01_FD[16], + x01_FD[17], x01_FD[18], x01_FD[19], x01_FD[20], x01_FD[21], x01_FD[22], x01_FD[23], x01_FD[24], + x01_FD[25], x01_FD[26], x01_FD[27], x01_FD[28], x01_FD[29], x01_FD[30], x01_FD[31], x01_FD[32], + x01_FD[33], x01_FD[34], x01_FD[35], x01_FD[36], x01_FD[37], x01_FD[38], x01_FD[39], x01_FD[40], + x01_FD[41], x01_FD[42], x01_FD[43], x01_FD[44], x01_FD[45], x01_FD[46], x01_FD[47], x01_FD[48], + x01_FD[49], x01_FD[50], x01_FD[51], x01_FD[52], x01_FD[53], x01_FD[54], x01_FD[55], x01_FD[56], + x01_FD[57], x01_FD[58], x01_FD[59], x01_FD[60], x01_FD[61], x01_FD[62], x01_FD[63], x01_FD[64], + x01_FD[65], x01_FD[66], x01_FD[67], x01_FD[68], x01_FD[69], x01_FD[70], x01_FD[71], x01_FD[72], + x01_FD[73], x01_FD[74], x01_FD[75], x01_FD[76], x01_FD[77], x01_FD[78], x01_FD[79], x01_FD[80], + x01_FD[81], x01_FD[82], x01_FD[83], x01_FD[84], x01_FD[85], x01_FD[86], x01_FD[87], x01_FD[88], + x01_FD[89], x01_FD[90], x01_FD[91], x01_FD[92], x01_FD[93], x01_FD[94], x01_FD[95], x01_FD[96], + x01_FD[97], x01_FD[98], x01_FD[99], x01_FD[100], x01_FD[101], x01_FD[102], x01_FD[103], x01_FD[104], + x01_FD[105], x01_FD[106], x01_FD[107], x01_FD[108], x01_FD[109], x01_FD[110], x01_FD[111], x01_FD[112], + x01_FD[113], x01_FD[114], x01_FD[115], x01_FD[116], x01_FD[117], x01_FD[118], x01_FD[119], x01_FD[120], + x01_FD[121], x01_FD[122], x01_FD[123], x01_FD[124], x01_FD[125], x01_FD[126], x01_FD[127], x01_FD[128], + x01_FD[129], x01_FD[130], x01_FD[131], x01_FD[132], x01_FD[133], x01_FD[134], x01_FD[135], x01_FD[136], + x01_FD[137], x01_FD[138], x01_FD[139], x01_FD[140], x01_FD[141], x01_FD[142], x01_FD[143], x01_FD[144], + x01_FD[145], x01_FD[146], x01_FD[147], x01_FD[148], x01_FD[149], x01_FD[150], x01_FD[151], x01_FD[152], + x01_FD[153], x01_FD[154], x01_FD[155], x01_FD[156], x01_FD[157], x01_FD[158], x01_FD[159], x01_FD[160], + x01_FD[161], x01_FD[162], x01_FD[163], x01_FD[164], x01_FD[165], x01_FD[166], x01_FD[167], x01_FD[168], + x01_FD[169], x01_FD[170], x01_FD[171], x01_FD[172], x01_FD[173], x01_FD[174], x01_FD[175], x01_FD[176], + x01_FD[177], x01_FD[178], x01_FD[179], x01_FD[180], x01_FD[181], x01_FD[182], x01_FD[183], x01_FD[184], + x01_FD[185], x01_FD[186], x01_FD[187], x01_FD[188], x01_FD[189], x01_FD[190], x01_FD[191], x01_FD[192], + x01_FD[193], x01_FD[194], x01_FD[195], x01_FD[196], x01_FD[197], x01_FD[198], x01_FD[199], x01_FD[200], + x01_FD[201], x01_FD[202], x01_FD[203], x01_FD[204], x01_FD[205], x01_FD[206], x01_FD[207], x01_FD[208], + x01_FD[209], x01_FD[210], x01_FD[211], x01_FD[212], x01_FD[213], x01_FD[214], x01_FD[215], x01_FD[216], + x01_FD[217], x01_FD[218], x01_FD[219], x01_FD[220], x01_FD[221], x01_FD[222], x01_FD[223], x01_FD[224], + x01_FD[225], x01_FD[226], x01_FD[227], x01_FD[228], x01_FD[229], x01_FD[230], x01_FD[231], x01_FD[232], + x01_FD[233], x01_FD[234], x01_FD[235], x01_FD[236], x01_FD[237], x01_FD[238], x01_FD[239], x01_FD[240], + x01_FD[241], x01_FD[242], x01_FD[243], x01_FD[244], x01_FD[245], x01_FD[246], x01_FD[247], x01_FD[248], + // + x01_FD[249], x01_FD[250], x01_FD[251], x01_FD[252], xFE); + } @Test public void test255() throws Throwable { @@ -1163,5 +1398,38 @@ public class BigArityTest { } catch (IllegalArgumentException ex) { System.out.println("OK: "+ex); } + MethodHandle mh_a; + try { + mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-2); + throw new AssertionError("should not create an arity 255 collector method handle"); + } catch (IllegalArgumentException ex) { + System.out.println("OK: "+ex); + mh_a = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY+"_a", MT_A).asCollector(1, Object[].class, ARITY-3); + } + try { + r = mh_a.invokeExact( + // + a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], a[0x05], a[0x06], a[0x07], a[0x08], a[0x09], a[0x0A], a[0x0B], a[0x0C], a[0x0D], a[0x0E], a[0x0F], + a[0x10], a[0x11], a[0x12], a[0x13], a[0x14], a[0x15], a[0x16], a[0x17], a[0x18], a[0x19], a[0x1A], a[0x1B], a[0x1C], a[0x1D], a[0x1E], a[0x1F], + a[0x20], a[0x21], a[0x22], a[0x23], a[0x24], a[0x25], a[0x26], a[0x27], a[0x28], a[0x29], a[0x2A], a[0x2B], a[0x2C], a[0x2D], a[0x2E], a[0x2F], + a[0x30], a[0x31], a[0x32], a[0x33], a[0x34], a[0x35], a[0x36], a[0x37], a[0x38], a[0x39], a[0x3A], a[0x3B], a[0x3C], a[0x3D], a[0x3E], a[0x3F], + a[0x40], a[0x41], a[0x42], a[0x43], a[0x44], a[0x45], a[0x46], a[0x47], a[0x48], a[0x49], a[0x4A], a[0x4B], a[0x4C], a[0x4D], a[0x4E], a[0x4F], + a[0x50], a[0x51], a[0x52], a[0x53], a[0x54], a[0x55], a[0x56], a[0x57], a[0x58], a[0x59], a[0x5A], a[0x5B], a[0x5C], a[0x5D], a[0x5E], a[0x5F], + a[0x60], a[0x61], a[0x62], a[0x63], a[0x64], a[0x65], a[0x66], a[0x67], a[0x68], a[0x69], a[0x6A], a[0x6B], a[0x6C], a[0x6D], a[0x6E], a[0x6F], + a[0x70], a[0x71], a[0x72], a[0x73], a[0x74], a[0x75], a[0x76], a[0x77], a[0x78], a[0x79], a[0x7A], a[0x7B], a[0x7C], a[0x7D], a[0x7E], a[0x7F], + a[0x80], a[0x81], a[0x82], a[0x83], a[0x84], a[0x85], a[0x86], a[0x87], a[0x88], a[0x89], a[0x8A], a[0x8B], a[0x8C], a[0x8D], a[0x8E], a[0x8F], + a[0x90], a[0x91], a[0x92], a[0x93], a[0x94], a[0x95], a[0x96], a[0x97], a[0x98], a[0x99], a[0x9A], a[0x9B], a[0x9C], a[0x9D], a[0x9E], a[0x9F], + a[0xA0], a[0xA1], a[0xA2], a[0xA3], a[0xA4], a[0xA5], a[0xA6], a[0xA7], a[0xA8], a[0xA9], a[0xAA], a[0xAB], a[0xAC], a[0xAD], a[0xAE], a[0xAF], + a[0xB0], a[0xB1], a[0xB2], a[0xB3], a[0xB4], a[0xB5], a[0xB6], a[0xB7], a[0xB8], a[0xB9], a[0xBA], a[0xBB], a[0xBC], a[0xBD], a[0xBE], a[0xBF], + a[0xC0], a[0xC1], a[0xC2], a[0xC3], a[0xC4], a[0xC5], a[0xC6], a[0xC7], a[0xC8], a[0xC9], a[0xCA], a[0xCB], a[0xCC], a[0xCD], a[0xCE], a[0xCF], + a[0xD0], a[0xD1], a[0xD2], a[0xD3], a[0xD4], a[0xD5], a[0xD6], a[0xD7], a[0xD8], a[0xD9], a[0xDA], a[0xDB], a[0xDC], a[0xDD], a[0xDE], a[0xDF], + a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF], + a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7], + // + a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD], a[0xFE]); + throw new AssertionError("should not call an arity 255 collector method handle"); + } catch (LinkageError ex) { + System.out.println("OK: "+ex); + } } } diff --git a/jdk/test/java/lang/invoke/FindClassSecurityManager.java b/jdk/test/java/lang/invoke/FindClassSecurityManager.java new file mode 100644 index 00000000000..b877e885364 --- /dev/null +++ b/jdk/test/java/lang/invoke/FindClassSecurityManager.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/* @test + * @run main/othervm/policy=findclass.security.policy/secure=java.lang.SecurityManager -ea -esa test.java.lang.invoke.FindClassSecurityManager + */ + +package test.java.lang.invoke; + +import java.lang.invoke.MethodHandles; + +public class FindClassSecurityManager { + public static void main(String[] args) throws Throwable { + assert null != System.getSecurityManager(); + Class thisClass = FindClassSecurityManager.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class lookedUp = lookup.findClass(thisClass.getName()); + assert thisClass == lookedUp; + Class accessed = lookup.accessClass(thisClass); + assert thisClass == accessed; + } +} diff --git a/jdk/test/java/lang/invoke/MethodHandlesTest.java b/jdk/test/java/lang/invoke/MethodHandlesTest.java index 90137024be4..7b578a4bf3f 100644 --- a/jdk/test/java/lang/invoke/MethodHandlesTest.java +++ b/jdk/test/java/lang/invoke/MethodHandlesTest.java @@ -32,6 +32,7 @@ package test.java.lang.invoke; import test.java.lang.invoke.remote.RemoteExample; import java.lang.invoke.*; +import static java.lang.invoke.MethodType.methodType; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.*; import java.util.*; @@ -448,6 +449,7 @@ public class MethodHandlesTest { } public static interface IntExample { public void v0(); + public default void vd() { called("vd", this); } public static class Impl implements IntExample { public void v0() { called("Int/v0", this); } final String name; @@ -719,9 +721,10 @@ public class MethodHandlesTest { public void testFindSpecial0() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("findSpecial"); - testFindSpecial(SubExample.class, Example.class, void.class, "v0"); - testFindSpecial(SubExample.class, Example.class, void.class, "pkg_v0"); - testFindSpecial(RemoteExample.class, PubExample.class, void.class, "Pub/pro_v0"); + testFindSpecial(SubExample.class, Example.class, void.class, false, "v0"); + testFindSpecial(SubExample.class, Example.class, void.class, false, "pkg_v0"); + testFindSpecial(RemoteExample.class, PubExample.class, void.class, false, "Pub/pro_v0"); + testFindSpecial(Example.class, IntExample.class, void.class, true, "vd"); // Do some negative testing: for (Lookup lookup : new Lookup[]{ PRIVATE, EXAMPLE, PACKAGE, PUBLIC }) { testFindSpecial(false, lookup, Object.class, Example.class, void.class, "v0"); @@ -729,11 +732,12 @@ public class MethodHandlesTest { testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "", int.class); testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "", Void.class); testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "s0"); + testFindSpecial(false, lookup, Example.class, IntExample.class, void.class, "v0"); } } void testFindSpecial(Class specialCaller, - Class defc, Class ret, String name, Class... params) throws Throwable { + Class defc, Class ret, boolean dflt, String name, Class... params) throws Throwable { if (specialCaller == RemoteExample.class) { testFindSpecial(false, EXAMPLE, specialCaller, defc, ret, name, params); testFindSpecial(false, PRIVATE, specialCaller, defc, ret, name, params); @@ -742,11 +746,11 @@ public class MethodHandlesTest { testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params); return; } - testFindSpecial(true, EXAMPLE, specialCaller, defc, ret, name, params); - testFindSpecial(true, PRIVATE, specialCaller, defc, ret, name, params); - testFindSpecial(false, PACKAGE, specialCaller, defc, ret, name, params); - testFindSpecial(false, SUBCLASS, specialCaller, defc, ret, name, params); - testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params); + testFindSpecial(true, EXAMPLE, specialCaller, defc, ret, name, params); + testFindSpecial(true, PRIVATE, specialCaller, defc, ret, name, params); + testFindSpecial(false || dflt, PACKAGE, specialCaller, defc, ret, name, params); + testFindSpecial(false, SUBCLASS, specialCaller, defc, ret, name, params); + testFindSpecial(false, PUBLIC, specialCaller, defc, ret, name, params); } void testFindSpecial(boolean positive, Lookup lookup, Class specialCaller, Class defc, Class ret, String name, Class... params) throws Throwable { @@ -1834,6 +1838,7 @@ public class MethodHandlesTest { @Test // SLOW public void testSpreadArguments() throws Throwable { CodeCacheOverflowProcessor.runMHTest(this::testSpreadArguments0); + CodeCacheOverflowProcessor.runMHTest(this::testSpreadArguments1); } public void testSpreadArguments0() throws Throwable { @@ -1842,44 +1847,27 @@ public class MethodHandlesTest { for (Class argType : new Class[]{Object.class, Integer.class, int.class}) { if (verbosity >= 3) System.out.println("spreadArguments "+argType); + Class arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass(); for (int nargs = 0; nargs < 50; nargs++) { if (CAN_TEST_LIGHTLY && nargs > 11) break; for (int pos = 0; pos <= nargs; pos++) { if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) continue; - testSpreadArguments(argType, pos, nargs); + testSpreadArguments(argType, arrayType, pos, nargs); } } } } - public void testSpreadArguments(Class argType, int pos, int nargs) throws Throwable { + public void testSpreadArguments(Class argType, Class arrayType, int pos, int nargs) throws Throwable { countTest(); - Class arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass(); MethodHandle target2 = varargsArray(arrayType, nargs); MethodHandle target = target2.asType(target2.type().generic()); if (verbosity >= 3) System.out.println("spread into "+target2+" ["+pos+".."+nargs+"]"); Object[] args = randomArgs(target2.type().parameterArray()); // make sure the target does what we think it does: - if (pos == 0 && nargs < 5 && !argType.isPrimitive()) { - Object[] check = (Object[]) target.invokeWithArguments(args); - assertArrayEquals(args, check); - switch (nargs) { - case 0: - check = (Object[]) (Object) target.invokeExact(); - assertArrayEquals(args, check); - break; - case 1: - check = (Object[]) (Object) target.invokeExact(args[0]); - assertArrayEquals(args, check); - break; - case 2: - check = (Object[]) (Object) target.invokeExact(args[0], args[1]); - assertArrayEquals(args, check); - break; - } - } + checkTarget(argType, pos, nargs, target, args); List> newParams = new ArrayList<>(target2.type().parameterList()); { // modify newParams in place List> spreadParams = newParams.subList(pos, nargs); @@ -1898,6 +1886,78 @@ public class MethodHandlesTest { args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, args.length)); returnValue = result.invokeWithArguments(args1); } + checkReturnValue(argType, args, result, returnValue); + } + public void testSpreadArguments1() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("spreadArguments/pos"); + for (Class argType : new Class[]{Object.class, Integer.class, int.class}) { + if (verbosity >= 3) + System.out.println("spreadArguments "+argType); + Class arrayType = java.lang.reflect.Array.newInstance(argType, 0).getClass(); + for (int nargs = 0; nargs < 50; nargs++) { + if (CAN_TEST_LIGHTLY && nargs > 11) break; + for (int pos = 0; pos <= nargs; pos++) { + if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; + if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) + continue; + for (int spr = 1; spr < nargs - pos; ++spr) { + if (spr > 4 && spr != 7 && spr != 11 && spr != 20 && spr < nargs - pos - 4) continue; + testSpreadArguments(argType, arrayType, pos, spr, nargs); + } + } + } + } + } + public void testSpreadArguments(Class argType, Class arrayType, int pos, int spread, int nargs) throws Throwable { + countTest(); + MethodHandle target2 = varargsArray(arrayType, nargs); + MethodHandle target = target2.asType(target2.type().generic()); + if (verbosity >= 3) + System.out.println("spread into " + target2 + " [" + pos + ".." + (pos + spread) + "["); + Object[] args = randomArgs(target2.type().parameterArray()); + // make sure the target does what we think it does: + checkTarget(argType, pos, nargs, target, args); + List> newParams = new ArrayList<>(target2.type().parameterList()); + { // modify newParams in place + List> spreadParams = newParams.subList(pos, pos + spread); + spreadParams.clear(); + spreadParams.add(arrayType); + } + MethodType newType = MethodType.methodType(arrayType, newParams); + MethodHandle result = target2.asSpreader(pos, arrayType, spread); + assert (result.type() == newType) : Arrays.asList(result, newType); + result = result.asType(newType.generic()); + // args1 has nargs-spread entries, plus one for the to-be-spread array + int args1Length = nargs - (spread - 1); + Object[] args1 = new Object[args1Length]; + System.arraycopy(args, 0, args1, 0, pos); + args1[pos] = ValueConversions.changeArrayType(arrayType, Arrays.copyOfRange(args, pos, pos + spread)); + System.arraycopy(args, pos + spread, args1, pos + 1, nargs - spread - pos); + Object returnValue = result.invokeWithArguments(args1); + checkReturnValue(argType, args, result, returnValue); + } + private static void checkTarget(Class argType, int pos, int nargs, MethodHandle target, Object[] args) throws Throwable { + if (pos == 0 && nargs < 5 && !argType.isPrimitive()) { + Object[] check = (Object[]) target.invokeWithArguments(args); + assertArrayEquals(args, check); + switch (nargs) { + case 0: + check = (Object[]) (Object) target.invokeExact(); + assertArrayEquals(args, check); + break; + case 1: + check = (Object[]) (Object) target.invokeExact(args[0]); + assertArrayEquals(args, check); + break; + case 2: + check = (Object[]) (Object) target.invokeExact(args[0], args[1]); + assertArrayEquals(args, check); + break; + } + } + } + private static void checkReturnValue(Class argType, Object[] args, MethodHandle result, Object returnValue) { String argstr = Arrays.toString(args); if (!argType.isPrimitive()) { Object[] rv = (Object[]) returnValue; @@ -1932,6 +1992,7 @@ public class MethodHandlesTest { @Test // SLOW public void testAsCollector() throws Throwable { CodeCacheOverflowProcessor.runMHTest(this::testAsCollector0); + CodeCacheOverflowProcessor.runMHTest(this::testAsCollector1); } public void testAsCollector0() throws Throwable { @@ -1974,6 +2035,51 @@ public class MethodHandlesTest { // collectedArgs[pos] = Arrays.asList((Object[]) collectedArgs[pos]); assertArrayEquals(collectedArgs, returnValue); } + public void testAsCollector1() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("asCollector/pos"); + for (Class argType : new Class[]{Object.class, Integer.class, int.class}) { + if (verbosity >= 3) + System.out.println("asCollector/pos "+argType); + for (int nargs = 0; nargs < 50; nargs++) { + if (CAN_TEST_LIGHTLY && nargs > 11) break; + for (int pos = 0; pos <= nargs; pos++) { + if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; + if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) + continue; + for (int coll = 1; coll < nargs - pos; ++coll) { + if (coll > 4 && coll != 7 && coll != 11 && coll != 20 && coll < nargs - pos - 4) continue; + testAsCollector(argType, pos, coll, nargs); + } + } + } + } + } + public void testAsCollector(Class argType, int pos, int collect, int nargs) throws Throwable { + countTest(); + // fake up a MH with the same type as the desired adapter: + MethodHandle fake = varargsArray(nargs); + fake = changeArgTypes(fake, argType); + MethodType newType = fake.type(); + Object[] args = randomArgs(newType.parameterArray()); + // here is what should happen: + // new arg list has "collect" less arguments, but one extra for collected arguments array + int collectedLength = nargs-(collect-1); + Object[] collectedArgs = new Object[collectedLength]; + System.arraycopy(args, 0, collectedArgs, 0, pos); + collectedArgs[pos] = Arrays.copyOfRange(args, pos, pos+collect); + System.arraycopy(args, pos+collect, collectedArgs, pos+1, args.length-(pos+collect)); + // here is the MH which will witness the collected argument part (not tail!): + MethodHandle target = varargsArray(collectedLength); + target = changeArgTypes(target, 0, pos, argType); + target = changeArgTypes(target, pos, pos+1, Object[].class); + target = changeArgTypes(target, pos+1, collectedLength, argType); + if (verbosity >= 3) + System.out.println("collect "+collect+" from "+Arrays.asList(args)+" ["+pos+".."+(pos+collect)+"["); + MethodHandle result = target.asCollector(pos, Object[].class, collect).asType(newType); + Object[] returnValue = (Object[]) result.invokeWithArguments(args); + assertArrayEquals(collectedArgs, returnValue); + } @Test // SLOW public void testInsertArguments() throws Throwable { @@ -2117,21 +2223,29 @@ public class MethodHandlesTest { public void testCollectArguments0() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("collectArguments"); - testFoldOrCollectArguments(true); + testFoldOrCollectArguments(true, false); } @Test public void testFoldArguments() throws Throwable { CodeCacheOverflowProcessor.runMHTest(this::testFoldArguments0); + CodeCacheOverflowProcessor.runMHTest(this::testFoldArguments1); } public void testFoldArguments0() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("foldArguments"); - testFoldOrCollectArguments(false); + testFoldOrCollectArguments(false, false); } - void testFoldOrCollectArguments(boolean isCollect) throws Throwable { + public void testFoldArguments1() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("foldArguments/pos"); + testFoldOrCollectArguments(false, true); + } + + void testFoldOrCollectArguments(boolean isCollect, boolean withFoldPos) throws Throwable { + assert !(isCollect && withFoldPos); // exclude illegal argument combination for (Class lastType : new Class[]{ Object.class, String.class, int.class }) { for (Class collectType : new Class[]{ Object.class, String.class, int.class, void.class }) { int maxArity = 10; @@ -2146,7 +2260,7 @@ public class MethodHandlesTest { if (!mixArgs(argTypes, mix, argTypesSeen)) continue; for (int collect = 0; collect <= nargs; collect++) { for (int pos = 0; pos <= nargs - collect; pos++) { - testFoldOrCollectArguments(argTypes, pos, collect, collectType, lastType, isCollect); + testFoldOrCollectArguments(argTypes, pos, collect, collectType, lastType, isCollect, withFoldPos); } } } @@ -2186,13 +2300,14 @@ public class MethodHandlesTest { int pos, int fold, // position and length of the folded arguments Class combineType, // type returned from the combiner Class lastType, // type returned from the target - boolean isCollect) throws Throwable { + boolean isCollect, + boolean withFoldPos) throws Throwable { int nargs = argTypes.size(); - if (pos != 0 && !isCollect) return; // can fold only at pos=0 for now + if (pos != 0 && !isCollect && !withFoldPos) return; // test MethodHandles.foldArguments(MH,MH) only for pos=0 countTest(); List> combineArgTypes = argTypes.subList(pos, pos + fold); List> targetArgTypes = new ArrayList<>(argTypes); - if (isCollect) // does targret see arg[pos..pos+cc-1]? + if (isCollect) // does target see arg[pos..pos+cc-1]? targetArgTypes.subList(pos, pos + fold).clear(); if (combineType != void.class) targetArgTypes.add(pos, combineType); @@ -2205,7 +2320,7 @@ public class MethodHandlesTest { if (isCollect) target2 = MethodHandles.collectArguments(target, pos, combine); else - target2 = MethodHandles.foldArguments(target, combine); + target2 = withFoldPos ? MethodHandles.foldArguments(target, pos, combine) : MethodHandles.foldArguments(target, combine); // Simulate expected effect of combiner on arglist: List expectedList = new ArrayList<>(argsToPass); List argsToFold = expectedList.subList(pos, pos + fold); @@ -2540,6 +2655,203 @@ public class MethodHandlesTest { } } + @Test + public void testGenericLoopCombinator() throws Throwable { + CodeCacheOverflowProcessor.runMHTest(this::testGenericLoopCombinator0); + } + public void testGenericLoopCombinator0() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("loop"); + // Test as follows: + // * Have an increasing number of loop-local state. Local state type diversity grows with the number. + // * Initializers set the starting value of loop-local state from the corresponding loop argument. + // * For each local state element, there is a predicate - for all state combinations, exercise all predicates. + // * Steps modify each local state element in each iteration. + // * Finalizers group all local state elements into a resulting array. Verify end values. + // * Exercise both pre- and post-checked loops. + // Local state types, start values, predicates, and steps: + // * int a, 0, a < 7, a = a + 1 + // * double b, 7.0, b > 0.5, b = b / 2.0 + // * String c, "start", c.length <= 9, c = c + a + final Class[] argTypes = new Class[] {int.class, double.class, String.class}; + final Object[][] args = new Object[][] { + new Object[]{0 }, + new Object[]{0, 7.0 }, + new Object[]{0, 7.0, "start"} + }; + // These are the expected final state tuples for argument type tuple / predicate combinations, for pre- and + // post-checked loops: + final Object[][] preCheckedResults = new Object[][] { + new Object[]{7 }, // (int) / int + new Object[]{7, 0.0546875 }, // (int,double) / int + new Object[]{5, 0.4375 }, // (int,double) / double + new Object[]{7, 0.0546875, "start1234567"}, // (int,double,String) / int + new Object[]{5, 0.4375, "start1234" }, // (int,double,String) / double + new Object[]{6, 0.109375, "start12345" } // (int,double,String) / String + }; + final Object[][] postCheckedResults = new Object[][] { + new Object[]{7 }, // (int) / int + new Object[]{7, 0.109375 }, // (int,double) / int + new Object[]{4, 0.4375 }, // (int,double) / double + new Object[]{7, 0.109375, "start123456"}, // (int,double,String) / int + new Object[]{4, 0.4375, "start123" }, // (int,double,String) / double + new Object[]{5, 0.21875, "start12345" } // (int,double,String) / String + }; + final Lookup l = MethodHandles.lookup(); + final Class MHT = MethodHandlesTest.class; + final Class B = boolean.class; + final Class I = int.class; + final Class D = double.class; + final Class S = String.class; + final MethodHandle hip = l.findStatic(MHT, "loopIntPred", methodType(B, I)); + final MethodHandle hdp = l.findStatic(MHT, "loopDoublePred", methodType(B, I, D)); + final MethodHandle hsp = l.findStatic(MHT, "loopStringPred", methodType(B, I, D, S)); + final MethodHandle his = l.findStatic(MHT, "loopIntStep", methodType(I, I)); + final MethodHandle hds = l.findStatic(MHT, "loopDoubleStep", methodType(D, I, D)); + final MethodHandle hss = l.findStatic(MHT, "loopStringStep", methodType(S, I, D, S)); + final MethodHandle[] preds = new MethodHandle[] {hip, hdp, hsp}; + final MethodHandle[] steps = new MethodHandle[] {his, hds, hss}; + for (int nargs = 1, useResultsStart = 0; nargs <= argTypes.length; useResultsStart += nargs++) { + Class[] useArgTypes = Arrays.copyOf(argTypes, nargs, Class[].class); + MethodHandle[] usePreds = Arrays.copyOf(preds, nargs, MethodHandle[].class); + MethodHandle[] useSteps = Arrays.copyOf(steps, nargs, MethodHandle[].class); + Object[] useArgs = args[nargs - 1]; + Object[][] usePreCheckedResults = new Object[nargs][]; + Object[][] usePostCheckedResults = new Object[nargs][]; + System.arraycopy(preCheckedResults, useResultsStart, usePreCheckedResults, 0, nargs); + System.arraycopy(postCheckedResults, useResultsStart, usePostCheckedResults, 0, nargs); + testGenericLoopCombinator(nargs, useArgTypes, usePreds, useSteps, useArgs, usePreCheckedResults, + usePostCheckedResults); + } + } + void testGenericLoopCombinator(int nargs, Class[] argTypes, MethodHandle[] preds, MethodHandle[] steps, + Object[] args, Object[][] preCheckedResults, Object[][] postCheckedResults) + throws Throwable { + List> lArgTypes = Arrays.asList(argTypes); + // Predicate and step handles are passed in as arguments, initializer and finalizer handles are constructed here + // from the available information. + MethodHandle[] inits = new MethodHandle[nargs]; + for (int i = 0; i < nargs; ++i) { + MethodHandle h; + // Initializers are meant to return whatever they are passed at a given argument position. This means that + // additional arguments may have to be appended and prepended. + h = MethodHandles.identity(argTypes[i]); + if (i < nargs - 1) { + h = MethodHandles.dropArguments(h, 1, lArgTypes.subList(i + 1, nargs)); + } + if (i > 0) { + h = MethodHandles.dropArguments(h, 0, lArgTypes.subList(0, i)); + } + inits[i] = h; + } + // Finalizers are all meant to collect all of the loop-local state in a single array and return that. Local + // state is passed before the loop args. Construct such a finalizer by first taking a varargsArray collector for + // the number of local state arguments, and then appending the loop args as to-be-dropped arguments. + MethodHandle[] finis = new MethodHandle[nargs]; + MethodHandle genericFini = MethodHandles.dropArguments( + varargsArray(nargs).asType(methodType(Object[].class, lArgTypes)), nargs, lArgTypes); + Arrays.fill(finis, genericFini); + // The predicate and step handles' signatures need to be extended. They currently just accept local state args; + // append possibly missing local state args and loop args using dropArguments. + for (int i = 0; i < nargs; ++i) { + List> additionalLocalStateArgTypes = lArgTypes.subList(i + 1, nargs); + preds[i] = MethodHandles.dropArguments( + MethodHandles.dropArguments(preds[i], i + 1, additionalLocalStateArgTypes), nargs, lArgTypes); + steps[i] = MethodHandles.dropArguments( + MethodHandles.dropArguments(steps[i], i + 1, additionalLocalStateArgTypes), nargs, lArgTypes); + } + // Iterate over all of the predicates, using only one of them at a time. + for (int i = 0; i < nargs; ++i) { + MethodHandle[] usePreds; + if (nargs == 1) { + usePreds = preds; + } else { + // Create an all-null preds array, and only use one predicate in this iteration. The null entries will + // be substituted with true predicates by the loop combinator. + usePreds = new MethodHandle[nargs]; + usePreds[i] = preds[i]; + } + // Go for it. + if (verbosity >= 3) { + System.out.println("calling loop for argument types " + lArgTypes + " with predicate at index " + i); + if (verbosity >= 5) { + System.out.println("predicates: " + Arrays.asList(usePreds)); + } + } + MethodHandle[] preInits = new MethodHandle[nargs + 1]; + MethodHandle[] prePreds = new MethodHandle[nargs + 1]; + MethodHandle[] preSteps = new MethodHandle[nargs + 1]; + MethodHandle[] preFinis = new MethodHandle[nargs + 1]; + System.arraycopy(inits, 0, preInits, 1, nargs); + System.arraycopy(usePreds, 0, prePreds, 0, nargs); // preds are offset by 1 for pre-checked loops + System.arraycopy(steps, 0, preSteps, 1, nargs); + System.arraycopy(finis, 0, preFinis, 0, nargs); // finis are also offset by 1 for pre-checked loops + // Convert to clause-major form. + MethodHandle[][] preClauses = new MethodHandle[nargs+1][4]; + MethodHandle[][] postClauses = new MethodHandle[nargs][4]; + toClauseMajor(preClauses, preInits, preSteps, prePreds, preFinis); + toClauseMajor(postClauses, inits, steps, usePreds, finis); + MethodHandle pre = MethodHandles.loop(preClauses); + MethodHandle post = MethodHandles.loop(postClauses); + Object[] preResults = (Object[]) pre.invokeWithArguments(args); + if (verbosity >= 4) { + System.out.println("pre-checked: expected " + Arrays.asList(preCheckedResults[i]) + ", actual " + + Arrays.asList(preResults)); + } + Object[] postResults = (Object[]) post.invokeWithArguments(args); + if (verbosity >= 4) { + System.out.println("post-checked: expected " + Arrays.asList(postCheckedResults[i]) + ", actual " + + Arrays.asList(postResults)); + } + assertArrayEquals(preCheckedResults[i], preResults); + assertArrayEquals(postCheckedResults[i], postResults); + } + } + static void toClauseMajor(MethodHandle[][] clauses, MethodHandle[] init, MethodHandle[] step, MethodHandle[] pred, MethodHandle[] fini) { + for (int i = 0; i < clauses.length; ++i) { + clauses[i][0] = init[i]; + clauses[i][1] = step[i]; + clauses[i][2] = pred[i]; + clauses[i][3] = fini[i]; + } + } + static boolean loopIntPred(int a) { + if (verbosity >= 5) { + System.out.println("int pred " + a + " -> " + (a < 7)); + } + return a < 7; + } + static boolean loopDoublePred(int a, double b) { + if (verbosity >= 5) { + System.out.println("double pred (a=" + a + ") " + b + " -> " + (b > 0.5)); + } + return b > 0.5; + } + static boolean loopStringPred(int a, double b, String c) { + if (verbosity >= 5) { + System.out.println("String pred (a=" + a + ",b=" + b + ") " + c + " -> " + (c.length() <= 9)); + } + return c.length() <= 9; + } + static int loopIntStep(int a) { + if (verbosity >= 5) { + System.out.println("int step " + a + " -> " + (a + 1)); + } + return a + 1; + } + static double loopDoubleStep(int a, double b) { + if (verbosity >= 5) { + System.out.println("double step (a=" + a + ") " + b + " -> " + (b / 2.0)); + } + return b / 2.0; + } + static String loopStringStep(int a, double b, String c) { + if (verbosity >= 5) { + System.out.println("String step (a=" + a + ",b=" + b + ") " + c + " -> " + (c + a)); + } + return c + a; + } + @Test public void testThrowException() throws Throwable { CodeCacheOverflowProcessor.runMHTest(this::testThrowException0); @@ -2575,13 +2887,108 @@ public class MethodHandlesTest { assertSame(thrown, caught); } + @Test + public void testTryFinally() throws Throwable { + CodeCacheOverflowProcessor.runMHTest(this::testTryFinally0); + } + public void testTryFinally0() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("tryFinally"); + String inputMessage = "returned"; + String augmentedMessage = "augmented"; + String thrownMessage = "thrown"; + String rethrownMessage = "rethrown"; + // Test these cases: + // * target returns, cleanup passes through + // * target returns, cleanup augments + // * target throws, cleanup augments and returns + // * target throws, cleanup augments and rethrows + MethodHandle target = MethodHandles.identity(String.class); + MethodHandle targetThrow = MethodHandles.dropArguments( + MethodHandles.throwException(String.class, Exception.class).bindTo(new Exception(thrownMessage)), 0, String.class); + MethodHandle cleanupPassThrough = MethodHandles.dropArguments(MethodHandles.identity(String.class), 0, + Throwable.class, String.class); + MethodHandle cleanupAugment = MethodHandles.dropArguments(MethodHandles.constant(String.class, augmentedMessage), + 0, Throwable.class, String.class, String.class); + MethodHandle cleanupCatch = MethodHandles.dropArguments(MethodHandles.constant(String.class, thrownMessage), 0, + Throwable.class, String.class, String.class); + MethodHandle cleanupThrow = MethodHandles.dropArguments(MethodHandles.throwException(String.class, Exception.class). + bindTo(new Exception(rethrownMessage)), 0, Throwable.class, String.class, String.class); + testTryFinally(target, cleanupPassThrough, inputMessage, inputMessage, false); + testTryFinally(target, cleanupAugment, inputMessage, augmentedMessage, false); + testTryFinally(targetThrow, cleanupCatch, inputMessage, thrownMessage, true); + testTryFinally(targetThrow, cleanupThrow, inputMessage, rethrownMessage, true); + // Test the same cases as above for void targets and cleanups. + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class C = this.getClass(); + MethodType targetType = methodType(void.class, String[].class); + MethodType cleanupType = methodType(void.class, Throwable.class, String[].class); + MethodHandle vtarget = lookup.findStatic(C, "vtarget", targetType); + MethodHandle vtargetThrow = lookup.findStatic(C, "vtargetThrow", targetType); + MethodHandle vcleanupPassThrough = lookup.findStatic(C, "vcleanupPassThrough", cleanupType); + MethodHandle vcleanupAugment = lookup.findStatic(C, "vcleanupAugment", cleanupType); + MethodHandle vcleanupCatch = lookup.findStatic(C, "vcleanupCatch", cleanupType); + MethodHandle vcleanupThrow = lookup.findStatic(C, "vcleanupThrow", cleanupType); + testTryFinally(vtarget, vcleanupPassThrough, inputMessage, inputMessage, false); + testTryFinally(vtarget, vcleanupAugment, inputMessage, augmentedMessage, false); + testTryFinally(vtargetThrow, vcleanupCatch, inputMessage, thrownMessage, true); + testTryFinally(vtargetThrow, vcleanupThrow, inputMessage, rethrownMessage, true); + } + void testTryFinally(MethodHandle target, MethodHandle cleanup, String input, String msg, boolean mustCatch) + throws Throwable { + countTest(); + MethodHandle tf = MethodHandles.tryFinally(target, cleanup); + String result = null; + boolean isVoid = target.type().returnType() == void.class; + String[] argArray = new String[]{input}; + try { + if (isVoid) { + tf.invoke(argArray); + } else { + result = (String) tf.invoke(input); + } + } catch (Throwable t) { + assertTrue(mustCatch); + assertEquals(msg, t.getMessage()); + return; + } + assertFalse(mustCatch); + if (isVoid) { + assertEquals(msg, argArray[0]); + } else { + assertEquals(msg, result); + } + } + static void vtarget(String[] a) { + // naught, akin to identity + } + static void vtargetThrow(String[] a) throws Exception { + throw new Exception("thrown"); + } + static void vcleanupPassThrough(Throwable t, String[] a) { + assertNull(t); + // naught, akin to identity + } + static void vcleanupAugment(Throwable t, String[] a) { + assertNull(t); + a[0] = "augmented"; + } + static void vcleanupCatch(Throwable t, String[] a) { + assertNotNull(t); + a[0] = "caught"; + } + static void vcleanupThrow(Throwable t, String[] a) throws Exception { + assertNotNull(t); + throw new Exception("rethrown"); + } + @Test public void testInterfaceCast() throws Throwable { CodeCacheOverflowProcessor.runMHTest(this::testInterfaceCast0); } public void testInterfaceCast0() throws Throwable { - //if (CAN_SKIP_WORKING) return; + if (CAN_SKIP_WORKING) return; startTest("interfaceCast"); assert( (((Object)"foo") instanceof CharSequence)); assert(!(((Object)"foo") instanceof Iterable)); diff --git a/jdk/test/java/lang/invoke/T8139885.java b/jdk/test/java/lang/invoke/T8139885.java new file mode 100644 index 00000000000..913111c9efe --- /dev/null +++ b/jdk/test/java/lang/invoke/T8139885.java @@ -0,0 +1,1082 @@ +/* + * 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. + */ + +/* @test + * @run testng/othervm -ea -esa test.java.lang.invoke.T8139885 + */ + +package test.java.lang.invoke; + +import java.io.StringWriter; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.util.*; + +import static java.lang.invoke.MethodType.methodType; + +import static org.testng.AssertJUnit.*; + +import org.testng.annotations.*; + +/** + * Example-scale and negative tests for JEP 274 extensions. + */ +public class T8139885 { + + static final Lookup LOOKUP = MethodHandles.lookup(); + + // + // Tests. + // + + @Test + public static void testLoopFac() throws Throwable { + MethodHandle[] counterClause = new MethodHandle[]{Fac.MH_zero, Fac.MH_inc}; + MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin}; + MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause); + assertEquals(Fac.MT_fac, loop.type()); + assertEquals(120, loop.invoke(5)); + } + + @Test + public static void testLoopFacNullInit() throws Throwable { + // null initializer for counter, should initialize to 0 + MethodHandle[] counterClause = new MethodHandle[]{null, Fac.MH_inc}; + MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin}; + MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause); + assertEquals(Fac.MT_fac, loop.type()); + assertEquals(120, loop.invoke(5)); + } + + @Test + public static void testLoopVoid1() throws Throwable { + // construct a post-checked loop that only does one iteration and has a void body and void local state + MethodHandle loop = MethodHandles.loop(new MethodHandle[]{Empty.MH_f, Empty.MH_f, Empty.MH_pred, null}); + assertEquals(MethodType.methodType(void.class), loop.type()); + loop.invoke(); + } + + @Test + public static void testLoopVoid2() throws Throwable { + // construct a post-checked loop that only does one iteration and has a void body and void local state, + // initialized implicitly from the step type + MethodHandle loop = MethodHandles.loop(new MethodHandle[]{null, Empty.MH_f, Empty.MH_pred, null}); + assertEquals(MethodType.methodType(void.class), loop.type()); + loop.invoke(); + } + + @Test + public static void testLoopFacWithVoidState() throws Throwable { + // like testLoopFac, but with additional void state that outputs a dot + MethodHandle[] counterClause = new MethodHandle[]{Fac.MH_zero, Fac.MH_inc}; + MethodHandle[] accumulatorClause = new MethodHandle[]{Fac.MH_one, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin}; + MethodHandle[] dotClause = new MethodHandle[]{null, Fac.MH_dot}; + MethodHandle loop = MethodHandles.loop(counterClause, accumulatorClause, dotClause); + assertEquals(Fac.MT_fac, loop.type()); + assertEquals(120, loop.invoke(5)); + } + + @Test + public static void testLoopNegative() throws Throwable { + MethodHandle mh_loop = + LOOKUP.findStatic(MethodHandles.class, "loop", methodType(MethodHandle.class, MethodHandle[][].class)); + MethodHandle i0 = MethodHandles.constant(int.class, 0); + MethodHandle ii = MethodHandles.dropArguments(i0, 0, int.class, int.class); + MethodHandle id = MethodHandles.dropArguments(i0, 0, int.class, double.class); + MethodHandle i3 = MethodHandles.dropArguments(i0, 0, int.class, int.class, int.class); + List inits = Arrays.asList(ii, id, i3); + List> ints = Arrays.asList(int.class, int.class, int.class); + List finis = Arrays.asList(Fac.MH_fin, Fac.MH_inc, Counted.MH_step); + List preds1 = Arrays.asList(null, null, null); + List preds2 = Arrays.asList(null, Fac.MH_fin, null); + MethodHandle eek = MethodHandles.dropArguments(i0, 0, int.class, int.class, double.class); + List nesteps = Arrays.asList(Fac.MH_inc, eek, Fac.MH_dot); + List nepreds = Arrays.asList(null, Fac.MH_pred, null); + List nefinis = Arrays.asList(null, Fac.MH_fin, null); + MethodHandle[][][] cases = { + null, + {}, + {{null, Fac.MH_inc}, {Fac.MH_one, null, Fac.MH_mult, Fac.MH_pred, Fac.MH_fin}}, + {{null, Fac.MH_inc}, null}, + {{Fac.MH_zero, Fac.MH_dot}}, + {{ii}, {id}, {i3}}, + {{null, Fac.MH_inc, null, Fac.MH_fin}, {null, Fac.MH_inc, null, Fac.MH_inc}, + {null, Counted.MH_start, null, Counted.MH_step}}, + {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, Fac.MH_mult, null, Fac.MH_fin}, {null, Fac.MH_dot}}, + {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, Fac.MH_mult, Fac.MH_fin, Fac.MH_fin}, {null, Fac.MH_dot}}, + {{Fac.MH_zero, Fac.MH_inc}, {Fac.MH_one, eek, Fac.MH_pred, Fac.MH_fin}, {null, Fac.MH_dot}} + }; + String[] messages = { + "null or no clauses passed", + "null or no clauses passed", + "All loop clauses must be represented as MethodHandle arrays with at most 4 elements.", + "null clauses are not allowed", + "clause 0: init and step return types must match: int != void", + "found non-effectively identical init parameter type lists: " + inits + " (common suffix: " + ints + ")", + "found non-identical finalizer return types: " + finis + " (return type: int)", + "no predicate found: " + preds1, + "predicates must have boolean return type: " + preds2, + "found non-effectively identical parameter type lists:\nstep: " + nesteps + "\npred: " + nepreds + + "\nfini: " + nefinis + " (common parameter sequence: " + ints + ")" + }; + for (int i = 0; i < cases.length; ++i) { + boolean caught = false; + try { + mh_loop.invokeWithArguments(cases[i]); + } catch (IllegalArgumentException iae) { + assertEquals(messages[i], iae.getMessage()); + caught = true; + } + assertTrue(caught); + } + } + + @Test + public static void testWhileLoop() throws Throwable { + // int i = 0; while (i < limit) { ++i; } return i; => limit + MethodHandle loop = MethodHandles.whileLoop(While.MH_zero, While.MH_pred, While.MH_step); + assertEquals(While.MT_while, loop.type()); + assertEquals(23, loop.invoke(23)); + } + + @Test + public static void testWhileLoopNoIteration() throws Throwable { + // a while loop that never executes its body because the predicate evaluates to false immediately + MethodHandle loop = MethodHandles.whileLoop(While.MH_initString, While.MH_predString, While.MH_stepString); + assertEquals(While.MT_string, loop.type()); + assertEquals("a", loop.invoke()); + } + + @Test + public static void testDoWhileLoop() throws Throwable { + // int i = 0; do { ++i; } while (i < limit); return i; => limit + MethodHandle loop = MethodHandles.doWhileLoop(While.MH_zero, While.MH_step, While.MH_pred); + assertEquals(While.MT_while, loop.type()); + assertEquals(23, loop.invoke(23)); + } + + @Test + public static void testWhileZip() throws Throwable { + MethodHandle loop = MethodHandles.doWhileLoop(While.MH_zipInitZip, While.MH_zipStep, While.MH_zipPred); + assertEquals(While.MT_zip, loop.type()); + List a = Arrays.asList("a", "b", "c", "d"); + List b = Arrays.asList("e", "f", "g", "h"); + List zipped = Arrays.asList("a", "e", "b", "f", "c", "g", "d", "h"); + assertEquals(zipped, (List) loop.invoke(a.iterator(), b.iterator())); + } + + @Test + public static void testCountedLoop() throws Throwable { + // String s = "Lambdaman!"; for (int i = 0; i < 13; ++i) { s = "na " + s; } return s; => a variation on a well known theme + MethodHandle fit13 = MethodHandles.constant(int.class, 13); + MethodHandle loop = MethodHandles.countedLoop(fit13, Counted.MH_start, Counted.MH_step); + assertEquals(Counted.MT_counted, loop.type()); + assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!")); + } + + @Test + public static void testCountedArrayLoop() throws Throwable { + // int[] a = new int[]{0}; for (int i = 0; i < 13; ++i) { ++a[0]; } => a[0] == 13 + MethodHandle fit13 = MethodHandles.dropArguments(MethodHandles.constant(int.class, 13), 0, int[].class); + MethodHandle loop = MethodHandles.countedLoop(fit13, null, Counted.MH_stepUpdateArray); + assertEquals(Counted.MT_arrayCounted, loop.type()); + int[] a = new int[]{0}; + loop.invoke(a); + assertEquals(13, a[0]); + } + + @Test + public static void testCountedPrintingLoop() throws Throwable { + MethodHandle fit5 = MethodHandles.constant(int.class, 5); + MethodHandle loop = MethodHandles.countedLoop(fit5, null, Counted.MH_printHello); + assertEquals(Counted.MT_countedPrinting, loop.type()); + loop.invoke(); + } + + @Test + public static void testCountedRangeLoop() throws Throwable { + // String s = "Lambdaman!"; for (int i = -5; i < 8; ++i) { s = "na " + s; } return s; => a well known theme + MethodHandle fitm5 = MethodHandles.dropArguments(Counted.MH_m5, 0, String.class); + MethodHandle fit8 = MethodHandles.dropArguments(Counted.MH_8, 0, String.class); + MethodHandle loop = MethodHandles.countedLoop(fitm5, fit8, Counted.MH_start, Counted.MH_step); + assertEquals(Counted.MT_counted, loop.type()); + assertEquals("na na na na na na na na na na na na na Lambdaman!", loop.invoke("Lambdaman!")); + } + + @Test + public static void testIterateSum() throws Throwable { + // Integer[] a = new Integer[]{1,2,3,4,5,6}; int sum = 0; for (int e : a) { sum += e; } return sum; => 21 + MethodHandle loop = MethodHandles.iteratedLoop(Iterate.MH_sumIterator, Iterate.MH_sumInit, Iterate.MH_sumStep); + assertEquals(Iterate.MT_sum, loop.type()); + assertEquals(21, loop.invoke(new Integer[]{1, 2, 3, 4, 5, 6})); + } + + @Test + public static void testIterateReverse() throws Throwable { + MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_reverseInit, Iterate.MH_reverseStep); + assertEquals(Iterate.MT_reverse, loop.type()); + List list = Arrays.asList("a", "b", "c", "d", "e"); + List reversedList = Arrays.asList("e", "d", "c", "b", "a"); + assertEquals(reversedList, (List) loop.invoke(list)); + } + + @Test + public static void testIterateLength() throws Throwable { + MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_lengthInit, Iterate.MH_lengthStep); + assertEquals(Iterate.MT_length, loop.type()); + List list = Arrays.asList(23.0, 148.0, 42.0); + assertEquals(list.size(), (int) loop.invoke(list)); + } + + @Test + public static void testIterateMap() throws Throwable { + MethodHandle loop = MethodHandles.iteratedLoop(null, Iterate.MH_mapInit, Iterate.MH_mapStep); + assertEquals(Iterate.MT_map, loop.type()); + List list = Arrays.asList("Hello", "world", "!"); + List upList = Arrays.asList("HELLO", "WORLD", "!"); + assertEquals(upList, (List) loop.invoke(list)); + } + + @Test + public static void testIteratePrint() throws Throwable { + MethodHandle loop = MethodHandles.iteratedLoop(null, null, Iterate.MH_printStep); + assertEquals(Iterate.MT_print, loop.type()); + loop.invoke(Arrays.asList("hello", "world")); + } + + @Test + public static void testIterateNullBody() { + boolean caught = false; + try { + MethodHandles.iteratedLoop(MethodHandles.identity(int.class), MethodHandles.identity(int.class), null); + } catch (IllegalArgumentException iae) { + assertEquals("iterated loop body must not be null", iae.getMessage()); + caught = true; + } + assertTrue(caught); + } + + @Test + public static void testTryFinally() throws Throwable { + MethodHandle hello = MethodHandles.tryFinally(TryFinally.MH_greet, TryFinally.MH_exclaim); + assertEquals(TryFinally.MT_hello, hello.type()); + assertEquals("Hello, world!", hello.invoke("world")); + } + + @Test + public static void testTryFinallyVoid() throws Throwable { + MethodHandle tfVoid = MethodHandles.tryFinally(TryFinally.MH_print, TryFinally.MH_printMore); + assertEquals(TryFinally.MT_printHello, tfVoid.type()); + tfVoid.invoke("world"); + } + + @Test + public static void testTryFinallySublist() throws Throwable { + MethodHandle helloMore = MethodHandles.tryFinally(TryFinally.MH_greetMore, TryFinally.MH_exclaimMore); + assertEquals(TryFinally.MT_moreHello, helloMore.type()); + assertEquals("Hello, world and universe (but world first)!", helloMore.invoke("world", "universe")); + } + + @Test + public static void testTryFinallyNegative() { + MethodHandle intid = MethodHandles.identity(int.class); + MethodHandle intco = MethodHandles.constant(int.class, 0); + MethodHandle errTarget = MethodHandles.dropArguments(intco, 0, int.class, double.class, String.class, int.class); + MethodHandle errCleanup = MethodHandles.dropArguments(MethodHandles.constant(int.class, 0), 0, Throwable.class, + int.class, double.class, Object.class); + MethodHandle[][] cases = { + {intid, MethodHandles.identity(double.class)}, + {intid, MethodHandles.dropArguments(intid, 0, String.class)}, + {intid, MethodHandles.dropArguments(intid, 0, Throwable.class, double.class)}, + {errTarget, errCleanup} + }; + String[] messages = { + "target and return types must match: double != int", + "cleanup first argument and Throwable must match: (String,int)int != class java.lang.Throwable", + "cleanup second argument and target return type must match: (Throwable,double,int)int != int", + "cleanup parameters after (Throwable,result) and target parameter list prefix must match: " + + errCleanup.type() + " != " + errTarget.type() + }; + for (int i = 0; i < cases.length; ++i) { + boolean caught = false; + try { + MethodHandles.tryFinally(cases[i][0], cases[i][1]); + } catch (IllegalArgumentException iae) { + assertEquals(messages[i], iae.getMessage()); + caught = true; + } + assertTrue(caught); + } + } + + @Test + public static void testFold0a() throws Throwable { + // equivalence to foldArguments(MethodHandle,MethodHandle) + MethodHandle fold = MethodHandles.foldArguments(Fold.MH_multer, 0, Fold.MH_adder); + assertEquals(Fold.MT_folded1, fold.type()); + assertEquals(720, (int) fold.invoke(3, 4, 5)); + } + + @Test + public static void testFold1a() throws Throwable { + // test foldArguments for folding position 1 + MethodHandle fold = MethodHandles.foldArguments(Fold.MH_multer, 1, Fold.MH_adder1); + assertEquals(Fold.MT_folded1, fold.type()); + assertEquals(540, (int) fold.invoke(3, 4, 5)); + } + + @Test + public static void testFold0b() throws Throwable { + // test foldArguments equivalence with multiple types + MethodHandle fold = MethodHandles.foldArguments(Fold.MH_str, 0, Fold.MH_comb); + assertEquals(Fold.MT_folded2, fold.type()); + assertEquals(23, (int) fold.invoke("true", true, 23)); + } + + @Test + public static void testFold1b() throws Throwable { + // test folgArguments for folding position 1, with multiple types + MethodHandle fold = MethodHandles.foldArguments(Fold.MH_str, 1, Fold.MH_comb2); + assertEquals(Fold.MT_folded3, fold.type()); + assertEquals(1, (int) fold.invoke(true, true, 1)); + assertEquals(-1, (int) fold.invoke(true, false, -1)); + } + + @Test + public static void testFoldArgumentsExample() throws Throwable { + // test the JavaDoc foldArguments-with-pos example + StringWriter swr = new StringWriter(); + MethodHandle trace = LOOKUP.findVirtual(StringWriter.class, "write", methodType(void.class, String.class)).bindTo(swr); + MethodHandle cat = LOOKUP.findVirtual(String.class, "concat", methodType(String.class, String.class)); + assertEquals("boojum", (String) cat.invokeExact("boo", "jum")); + MethodHandle catTrace = MethodHandles.foldArguments(cat, 1, trace); + assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum")); + assertEquals("jum", swr.toString()); + } + + @Test + public static void testAsSpreader() throws Throwable { + MethodHandle spreader = SpreadCollect.MH_forSpreading.asSpreader(1, int[].class, 3); + assertEquals(SpreadCollect.MT_spreader, spreader.type()); + assertEquals("A456B", (String) spreader.invoke("A", new int[]{4, 5, 6}, "B")); + } + + @Test + public static void testAsSpreaderExample() throws Throwable { + // test the JavaDoc asSpreader-with-pos example + MethodHandle compare = LOOKUP.findStatic(Objects.class, "compare", methodType(int.class, Object.class, Object.class, Comparator.class)); + MethodHandle compare2FromArray = compare.asSpreader(0, Object[].class, 2); + Object[] ints = new Object[]{3, 9, 7, 7}; + Comparator cmp = (a, b) -> a - b; + assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 0, 2), cmp) < 0); + assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 1, 3), cmp) > 0); + assertTrue((int) compare2FromArray.invoke(Arrays.copyOfRange(ints, 2, 4), cmp) == 0); + } + + @Test + public static void testAsSpreaderIllegalPos() throws Throwable { + int[] illegalPos = {-7, 3, 19}; + int caught = 0; + for (int p : illegalPos) { + try { + SpreadCollect.MH_forSpreading.asSpreader(p, Object[].class, 3); + } catch (IllegalArgumentException iae) { + assertEquals("bad spread position", iae.getMessage()); + ++caught; + } + } + assertEquals(illegalPos.length, caught); + } + + @Test + public static void testAsCollector() throws Throwable { + MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1); + assertEquals(SpreadCollect.MT_collector1, collector.type()); + assertEquals("A4B", (String) collector.invoke("A", 4, "B")); + collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2); + assertEquals(SpreadCollect.MT_collector2, collector.type()); + assertEquals("A45B", (String) collector.invoke("A", 4, 5, "B")); + collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3); + assertEquals(SpreadCollect.MT_collector3, collector.type()); + assertEquals("A456B", (String) collector.invoke("A", 4, 5, 6, "B")); + } + + @Test + public static void testAsCollectorInvokeWithArguments() throws Throwable { + MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 1); + assertEquals(SpreadCollect.MT_collector1, collector.type()); + assertEquals("A4B", (String) collector.invokeWithArguments("A", 4, "B")); + collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 2); + assertEquals(SpreadCollect.MT_collector2, collector.type()); + assertEquals("A45B", (String) collector.invokeWithArguments("A", 4, 5, "B")); + collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 3); + assertEquals(SpreadCollect.MT_collector3, collector.type()); + assertEquals("A456B", (String) collector.invokeWithArguments("A", 4, 5, 6, "B")); + } + + @Test + public static void testAsCollectorLeading() throws Throwable { + MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1); + assertEquals(SpreadCollect.MT_collectorLeading1, collector.type()); + assertEquals("7Q", (String) collector.invoke(7, "Q")); + collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2); + assertEquals(SpreadCollect.MT_collectorLeading2, collector.type()); + assertEquals("78Q", (String) collector.invoke(7, 8, "Q")); + collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3); + assertEquals(SpreadCollect.MT_collectorLeading3, collector.type()); + assertEquals("789Q", (String) collector.invoke(7, 8, 9, "Q")); + } + + @Test + public static void testAsCollectorLeadingInvokeWithArguments() throws Throwable { + MethodHandle collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 1); + assertEquals(SpreadCollect.MT_collectorLeading1, collector.type()); + assertEquals("7Q", (String) collector.invokeWithArguments(7, "Q")); + collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 2); + assertEquals(SpreadCollect.MT_collectorLeading2, collector.type()); + assertEquals("78Q", (String) collector.invokeWithArguments(7, 8, "Q")); + collector = SpreadCollect.MH_forCollectingLeading.asCollector(0, int[].class, 3); + assertEquals(SpreadCollect.MT_collectorLeading3, collector.type()); + assertEquals("789Q", (String) collector.invokeWithArguments(7, 8, 9, "Q")); + } + + @Test + public static void testAsCollectorNone() throws Throwable { + MethodHandle collector = SpreadCollect.MH_forCollecting.asCollector(1, int[].class, 0); + assertEquals(SpreadCollect.MT_collector0, collector.type()); + assertEquals("AB", (String) collector.invoke("A", "B")); + } + + @Test + public static void testAsCollectorIllegalPos() throws Throwable { + int[] illegalPos = {-1, 17}; + int caught = 0; + for (int p : illegalPos) { + try { + SpreadCollect.MH_forCollecting.asCollector(p, int[].class, 0); + } catch (IllegalArgumentException iae) { + assertEquals("bad collect position", iae.getMessage()); + ++caught; + } + } + assertEquals(illegalPos.length, caught); + } + + @Test + public static void testAsCollectorExample() throws Throwable { + // test the JavaDoc asCollector-with-pos example + StringWriter swr = new StringWriter(); + MethodHandle swWrite = LOOKUP. + findVirtual(StringWriter.class, "write", methodType(void.class, char[].class, int.class, int.class)). + bindTo(swr); + MethodHandle swWrite4 = swWrite.asCollector(0, char[].class, 4); + swWrite4.invoke('A', 'B', 'C', 'D', 1, 2); + assertEquals("BC", swr.toString()); + swWrite4.invoke('P', 'Q', 'R', 'S', 0, 4); + assertEquals("BCPQRS", swr.toString()); + swWrite4.invoke('W', 'X', 'Y', 'Z', 3, 1); + assertEquals("BCPQRSZ", swr.toString()); + } + + @Test + public static void testFindSpecial() throws Throwable { + FindSpecial.C c = new FindSpecial.C(); + assertEquals("I1.m", c.m()); + MethodType t = MethodType.methodType(String.class); + MethodHandle ci1m = LOOKUP.findSpecial(FindSpecial.I1.class, "m", t, FindSpecial.C.class); + assertEquals("I1.m", (String) ci1m.invoke(c)); + } + + @Test + public static void testFindSpecialAbstract() throws Throwable { + FindSpecial.C c = new FindSpecial.C(); + assertEquals("q", c.q()); + MethodType t = MethodType.methodType(String.class); + boolean caught = false; + try { + MethodHandle ci3q = LOOKUP.findSpecial(FindSpecial.I3.class, "q", t, FindSpecial.C.class); + } catch (Throwable thrown) { + if (!(thrown instanceof IllegalAccessException) || !FindSpecial.ABSTRACT_ERROR.equals(thrown.getMessage())) { + throw new AssertionError(thrown.getMessage(), thrown); + } + caught = true; + } + assertTrue(caught); + } + + @Test + public static void testFindClassCNFE() throws Throwable { + boolean caught = false; + try { + LOOKUP.findClass("does.not.Exist"); + } catch (ClassNotFoundException cnfe) { + caught = true; + } + assertTrue(caught); + } + + // + // Methods used to assemble tests. + // + + static class Empty { + + static void f() { } + + static boolean pred() { + return false; + } + + static final Class EMPTY = Empty.class; + + static final MethodType MT_f = methodType(void.class); + static final MethodType MT_pred = methodType(boolean.class); + + static final MethodHandle MH_f; + static final MethodHandle MH_pred; + + static { + try { + MH_f = LOOKUP.findStatic(EMPTY, "f", MT_f); + MH_pred = LOOKUP.findStatic(EMPTY, "pred", MT_pred); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + } + + static class Fac { + + static int zero(int k) { + return 0; + } + + static int one(int k) { + return 1; + } + + static boolean pred(int i, int acc, int k) { + return i < k; + } + + static int inc(int i, int acc, int k) { + return i + 1; + } + + static int mult(int i, int acc, int k) { + return i * acc; + } + + static void dot(int i, int acc, int k) { + System.out.print('.'); + } + + static int fin(int i, int acc, int k) { + return acc; + } + + static final Class FAC = Fac.class; + + static final MethodType MT_init = methodType(int.class, int.class); + static final MethodType MT_fn = methodType(int.class, int.class, int.class, int.class); + static final MethodType MT_dot = methodType(void.class, int.class, int.class, int.class); + static final MethodType MT_pred = methodType(boolean.class, int.class, int.class, int.class); + + static final MethodHandle MH_zero; + static final MethodHandle MH_one; + static final MethodHandle MH_pred; + static final MethodHandle MH_inc; + static final MethodHandle MH_mult; + static final MethodHandle MH_dot; + static final MethodHandle MH_fin; + + static final MethodType MT_fac = methodType(int.class, int.class); + + static { + try { + MH_zero = LOOKUP.findStatic(FAC, "zero", MT_init); + MH_one = LOOKUP.findStatic(FAC, "one", MT_init); + MH_pred = LOOKUP.findStatic(FAC, "pred", MT_pred); + MH_inc = LOOKUP.findStatic(FAC, "inc", MT_fn); + MH_mult = LOOKUP.findStatic(FAC, "mult", MT_fn); + MH_dot = LOOKUP.findStatic(FAC, "dot", MT_dot); + MH_fin = LOOKUP.findStatic(FAC, "fin", MT_fn); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class While { + + static int zero(int limit) { + return 0; + } + + static boolean pred(int i, int limit) { + return i < limit; + } + + static int step(int i, int limit) { + return i + 1; + } + + static String initString() { + return "a"; + } + + static boolean predString(String s) { + return s.length() != 1; + } + + static String stepString(String s) { + return s + "a"; + } + + static List zipInitZip(Iterator a, Iterator b) { + return new ArrayList<>(); + } + + static boolean zipPred(List zip, Iterator a, Iterator b) { + return a.hasNext() && b.hasNext(); + } + + static List zipStep(List zip, Iterator a, Iterator b) { + zip.add(a.next()); + zip.add(b.next()); + return zip; + } + + static final Class WHILE = While.class; + + static final MethodType MT_zero = methodType(int.class, int.class); + static final MethodType MT_pred = methodType(boolean.class, int.class, int.class); + static final MethodType MT_fn = methodType(int.class, int.class, int.class); + static final MethodType MT_initString = methodType(String.class); + static final MethodType MT_predString = methodType(boolean.class, String.class); + static final MethodType MT_stepString = methodType(String.class, String.class); + static final MethodType MT_zipInitZip = methodType(List.class, Iterator.class, Iterator.class); + static final MethodType MT_zipPred = methodType(boolean.class, List.class, Iterator.class, Iterator.class); + static final MethodType MT_zipStep = methodType(List.class, List.class, Iterator.class, Iterator.class); + + static final MethodHandle MH_zero; + static final MethodHandle MH_pred; + static final MethodHandle MH_step; + static final MethodHandle MH_initString; + static final MethodHandle MH_predString; + static final MethodHandle MH_stepString; + static final MethodHandle MH_zipInitZip; + static final MethodHandle MH_zipPred; + static final MethodHandle MH_zipStep; + + static final MethodType MT_while = methodType(int.class, int.class); + static final MethodType MT_string = methodType(String.class); + static final MethodType MT_zip = methodType(List.class, Iterator.class, Iterator.class); + + static { + try { + MH_zero = LOOKUP.findStatic(WHILE, "zero", MT_zero); + MH_pred = LOOKUP.findStatic(WHILE, "pred", MT_pred); + MH_step = LOOKUP.findStatic(WHILE, "step", MT_fn); + MH_initString = LOOKUP.findStatic(WHILE, "initString", MT_initString); + MH_predString = LOOKUP.findStatic(WHILE, "predString", MT_predString); + MH_stepString = LOOKUP.findStatic(WHILE, "stepString", MT_stepString); + MH_zipInitZip = LOOKUP.findStatic(WHILE, "zipInitZip", MT_zipInitZip); + MH_zipPred = LOOKUP.findStatic(WHILE, "zipPred", MT_zipPred); + MH_zipStep = LOOKUP.findStatic(WHILE, "zipStep", MT_zipStep); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class Counted { + + static String start(String arg) { + return arg; + } + + static String step(int counter, String v, String arg) { + return "na " + v; + } + + static void stepUpdateArray(int counter, int[] a) { + ++a[0]; + } + + static void printHello(int counter) { + System.out.print("hello"); + } + + static final Class COUNTED = Counted.class; + + static final MethodType MT_start = methodType(String.class, String.class); + static final MethodType MT_step = methodType(String.class, int.class, String.class, String.class); + static final MethodType MT_stepUpdateArray = methodType(void.class, int.class, int[].class); + static final MethodType MT_printHello = methodType(void.class, int.class); + + static final MethodHandle MH_13; + static final MethodHandle MH_m5; + static final MethodHandle MH_8; + static final MethodHandle MH_start; + static final MethodHandle MH_step; + static final MethodHandle MH_stepUpdateArray; + static final MethodHandle MH_printHello; + + static final MethodType MT_counted = methodType(String.class, String.class); + static final MethodType MT_arrayCounted = methodType(void.class, int[].class); + static final MethodType MT_countedPrinting = methodType(void.class); + + static { + try { + MH_13 = MethodHandles.constant(int.class, 13); + MH_m5 = MethodHandles.constant(int.class, -5); + MH_8 = MethodHandles.constant(int.class, 8); + MH_start = LOOKUP.findStatic(COUNTED, "start", MT_start); + MH_step = LOOKUP.findStatic(COUNTED, "step", MT_step); + MH_stepUpdateArray = LOOKUP.findStatic(COUNTED, "stepUpdateArray", MT_stepUpdateArray); + MH_printHello = LOOKUP.findStatic(COUNTED, "printHello", MT_printHello); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class Iterate { + + static Iterator sumIterator(Integer[] a) { + return Arrays.asList(a).iterator(); + } + + static int sumInit(Integer[] a) { + return 0; + } + + static int sumStep(int s, int e, Integer[] a) { + return s + e; + } + + static List reverseInit(List l) { + return new ArrayList<>(); + } + + static List reverseStep(String e, List r, List l) { + r.add(0, e); + return r; + } + + static int lengthInit(List l) { + return 0; + } + + static int lengthStep(Object o, int len, List l) { + return len + 1; + } + + static List mapInit(List l) { + return new ArrayList<>(); + } + + static List mapStep(String e, List r, List l) { + r.add(e.toUpperCase()); + return r; + } + + static void printStep(String s, List l) { + System.out.print(s); + } + + static final Class ITERATE = Iterate.class; + + static final MethodType MT_sumIterator = methodType(Iterator.class, Integer[].class); + + static final MethodType MT_sumInit = methodType(int.class, Integer[].class); + static final MethodType MT_reverseInit = methodType(List.class, List.class); + static final MethodType MT_lenghInit = methodType(int.class, List.class); + static final MethodType MT_mapInit = methodType(List.class, List.class); + + static final MethodType MT_sumStep = methodType(int.class, int.class, int.class, Integer[].class); + static final MethodType MT_reverseStep = methodType(List.class, String.class, List.class, List.class); + static final MethodType MT_lengthStep = methodType(int.class, Object.class, int.class, List.class); + static final MethodType MT_mapStep = methodType(List.class, String.class, List.class, List.class); + static final MethodType MT_printStep = methodType(void.class, String.class, List.class); + + static final MethodHandle MH_sumIterator; + static final MethodHandle MH_sumInit; + static final MethodHandle MH_sumStep; + static final MethodHandle MH_printStep; + + static final MethodHandle MH_reverseInit; + static final MethodHandle MH_reverseStep; + + static final MethodHandle MH_lengthInit; + static final MethodHandle MH_lengthStep; + + static final MethodHandle MH_mapInit; + static final MethodHandle MH_mapStep; + + static final MethodType MT_sum = methodType(int.class, Integer[].class); + static final MethodType MT_reverse = methodType(List.class, List.class); + static final MethodType MT_length = methodType(int.class, List.class); + static final MethodType MT_map = methodType(List.class, List.class); + static final MethodType MT_print = methodType(void.class, List.class); + + static { + try { + MH_sumIterator = LOOKUP.findStatic(ITERATE, "sumIterator", MT_sumIterator); + MH_sumInit = LOOKUP.findStatic(ITERATE, "sumInit", MT_sumInit); + MH_sumStep = LOOKUP.findStatic(ITERATE, "sumStep", MT_sumStep); + MH_reverseInit = LOOKUP.findStatic(ITERATE, "reverseInit", MT_reverseInit); + MH_reverseStep = LOOKUP.findStatic(ITERATE, "reverseStep", MT_reverseStep); + MH_lengthInit = LOOKUP.findStatic(ITERATE, "lengthInit", MT_lenghInit); + MH_lengthStep = LOOKUP.findStatic(ITERATE, "lengthStep", MT_lengthStep); + MH_mapInit = LOOKUP.findStatic(ITERATE, "mapInit", MT_mapInit); + MH_mapStep = LOOKUP.findStatic(ITERATE, "mapStep", MT_mapStep); + MH_printStep = LOOKUP.findStatic(ITERATE, "printStep", MT_printStep); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class TryFinally { + + static String greet(String whom) { + return "Hello, " + whom; + } + + static String exclaim(Throwable t, String r, String whom) { + return r + "!"; + } + + static void print(String what) { + System.out.print("Hello, " + what); + } + + static void printMore(Throwable t, String what) { + System.out.println("!"); + } + + static String greetMore(String first, String second) { + return "Hello, " + first + " and " + second; + } + + static String exclaimMore(Throwable t, String r, String first) { + return r + " (but " + first + " first)!"; + } + + static final Class TRY_FINALLY = TryFinally.class; + + static final MethodType MT_greet = methodType(String.class, String.class); + static final MethodType MT_exclaim = methodType(String.class, Throwable.class, String.class, String.class); + static final MethodType MT_print = methodType(void.class, String.class); + static final MethodType MT_printMore = methodType(void.class, Throwable.class, String.class); + static final MethodType MT_greetMore = methodType(String.class, String.class, String.class); + static final MethodType MT_exclaimMore = methodType(String.class, Throwable.class, String.class, String.class); + + static final MethodHandle MH_greet; + static final MethodHandle MH_exclaim; + static final MethodHandle MH_print; + static final MethodHandle MH_printMore; + static final MethodHandle MH_greetMore; + static final MethodHandle MH_exclaimMore; + + static final MethodType MT_hello = methodType(String.class, String.class); + static final MethodType MT_printHello = methodType(void.class, String.class); + static final MethodType MT_moreHello = methodType(String.class, String.class, String.class); + + static { + try { + MH_greet = LOOKUP.findStatic(TRY_FINALLY, "greet", MT_greet); + MH_exclaim = LOOKUP.findStatic(TRY_FINALLY, "exclaim", MT_exclaim); + MH_print = LOOKUP.findStatic(TRY_FINALLY, "print", MT_print); + MH_printMore = LOOKUP.findStatic(TRY_FINALLY, "printMore", MT_printMore); + MH_greetMore = LOOKUP.findStatic(TRY_FINALLY, "greetMore", MT_greetMore); + MH_exclaimMore = LOOKUP.findStatic(TRY_FINALLY, "exclaimMore", MT_exclaimMore); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class Fold { + + static int adder(int a, int b, int c) { + return a + b + c; + } + + static int adder1(int a, int b) { + return a + b; + } + + static int multer(int x, int q, int r, int s) { + return x * q * r * s; + } + + static int str(boolean b1, String s, boolean b2, int x) { + return b1 && s.equals(String.valueOf(b2)) ? x : -x; + } + + static boolean comb(String s, boolean b2) { + return !s.equals(b2); + } + + static String comb2(boolean b2, int x) { + int ib = b2 ? 1 : 0; + return ib == x ? "true" : "false"; + } + + static final Class FOLD = Fold.class; + + static final MethodType MT_adder = methodType(int.class, int.class, int.class, int.class); + static final MethodType MT_adder1 = methodType(int.class, int.class, int.class); + static final MethodType MT_multer = methodType(int.class, int.class, int.class, int.class, int.class); + static final MethodType MT_str = methodType(int.class, boolean.class, String.class, boolean.class, int.class); + static final MethodType MT_comb = methodType(boolean.class, String.class, boolean.class); + static final MethodType MT_comb2 = methodType(String.class, boolean.class, int.class); + + static final MethodHandle MH_adder; + static final MethodHandle MH_adder1; + static final MethodHandle MH_multer; + static final MethodHandle MH_str; + static final MethodHandle MH_comb; + static final MethodHandle MH_comb2; + + static final MethodType MT_folded1 = methodType(int.class, int.class, int.class, int.class); + static final MethodType MT_folded2 = methodType(int.class, String.class, boolean.class, int.class); + static final MethodType MT_folded3 = methodType(int.class, boolean.class, boolean.class, int.class); + + static { + try { + MH_adder = LOOKUP.findStatic(FOLD, "adder", MT_adder); + MH_adder1 = LOOKUP.findStatic(FOLD, "adder1", MT_adder1); + MH_multer = LOOKUP.findStatic(FOLD, "multer", MT_multer); + MH_str = LOOKUP.findStatic(FOLD, "str", MT_str); + MH_comb = LOOKUP.findStatic(FOLD, "comb", MT_comb); + MH_comb2 = LOOKUP.findStatic(FOLD, "comb2", MT_comb2); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + } + + static class SpreadCollect { + + static String forSpreading(String s1, int i1, int i2, int i3, String s2) { + return s1 + i1 + i2 + i3 + s2; + } + + static String forCollecting(String s1, int[] is, String s2) { + StringBuilder sb = new StringBuilder(s1); + for (int i : is) { + sb.append(i); + } + return sb.append(s2).toString(); + } + + static String forCollectingLeading(int[] is, String s) { + return forCollecting("", is, s); + } + + static final Class SPREAD_COLLECT = SpreadCollect.class; + + static final MethodType MT_forSpreading = methodType(String.class, String.class, int.class, int.class, int.class, String.class); + static final MethodType MT_forCollecting = methodType(String.class, String.class, int[].class, String.class); + static final MethodType MT_forCollectingLeading = methodType(String.class, int[].class, String.class); + + static final MethodHandle MH_forSpreading; + static final MethodHandle MH_forCollecting; + static final MethodHandle MH_forCollectingLeading; + + static final MethodType MT_spreader = methodType(String.class, String.class, int[].class, String.class); + static final MethodType MT_collector0 = methodType(String.class, String.class, String.class); + static final MethodType MT_collector1 = methodType(String.class, String.class, int.class, String.class); + static final MethodType MT_collector2 = methodType(String.class, String.class, int.class, int.class, String.class); + static final MethodType MT_collector3 = methodType(String.class, String.class, int.class, int.class, int.class, String.class); + static final MethodType MT_collectorLeading1 = methodType(String.class, int.class, String.class); + static final MethodType MT_collectorLeading2 = methodType(String.class, int.class, int.class, String.class); + static final MethodType MT_collectorLeading3 = methodType(String.class, int.class, int.class, int.class, String.class); + + static final String NONE_ERROR = "zero array length in MethodHandle.asCollector"; + + static { + try { + MH_forSpreading = LOOKUP.findStatic(SPREAD_COLLECT, "forSpreading", MT_forSpreading); + MH_forCollecting = LOOKUP.findStatic(SPREAD_COLLECT, "forCollecting", MT_forCollecting); + MH_forCollectingLeading = LOOKUP.findStatic(SPREAD_COLLECT, "forCollectingLeading", MT_forCollectingLeading); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + } + + static class FindSpecial { + + interface I1 { + default String m() { + return "I1.m"; + } + } + + interface I2 { + default String m() { + return "I2.m"; + } + } + + interface I3 { + String q(); + } + + static class C implements I1, I2, I3 { + public String m() { + return I1.super.m(); + } + public String q() { + return "q"; + } + } + + static final String ABSTRACT_ERROR = "no such method: test.java.lang.invoke.T8139885$FindSpecial$I3.q()String/invokeSpecial"; + + } + + // + // Auxiliary methods. + // + + static MethodHandle[] mha(MethodHandle... mhs) { + return mhs; + } + +} diff --git a/jdk/test/java/lang/invoke/findclass.security.policy b/jdk/test/java/lang/invoke/findclass.security.policy new file mode 100644 index 00000000000..7bf9d7a99c1 --- /dev/null +++ b/jdk/test/java/lang/invoke/findclass.security.policy @@ -0,0 +1,9 @@ +/* + * Security policy used by the FindClassSecurityManager test. + * Must allow file reads so that jtreg itself can run, and getting class loaders. + */ + +grant { + permission java.io.FilePermission "*", "read"; + permission java.lang.RuntimePermission "getClassLoader"; +}; From bd8942b7a1e37b17ef3de1a6bec5dd238ff719c5 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Fri, 20 Nov 2015 19:26:16 +0100 Subject: [PATCH 09/12] 8140364: JEP 264 Platform Logger API and Service Implementation Initial implementation for JEP 264 Platform Logger API and Service Reviewed-by: mchung, psandoz, rriggs --- .../classes/java/lang/RuntimePermission.java | 13 + .../share/classes/java/lang/System.java | 650 ++++- .../logger/AbstractLoggerWrapper.java | 380 +++ .../jdk/internal/logger/BootstrapLogger.java | 1074 ++++++++ .../internal/logger/DefaultLoggerFinder.java | 173 ++ .../jdk/internal/logger/LazyLoggers.java | 446 ++++ .../logger/LocalizedLoggerWrapper.java | 155 ++ .../internal/logger/LoggerFinderLoader.java | 210 ++ .../jdk/internal/logger/LoggerWrapper.java | 65 + .../internal/logger/SimpleConsoleLogger.java | 486 ++++ .../jdk/internal/logger/package-info.java | 68 + .../sun/util/logging/LoggingProxy.java | 68 - .../sun/util/logging/LoggingSupport.java | 190 -- .../sun/util/logging/PlatformLogger.java | 647 ++--- .../share/classes/sun/font/FontUtilities.java | 2 + .../jdk.internal.logger.DefaultLoggerFinder | 1 + .../classes/java/util/logging/LogManager.java | 74 +- .../classes/java/util/logging/LogRecord.java | 31 +- .../classes/java/util/logging/Logger.java | 88 +- .../java/util/logging/LoggingProxyImpl.java | 117 - .../java/util/logging/SimpleFormatter.java | 13 +- .../logging/internal/LoggingProviderImpl.java | 466 ++++ .../util/logging/internal/package-info.java | 55 + .../DefaultPlatformMBeanProvider.java | 46 +- .../management/ManagementFactoryHelper.java | 56 +- .../System/Logger/Level/LoggerLevelTest.java | 86 + .../Logger/custom/AccessSystemLogger.java | 78 + .../Logger/custom/CustomLoggerTest.java | 728 ++++++ .../services/java.lang.System$LoggerFinder | 1 + .../Logger/default/AccessSystemLogger.java | 83 + .../Logger/default/DefaultLoggerTest.java | 726 ++++++ .../Logger/interface/LoggerInterfaceTest.java | 592 +++++ .../AccessSystemLogger.java | 78 + .../BaseLoggerFinder.java | 47 + .../BaseLoggerFinderTest.java | 694 +++++ .../CustomSystemClassLoader.java | 101 + .../services/java.lang.System$LoggerFinder | 1 + .../TestLoggerFinder.java | 181 ++ .../AccessSystemLogger.java | 82 + .../DefaultLoggerFinderTest.java | 887 +++++++ .../AccessSystemLogger.java | 82 + .../BaseDefaultLoggerFinderTest.java | 768 ++++++ .../CustomSystemClassLoader.java | 117 + .../services/java.lang.System$LoggerFinder | 1 + .../BaseLoggerBridgeTest.java | 1058 ++++++++ .../CustomSystemClassLoader.java | 101 + .../services/java.lang.System$LoggerFinder | 1 + .../BasePlatformLoggerTest.java | 732 ++++++ .../CustomSystemClassLoader.java | 101 + .../services/java.lang.System$LoggerFinder | 1 + .../BootstrapLogger/BootstrapLoggerTest.java | 430 +++ .../CustomSystemClassLoader.java | 141 + .../LoggerBridgeTest/LoggerBridgeTest.java | 1087 ++++++++ .../services/java.lang.System$LoggerFinder | 1 + .../AccessSystemLogger.java | 82 + .../CustomSystemClassLoader.java | 118 + .../LoggerFinderLoaderTest.java | 882 +++++++ .../services/java.lang.System$LoggerFinder | 3 + .../CustomSystemClassLoader.java | 101 + .../services/java.lang.System$LoggerFinder | 1 + .../PlatformLoggerBridgeTest.java | 876 +++++++ .../internal/api/LoggerFinderAPITest.java | 497 ++++ .../backend/LoggerFinderBackendTest.java | 2316 +++++++++++++++++ .../services/java.lang.System$LoggerFinder | 2 + .../internal/backend/SystemClassLoader.java | 98 + .../DefaultLoggerBridgeTest.java | 850 ++++++ .../DefaultPlatformLoggerTest.java | 544 ++++ .../java/util/logging/LoggerSubclass.java | 2 +- .../sun/util/logging/PlatformLoggerTest.java | 5 +- 69 files changed, 20108 insertions(+), 829 deletions(-) create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java create mode 100644 jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java delete mode 100644 jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java delete mode 100644 jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java create mode 100644 jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder delete mode 100644 jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java create mode 100644 jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java create mode 100644 jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java create mode 100644 jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java create mode 100644 jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java create mode 100644 jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java create mode 100644 jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder create mode 100644 jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java create mode 100644 jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java diff --git a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java index 36b48b75d39..d014ddb3378 100644 --- a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java +++ b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java @@ -348,6 +348,19 @@ import java.util.StringTokenizer; * {@code java.util.spi.LocaleServiceProvider} for more * information. * + * + * + * loggerFinder + * This {@code RuntimePermission} is required to be granted to + * classes which subclass or call methods on + * {@code java.lang.System.LoggerFinder}. The permission is + * checked during invocation of the abstract base class constructor, as + * well as on the invocation of its public methods. + * This permission ensures trust in classes which provide loggers + * to system classes. + * See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder} + * for more information. + * * * * @implNote diff --git a/jdk/src/java.base/share/classes/java/lang/System.java b/jdk/src/java.base/share/classes/java/lang/System.java index 44336f9ca2b..9a109ce756e 100644 --- a/jdk/src/java.base/share/classes/java/lang/System.java +++ b/jdk/src/java.base/share/classes/java/lang/System.java @@ -30,13 +30,14 @@ import java.lang.annotation.Annotation; import java.security.AccessControlContext; import java.util.Properties; import java.util.PropertyPermission; -import java.util.StringTokenizer; import java.util.Map; import java.security.AccessController; import java.security.PrivilegedAction; -import java.security.AllPermission; import java.nio.channels.Channel; import java.nio.channels.spi.SelectorProvider; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Supplier; import sun.nio.ch.Interruptible; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; @@ -45,6 +46,9 @@ import sun.reflect.annotation.AnnotationType; import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangAccess;; import jdk.internal.misc.SharedSecrets;; +import jdk.internal.logger.LoggerFinderLoader; +import jdk.internal.logger.LazyLoggers; +import jdk.internal.logger.LocalizedLoggerWrapper; /** * The System class contains several useful class fields @@ -943,6 +947,648 @@ public final class System { return ProcessEnvironment.getenv(); } + /** + * {@code System.Logger} instances log messages that will be + * routed to the underlying logging framework the {@link System.LoggerFinder + * LoggerFinder} uses. + *

    + * {@code System.Logger} instances are typically obtained from + * the {@link java.lang.System System} class, by calling + * {@link java.lang.System#getLogger(java.lang.String) System.getLogger(loggerName)} + * or {@link java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle) + * System.getLogger(loggerName, bundle)}. + * + * @see java.lang.System#getLogger(java.lang.String) + * @see java.lang.System#getLogger(java.lang.String, java.util.ResourceBundle) + * @see java.lang.System.LoggerFinder + * + * @since 9 + * + */ + public interface Logger { + + /** + * System {@linkplain Logger loggers} levels. + *

    + * A level has a {@linkplain #getName() name} and {@linkplain + * #getSeverity() severity}. + * Level values are {@link #ALL}, {@link #TRACE}, {@link #DEBUG}, + * {@link #INFO}, {@link #WARNING}, {@link #ERROR}, {@link #OFF}, + * by order of increasing severity. + *
    + * {@link #ALL} and {@link #OFF} + * are simple markers with severities mapped respectively to + * {@link java.lang.Integer#MIN_VALUE Integer.MIN_VALUE} and + * {@link java.lang.Integer#MAX_VALUE Integer.MAX_VALUE}. + *

    + * Severity values and Mapping to {@code java.util.logging.Level}. + *

    + * {@linkplain System.Logger.Level System logger levels} are mapped to + * {@linkplain java.util.logging.Level java.util.logging levels} + * of corresponding severity. + *
    The mapping is as follows: + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    System.Logger Severity Level Mapping
    System.Logger Levels{@link Logger.Level#ALL ALL}{@link Logger.Level#TRACE TRACE}{@link Logger.Level#DEBUG DEBUG}{@link Logger.Level#INFO INFO}{@link Logger.Level#WARNING WARNING}{@link Logger.Level#ERROR ERROR}{@link Logger.Level#OFF OFF}
    java.util.logging Levels{@link java.util.logging.Level#ALL ALL}{@link java.util.logging.Level#FINER FINER}{@link java.util.logging.Level#FINE FINE}{@link java.util.logging.Level#INFO INFO}{@link java.util.logging.Level#WARNING WARNING}{@link java.util.logging.Level#SEVERE SEVERE}{@link java.util.logging.Level#OFF OFF}
    + * + * @since 9 + * + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + */ + public enum Level { + + // for convenience, we're reusing java.util.logging.Level int values + // the mapping logic in sun.util.logging.PlatformLogger depends + // on this. + /** + * A marker to indicate that all levels are enabled. + * This level {@linkplain #getSeverity() severity} is + * {@link Integer#MIN_VALUE}. + */ + ALL(Integer.MIN_VALUE), // typically mapped to/from j.u.l.Level.ALL + /** + * {@code TRACE} level: usually used to log diagnostic information. + * This level {@linkplain #getSeverity() severity} is + * {@code 400}. + */ + TRACE(400), // typically mapped to/from j.u.l.Level.FINER + /** + * {@code DEBUG} level: usually used to log debug information traces. + * This level {@linkplain #getSeverity() severity} is + * {@code 500}. + */ + DEBUG(500), // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG + /** + * {@code INFO} level: usually used to log information messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 800}. + */ + INFO(800), // typically mapped to/from j.u.l.Level.INFO + /** + * {@code WARNING} level: usually used to log warning messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 900}. + */ + WARNING(900), // typically mapped to/from j.u.l.Level.WARNING + /** + * {@code ERROR} level: usually used to log error messages. + * This level {@linkplain #getSeverity() severity} is + * {@code 1000}. + */ + ERROR(1000), // typically mapped to/from j.u.l.Level.SEVERE + /** + * A marker to indicate that all levels are disabled. + * This level {@linkplain #getSeverity() severity} is + * {@link Integer#MAX_VALUE}. + */ + OFF(Integer.MAX_VALUE); // typically mapped to/from j.u.l.Level.OFF + + private final int severity; + + private Level(int severity) { + this.severity = severity; + } + + /** + * Returns the name of this level. + * @return this level {@linkplain #name()}. + */ + public final String getName() { + return name(); + } + + /** + * Returns the severity of this level. + * A higher severity means a more severe condition. + * @return this level severity. + */ + public final int getSeverity() { + return severity; + } + } + + /** + * Returns the name of this logger. + * + * @return the logger name. + */ + public String getName(); + + /** + * Checks if a message of the given level would be logged by + * this logger. + * + * @param level the log message level. + * @return {@code true} if the given log message level is currently + * being logged. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public boolean isLoggable(Level level); + + /** + * Logs a message. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msg, (Object[])null);} + * + * @param level the log message level. + * @param msg the string message (or a key in the message catalog, if + * this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String msg) { + log(level, (ResourceBundle) null, msg, (Object[]) null); + } + + /** + * Logs a lazily supplied message. + *

    + * If the logger is currently enabled for the given log message level + * then a message is logged that is the result produced by the + * given supplier function. Otherwise, the supplier is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), (Object[])null);} + * + * @param level the log message level. + * @param msgSupplier a supplier function that produces a message. + * + * @throws NullPointerException if {@code level} is {@code null}, + * or {@code msgSupplier} is {@code null}. + */ + public default void log(Level level, Supplier msgSupplier) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + log(level, (ResourceBundle) null, msgSupplier.get(), (Object[]) null); + } + } + + /** + * Logs a message produced from the given object. + *

    + * If the logger is currently enabled for the given log message level then + * a message is logged that, by default, is the result produced from + * calling toString on the given object. + * Otherwise, the object is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, obj.toString(), (Object[])null);} + * + * @param level the log message level. + * @param obj the object to log. + * + * @throws NullPointerException if {@code level} is {@code null}, or + * {@code obj} is {@code null}. + */ + public default void log(Level level, Object obj) { + Objects.requireNonNull(obj); + if (isLoggable(Objects.requireNonNull(level))) { + this.log(level, (ResourceBundle) null, obj.toString(), (Object[]) null); + } + } + + /** + * Logs a message associated with a given throwable. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msg, thrown);} + * + * @param level the log message level. + * @param msg the string message (or a key in the message catalog, if + * this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * @param thrown a {@code Throwable} associated with the log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String msg, Throwable thrown) { + this.log(level, null, msg, thrown); + } + + /** + * Logs a lazily supplied message associated with a given throwable. + *

    + * If the logger is currently enabled for the given log message level + * then a message is logged that is the result produced by the + * given supplier function. Otherwise, the supplier is not operated on. + * + * @implSpec When logging is enabled for the given level, the default + * implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, msgSupplier.get(), thrown);} + * + * @param level one of the log message level identifiers. + * @param msgSupplier a supplier function that produces a message. + * @param thrown a {@code Throwable} associated with log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}, or + * {@code msgSupplier} is {@code null}. + */ + public default void log(Level level, Supplier msgSupplier, + Throwable thrown) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + this.log(level, null, msgSupplier.get(), thrown); + } + } + + /** + * Logs a message with an optional list of parameters. + * + * @implSpec The default implementation for this method calls + * {@code this.log(level, (ResourceBundle)null, format, params);} + * + * @param level one of the log message level identifiers. + * @param format the string message format in {@link + * java.text.MessageFormat} format, (or a key in the message + * catalog, if this logger is a {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class) + * localized logger}); can be {@code null}. + * @param params an optional list of parameters to the message (may be + * none). + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public default void log(Level level, String format, Object... params) { + this.log(level, null, format, params); + } + + /** + * Logs a localized message associated with a given throwable. + *

    + * If the given resource bundle is non-{@code null}, the {@code msg} + * string is localized using the given resource bundle. + * Otherwise the {@code msg} string is not localized. + * + * @param level the log message level. + * @param bundle a resource bundle to localize {@code msg}; can be + * {@code null}. + * @param msg the string message (or a key in the message catalog, + * if {@code bundle} is not {@code null}); can be {@code null}. + * @param thrown a {@code Throwable} associated with the log message; + * can be {@code null}. + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public void log(Level level, ResourceBundle bundle, String msg, + Throwable thrown); + + /** + * Logs a message with resource bundle and an optional list of + * parameters. + *

    + * If the given resource bundle is non-{@code null}, the {@code format} + * string is localized using the given resource bundle. + * Otherwise the {@code format} string is not localized. + * + * @param level the log message level. + * @param bundle a resource bundle to localize {@code format}; can be + * {@code null}. + * @param format the string message format in {@link + * java.text.MessageFormat} format, (or a key in the message + * catalog if {@code bundle} is not {@code null}); can be {@code null}. + * @param params an optional list of parameters to the message (may be + * none). + * + * @throws NullPointerException if {@code level} is {@code null}. + */ + public void log(Level level, ResourceBundle bundle, String format, + Object... params); + + + } + + /** + * The {@code LoggerFinder} service is responsible for creating, managing, + * and configuring loggers to the underlying framework it uses. + *

    + * A logger finder is a concrete implementation of this class that has a + * zero-argument constructor and implements the abstract methods defined + * by this class. + * The loggers returned from a logger finder are capable of routing log + * messages to the logging backend this provider supports. + * A given invocation of the Java Runtime maintains a single + * system-wide LoggerFinder instance that is loaded as follows: + *

      + *
    • First it finds any custom {@code LoggerFinder} provider + * using the {@link java.util.ServiceLoader} facility with the + * {@linkplain ClassLoader#getSystemClassLoader() system class + * loader}.
    • + *
    • If no {@code LoggerFinder} provider is found, the system default + * {@code LoggerFinder} implementation will be used.
    • + *
    + *

    + * An application can replace the logging backend + * even when the java.logging module is present, by simply providing + * and declaring an implementation of the {@link LoggerFinder} service. + *

    + * Default Implementation + *

    + * The system default {@code LoggerFinder} implementation uses + * {@code java.util.logging} as the backend framework when the + * {@code java.logging} module is present. + * It returns a {@linkplain System.Logger logger} instance + * that will route log messages to a {@link java.util.logging.Logger + * java.util.logging.Logger}. Otherwise, if {@code java.logging} is not + * present, the default implementation will return a simple logger + * instance that will route log messages of {@code INFO} level and above to + * the console ({@code System.err}). + *

    + * Logging Configuration + *

    + * {@linkplain Logger Logger} instances obtained from the + * {@code LoggerFinder} factory methods are not directly configurable by + * the application. Configuration is the responsibility of the underlying + * logging backend, and usually requires using APIs specific to that backend. + *

    For the default {@code LoggerFinder} implementation + * using {@code java.util.logging} as its backend, refer to + * {@link java.util.logging java.util.logging} for logging configuration. + * For the default {@code LoggerFinder} implementation returning simple loggers + * when the {@code java.logging} module is absent, the configuration + * is implementation dependent. + *

    + * Usually an application that uses a logging framework will log messages + * through a logger facade defined (or supported) by that framework. + * Applications that wish to use an external framework should log + * through the facade associated with that framework. + *

    + * A system class that needs to log messages will typically obtain + * a {@link System.Logger} instance to route messages to the logging + * framework selected by the application. + *

    + * Libraries and classes that only need loggers to produce log messages + * should not attempt to configure loggers by themselves, as that + * would make them dependent from a specific implementation of the + * {@code LoggerFinder} service. + *

    + * In addition, when a security manager is present, loggers provided to + * system classes should not be directly configurable through the logging + * backend without requiring permissions. + *
    + * It is the responsibility of the provider of + * the concrete {@code LoggerFinder} implementation to ensure that + * these loggers are not configured by untrusted code without proper + * permission checks, as configuration performed on such loggers usually + * affects all applications in the same Java Runtime. + *

    + * Message Levels and Mapping to backend levels + *

    + * A logger finder is responsible for mapping from a {@code + * System.Logger.Level} to a level supported by the logging backend it uses. + *
    The default LoggerFinder using {@code java.util.logging} as the backend + * maps {@code System.Logger} levels to + * {@linkplain java.util.logging.Level java.util.logging} levels + * of corresponding severity - as described in {@link Logger.Level + * Logger.Level}. + * + * @see java.lang.System + * @see java.lang.System.Logger + * + * @since 9 + */ + public static abstract class LoggerFinder { + /** + * The {@code RuntimePermission("loggerFinder")} is + * necessary to subclass and instantiate the {@code LoggerFinder} class, + * as well as to obtain loggers from an instance of that class. + */ + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + /** + * Creates a new instance of {@code LoggerFinder}. + * + * @implNote It is recommended that a {@code LoggerFinder} service + * implementation does not perform any heavy initialization in its + * constructor, in order to avoid possible risks of deadlock or class + * loading cycles during the instantiation of the service provider. + * + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + protected LoggerFinder() { + this(checkPermission()); + } + + private LoggerFinder(Void unused) { + // nothing to do. + } + + private static Void checkPermission() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return null; + } + + /** + * Returns an instance of {@link Logger Logger} + * for the given {@code caller}. + * + * @param name the name of the logger. + * @param caller the class for which the logger is being requested; + * can be {@code null}. + * + * @return a {@link Logger logger} suitable for the given caller's + * use. + * @throws NullPointerException if {@code name} is {@code null} or + * {@code caller} is {@code null}. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public abstract Logger getLogger(String name, /* Module */ Class caller); + + /** + * Returns a localizable instance of {@link Logger Logger} + * for the given {@code caller}. + * The returned logger will use the provided resource bundle for + * message localization. + * + * @implSpec By default, this method calls {@link + * #getLogger(java.lang.String, java.lang.Class) + * this.getLogger(name, caller)} to obtain a logger, then wraps that + * logger in a {@link Logger} instance where all methods that do not + * take a {@link ResourceBundle} as parameter are redirected to one + * which does - passing the given {@code bundle} for + * localization. So for instance, a call to {@link + * Logger#log(Level, String) Logger.log(Level.INFO, msg)} + * will end up as a call to {@link + * Logger#log(Level, ResourceBundle, String, Object...) + * Logger.log(Level.INFO, bundle, msg, (Object[])null)} on the wrapped + * logger instance. + * Note however that by default, string messages returned by {@link + * java.util.function.Supplier Supplier<String>} will not be + * localized, as it is assumed that such strings are messages which are + * already constructed, rather than keys in a resource bundle. + *

    + * An implementation of {@code LoggerFinder} may override this method, + * for example, when the underlying logging backend provides its own + * mechanism for localizing log messages, then such a + * {@code LoggerFinder} would be free to return a logger + * that makes direct use of the mechanism provided by the backend. + * + * @param name the name of the logger. + * @param bundle a resource bundle; can be {@code null}. + * @param caller the class for which the logger is being requested. + * @return an instance of {@link Logger Logger} which will use the + * provided resource bundle for message localization. + * + * @throws NullPointerException if {@code name} is {@code null} or + * {@code caller} is {@code null}. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public Logger getLocalizedLogger(String name, ResourceBundle bundle, + /* Module */ Class caller) { + return new LocalizedLoggerWrapper<>(getLogger(name, caller), bundle); + } + + /** + * Returns the {@code LoggerFinder} instance. There is one + * single system-wide {@code LoggerFinder} instance in + * the Java Runtime. See the class specification of how the + * {@link LoggerFinder LoggerFinder} implementation is located and + * loaded. + + * @return the {@link LoggerFinder LoggerFinder} instance. + * @throws SecurityException if a security manager is present and its + * {@code checkPermission} method doesn't allow the + * {@code RuntimePermission("loggerFinder")}. + */ + public static LoggerFinder getLoggerFinder() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return accessProvider(); + } + + + private static volatile LoggerFinder service; + static LoggerFinder accessProvider() { + // We do not need to synchronize: LoggerFinderLoader will + // always return the same instance, so if we don't have it, + // just fetch it again. + if (service == null) { + PrivilegedAction pa = + () -> LoggerFinderLoader.getLoggerFinder(); + service = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION); + } + return service; + } + + } + + + /** + * Returns an instance of {@link Logger Logger} for the caller's + * use. + * + * @implSpec + * Instances returned by this method route messages to loggers + * obtained by calling {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class) + * LoggerFinder.getLogger(name, caller)}. + * + * @apiNote + * This method may defer calling the {@link + * LoggerFinder#getLogger(java.lang.String, java.lang.Class) + * LoggerFinder.getLogger} method to create an actual logger supplied by + * the logging backend, for instance, to allow loggers to be obtained during + * the system initialization time. + * + * @param name the name of the logger. + * @return an instance of {@link Logger} that can be used by the calling + * class. + * @throws NullPointerException if {@code name} is {@code null}. + */ + @CallerSensitive + public static Logger getLogger(String name) { + Objects.requireNonNull(name); + final Class caller = Reflection.getCallerClass(); + return LazyLoggers.getLogger(name, caller); + } + + /** + * Returns a localizable instance of {@link Logger + * Logger} for the caller's use. + * The returned logger will use the provided resource bundle for message + * localization. + * + * @implSpec + * The returned logger will perform message localization as specified + * by {@link LoggerFinder#getLocalizedLogger(java.lang.String, + * java.util.ResourceBundle, java.lang.Class) + * LoggerFinder.getLocalizedLogger(name, bundle, caller}. + * + * @apiNote + * This method is intended to be used after the system is fully initialized. + * This method may trigger the immediate loading and initialization + * of the {@link LoggerFinder} service, which may cause issues if the + * Java Runtime is not ready to initialize the concrete service + * implementation yet. + * System classes which may be loaded early in the boot sequence and + * need to log localized messages should create a logger using + * {@link #getLogger(java.lang.String)} and then use the log methods that + * take a resource bundle as parameter. + * + * @param name the name of the logger. + * @param bundle a resource bundle. + * @return an instance of {@link Logger} which will use the provided + * resource bundle for message localization. + * @throws NullPointerException if {@code name} is {@code null} or + * {@code bundle} is {@code null}. + */ + @CallerSensitive + public static Logger getLogger(String name, ResourceBundle bundle) { + final ResourceBundle rb = Objects.requireNonNull(bundle); + Objects.requireNonNull(name); + final Class caller = Reflection.getCallerClass(); + final SecurityManager sm = System.getSecurityManager(); + // We don't use LazyLoggers if a resource bundle is specified. + // Bootstrap sensitive classes in the JDK do not use resource bundles + // when logging. This could be revisited later, if it needs to. + if (sm != null) { + return AccessController.doPrivileged((PrivilegedAction) + () -> LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller), + null, + LoggerFinder.LOGGERFINDER_PERMISSION); + } + return LoggerFinder.accessProvider().getLocalizedLogger(name, rb, caller); + } + /** * Terminates the currently running Java Virtual Machine. The * argument serves as a status code; by convention, a nonzero status diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java new file mode 100644 index 00000000000..95d35ed4fa2 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/AbstractLoggerWrapper.java @@ -0,0 +1,380 @@ +/* + * 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 jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link System.Logger System.Logger} + * that redirects all calls to a wrapped instance of {@link + * System.Logger System.Logger} + * + * @param Type of the wrapped Logger: {@code Logger} or + * an extension of that interface. + * + */ +abstract class AbstractLoggerWrapper + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + AbstractLoggerWrapper() { } + + abstract L wrapped(); + + abstract PlatformLogger.Bridge platformProxy(); + + L getWrapped() { + return wrapped(); + } + + @Override + public final String getName() { + return wrapped().getName(); + } + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + + @Override + public boolean isLoggable(Level level) { + return wrapped().isLoggable(level); + } + + @Override + public void log(Level level, String msg) { + wrapped().log(level, msg); + } + + @Override + public void log(Level level, + Supplier msgSupplier) { + wrapped().log(level, msgSupplier); + } + + @Override + public void log(Level level, Object obj) { + wrapped().log(level, obj); + } + + @Override + public void log(Level level, + String msg, Throwable thrown) { + wrapped().log(level, msg, thrown); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped().log(level, msgSupplier, thrown); + } + + @Override + public void log(Level level, + String format, Object... params) { + wrapped().log(level, format, params); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + wrapped().log(level, bundle, key, thrown); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + wrapped().log(level, bundle, format, params); + } + + // --------------------------------------------------------- + // Methods from PlatformLogger.Bridge + // --------------------------------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) return isLoggable(level.systemLevel()); + else return platformProxy.isLoggable(level); + } + + @Override + public boolean isEnabled() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null || platformProxy.isEnabled(); + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg); + } else { + platformProxy.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + platformProxy.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + platformProxy.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(),msgSupplier); + } else { + platformProxy.log(level,msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + platformProxy.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + if (sourceClass == null && sourceMethod == null) { // best effort + wrapped().log(level.systemLevel(), msg); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg)); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get())); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get()), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, + thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, + bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, bundle, + msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + platformProxy.logrb(level, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + platformProxy.logrb(level, bundle, msg, params); + } + } + + + @Override + public LoggerConfiguration getLoggerConfiguration() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null ? null + : PlatformLogger.ConfigurableBridge + .getLoggerConfiguration(platformProxy); + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java b/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java new file mode 100644 index 00000000000..6338f871644 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/BootstrapLogger.java @@ -0,0 +1,1074 @@ +/* + * 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 jdk.internal.logger; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import sun.misc.InnocuousThread; +import sun.misc.VM; +import sun.util.logging.PlatformLogger; +import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor; + +/** + * The BootstrapLogger class handles all the logic needed by Lazy Loggers + * to delay the creation of System.Logger instances until the VM is booted. + * By extension - it also contains the logic that will delay the creation + * of JUL Loggers until the LogManager is initialized by the application, in + * the common case where JUL is the default and there is no custom JUL + * configuration. + * + * A BootstrapLogger instance is both a Logger and a + * PlatformLogger.Bridge instance, which will put all Log messages in a queue + * until the VM is booted. + * Once the VM is booted, it obtain the real System.Logger instance from the + * LoggerFinder and flushes the message to the queue. + * + * There are a few caveat: + * - the queue may not be flush until the next message is logged after + * the VM is booted + * - while the BootstrapLogger is active, the default implementation + * for all convenience methods is used + * - PlatformLogger.setLevel calls are ignored + * + * + */ +public final class BootstrapLogger implements Logger, PlatformLogger.Bridge, + PlatformLogger.ConfigurableBridge { + + // We use the BootstrapExecutors class to submit delayed messages + // to an independent InnocuousThread which will ensure that + // delayed log events will be clearly identified as messages that have + // been delayed during the boot sequence. + private static class BootstrapExecutors implements ThreadFactory { + + // Maybe that should be made configurable with system properties. + static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30; + + // The BootstrapMessageLoggerTask is a Runnable which keeps + // a hard ref to the ExecutorService that owns it. + // This ensure that the ExecutorService is not gc'ed until the thread + // has stopped running. + private static class BootstrapMessageLoggerTask implements Runnable { + ExecutorService owner; + Runnable run; + public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) { + this.owner = owner; + this.run = r; + } + @Override + public void run() { + try { + run.run(); + } finally { + owner = null; // allow the ExecutorService to be gced. + } + } + } + + private static volatile WeakReference executorRef; + private static ExecutorService getExecutor() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return executor; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + if (executor == null) { + executor = new ThreadPoolExecutor(0, 1, + KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new BootstrapExecutors()); + } + // The executor service will be elligible for gc + // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s) + // after the execution of its last pending task. + executorRef = new WeakReference<>(executor); + return executorRef.get(); + } + } + + @Override + public Thread newThread(Runnable r) { + ExecutorService owner = getExecutor(); + Thread thread = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Thread run() { + Thread t = new InnocuousThread(new BootstrapMessageLoggerTask(owner, r)); + t.setName("BootstrapMessageLoggerTask-"+t.getName()); + return t; + } + }, null, new RuntimePermission("enableContextClassLoaderOverride")); + thread.setDaemon(true); + return thread; + } + + static void submit(Runnable r) { + getExecutor().execute(r); + } + + // This is used by tests. + static void join(Runnable r) { + try { + getExecutor().submit(r).get(); + } catch (InterruptedException | ExecutionException ex) { + // should not happen + throw new RuntimeException(ex); + } + } + + // This is used by tests. + static void awaitPendingTasks() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (ref == null) { + synchronized(BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + } + } + if (executor != null) { + // since our executor uses a FIFO and has a single thread + // then awaiting the execution of its pending tasks can be done + // simply by registering a new task and waiting until it + // completes. This of course would not work if we were using + // several threads, but we don't. + join(()->{}); + } + } + + // This is used by tests. + static boolean isAlive() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return true; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + return executor != null; + } + } + + // The pending log event queue. The first event is the head, and + // new events are added at the tail + static LogEvent head, tail; + + static void enqueue(LogEvent event) { + if (event.next != null) return; + synchronized (BootstrapExecutors.class) { + if (event.next != null) return; + event.next = event; + if (tail == null) { + head = tail = event; + } else { + tail.next = event; + tail = event; + } + } + } + + static void flush() { + LogEvent event; + // drain the whole queue + synchronized(BootstrapExecutors.class) { + event = head; + head = tail = null; + } + while(event != null) { + LogEvent.log(event); + synchronized(BootstrapExecutors.class) { + LogEvent prev = event; + event = (event.next == event ? null : event.next); + prev.next = null; + } + } + } + } + + // The accessor in which this logger is temporarily set. + final LazyLoggerAccessor holder; + + BootstrapLogger(LazyLoggerAccessor holder) { + this.holder = holder; + } + + // Temporary data object storing log events + // It would be nice to use a Consumer instead of a LogEvent. + // This way we could simply do things like: + // push((logger) -> logger.log(level, msg)); + // Unfortunately, if we come to here it means we are in the bootsraping + // phase where using lambdas is not safe yet - so we have to use a + // a data object instead... + // + static final class LogEvent { + // only one of these two levels should be non null + final Level level; + final PlatformLogger.Level platformLevel; + final BootstrapLogger bootstrap; + + final ResourceBundle bundle; + final String msg; + final Throwable thrown; + final Object[] params; + final Supplier msgSupplier; + final String sourceClass; + final String sourceMethod; + final long timeMillis; + final long nanoAdjustment; + + // because logging a message may entail calling toString() on + // the parameters etc... we need to store the context of the + // caller who logged the message - so that we can reuse it when + // we finally log the message. + final AccessControlContext acc; + + // The next event in the queue + LogEvent next; + + private LogEvent(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.log(LogEvent, logger) instead. + private void log(Logger logger) { + assert platformLevel == null && level != null; + //new Exception("logging delayed message").printStackTrace(); + if (msgSupplier != null) { + if (thrown != null) { + logger.log(level, msgSupplier, thrown); + } else { + logger.log(level, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.log(level, bundle, msg, thrown); + } else { + logger.log(level, bundle, msg, params); + } + } + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.doLog(LogEvent, logger) instead. + private void log(PlatformLogger.Bridge logger) { + assert platformLevel != null && level == null; + if (sourceClass == null) { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, thrown, msgSupplier); + } else { + logger.log(platformLevel, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, bundle, msg, params); + } + } + } else { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier); + } else { + logger.log(platformLevel,sourceClass, sourceMethod, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params); + } + } + } + } + + // non default methods from Logger interface + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String key, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, key, + thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String format, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, format, + null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), null, null); + } + static void log(LogEvent log, Logger logger) { + final SecurityManager sm = System.getSecurityManager(); + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + if (sm == null || log.acc == null) { + BootstrapExecutors.submit(() -> log.log(logger)); + } else { + BootstrapExecutors.submit(() -> + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc)); + } + } + + // non default methods from PlatformLogger.Bridge interface + static LogEvent valueOf(BootstrapLogger bootstrap, + PlatformLogger.Level level, String msg) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, + msg, null, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, msgSupplier, null, null); + } + static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier, + Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, + msgSupplier, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, msgSupplier, thrown, null); + } + static void log(LogEvent log, PlatformLogger.Bridge logger) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null || log.acc == null) { + log.log(logger); + } else { + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc); + } + } + + static void log(LogEvent event) { + event.bootstrap.flush(event); + } + + } + + // Push a log event at the end of the pending LogEvent queue. + void push(LogEvent log) { + BootstrapExecutors.enqueue(log); + // if the queue has been flushed just before we entered + // the synchronized block we need to flush it again. + checkBootstrapping(); + } + + // Flushes the queue of pending LogEvents to the logger. + void flush(LogEvent event) { + assert event.bootstrap == this; + if (event.platformLevel != null) { + PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this); + LogEvent.log(event, concrete); + } else { + Logger concrete = holder.getConcreteLogger(this); + LogEvent.log(event, concrete); + } + } + + /** + * The name of this logger. This is the name of the actual logger for which + * this logger acts as a temporary proxy. + * @return The logger name. + */ + @Override + public String getName() { + return holder.name; + } + + /** + * Check whether the VM is still bootstrapping, and if not, arranges + * for this logger's holder to create the real logger and flush the + * pending event queue. + * @return true if the VM is still bootstrapping. + */ + boolean checkBootstrapping() { + if (isBooted()) { + BootstrapExecutors.flush(); + return false; + } + return true; + } + + // ---------------------------------- + // Methods from Logger + // ---------------------------------- + + @Override + public boolean isLoggable(Level level) { + if (checkBootstrapping()) { + return level.getSeverity() >= Level.INFO.getSeverity(); + } else { + final Logger spi = holder.wrapped(); + return spi.isLoggable(level); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, key, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, format, params); + } + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(Level level, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, format, params); + } + } + + @Override + public void log(Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(Level level, Object obj) { + if (checkBootstrapping()) { + Logger.super.log(level, obj); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, obj); + } + } + + @Override + public void log(Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, (Object[])null)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg); + } + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier, thrown); + } + } + + // ---------------------------------- + // Methods from PlatformLogger.Bridge + // ---------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + if (checkBootstrapping()) { + return level.intValue() >= PlatformLogger.Level.INFO.intValue(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isLoggable(level); + } + } + + @Override + public boolean isEnabled() { + if (checkBootstrapping()) { + return true; + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isEnabled(); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.vaueOf(this, level, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, + msg, (Object[])null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, thrown); + } + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + if (checkBootstrapping()) { + // This practically means that PlatformLogger.setLevel() + // calls will be ignored if the VM is still bootstrapping. We could + // attempt to fix that but is it worth it? + return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi); + } + } + + // This BooleanSupplier is a hook for tests - so that we can simulate + // what would happen before the VM is booted. + private static volatile BooleanSupplier isBooted; + public static boolean isBooted() { + if (isBooted != null) return isBooted.getAsBoolean(); + else return VM.isBooted(); + } + + // A bit of black magic. We try to find out the nature of the logging + // backend without actually loading it. + private static enum LoggingBackend { + // There is no LoggerFinder and JUL is not present + NONE(true), + + // There is no LoggerFinder, but we have found a + // JdkLoggerFinder installed (which means JUL is present), + // and we haven't found any custom configuration for JUL. + // Until LogManager is initialized we can use a simple console + // logger. + JUL_DEFAULT(false), + + // Same as above, except that we have found a custom configuration + // for JUL. We cannot use the simple console logger in this case. + JUL_WITH_CONFIG(true), + + // We have found a custom LoggerFinder. + CUSTOM(true); + + final boolean useLoggerFinder; + private LoggingBackend(boolean useLoggerFinder) { + this.useLoggerFinder = useLoggerFinder; + } + }; + + // The purpose of this class is to delay the initialization of + // the detectedBackend field until it is actually read. + // We do not want this field to get initialized if VM.isBooted() is false. + private static final class DetectBackend { + static final LoggingBackend detectedBackend; + static { + detectedBackend = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public LoggingBackend run() { + final Iterator iterator = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()) + .iterator(); + if (iterator.hasNext()) { + return LoggingBackend.CUSTOM; // Custom Logger Provider is registered + } + // No custom logger provider: we will be using the default + // backend. + final Iterator iterator2 = + ServiceLoader.loadInstalled(DefaultLoggerFinder.class) + .iterator(); + if (iterator2.hasNext()) { + // LoggingProviderImpl is registered. The default + // implementation is java.util.logging + String cname = System.getProperty("java.util.logging.config.class"); + String fname = System.getProperty("java.util.logging.config.file"); + return (cname != null || fname != null) + ? LoggingBackend.JUL_WITH_CONFIG + : LoggingBackend.JUL_DEFAULT; + } else { + // SimpleLogger is used + return LoggingBackend.NONE; + } + } + }); + + } + } + + // We will use temporary SimpleConsoleLoggers if + // the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + private static boolean useTemporaryLoggers() { + // being paranoid: this should already have been checked + if (!isBooted()) return true; + return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT + && !logManagerConfigured; + } + + // We will use lazy loggers if: + // - the VM is not yet booted + // - the logging backend is a custom backend + // - the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + public static synchronized boolean useLazyLoggers() { + return !BootstrapLogger.isBooted() + || DetectBackend.detectedBackend == LoggingBackend.CUSTOM + || useTemporaryLoggers(); + } + + // Called by LazyLoggerAccessor. This method will determine whether + // to create a BootstrapLogger (if the VM is not yet booted), + // a SimpleConsoleLogger (if JUL is the default backend and there + // is no custom JUL configuration and LogManager is not yet initialized), + // or a logger returned by the loaded LoggerFinder (all other cases). + static Logger getLogger(LazyLoggerAccessor accessor) { + if (!BootstrapLogger.isBooted()) { + return new BootstrapLogger(accessor); + } else { + boolean temporary = useTemporaryLoggers(); + if (temporary) { + // JUL is the default backend, there is no custom configuration, + // LogManager has not been used. + synchronized(BootstrapLogger.class) { + if (useTemporaryLoggers()) { + return makeTemporaryLogger(accessor); + } + } + } + // Already booted. Return the real logger. + return accessor.createLogger(); + } + } + + + // If the backend is JUL, and there is no custom configuration, and + // nobody has attempted to call LogManager.getLogManager() yet, then + // we can temporarily substitute JUL Logger with SimpleConsoleLoggers, + // which avoids the cost of actually loading up the LogManager... + // The TemporaryLoggers class has the logic to create such temporary + // loggers, and to possibly replace them with real JUL loggers if + // someone calls LogManager.getLogManager(). + static final class TemporaryLoggers implements + Function { + + // all accesses must be synchronized on the outer BootstrapLogger.class + final Map temporaryLoggers = + new HashMap<>(); + + // all accesses must be synchronized on the outer BootstrapLogger.class + // The temporaryLoggers map will be cleared when LogManager is initialized. + boolean cleared; + + @Override + // all accesses must be synchronized on the outer BootstrapLogger.class + public SimpleConsoleLogger apply(LazyLoggerAccessor t) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return SimpleConsoleLogger.makeSimpleLogger(t.getLoggerName(), true); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + SimpleConsoleLogger get(LazyLoggerAccessor a) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return temporaryLoggers.computeIfAbsent(a, this); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + Map drainTemporaryLoggers() { + if (temporaryLoggers.isEmpty()) return null; + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + final Map accessors = new HashMap<>(temporaryLoggers); + temporaryLoggers.clear(); + cleared = true; + return accessors; + } + + static void resetTemporaryLoggers(Map accessors) { + // When the backend is JUL we want to force the creation of + // JUL loggers here: some tests are expecting that the + // PlatformLogger will create JUL loggers as soon as the + // LogManager is initialized. + // + // If the backend is not JUL then we can delay the re-creation + // of the wrapped logger until they are next accessed. + // + final LoggingBackend detectedBackend = DetectBackend.detectedBackend; + final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT + && detectedBackend != LoggingBackend.JUL_WITH_CONFIG; + for (Map.Entry a : accessors.entrySet()) { + a.getKey().release(a.getValue(), !lazy); + } + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + static final TemporaryLoggers INSTANCE = new TemporaryLoggers(); + } + + static synchronized Logger makeTemporaryLogger(LazyLoggerAccessor a) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.get(a); + } + + private static volatile boolean logManagerConfigured; + + private static synchronized Map + releaseTemporaryLoggers() { + // first check whether there's a chance that we have used + // temporary loggers; Will be false if logManagerConfigured is already + // true. + final boolean clearTemporaryLoggers = useTemporaryLoggers(); + + // then sets the flag that tells that the log manager is configured + logManagerConfigured = true; + + // finally replace all temporary loggers by real JUL loggers + if (clearTemporaryLoggers) { + // accesses to TemporaryLoggers is synchronized on BootstrapLogger.class + return TemporaryLoggers.INSTANCE.drainTemporaryLoggers(); + } else { + return null; + } + } + + public static void redirectTemporaryLoggers() { + // This call is synchronized on BootstrapLogger.class. + final Map accessors = + releaseTemporaryLoggers(); + + // We will now reset the logger accessors, triggering the + // (possibly lazy) replacement of any temporary logger by the + // real logger returned from the loaded LoggerFinder. + if (accessors != null) { + TemporaryLoggers.resetTemporaryLoggers(accessors); + } + + BootstrapExecutors.flush(); + } + + // Hook for tests which need to wait until pending messages + // are processed. + static void awaitPendingTasks() { + BootstrapExecutors.awaitPendingTasks(); + } + static boolean isAlive() { + return BootstrapExecutors.isAlive(); + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java b/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java new file mode 100644 index 00000000000..da255a3e2b8 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/DefaultLoggerFinder.java @@ -0,0 +1,173 @@ +/* + * 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 jdk.internal.logger; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.ReferenceQueue; +import java.util.Collection; +import java.util.ResourceBundle; + +/** + * Internal Service Provider Interface (SPI) that makes it possible to use + * {@code java.util.logging} as backend when the {@link + * sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is present. + *

    + * The JDK default implementation of the {@link LoggerFinder} will + * attempt to locate and load an {@linkplain + * java.util.ServiceLoader#loadInstalled(java.lang.Class) installed} + * implementation of the {@code DefaultLoggerFinder}. If {@code java.util.logging} + * is present, this will usually resolve to an instance of {@link + * sun.util.logging.internal.LoggingProviderImpl sun.util.logging.internal.LoggingProviderImpl}. + * Otherwise, if no concrete service provider is declared for + * {@code DefaultLoggerFinder}, the default implementation provided by this class + * will be used. + *

    + * When the {@link sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is not present then the + * default implementation provided by this class is to use a simple logger + * that will log messages whose level is INFO and above to the console. + * These simple loggers are not configurable. + *

    + * When configuration is needed, an application should either link with + * {@code java.util.logging} - and use the {@code java.util.logging} for + * configuration, or link with {@link LoggerFinder another implementation} + * of the {@link LoggerFinder} + * that provides the necessary configuration. + * + * @apiNote Programmers are not expected to call this class directly. + * Instead they should rely on the static methods defined by {@link + * java.lang.System java.lang.System} or {@link sun.util.logging.PlatformLogger + * sun.util.logging.PlatformLogger}. + * + * @see java.lang.System.LoggerFinder + * @see jdk.internal.logger + * @see sun.util.logging.internal + * + */ +public class DefaultLoggerFinder extends LoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + /** + * Creates a new instance of DefaultLoggerFinder. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")} + */ + protected DefaultLoggerFinder() { + this(checkPermission()); + } + + private DefaultLoggerFinder(Void unused) { + // nothing to do. + } + + private static Void checkPermission() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return null; + } + + // SharedLoggers is a default cache of loggers used when JUL is not + // present - in that case we use instances of SimpleConsoleLogger which + // cannot be directly configure through public APIs. + // + // We can therefore afford to simply maintain two domains - one for the + // system, and one for the application. + // + static final class SharedLoggers { + private final Map> loggers = + new HashMap<>(); + private final ReferenceQueue queue = new ReferenceQueue<>(); + + synchronized Logger get(Function loggerSupplier, final String name) { + Reference ref = loggers.get(name); + Logger w = ref == null ? null : ref.get(); + if (w == null) { + w = loggerSupplier.apply(name); + loggers.put(name, new WeakReference<>(w, queue)); + } + + // Remove stale mapping... + Collection> values = null; + while ((ref = queue.poll()) != null) { + if (values == null) values = loggers.values(); + values.remove(ref); + } + return w; + } + + + final static SharedLoggers system = new SharedLoggers(); + final static SharedLoggers application = new SharedLoggers(); + } + + @Override + public final Logger getLogger(String name, /* Module */ Class caller) { + checkPermission(); + return demandLoggerFor(name, caller); + } + + @Override + public final Logger getLocalizedLogger(String name, ResourceBundle bundle, + /* Module */ Class caller) { + return super.getLocalizedLogger(name, bundle, caller); + } + + + + /** + * Returns a {@link Logger logger} suitable for the caller usage. + * + * @implSpec The default implementation for this method is to return a + * simple logger that will print all messages of INFO level and above + * to the console. That simple logger is not configurable. + * + * @param name The name of the logger. + * @param caller The class on behalf of which the logger is created. + * @return A {@link Logger logger} suitable for the application usage. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")}. + */ + protected Logger demandLoggerFor(String name, /* Module */ Class caller) { + checkPermission(); + if (caller.getClassLoader() == null) { + return SharedLoggers.system.get(SimpleConsoleLogger::makeSimpleLogger, name); + } else { + return SharedLoggers.application.get(SimpleConsoleLogger::makeSimpleLogger, name); + } + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java new file mode 100644 index 00000000000..c59ff195cc4 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LazyLoggers.java @@ -0,0 +1,446 @@ +/* + * 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 jdk.internal.logger; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.BiFunction; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.WeakReference; +import java.util.Objects; +import sun.misc.VM; +import sun.util.logging.PlatformLogger; + +/** + * This class is a factory for Lazy Loggers; only system loggers can be + * Lazy Loggers. + */ +public final class LazyLoggers { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + private LazyLoggers() { + throw new InternalError(); + } + + /** + * This class is used to hold the factories that a Lazy Logger will use + * to create (or map) its wrapped logger. + * @param {@link Logger} or a subclass of {@link Logger}. + */ + private static final class LazyLoggerFactories { + + /** + * A factory method to create an SPI logger. + * Usually, this will be something like LazyLoggers::getSystemLogger. + */ + final BiFunction, L> loggerSupplier; + + + public LazyLoggerFactories(BiFunction, L> loggerSupplier) { + this(Objects.requireNonNull(loggerSupplier), + (Void)null); + } + + private LazyLoggerFactories(BiFunction, L> loggerSupplier, + Void unused) { + this.loggerSupplier = loggerSupplier; + } + + } + + static interface LoggerAccessor { + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + public String getLoggerName(); + + /** + * Returns the wrapped logger object. + * @return the wrapped logger object. + */ + public Logger wrapped(); + + /** + * A PlatformLogger.Bridge view of the wrapped logger object. + * @return A PlatformLogger.Bridge view of the wrapped logger object. + */ + public PlatformLogger.Bridge platform(); + } + + /** + * The LazyLoggerAccessor class holds all the logic that delays the creation + * of the SPI logger until such a time that the VM is booted and the logger + * is actually used for logging. + * + * This class uses the services of the BootstrapLogger class to instantiate + * temporary loggers if appropriate. + */ + static final class LazyLoggerAccessor implements LoggerAccessor { + + // The factories that will be used to create the logger lazyly + final LazyLoggerFactories factories; + + // We need to pass the actual caller when creating the logger. + private final WeakReference> callerRef; + + // The name of the logger that will be created lazyly + final String name; + // The plain logger SPI object - null until it is accessed for the + // first time. + private volatile Logger w; + // A PlatformLogger.Bridge view of w. + private volatile PlatformLogger.Bridge p; + + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Class caller) { + this(Objects.requireNonNull(name), Objects.requireNonNull(factories), + Objects.requireNonNull(caller), null); + } + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Class caller, Void unused) { + this.name = name; + this.factories = factories; + this.callerRef = new WeakReference>(caller); + } + + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + @Override + public String getLoggerName() { + return name; + } + + // must be called in synchronized block + // set wrapped logger if not set + private void setWrappedIfNotSet(Logger wrapped) { + if (w == null) { + w = wrapped; + } + } + + /** + * Returns the logger SPI object, creating it if 'w' is still null. + * @return the logger SPI object. + */ + public Logger wrapped() { + Logger wrapped = w; + if (wrapped != null) return wrapped; + // Wrapped logger not created yet: create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + wrapped = BootstrapLogger.getLogger(this); + synchronized(this) { + // if w has already been in between, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + return w; + } + } + + /** + * A PlatformLogger.Bridge view of the wrapped logger. + * @return A PlatformLogger.Bridge view of the wrapped logger. + */ + public PlatformLogger.Bridge platform() { + // We can afford to return the platform view of the previous + // logger - if that view is not null. + // Because that view will either be the BootstrapLogger, which + // will redirect to the new wrapper properly, or the temporary + // logger - which in effect is equivalent to logging something + // just before the application initialized LogManager. + PlatformLogger.Bridge platform = p; + if (platform != null) return platform; + synchronized (this) { + if (w != null) { + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + // If we reach here it means that the wrapped logger may not + // have been created yet: attempt to create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + final Logger wrapped = BootstrapLogger.getLogger(this); + synchronized(this) { + // if w has already been set, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + + /** + * Makes this accessor release a temporary logger. + * This method is called + * by BootstrapLogger when JUL is the default backend and LogManager + * is initialized, in order to replace temporary SimpleConsoleLoggers by + * real JUL loggers. See BootstrapLogger for more details. + * If {@code replace} is {@code true}, then this method will force + * the accessor to eagerly recreate its wrapped logger. + * Note: passing {@code replace=false} is no guarantee that the + * method will not actually replace the released logger. + * @param temporary The temporary logger too be released. + * @param replace Whether the released logger should be eagerly + * replaced. + */ + void release(SimpleConsoleLogger temporary, boolean replace) { + PlatformLogger.ConfigurableBridge.LoggerConfiguration conf = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(temporary); + PlatformLogger.Level level = conf != null + ? conf.getPlatformLevel() + : null; + synchronized (this) { + if (this.w == temporary) { + this.w = null; this.p = null; + } + } + PlatformLogger.Bridge platform = replace || level != null + ? this.platform() : null; + + if (level != null) { + conf = (platform != null && platform != temporary) + ? PlatformLogger.ConfigurableBridge.getLoggerConfiguration(platform) + : null; + if (conf != null) conf.setPlatformLevel(level); + } + } + + /** + * Replace 'w' by the real SPI logger and flush the log messages pending + * in the temporary 'bootstrap' Logger. Called by BootstrapLogger when + * this accessor's bootstrap logger is accessed and BootstrapLogger + * notices that the VM is no longer booting. + * @param bootstrap This accessor's bootstrap logger (usually this is 'w'). + */ + Logger getConcreteLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.wrapped(); + } + + PlatformLogger.Bridge getConcretePlatformLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.platform(); + } + + // Creates the wrapped logger by invoking the SPI. + Logger createLogger() { + final Class caller = callerRef.get(); + if (caller == null) { + throw new IllegalStateException("The class for which this logger" + + " was created has been garbage collected"); + } + return this.factories.loggerSupplier.apply(name, caller); + } + + /** + * Creates a new lazy logger accessor for the named logger. The given + * factories will be use when it becomes necessary to actually create + * the logger. + * @param An interface that extends {@link Logger}. + * @param name The logger name. + * @param factories The factories that should be used to create the + * wrapped logger. + * @return A new LazyLoggerAccessor. + */ + public static LazyLoggerAccessor makeAccessor(String name, + LazyLoggerFactories factories, Class caller) { + return new LazyLoggerAccessor(name, factories, caller); + } + + } + + /** + * An implementation of {@link Logger} that redirects all calls to a wrapped + * instance of {@code Logger}. + */ + private static class LazyLoggerWrapper + extends AbstractLoggerWrapper { + + final LoggerAccessor loggerAccessor; + + public LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier) { + this(Objects.requireNonNull(loggerSinkSupplier), (Void)null); + } + + private LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier, + Void unused) { + this.loggerAccessor = loggerSinkSupplier; + } + + @Override + final Logger wrapped() { + return loggerAccessor.wrapped(); + } + + @Override + PlatformLogger.Bridge platformProxy() { + return loggerAccessor.platform(); + } + + } + + // Do not expose this outside of this package. + private static volatile LoggerFinder provider = null; + private static LoggerFinder accessLoggerFinder() { + if (provider == null) { + // no need to lock: it doesn't matter if we call + // getLoggerFinder() twice - since LoggerFinder already caches + // the result. + // This is just an optimization to avoid the cost of calling + // doPrivileged every time. + final SecurityManager sm = System.getSecurityManager(); + provider = sm == null ? LoggerFinder.getLoggerFinder() : + AccessController.doPrivileged( + (PrivilegedAction)LoggerFinder::getLoggerFinder); + } + return provider; + } + + // Avoid using lambda here as lazy loggers could be created early + // in the bootstrap sequence... + private static final BiFunction, Logger> loggerSupplier = + new BiFunction<>() { + @Override + public Logger apply(String name, Class caller) { + return LazyLoggers.getLoggerFromFinder(name, caller); + } + }; + + private static final LazyLoggerFactories factories = + new LazyLoggerFactories<>(loggerSupplier); + + + + // A concrete implementation of Logger that delegates to a System.Logger, + // but only creates the System.Logger instance lazily when it's used for + // the first time. + // The JdkLazyLogger uses a LazyLoggerAccessor objects, which relies + // on the logic embedded in BootstrapLogger to avoid loading the concrete + // logger provider until the VM has finished booting. + // + private static final class JdkLazyLogger extends LazyLoggerWrapper { + JdkLazyLogger(String name, Class caller) { + this(LazyLoggerAccessor.makeAccessor(name, factories, caller), + (Void)null); + } + private JdkLazyLogger(LazyLoggerAccessor holder, Void unused) { + super(holder); + } + } + + /** + * Gets a logger from the LoggerFinder. Creates the actual concrete + * logger. + * @param name name of the logger + * @param caller class on behalf of which the logger is created + * @return The logger returned by the LoggerFinder. + */ + static Logger getLoggerFromFinder(String name, Class caller) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return accessLoggerFinder().getLogger(name, caller); + } else { + return AccessController.doPrivileged((PrivilegedAction) + () -> {return accessLoggerFinder().getLogger(name, caller);}, + null, LOGGERFINDER_PERMISSION); + } + } + + /** + * Returns a (possibly lazy) Logger for the caller. + * + * @param name the logger name + * @param caller The class on behalf of which the logger is created. + * If the caller is not loaded from the Boot ClassLoader, + * the LoggerFinder is accessed and the logger returned + * by {@link LoggerFinder#getLogger(java.lang.String, java.lang.Class)} + * is returned to the caller directly. + * Otherwise, the logger returned by + * {@link #getLazyLogger(java.lang.String, java.lang.Class)} + * is returned to the caller. + * + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLogger(String name, Class caller) { + if (caller.getClassLoader() == null) { + return getLazyLogger(name, caller); + } else { + return getLoggerFromFinder(name, caller); + } + } + + /** + * Returns a (possibly lazy) Logger suitable for system classes. + * Whether the returned logger is lazy or not depend on the result + * returned by {@link BootstrapLogger#useLazyLoggers()}. + * + * @param name the logger name + * @param caller the class on behalf of which the logger is created. + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLazyLogger(String name, Class caller) { + + // BootstrapLogger has the logic to determine whether a LazyLogger + // should be used. Usually, it is worth it only if: + // - the VM is not yet booted + // - or, the backend is JUL and there is no configuration + // - or, the backend is a custom backend, as we don't know what + // that is going to load... + // So if for instance the VM is booted and we use JUL with a custom + // configuration, we're not going to delay the creation of loggers... + final boolean useLazyLogger = BootstrapLogger.useLazyLoggers(); + if (useLazyLogger) { + return new JdkLazyLogger(name, caller); + } else { + // Directly invoke the LoggerFinder. + return getLoggerFromFinder(name, caller); + } + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java new file mode 100644 index 00000000000..361ebc30e7b --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LocalizedLoggerWrapper.java @@ -0,0 +1,155 @@ +/* + * 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 jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * This implementation of {@link Logger} redirects all logging method + * calls to calls to {@code log(Level, String, ResourceBundle, ...)} + * methods, passing the Logger's ResourceBundle as parameter. + * So for instance a call to {@link Logger#log(Level, String) + * log(Level.INFO, msg)} will be redirected + * to a call to {@link #log(java.lang.System.Logger.Level, + * java.util.ResourceBundle, java.lang.String, java.lang.Object...) + * this.log(Level.INFO, this.bundle, msg, (Object[]) null)}. + *

    + * Note that methods that take a {@link Supplier Supplier<String>} + * or an Object are not redirected. It is assumed that a string returned + * by a {@code Supplier} is already localized, or cannot be localized. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of the {@code Logger} interface. + */ +public class LocalizedLoggerWrapper extends LoggerWrapper { + + private final ResourceBundle bundle; + + public LocalizedLoggerWrapper(L wrapped, ResourceBundle bundle) { + super(wrapped); + this.bundle = bundle; + } + + public final ResourceBundle getBundle() { + return bundle; + } + + // We assume that messages returned by Supplier and Object are + // either already localized or not localizable. To be evaluated. + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + @Override + public final void log(Level level, String msg) { + log(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(Level level, + String msg, Throwable thrown) { + log(level, bundle, msg, thrown); + } + + @Override + public final void log(Level level, + String format, Object... params) { + log(level, bundle, format, params); + } + + @Override + public final void log(Level level, Object obj) { + wrapped.log(level, obj); + } + + @Override + public final void log(Level level, Supplier msgSupplier) { + wrapped.log(level, msgSupplier); + } + + @Override + public final void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped.log(level, msgSupplier, thrown); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String format, Object... params) { + wrapped.log(level, bundle, format, params); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + wrapped.log(level, bundle, key, thrown); + } + + @Override + public final boolean isLoggable(Level level) { + return wrapped.isLoggable(level); + } + + // Override methods from PlatformLogger.Bridge that don't take a + // resource bundle... + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key) { + logrb(level, sourceClass, sourceMethod, bundle, key, (Object[]) null); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Throwable thrown) { + logrb(level, sourceClass, sourceMethod, bundle, key, thrown); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Object... params) { + logrb(level, sourceClass, sourceMethod, bundle, key, params); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable thrown) { + logrb(level, bundle, msg, thrown); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg) { + logrb(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) { + logrb(level, bundle, format, params); + } + + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java new file mode 100644 index 00000000000..7d315ba7057 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerFinderLoader.java @@ -0,0 +1,210 @@ +/* + * 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 jdk.internal.logger; + +import java.io.FilePermission; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.util.Iterator; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import sun.security.util.SecurityConstants; + +/** + * Helper class used to load the {@link java.lang.System.LoggerFinder}. + */ +public final class LoggerFinderLoader { + private static volatile System.LoggerFinder service; + private static final Object lock = new int[0]; + static final Permission CLASSLOADER_PERMISSION = + SecurityConstants.GET_CLASSLOADER_PERMISSION; + static final Permission READ_PERMISSION = + new FilePermission("<>", + SecurityConstants.FILE_READ_ACTION); + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + // This is used to control how the LoggerFinderLoader handles + // errors when instantiating the LoggerFinder provider. + // ERROR => throws ServiceConfigurationError + // WARNING => Do not fail, use plain default (simple logger) implementation, + // prints warning on console. (this is the default) + // DEBUG => Do not fail, use plain default (simple logger) implementation, + // prints warning and exception stack trace on console. + // QUIET => Do not fail and stay silent. + private static enum ErrorPolicy { ERROR, WARNING, DEBUG, QUIET }; + + // This class is static and cannot be instantiated. + private LoggerFinderLoader() { + throw new InternalError("LoggerFinderLoader cannot be instantiated"); + } + + + // Return the loaded LoggerFinder, or load it if not already loaded. + private static System.LoggerFinder service() { + if (service != null) return service; + synchronized(lock) { + if (service != null) return service; + service = loadLoggerFinder(); + } + // Since the LoggerFinder is already loaded - we can stop using + // temporary loggers. + BootstrapLogger.redirectTemporaryLoggers(); + return service; + } + + // Get configuration error policy + private static ErrorPolicy configurationErrorPolicy() { + final PrivilegedAction getConfigurationErrorPolicy = + () -> System.getProperty("jdk.logger.finder.error"); + String errorPolicy = AccessController.doPrivileged(getConfigurationErrorPolicy); + if (errorPolicy == null || errorPolicy.isEmpty()) { + return ErrorPolicy.WARNING; + } + try { + return ErrorPolicy.valueOf(errorPolicy.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException x) { + return ErrorPolicy.WARNING; + } + } + + // Whether multiple provider should be considered as an error. + // This is further submitted to the configuration error policy. + private static boolean ensureSingletonProvider() { + final PrivilegedAction ensureSingletonProvider = + () -> Boolean.getBoolean("jdk.logger.finder.singleton"); + return AccessController.doPrivileged(ensureSingletonProvider); + } + + private static Iterator findLoggerFinderProviders() { + final Iterator iterator; + if (System.getSecurityManager() == null) { + iterator = ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + } else { + final PrivilegedAction> pa = + () -> ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + return iterator; + } + + // Loads the LoggerFinder using ServiceLoader. If no LoggerFinder + // is found returns the default (possibly JUL based) implementation + private static System.LoggerFinder loadLoggerFinder() { + System.LoggerFinder result; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + final Iterator iterator = + findLoggerFinderProviders(); + if (iterator.hasNext()) { + result = iterator.next(); + if (iterator.hasNext() && ensureSingletonProvider()) { + throw new ServiceConfigurationError( + "More than on LoggerFinder implementation"); + } + } else { + result = loadDefaultImplementation(); + } + } catch (Error | RuntimeException x) { + // next caller will get the plain default impl (not linked + // to java.util.logging) + service = result = new DefaultLoggerFinder(); + ErrorPolicy errorPolicy = configurationErrorPolicy(); + if (errorPolicy == ErrorPolicy.ERROR) { + // rethrow any exception as a ServiceConfigurationError. + if (x instanceof Error) { + throw x; + } else { + throw new ServiceConfigurationError( + "Failed to instantiate LoggerFinder provider; Using default.", x); + } + } else if (errorPolicy != ErrorPolicy.QUIET) { + // if QUIET just silently use the plain default impl + // otherwise, log a warning, possibly adding the exception + // stack trace (if DEBUG is specified). + SimpleConsoleLogger logger = + new SimpleConsoleLogger("jdk.internal.logger", false); + logger.log(System.Logger.Level.WARNING, + "Failed to instantiate LoggerFinder provider; Using default."); + if (errorPolicy == ErrorPolicy.DEBUG) { + logger.log(System.Logger.Level.WARNING, + "Exception raised trying to instantiate LoggerFinder", x); + } + } + } + return result; + } + + private static System.LoggerFinder loadDefaultImplementation() { + final SecurityManager sm = System.getSecurityManager(); + final Iterator iterator; + if (sm == null) { + iterator = ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + } else { + // We use limited do privileged here - the minimum set of + // permissions required to 'see' the META-INF/services resources + // seems to be CLASSLOADER_PERMISSION and READ_PERMISSION. + // Note that do privileged is required because + // otherwise the SecurityManager will prevent the ServiceLoader + // from seeing the installed provider. + PrivilegedAction> pa = () -> + ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + DefaultLoggerFinder result = null; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + if (iterator.hasNext()) { + result = iterator.next(); + } + } catch (RuntimeException x) { + throw new ServiceConfigurationError( + "Failed to instantiate default LoggerFinder", x); + } + if (result == null) { + result = new DefaultLoggerFinder(); + } + return result; + } + + public static System.LoggerFinder getLoggerFinder() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return service(); + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java new file mode 100644 index 00000000000..8f214239d3e --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/LoggerWrapper.java @@ -0,0 +1,65 @@ +/* + * 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 jdk.internal.logger; + +import java.util.Objects; +import java.lang.System.Logger; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link Logger} that redirects all calls to a wrapped + instance of Logger. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of that interface. + */ +public class LoggerWrapper extends AbstractLoggerWrapper { + + final L wrapped; + final PlatformLogger.Bridge platformProxy; + + public LoggerWrapper(L wrapped) { + this(Objects.requireNonNull(wrapped), (Void)null); + } + + LoggerWrapper(L wrapped, Void unused) { + this.wrapped = wrapped; + this.platformProxy = (wrapped instanceof PlatformLogger.Bridge) ? + (PlatformLogger.Bridge) wrapped : null; + } + + @Override + public final L wrapped() { + return wrapped; + } + + @Override + public final PlatformLogger.Bridge platformProxy() { + return platformProxy; + } + +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java b/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java new file mode 100644 index 00000000000..8f6ddf7764d --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java @@ -0,0 +1,486 @@ +/* + * 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 jdk.internal.logger; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.time.ZonedDateTime; +import java.util.ResourceBundle; +import java.util.function.Function; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.function.Supplier; +import jdk.internal.misc.JavaLangAccess; +import jdk.internal.misc.SharedSecrets; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration; + +/** + * A simple console logger to emulate the behavior of JUL loggers when + * in the default configuration. SimpleConsoleLoggers are also used when + * JUL is not present and no DefaultLoggerFinder is installed. + */ +public class SimpleConsoleLogger extends LoggerConfiguration + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + static final PlatformLogger.Level DEFAULT_LEVEL = PlatformLogger.Level.INFO; + + final String name; + volatile PlatformLogger.Level level; + final boolean usePlatformLevel; + SimpleConsoleLogger(String name, boolean usePlatformLevel) { + this.name = name; + this.usePlatformLevel = usePlatformLevel; + } + + @Override + public String getName() { + return name; + } + + private Enum logLevel(PlatformLogger.Level level) { + return usePlatformLevel ? level : level.systemLevel(); + } + + private Enum logLevel(Level level) { + return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level; + } + + // --------------------------------------------------- + // From Logger + // --------------------------------------------------- + + @Override + public boolean isLoggable(Level level) { + return isLoggable(PlatformLogger.toPlatformLevel(level)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + if (bundle != null) { + key = bundle.getString(key); + } + publish(getCallerInfo(), logLevel(level), key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (isLoggable(level)) { + if (bundle != null) { + format = bundle.getString(format); + } + publish(getCallerInfo(), logLevel(level), format, params); + } + } + + // --------------------------------------------------- + // From PlatformLogger.Bridge + // --------------------------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Level effectiveLevel = effectiveLevel(); + return level != PlatformLogger.Level.OFF + && level.ordinal() >= effectiveLevel.ordinal(); + } + + @Override + public boolean isEnabled() { + return level != PlatformLogger.Level.OFF; + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + private PlatformLogger.Level effectiveLevel() { + if (level == null) return DEFAULT_LEVEL; + return level; + } + + @Override + public PlatformLogger.Level getPlatformLevel() { + return level; + } + + @Override + public void setPlatformLevel(PlatformLogger.Level newLevel) { + level = newLevel; + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + return this; + } + + /** + * Default platform logging support - output messages to System.err - + * equivalent to ConsoleHandler with SimpleFormatter. + */ + static PrintStream outputStream() { + return System.err; + } + + // Returns the caller's class and method's name; best effort + // if cannot infer, return the logger's name. + private String getCallerInfo() { + String sourceClassName = null; + String sourceMethodName = null; + + JavaLangAccess access = SharedSecrets.getJavaLangAccess(); + Throwable throwable = new Throwable(); + int depth = access.getStackTraceDepth(throwable); + + String logClassName = "sun.util.logging.PlatformLogger"; + String simpleLoggerClassName = "jdk.internal.logger.SimpleConsoleLogger"; + boolean lookingForLogger = true; + for (int ix = 0; ix < depth; ix++) { + // Calling getStackTraceElement directly prevents the VM + // from paying the cost of building the entire stack frame. + final StackTraceElement frame = + access.getStackTraceElement(throwable, ix); + final String cname = frame.getClassName(); + if (lookingForLogger) { + // Skip all frames until we have found the first logger frame. + if (cname.equals(logClassName) || cname.equals(simpleLoggerClassName)) { + lookingForLogger = false; + } + } else { + if (skipLoggingFrame(cname)) continue; + if (!cname.equals(logClassName) && !cname.equals(simpleLoggerClassName)) { + // We've found the relevant frame. + sourceClassName = cname; + sourceMethodName = frame.getMethodName(); + break; + } + } + } + + if (sourceClassName != null) { + return sourceClassName + " " + sourceMethodName; + } else { + return name; + } + } + + private String getCallerInfo(String sourceClassName, String sourceMethodName) { + if (sourceClassName == null) return name; + if (sourceMethodName == null) return sourceClassName; + return sourceClassName + " " + sourceMethodName; + } + + private String toString(Throwable thrown) { + String throwable = ""; + if (thrown != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + thrown.printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return throwable; + } + + private synchronized String format(Enum level, + String msg, Throwable thrown, String callerInfo) { + + ZonedDateTime zdt = ZonedDateTime.now(); + String throwable = toString(thrown); + + return String.format(Formatting.formatString, + zdt, + callerInfo, + name, + level.name(), + msg, + throwable); + } + + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg) { + outputStream().print(format(level, msg, null, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Throwable thrown) { + outputStream().print(format(level, msg, thrown, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Object... params) { + msg = params == null || params.length == 0 ? msg + : Formatting.formatMessage(msg, params); + outputStream().print(format(level, msg, null, callerInfo)); + } + + public static SimpleConsoleLogger makeSimpleLogger(String name, boolean usePlatformLevel) { + return new SimpleConsoleLogger(name, usePlatformLevel); + } + + public static SimpleConsoleLogger makeSimpleLogger(String name) { + return new SimpleConsoleLogger(name, false); + } + + public static String getSimpleFormat(Function defaultPropertyGetter) { + return Formatting.getSimpleFormat(defaultPropertyGetter); + } + + public static boolean skipLoggingFrame(String cname) { + return Formatting.skipLoggingFrame(cname); + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get()); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get()); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : bundle.getString(key); + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + private static final class Formatting { + static final String DEFAULT_FORMAT = + "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; + static final String FORMAT_PROP_KEY = + "java.util.logging.SimpleFormatter.format"; + static final String formatString = getSimpleFormat(null); + + // Make it easier to wrap Logger... + static private final String[] skips; + static { + String additionalPkgs = AccessController.doPrivileged( + (PrivilegedAction) + () -> System.getProperty("jdk.logger.packages")); + skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(","); + + } + + static boolean skipLoggingFrame(String cname) { + // skip logging/logger infrastructure + + // fast escape path: all the prefixes below start with 's' or 'j' and + // have more than 12 characters. + char c = cname.length() < 12 ? 0 : cname.charAt(0); + if (c == 's') { + // skip internal machinery classes + if (cname.startsWith("sun.util.logging.")) return true; + if (cname.startsWith("sun.reflect.")) return true; + if (cname.startsWith("sun.rmi.runtime.Log")) return true; + } else if (c == 'j') { + // Message delayed at Bootstrap: no need to go further up. + if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false; + // skip public machinery classes + if (cname.startsWith("jdk.internal.logger.")) return true; + if (cname.startsWith("java.util.logging.")) return true; + if (cname.startsWith("java.lang.System$Logger")) return true; + if (cname.startsWith("java.lang.reflect.")) return true; + if (cname.startsWith("java.lang.invoke.MethodHandle")) return true; + if (cname.startsWith("java.lang.invoke.LambdaForm")) return true; + if (cname.startsWith("java.security.AccessController")) return true; + } + + // check additional prefixes if any are specified. + if (skips.length > 0) { + for (int i=0; i defaultPropertyGetter) { + // Using a lambda here causes + // jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java + // to fail - because that test has a testcase which somehow references + // PlatformLogger and counts the number of generated lambda classes + // So we explicitely use new PrivilegedAction here. + String format = + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return System.getProperty(FORMAT_PROP_KEY); + } + }); + if (format == null && defaultPropertyGetter != null) { + format = defaultPropertyGetter.apply(FORMAT_PROP_KEY); + } + if (format != null) { + try { + // validate the user-defined format string + String.format(format, ZonedDateTime.now(), "", "", "", "", ""); + } catch (IllegalArgumentException e) { + // illegal syntax; fall back to the default format + format = DEFAULT_FORMAT; + } + } else { + format = DEFAULT_FORMAT; + } + return format; + } + + + // Copied from java.util.logging.Formatter.formatMessage + static String formatMessage(String format, Object... parameters) { + // Do the formatting. + try { + if (parameters == null || parameters.length == 0) { + // No parameters. Just return format string. + return format; + } + // Is it a java.text style format? + // Ideally we could match with + // Pattern.compile("\\{\\d").matcher(format).find()) + // However the cost is 14% higher, so we cheaply check for + // + boolean isJavaTestFormat = false; + final int len = format.length(); + for (int i=0; i= '0' && d <= '9') { + isJavaTestFormat = true; + break; + } + } + } + if (isJavaTestFormat) { + return java.text.MessageFormat.format(format, parameters); + } + return format; + } catch (Exception ex) { + // Formatting failed: use format string. + return format; + } + } + } +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java b/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java new file mode 100644 index 00000000000..28c622d4cde --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/logger/package-info.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +/** + * [JDK INTERNAL] + * The {@code jdk.internal.logger} package defines an internal provider + * whose default naive implementation is replaced by the {@code java.logging} + * module when the {@code java.logging} module is present. + *

    + * Default Implementation + *

    + * The JDK default implementation of the System.LoggerFinder will attempt to + * load an installed instance of the {@link jdk.internal.logger.DefaultLoggerFinder} + * defined in this package. + * When the {@code java.util.logging} package is present, this will usually + * resolve to an instance of {@link sun.util.logging.internal.LoggingProviderImpl} - + * which provides an implementation of the Logger whose backend is a + * {@link java.util.logging.Logger java.util.logging.Logger}. + * Configuration can thus be performed by direct access to the regular + * {@code java.util.logging} APIs, + * using {@link java.util.logging.Logger java.util.logging.Logger} and + * {@link java.util.logging.LogManager} to access and configure the backend + * Loggers. + *
    + * If however {@code java.util.logging} is not linked with the application, then + * the default implementation will return a simple logger that will print out + * all log messages of INFO level and above to the console ({@code System.err}), + * as implemented by the base {@link jdk.internal.logger.DefaultLoggerFinder} class. + *

    + * Message Levels and Mapping to java.util.logging + *

    + * The {@link java.lang.System.LoggerFinder} class documentation describe how + * {@linkplain java.lang.System.Logger.Level System.Logger levels} are mapped + * to {@linkplain java.util.logging.Level JUL levels} when {@code + * java.util.logging} is the backend. + * + * @see jdk.internal.logger.DefaultLoggerFinder + * @see sun.util.logging.internal.LoggingProviderImpl + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + * @see sun.util.logging.PlatformLogger.Bridge + * @see sun.util.logging.internal + * + * @since 1.9 + */ +package jdk.internal.logger; diff --git a/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java b/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java deleted file mode 100644 index c044a5a260e..00000000000 --- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingProxy.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009, 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.logging; - -/** - * A proxy interface for the java.util.logging support. - * - * @see sun.util.logging.LoggingSupport - */ -public interface LoggingProxy { - // Methods to bridge java.util.logging.Logger methods - public Object getLogger(String name); - - public Object getLevel(Object logger); - - public void setLevel(Object logger, Object newLevel); - - public boolean isLoggable(Object logger, Object level); - - public void log(Object logger, Object level, String msg); - - public void log(Object logger, Object level, String msg, Throwable t); - - public void log(Object logger, Object level, String msg, Object... params); - - // Methods to bridge java.util.logging.LoggingMXBean methods - public java.util.List getLoggerNames(); - - public String getLoggerLevel(String loggerName); - - public void setLoggerLevel(String loggerName, String levelName); - - public String getParentLoggerName(String loggerName); - - // Methods to bridge Level.parse() and Level.getName() method - public Object parseLevel(String levelName); - - public String getLevelName(Object level); - - public int getLevelValue(Object level); - - // return the logging property - public String getProperty(String key); -} diff --git a/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java b/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java deleted file mode 100644 index 3fb06be8541..00000000000 --- a/jdk/src/java.base/share/classes/sun/util/logging/LoggingSupport.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2009, 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.util.logging; - -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.time.ZonedDateTime; - -/** - * Internal API to support JRE implementation to detect if the java.util.logging - * support is available but with no dependency on the java.util.logging - * classes. This LoggingSupport class provides several static methods to - * access the java.util.logging functionality that requires the caller - * to ensure that the logging support is {@linkplain #isAvailable available} - * before invoking it. - * - * @see sun.util.logging.PlatformLogger if you want to log messages even - * if the logging support is not available - */ -public class LoggingSupport { - private LoggingSupport() { } - - private static final LoggingProxy proxy = - AccessController.doPrivileged(new PrivilegedAction() { - public LoggingProxy run() { - try { - // create a LoggingProxyImpl instance when - // java.util.logging classes exist - Class c = Class.forName("java.util.logging.LoggingProxyImpl", true, null); - Field f = c.getDeclaredField("INSTANCE"); - f.setAccessible(true); - return (LoggingProxy) f.get(null); - } catch (ClassNotFoundException cnf) { - return null; - } catch (NoSuchFieldException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - }}); - - /** - * Returns true if java.util.logging support is available. - */ - public static boolean isAvailable() { - return proxy != null; - } - - private static void ensureAvailable() { - if (proxy == null) - throw new AssertionError("Should not here"); - } - - public static java.util.List getLoggerNames() { - ensureAvailable(); - return proxy.getLoggerNames(); - } - public static String getLoggerLevel(String loggerName) { - ensureAvailable(); - return proxy.getLoggerLevel(loggerName); - } - - public static void setLoggerLevel(String loggerName, String levelName) { - ensureAvailable(); - proxy.setLoggerLevel(loggerName, levelName); - } - - public static String getParentLoggerName(String loggerName) { - ensureAvailable(); - return proxy.getParentLoggerName(loggerName); - } - - public static Object getLogger(String name) { - ensureAvailable(); - return proxy.getLogger(name); - } - - public static Object getLevel(Object logger) { - ensureAvailable(); - return proxy.getLevel(logger); - } - - public static void setLevel(Object logger, Object newLevel) { - ensureAvailable(); - proxy.setLevel(logger, newLevel); - } - - public static boolean isLoggable(Object logger, Object level) { - ensureAvailable(); - return proxy.isLoggable(logger,level); - } - - public static void log(Object logger, Object level, String msg) { - ensureAvailable(); - proxy.log(logger, level, msg); - } - - public static void log(Object logger, Object level, String msg, Throwable t) { - ensureAvailable(); - proxy.log(logger, level, msg, t); - } - - public static void log(Object logger, Object level, String msg, Object... params) { - ensureAvailable(); - proxy.log(logger, level, msg, params); - } - - public static Object parseLevel(String levelName) { - ensureAvailable(); - return proxy.parseLevel(levelName); - } - - public static String getLevelName(Object level) { - ensureAvailable(); - return proxy.getLevelName(level); - } - - public static int getLevelValue(Object level) { - ensureAvailable(); - return proxy.getLevelValue(level); - } - - // Since JDK 9, logging uses java.time to get more precise time stamps. - // It is possible to configure the simple format to print nano seconds (.%1$tN) - // by specifying: - // java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp %2$s%n%4$s: %5$s%6$s%n - // in the logging configuration - private static final String DEFAULT_FORMAT = - "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; - - private static final String FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format"; - public static String getSimpleFormat() { - return getSimpleFormat(true); - } - - // useProxy if true will cause initialization of - // java.util.logging and read its configuration - static String getSimpleFormat(boolean useProxy) { - String format = - AccessController.doPrivileged( - new PrivilegedAction() { - public String run() { - return System.getProperty(FORMAT_PROP_KEY); - } - }); - - if (useProxy && proxy != null && format == null) { - format = proxy.getProperty(FORMAT_PROP_KEY); - } - - if (format != null) { - try { - // validate the user-defined format string - String.format(format, ZonedDateTime.now(), "", "", "", "", ""); - } catch (IllegalArgumentException e) { - // illegal syntax; fall back to the default format - format = DEFAULT_FORMAT; - } - } else { - format = DEFAULT_FORMAT; - } - return format; - } - -} diff --git a/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java b/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java index 66de672d02c..655dc96a299 100644 --- a/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java +++ b/jdk/src/java.base/share/classes/sun/util/logging/PlatformLogger.java @@ -27,20 +27,13 @@ package sun.util.logging; import java.lang.ref.WeakReference; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import jdk.internal.misc.JavaLangAccess; -import jdk.internal.misc.SharedSecrets; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import jdk.internal.logger.LazyLoggers; +import jdk.internal.logger.LoggerWrapper; /** * Platform logger provides an API for the JRE components to log @@ -56,18 +49,28 @@ import jdk.internal.misc.SharedSecrets; * the stack frame information issuing the log message. * * When the logging facility is enabled (at startup or runtime), - * the java.util.logging.Logger will be created for each platform + * the backend logger will be created for each platform * logger and all log messages will be forwarded to the Logger * to handle. * + * The PlatformLogger uses an underlying PlatformLogger.Bridge instance + * obtained by calling {@link PlatformLogger.Bridge#convert PlatformLogger.Bridge.convert(} + * {@link jdk.internal.logger.LazyLoggers#getLazyLogger(java.lang.String, java.lang.Class) + * jdk.internal.logger.LazyLoggers#getLazyLogger(name, PlatformLogger.class))}. + * * Logging facility is "enabled" when one of the following * conditions is met: - * 1) a system property "java.util.logging.config.class" or - * "java.util.logging.config.file" is set - * 2) java.util.logging.LogManager or java.util.logging.Logger - * is referenced that will trigger the logging initialization. + * 1) ServiceLoader.load({@link java.lang.System.LoggerFinder LoggerFinder.class}, + * ClassLoader.getSystemClassLoader()).iterator().hasNext(). + * 2) ServiceLoader.loadInstalled({@link jdk.internal.logger.DefaultLoggerFinder}).iterator().hasNext(), + * and 2.1) a system property "java.util.logging.config.class" or + * "java.util.logging.config.file" is set + * or 2.2) java.util.logging.LogManager or java.util.logging.Logger + * is referenced that will trigger the logging initialization. * * Default logging configuration: + * + * No LoggerFinder service implementation declared * global logging level = INFO * handlers = java.util.logging.ConsoleHandler * java.util.logging.ConsoleHandler.level = INFO @@ -84,71 +87,84 @@ import jdk.internal.misc.SharedSecrets; * The platform loggers are designed for JDK developers use and * this limitation can be workaround with setting * -Djava.util.logging.config.file system property. + *
    + * Calling PlatformLogger.setLevel will not work when there is a custom + * LoggerFinder installed - and as a consequence {@link #setLevel setLevel} + * is now deprecated. * * @since 1.7 */ public class PlatformLogger { - // The integer values must match that of {@code java.util.logging.Level} - // objects. - private static final int OFF = Integer.MAX_VALUE; - private static final int SEVERE = 1000; - private static final int WARNING = 900; - private static final int INFO = 800; - private static final int CONFIG = 700; - private static final int FINE = 500; - private static final int FINER = 400; - private static final int FINEST = 300; - private static final int ALL = Integer.MIN_VALUE; - /** * PlatformLogger logging levels. */ public static enum Level { // The name and value must match that of {@code java.util.logging.Level}s. // Declare in ascending order of the given value for binary search. - ALL, - FINEST, - FINER, - FINE, - CONFIG, - INFO, - WARNING, - SEVERE, - OFF; + ALL(System.Logger.Level.ALL), + FINEST(System.Logger.Level.TRACE), + FINER(System.Logger.Level.TRACE), + FINE(System.Logger.Level.DEBUG), + CONFIG(System.Logger.Level.DEBUG), + INFO(System.Logger.Level.INFO), + WARNING(System.Logger.Level.WARNING), + SEVERE(System.Logger.Level.ERROR), + OFF(System.Logger.Level.OFF); - /** - * Associated java.util.logging.Level lazily initialized in - * JavaLoggerProxy's static initializer only once - * when java.util.logging is available and enabled. - * Only accessed by JavaLoggerProxy. - */ - /* java.util.logging.Level */ Object javaLevel; + final System.Logger.Level systemLevel; + Level(System.Logger.Level systemLevel) { + this.systemLevel = systemLevel; + } + + // The integer values must match that of {@code java.util.logging.Level} + // objects. + private static final int SEVERITY_OFF = Integer.MAX_VALUE; + private static final int SEVERITY_SEVERE = 1000; + private static final int SEVERITY_WARNING = 900; + private static final int SEVERITY_INFO = 800; + private static final int SEVERITY_CONFIG = 700; + private static final int SEVERITY_FINE = 500; + private static final int SEVERITY_FINER = 400; + private static final int SEVERITY_FINEST = 300; + private static final int SEVERITY_ALL = Integer.MIN_VALUE; // ascending order for binary search matching the list of enum constants private static final int[] LEVEL_VALUES = new int[] { - PlatformLogger.ALL, PlatformLogger.FINEST, PlatformLogger.FINER, - PlatformLogger.FINE, PlatformLogger.CONFIG, PlatformLogger.INFO, - PlatformLogger.WARNING, PlatformLogger.SEVERE, PlatformLogger.OFF + SEVERITY_ALL, SEVERITY_FINEST, SEVERITY_FINER, + SEVERITY_FINE, SEVERITY_CONFIG, SEVERITY_INFO, + SEVERITY_WARNING, SEVERITY_SEVERE, SEVERITY_OFF }; + public System.Logger.Level systemLevel() { + return systemLevel; + } + public int intValue() { return LEVEL_VALUES[this.ordinal()]; } - static Level valueOf(int level) { + /** + * Maps a severity value to an effective logger level. + * @param level The severity of the messages that should be + * logged with a logger set to the returned level. + * @return The effective logger level, which is the nearest Level value + * whose severity is greater or equal to the given level. + * For level > SEVERE (OFF excluded), return SEVERE. + */ + public static Level valueOf(int level) { switch (level) { // ordering per the highest occurrences in the jdk source // finest, fine, finer, info first - case PlatformLogger.FINEST : return Level.FINEST; - case PlatformLogger.FINE : return Level.FINE; - case PlatformLogger.FINER : return Level.FINER; - case PlatformLogger.INFO : return Level.INFO; - case PlatformLogger.WARNING : return Level.WARNING; - case PlatformLogger.CONFIG : return Level.CONFIG; - case PlatformLogger.SEVERE : return Level.SEVERE; - case PlatformLogger.OFF : return Level.OFF; - case PlatformLogger.ALL : return Level.ALL; + case SEVERITY_FINEST : return Level.FINEST; + case SEVERITY_FINE : return Level.FINE; + case SEVERITY_FINER : return Level.FINER; + case SEVERITY_INFO : return Level.INFO; + case SEVERITY_WARNING : return Level.WARNING; + case SEVERITY_CONFIG : return Level.CONFIG; + case SEVERITY_SEVERE : return Level.SEVERE; + case SEVERITY_OFF : return Level.OFF; + case SEVERITY_ALL : return Level.ALL; } // return the nearest Level value >= the given level, // for level > SEVERE, return SEVERE and exclude OFF @@ -157,39 +173,110 @@ public class PlatformLogger { } } - private static final Level DEFAULT_LEVEL = Level.INFO; - private static boolean loggingEnabled; - static { - loggingEnabled = AccessController.doPrivileged( - new PrivilegedAction<>() { - public Boolean run() { - String cname = System.getProperty("java.util.logging.config.class"); - String fname = System.getProperty("java.util.logging.config.file"); - return (cname != null || fname != null); - } - }); + /** + * + * The PlatformLogger.Bridge interface is implemented by the System.Logger + * objects returned by our default JUL provider - so that JRE classes using + * PlatformLogger see no difference when JUL is the actual backend. + * + * PlatformLogger is now only a thin adaptation layer over the same + * loggers than returned by java.lang.System.getLogger(String name). + * + * The recommendation for JRE classes going forward is to use + * java.lang.System.getLogger(String name), which will + * use Lazy Loggers when possible and necessary. + * + */ + public static interface Bridge { - // force loading of all JavaLoggerProxy (sub)classes to make JIT de-optimizations - // less probable. Don't initialize JavaLoggerProxy class since - // java.util.logging may not be enabled. - try { - Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy", - false, - PlatformLogger.class.getClassLoader()); - Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy", - false, // do not invoke class initializer - PlatformLogger.class.getClassLoader()); - } catch (ClassNotFoundException ex) { - throw new InternalError(ex); + /** + * Gets the name for this platform logger. + * @return the name of the platform logger. + */ + public String getName(); + + /** + * Returns true if a message of the given level would actually + * be logged by this logger. + * @param level the level + * @return whether a message of that level would be logged + */ + public boolean isLoggable(Level level); + public boolean isEnabled(); + + public void log(Level level, String msg); + public void log(Level level, String msg, Throwable thrown); + public void log(Level level, String msg, Object... params); + public void log(Level level, Supplier msgSupplier); + public void log(Level level, Throwable thrown, Supplier msgSupplier); + public void logp(Level level, String sourceClass, String sourceMethod, String msg); + public void logp(Level level, String sourceClass, String sourceMethod, + Supplier msgSupplier); + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Object... params); + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Throwable thrown); + public void logp(Level level, String sourceClass, String sourceMethod, + Throwable thrown, Supplier msgSupplier); + public void logrb(Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Object... params); + public void logrb(Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown); + public void logrb(Level level, ResourceBundle bundle, String msg, + Object... params); + public void logrb(Level level, ResourceBundle bundle, String msg, + Throwable thrown); + + + public static Bridge convert(System.Logger logger) { + if (logger instanceof PlatformLogger.Bridge) { + return (Bridge) logger; + } else { + return new LoggerWrapper<>(logger); + } + } + } + + /** + * The {@code PlatformLogger.ConfigurableBridge} interface is used to + * implement the deprecated {@link PlatformLogger#setLevel} method. + * + * PlatformLogger is now only a thin adaptation layer over the same + * loggers than returned by java.lang.System.getLogger(String name). + * + * The recommendation for JRE classes going forward is to use + * java.lang.System.getLogger(String name), which will + * use Lazy Loggers when possible and necessary. + * + */ + public static interface ConfigurableBridge { + + public abstract class LoggerConfiguration { + public abstract Level getPlatformLevel(); + public abstract void setPlatformLevel(Level level); + } + + public default LoggerConfiguration getLoggerConfiguration() { + return null; + } + + public static LoggerConfiguration getLoggerConfiguration(PlatformLogger.Bridge logger) { + if (logger instanceof PlatformLogger.ConfigurableBridge) { + return ((ConfigurableBridge) logger).getLoggerConfiguration(); + } else { + return null; + } } } // Table of known loggers. Maps names to PlatformLoggers. - private static Map> loggers = + private static final Map> loggers = new HashMap<>(); /** * Returns a PlatformLogger of a given name. + * @param name the name of the logger + * @return a PlatformLogger */ public static synchronized PlatformLogger getLogger(String name) { PlatformLogger log = null; @@ -198,56 +285,31 @@ public class PlatformLogger { log = ref.get(); } if (log == null) { - log = new PlatformLogger(name); + log = new PlatformLogger(PlatformLogger.Bridge.convert( + // We pass PlatformLogger.class rather than the actual caller + // because we want PlatformLoggers to be system loggers: we + // won't need to resolve any resource bundles anyway. + // Note: Many unit tests depend on the fact that + // PlatformLogger.getLoggerFromFinder is not caller sensitive. + LazyLoggers.getLazyLogger(name, PlatformLogger.class))); loggers.put(name, new WeakReference<>(log)); } return log; } - /** - * Initialize java.util.logging.Logger objects for all platform loggers. - * This method is called from LogManager.readPrimordialConfiguration(). - */ - public static synchronized void redirectPlatformLoggers() { - if (loggingEnabled || !LoggingSupport.isAvailable()) return; - - loggingEnabled = true; - for (Map.Entry> entry : loggers.entrySet()) { - WeakReference ref = entry.getValue(); - PlatformLogger plog = ref.get(); - if (plog != null) { - plog.redirectToJavaLoggerProxy(); - } - } - } - - /** - * Creates a new JavaLoggerProxy and redirects the platform logger to it - */ - private void redirectToJavaLoggerProxy() { - DefaultLoggerProxy lp = DefaultLoggerProxy.class.cast(this.loggerProxy); - JavaLoggerProxy jlp = new JavaLoggerProxy(lp.name, lp.level); - // the order of assignments is important - this.javaLoggerProxy = jlp; // isLoggable checks javaLoggerProxy if set - this.loggerProxy = jlp; - } - - // DefaultLoggerProxy may be replaced with a JavaLoggerProxy object - // when the java.util.logging facility is enabled - private volatile LoggerProxy loggerProxy; - // javaLoggerProxy is only set when the java.util.logging facility is enabled - private volatile JavaLoggerProxy javaLoggerProxy; - private PlatformLogger(String name) { - if (loggingEnabled) { - this.loggerProxy = this.javaLoggerProxy = new JavaLoggerProxy(name); - } else { - this.loggerProxy = new DefaultLoggerProxy(name); - } + // The system loggerProxy returned by LazyLoggers + // This may be a lazy logger - see jdk.internal.logger.LazyLoggers, + // or may be a Logger instance (or a wrapper thereof). + // + private final PlatformLogger.Bridge loggerProxy; + private PlatformLogger(PlatformLogger.Bridge loggerProxy) { + this.loggerProxy = loggerProxy; } /** * A convenience method to test if the logger is turned off. * (i.e. its level is OFF). + * @return whether the logger is turned off. */ public boolean isEnabled() { return loggerProxy.isEnabled(); @@ -255,22 +317,24 @@ public class PlatformLogger { /** * Gets the name for this platform logger. + * @return the name of the platform logger. */ public String getName() { - return loggerProxy.name; + return loggerProxy.getName(); } /** * Returns true if a message of the given level would actually * be logged by this logger. + * @param level the level + * @return whether a message of that level would be logged */ public boolean isLoggable(Level level) { if (level == null) { throw new NullPointerException(); } - // performance-sensitive method: use two monomorphic call-sites - JavaLoggerProxy jlp = javaLoggerProxy; - return jlp != null ? jlp.isLoggable(level) : loggerProxy.isLoggable(level); + + return loggerProxy.isLoggable(level); } /** @@ -281,13 +345,15 @@ public class PlatformLogger { * @return this PlatformLogger's level */ public Level level() { - return loggerProxy.getLevel(); + final ConfigurableBridge.LoggerConfiguration spi = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy); + return spi == null ? null : spi.getPlatformLevel(); } /** * Set the log level specifying which message levels will be * logged by this logger. Message levels lower than this - * value will be discarded. The level value {@link #OFF} + * value will be discarded. The level value {@link Level#OFF} * can be used to turn off logging. *

    * If the new level is null, it means that this node should @@ -295,366 +361,153 @@ public class PlatformLogger { * (non-null) level value. * * @param newLevel the new value for the log level (may be null) + * @deprecated Platform Loggers should not be configured programmatically. + * This method will not work if a custom {@link + * java.lang.System.LoggerFinder} is installed. */ + @Deprecated public void setLevel(Level newLevel) { - loggerProxy.setLevel(newLevel); + final ConfigurableBridge.LoggerConfiguration spi = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(loggerProxy);; + if (spi != null) { + spi.setPlatformLevel(newLevel); + } } /** * Logs a SEVERE message. + * @param msg the message */ public void severe(String msg) { - loggerProxy.doLog(Level.SEVERE, msg); + loggerProxy.log(Level.SEVERE, msg, (Object[])null); } public void severe(String msg, Throwable t) { - loggerProxy.doLog(Level.SEVERE, msg, t); + loggerProxy.log(Level.SEVERE, msg, t); } public void severe(String msg, Object... params) { - loggerProxy.doLog(Level.SEVERE, msg, params); + loggerProxy.log(Level.SEVERE, msg, params); } /** * Logs a WARNING message. + * @param msg the message */ public void warning(String msg) { - loggerProxy.doLog(Level.WARNING, msg); + loggerProxy.log(Level.WARNING, msg, (Object[])null); } public void warning(String msg, Throwable t) { - loggerProxy.doLog(Level.WARNING, msg, t); + loggerProxy.log(Level.WARNING, msg, t); } public void warning(String msg, Object... params) { - loggerProxy.doLog(Level.WARNING, msg, params); + loggerProxy.log(Level.WARNING, msg, params); } /** * Logs an INFO message. + * @param msg the message */ public void info(String msg) { - loggerProxy.doLog(Level.INFO, msg); + loggerProxy.log(Level.INFO, msg, (Object[])null); } public void info(String msg, Throwable t) { - loggerProxy.doLog(Level.INFO, msg, t); + loggerProxy.log(Level.INFO, msg, t); } public void info(String msg, Object... params) { - loggerProxy.doLog(Level.INFO, msg, params); + loggerProxy.log(Level.INFO, msg, params); } /** * Logs a CONFIG message. + * @param msg the message */ public void config(String msg) { - loggerProxy.doLog(Level.CONFIG, msg); + loggerProxy.log(Level.CONFIG, msg, (Object[])null); } public void config(String msg, Throwable t) { - loggerProxy.doLog(Level.CONFIG, msg, t); + loggerProxy.log(Level.CONFIG, msg, t); } public void config(String msg, Object... params) { - loggerProxy.doLog(Level.CONFIG, msg, params); + loggerProxy.log(Level.CONFIG, msg, params); } /** * Logs a FINE message. + * @param msg the message */ public void fine(String msg) { - loggerProxy.doLog(Level.FINE, msg); + loggerProxy.log(Level.FINE, msg, (Object[])null); } public void fine(String msg, Throwable t) { - loggerProxy.doLog(Level.FINE, msg, t); + loggerProxy.log(Level.FINE, msg, t); } public void fine(String msg, Object... params) { - loggerProxy.doLog(Level.FINE, msg, params); + loggerProxy.log(Level.FINE, msg, params); } /** * Logs a FINER message. + * @param msg the message */ public void finer(String msg) { - loggerProxy.doLog(Level.FINER, msg); + loggerProxy.log(Level.FINER, msg, (Object[])null); } public void finer(String msg, Throwable t) { - loggerProxy.doLog(Level.FINER, msg, t); + loggerProxy.log(Level.FINER, msg, t); } public void finer(String msg, Object... params) { - loggerProxy.doLog(Level.FINER, msg, params); + loggerProxy.log(Level.FINER, msg, params); } /** * Logs a FINEST message. + * @param msg the message */ public void finest(String msg) { - loggerProxy.doLog(Level.FINEST, msg); + loggerProxy.log(Level.FINEST, msg, (Object[])null); } public void finest(String msg, Throwable t) { - loggerProxy.doLog(Level.FINEST, msg, t); + loggerProxy.log(Level.FINEST, msg, t); } public void finest(String msg, Object... params) { - loggerProxy.doLog(Level.FINEST, msg, params); + loggerProxy.log(Level.FINEST, msg, params); } - /** - * Abstract base class for logging support, defining the API and common field. - */ - private abstract static class LoggerProxy { - final String name; + // ------------------------------------ + // Maps used for Level conversion + // ------------------------------------ - protected LoggerProxy(String name) { - this.name = name; - } + // This map is indexed by java.util.spi.Logger.Level.ordinal() and returns + // a PlatformLogger.Level + // + // ALL, TRACE, DEBUG, INFO, WARNING, ERROR, OFF + private static final Level[] spi2platformLevelMapping = { + Level.ALL, // mapped from ALL + Level.FINER, // mapped from TRACE + Level.FINE, // mapped from DEBUG + Level.INFO, // mapped from INFO + Level.WARNING, // mapped from WARNING + Level.SEVERE, // mapped from ERROR + Level.OFF // mapped from OFF + }; - abstract boolean isEnabled(); - - abstract Level getLevel(); - abstract void setLevel(Level newLevel); - - abstract void doLog(Level level, String msg); - abstract void doLog(Level level, String msg, Throwable thrown); - abstract void doLog(Level level, String msg, Object... params); - - abstract boolean isLoggable(Level level); + public static Level toPlatformLevel(java.lang.System.Logger.Level level) { + if (level == null) return null; + assert level.ordinal() < spi2platformLevelMapping.length; + return spi2platformLevelMapping[level.ordinal()]; } - - private static final class DefaultLoggerProxy extends LoggerProxy { - /** - * Default platform logging support - output messages to System.err - - * equivalent to ConsoleHandler with SimpleFormatter. - */ - private static PrintStream outputStream() { - return System.err; - } - - volatile Level effectiveLevel; // effective level (never null) - volatile Level level; // current level set for this node (may be null) - - DefaultLoggerProxy(String name) { - super(name); - this.effectiveLevel = deriveEffectiveLevel(null); - this.level = null; - } - - boolean isEnabled() { - return effectiveLevel != Level.OFF; - } - - Level getLevel() { - return level; - } - - void setLevel(Level newLevel) { - Level oldLevel = level; - if (oldLevel != newLevel) { - level = newLevel; - effectiveLevel = deriveEffectiveLevel(newLevel); - } - } - - void doLog(Level level, String msg) { - if (isLoggable(level)) { - outputStream().print(format(level, msg, null)); - } - } - - void doLog(Level level, String msg, Throwable thrown) { - if (isLoggable(level)) { - outputStream().print(format(level, msg, thrown)); - } - } - - void doLog(Level level, String msg, Object... params) { - if (isLoggable(level)) { - String newMsg = formatMessage(msg, params); - outputStream().print(format(level, newMsg, null)); - } - } - - boolean isLoggable(Level level) { - Level effectiveLevel = this.effectiveLevel; - return level.intValue() >= effectiveLevel.intValue() && effectiveLevel != Level.OFF; - } - - // derive effective level (could do inheritance search like j.u.l.Logger) - private Level deriveEffectiveLevel(Level level) { - return level == null ? DEFAULT_LEVEL : level; - } - - // Copied from java.util.logging.Formatter.formatMessage - private String formatMessage(String format, Object... parameters) { - // Do the formatting. - try { - if (parameters == null || parameters.length == 0) { - // No parameters. Just return format string. - return format; - } - // Is it a java.text style format? - // Ideally we could match with - // Pattern.compile("\\{\\d").matcher(format).find()) - // However the cost is 14% higher, so we cheaply check for - // 1 of the first 4 parameters - if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 || - format.indexOf("{2") >=0|| format.indexOf("{3") >=0) { - return java.text.MessageFormat.format(format, parameters); - } - return format; - } catch (Exception ex) { - // Formatting failed: use format string. - return format; - } - } - - private static final String formatString = - LoggingSupport.getSimpleFormat(false); // don't check logging.properties - private final ZoneId zoneId = ZoneId.systemDefault(); - private synchronized String format(Level level, String msg, Throwable thrown) { - ZonedDateTime zdt = ZonedDateTime.now(zoneId); - String throwable = ""; - if (thrown != null) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - pw.println(); - thrown.printStackTrace(pw); - pw.close(); - throwable = sw.toString(); - } - - return String.format(formatString, - zdt, - getCallerInfo(), - name, - level.name(), - msg, - throwable); - } - - // Returns the caller's class and method's name; best effort - // if cannot infer, return the logger's name. - private String getCallerInfo() { - String sourceClassName = null; - String sourceMethodName = null; - - JavaLangAccess access = SharedSecrets.getJavaLangAccess(); - Throwable throwable = new Throwable(); - int depth = access.getStackTraceDepth(throwable); - - String logClassName = "sun.util.logging.PlatformLogger"; - boolean lookingForLogger = true; - for (int ix = 0; ix < depth; ix++) { - // Calling getStackTraceElement directly prevents the VM - // from paying the cost of building the entire stack frame. - StackTraceElement frame = - access.getStackTraceElement(throwable, ix); - String cname = frame.getClassName(); - if (lookingForLogger) { - // Skip all frames until we have found the first logger frame. - if (cname.equals(logClassName)) { - lookingForLogger = false; - } - } else { - if (!cname.equals(logClassName)) { - // We've found the relevant frame. - sourceClassName = cname; - sourceMethodName = frame.getMethodName(); - break; - } - } - } - - if (sourceClassName != null) { - return sourceClassName + " " + sourceMethodName; - } else { - return name; - } - } - } - - /** - * JavaLoggerProxy forwards all the calls to its corresponding - * java.util.logging.Logger object. - */ - private static final class JavaLoggerProxy extends LoggerProxy { - // initialize javaLevel fields for mapping from Level enum -> j.u.l.Level object - static { - for (Level level : Level.values()) { - level.javaLevel = LoggingSupport.parseLevel(level.name()); - } - } - - private final /* java.util.logging.Logger */ Object javaLogger; - - JavaLoggerProxy(String name) { - this(name, null); - } - - JavaLoggerProxy(String name, Level level) { - super(name); - this.javaLogger = LoggingSupport.getLogger(name); - if (level != null) { - // level has been updated and so set the Logger's level - LoggingSupport.setLevel(javaLogger, level.javaLevel); - } - } - - void doLog(Level level, String msg) { - LoggingSupport.log(javaLogger, level.javaLevel, msg); - } - - void doLog(Level level, String msg, Throwable t) { - LoggingSupport.log(javaLogger, level.javaLevel, msg, t); - } - - void doLog(Level level, String msg, Object... params) { - if (!isLoggable(level)) { - return; - } - // only pass String objects to the j.u.l.Logger which may - // be created by untrusted code - int len = (params != null) ? params.length : 0; - Object[] sparams = new String[len]; - for (int i = 0; i < len; i++) { - sparams [i] = String.valueOf(params[i]); - } - LoggingSupport.log(javaLogger, level.javaLevel, msg, sparams); - } - - boolean isEnabled() { - return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel); - } - - /** - * Returns the PlatformLogger.Level mapped from j.u.l.Level - * set in the logger. If the j.u.l.Logger is set to a custom Level, - * this method will return the nearest Level. - */ - Level getLevel() { - Object javaLevel = LoggingSupport.getLevel(javaLogger); - if (javaLevel == null) return null; - - try { - return Level.valueOf(LoggingSupport.getLevelName(javaLevel)); - } catch (IllegalArgumentException e) { - return Level.valueOf(LoggingSupport.getLevelValue(javaLevel)); - } - } - - void setLevel(Level level) { - LoggingSupport.setLevel(javaLogger, level == null ? null : level.javaLevel); - } - - boolean isLoggable(Level level) { - return LoggingSupport.isLoggable(javaLogger, level.javaLevel); - } - } } diff --git a/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java b/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java index 0af78546c21..3aec0a72d89 100644 --- a/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java +++ b/jdk/src/java.desktop/share/classes/sun/font/FontUtilities.java @@ -72,6 +72,8 @@ public final class FontUtilities { static { AccessController.doPrivileged(new PrivilegedAction() { + @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated. + @Override public Object run() { String osName = System.getProperty("os.name", "unknownOS"); isSolaris = osName.startsWith("SunOS"); diff --git a/jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder b/jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder new file mode 100644 index 00000000000..d35467b1683 --- /dev/null +++ b/jdk/src/java.logging/share/classes/META-INF/services/jdk.internal.logger.DefaultLoggerFinder @@ -0,0 +1 @@ +sun.util.logging.internal.LoggingProviderImpl diff --git a/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java b/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java index 0ccb7f8fe97..d1c59fc763f 100644 --- a/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java +++ b/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java @@ -43,6 +43,7 @@ import java.util.stream.Stream; import jdk.internal.misc.JavaAWTAccess; import jdk.internal.misc.SharedSecrets; import sun.misc.ManagedLocalsThread; +import sun.util.logging.internal.LoggingProviderImpl; /** * There is a single global LogManager object that is used to @@ -436,7 +437,8 @@ public class LogManager { readConfiguration(); // Platform loggers begin to delegate to java.util.logging.Logger - sun.util.logging.PlatformLogger.redirectPlatformLoggers(); + jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers(); + } catch (Exception ex) { assert false : "Exception raised while reading logging configuration: " + ex; } @@ -1481,7 +1483,7 @@ public class LogManager { *

    * Any {@linkplain #addConfigurationListener registered configuration * listener} will be invoked after the properties are read. - *

    + * * @apiNote This {@code readConfiguration} method should only be used for * initializing the configuration during LogManager initialization or * used with the "java.util.logging.config.class" property. @@ -2363,7 +2365,8 @@ public class LogManager { } } - static final Permission controlPermission = new LoggingPermission("control", null); + static final Permission controlPermission = + new LoggingPermission("control", null); void checkPermission() { SecurityManager sm = System.getSecurityManager(); @@ -2607,4 +2610,69 @@ public class LogManager { if (t instanceof RuntimeException) throw (RuntimeException)t; } + /** + * This class allows the {@link LoggingProviderImpl} to demand loggers on + * behalf of system and application classes. + */ + private static final class LoggingProviderAccess + implements LoggingProviderImpl.LogManagerAccess, + PrivilegedAction { + + private LoggingProviderAccess() { + } + + /** + * Demands a logger on behalf of the given {@code caller}. + *

    + * If a named logger suitable for the given caller is found + * returns it. + * Otherwise, creates a new logger suitable for the given caller. + * + * @param name The logger name. + * @param caller The caller on which behalf the logger is created/retrieved. + * @return A logger for the given {@code caller}. + * + * @throws NullPointerException if {@code name} is {@code null} + * or {@code caller} is {@code null}. + * @throws IllegalArgumentException if {@code manager} is not the default + * LogManager. + * @throws SecurityException if a security manager is present and the + * calling code doesn't have the + * {@link LoggingPermission LoggingPermission("demandLogger", null)}. + */ + @Override + public Logger demandLoggerFor(LogManager manager, String name, /* Module */ Class caller) { + if (manager != getLogManager()) { + // having LogManager as parameter just ensures that the + // caller will have initialized the LogManager before reaching + // here. + throw new IllegalArgumentException("manager"); + } + Objects.requireNonNull(name); + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(controlPermission); + } + if (caller.getClassLoader() == null) { + return manager.demandSystemLogger(name, + Logger.SYSTEM_LOGGER_RB_NAME, caller); + } else { + return manager.demandLogger(name, null, caller); + } + } + + @Override + public Void run() { + LoggingProviderImpl.setLogManagerAccess(INSTANCE); + return null; + } + + static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess(); + } + + static { + AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null, + controlPermission); + } + } diff --git a/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java b/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java index 45aca1f9c0e..ec760777b66 100644 --- a/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java +++ b/jdk/src/java.logging/share/classes/java/util/logging/LogRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -33,6 +33,7 @@ import java.time.Clock; import jdk.internal.misc.JavaLangAccess; import jdk.internal.misc.SharedSecrets; +import static jdk.internal.logger.SimpleConsoleLogger.skipLoggingFrame; /** * LogRecord objects are used to pass logging requests between @@ -637,6 +638,27 @@ public class LogRecord implements java.io.Serializable { } // Private method to infer the caller's class and method names + // + // Note: + // For testing purposes - it is possible to customize the process + // by which LogRecord will infer the source class name and source method name + // when analyzing the call stack. + //

    + // The system property {@code jdk.logger.packages} can define a comma separated + // list of strings corresponding to additional package name prefixes that + // should be ignored when trying to infer the source caller class name. + // Those stack frames whose {@linkplain StackTraceElement#getClassName() + // declaring class name} start with one such prefix will be ignored. + //

    + // This is primarily useful when providing utility logging classes wrapping + // a logger instance, as it makes it possible to instruct LogRecord to skip + // those utility frames when inferring the caller source class name. + //

    + // The {@code jdk.logger.packages} system property is consulted only once. + //

    + // This property is not standard, implementation specific, and yet + // undocumented (and thus subject to changes without notice). + // private void inferCaller() { needToInferCaller = false; JavaLangAccess access = SharedSecrets.getJavaLangAccess(); @@ -658,8 +680,8 @@ public class LogRecord implements java.io.Serializable { } } else { if (!isLoggerImpl) { - // skip reflection call - if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) { + // skip logging/logger infrastructure and reflection calls + if (!skipLoggingFrame(cname)) { // We've found the relevant frame. setSourceClassName(cname); setSourceMethodName(frame.getMethodName()); @@ -675,7 +697,6 @@ public class LogRecord implements java.io.Serializable { private boolean isLoggerImplFrame(String cname) { // the log record could be created for a platform logger return (cname.equals("java.util.logging.Logger") || - cname.startsWith("java.util.logging.LoggingProxyImpl") || - cname.startsWith("sun.util.logging.")); + cname.startsWith("sun.util.logging.PlatformLogger")); } } diff --git a/jdk/src/java.logging/share/classes/java/util/logging/Logger.java b/jdk/src/java.logging/share/classes/java/util/logging/Logger.java index a9c4dc3fab3..077b272f3ac 100644 --- a/jdk/src/java.logging/share/classes/java/util/logging/Logger.java +++ b/jdk/src/java.logging/share/classes/java/util/logging/Logger.java @@ -447,8 +447,7 @@ public class Logger { private static Logger demandLogger(String name, String resourceBundleName, Class caller) { LogManager manager = LogManager.getLogManager(); - SecurityManager sm = System.getSecurityManager(); - if (sm != null && !SystemLoggerHelper.disableCallerCheck) { + if (!SystemLoggerHelper.disableCallerCheck) { if (caller.getClassLoader() == null) { return manager.demandSystemLogger(name, resourceBundleName, caller); } @@ -1254,14 +1253,14 @@ public class Logger { * with an optional list of message parameters. *

    * If the logger is currently enabled for the given message - * level then a corresponding LogRecord is created and forwarded - * to all the registered output Handler objects. + * {@code level} then a corresponding {@code LogRecord} is created and + * forwarded to all the registered output {@code Handler} objects. *

    * The {@code msg} string is localized using the given resource bundle. * If the resource bundle is {@code null}, then the {@code msg} string is not * localized. * - * @param level One of the message level identifiers, e.g., SEVERE + * @param level One of the message level identifiers, e.g., {@code SEVERE} * @param sourceClass Name of the class that issued the logging request * @param sourceMethod Name of the method that issued the logging request * @param bundle Resource bundle to localize {@code msg}, @@ -1284,6 +1283,36 @@ public class Logger { doLog(lr, bundle); } + /** + * Log a message, specifying source class, method, and resource bundle, + * with an optional list of message parameters. + *

    + * If the logger is currently enabled for the given message + * {@code level} then a corresponding {@code LogRecord} is created + * and forwarded to all the registered output {@code Handler} objects. + *

    + * The {@code msg} string is localized using the given resource bundle. + * If the resource bundle is {@code null}, then the {@code msg} string is not + * localized. + *

    + * @param level One of the message level identifiers, e.g., {@code SEVERE} + * @param bundle Resource bundle to localize {@code msg}; + * can be {@code null}. + * @param msg The string message (or a key in the message catalog) + * @param params Parameters to the message (optional, may be none). + * @since 1.9 + */ + public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) { + if (!isLoggable(level)) { + return; + } + LogRecord lr = new LogRecord(level, msg); + if (params != null && params.length != 0) { + lr.setParameters(params); + } + doLog(lr, bundle); + } + /** * Log a message, specifying source class, method, and resource bundle name, * with associated Throwable information. @@ -1330,19 +1359,20 @@ public class Logger { * with associated Throwable information. *

    * If the logger is currently enabled for the given message - * level then the given arguments are stored in a LogRecord + * {@code level} then the given arguments are stored in a {@code LogRecord} * which is forwarded to all registered output handlers. *

    * The {@code msg} string is localized using the given resource bundle. * If the resource bundle is {@code null}, then the {@code msg} string is not * localized. *

    - * Note that the thrown argument is stored in the LogRecord thrown - * property, rather than the LogRecord parameters property. Thus it is - * processed specially by output Formatters and is not treated - * as a formatting parameter to the LogRecord message property. + * Note that the {@code thrown} argument is stored in the {@code LogRecord} + * {@code thrown} property, rather than the {@code LogRecord} + * {@code parameters} property. Thus it is + * processed specially by output {@code Formatter} objects and is not treated + * as a formatting parameter to the {@code LogRecord} {@code message} property. * - * @param level One of the message level identifiers, e.g., SEVERE + * @param level One of the message level identifiers, e.g., {@code SEVERE} * @param sourceClass Name of the class that issued the logging request * @param sourceMethod Name of the method that issued the logging request * @param bundle Resource bundle to localize {@code msg}, @@ -1363,6 +1393,42 @@ public class Logger { doLog(lr, bundle); } + /** + * Log a message, specifying source class, method, and resource bundle, + * with associated Throwable information. + *

    + * If the logger is currently enabled for the given message + * {@code level} then the given arguments are stored in a {@code LogRecord} + * which is forwarded to all registered output handlers. + *

    + * The {@code msg} string is localized using the given resource bundle. + * If the resource bundle is {@code null}, then the {@code msg} string is not + * localized. + *

    + * Note that the {@code thrown} argument is stored in the {@code LogRecord} + * {@code thrown} property, rather than the {@code LogRecord} + * {@code parameters} property. Thus it is + * processed specially by output {@code Formatter} objects and is not treated + * as a formatting parameter to the {@code LogRecord} {@code message} + * property. + *

    + * @param level One of the message level identifiers, e.g., {@code SEVERE} + * @param bundle Resource bundle to localize {@code msg}; + * can be {@code null}. + * @param msg The string message (or a key in the message catalog) + * @param thrown Throwable associated with the log message. + * @since 1.9 + */ + public void logrb(Level level, ResourceBundle bundle, String msg, + Throwable thrown) { + if (!isLoggable(level)) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setThrown(thrown); + doLog(lr, bundle); + } + //====================================================================== // Start of convenience methods for logging method entries and returns. //====================================================================== diff --git a/jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java b/jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java deleted file mode 100644 index 61fcb7bc752..00000000000 --- a/jdk/src/java.logging/share/classes/java/util/logging/LoggingProxyImpl.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2009, 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 java.util.logging; - -import sun.util.logging.LoggingProxy; - -/** - * Implementation of LoggingProxy when java.util.logging classes exist. - */ -class LoggingProxyImpl implements LoggingProxy { - static final LoggingProxy INSTANCE = new LoggingProxyImpl(); - - private LoggingProxyImpl() { } - - @Override - public Object getLogger(String name) { - // always create a platform logger with the resource bundle name - return Logger.getPlatformLogger(name); - } - - @Override - public Object getLevel(Object logger) { - return ((Logger) logger).getLevel(); - } - - @Override - public void setLevel(Object logger, Object newLevel) { - ((Logger) logger).setLevel((Level) newLevel); - } - - @Override - public boolean isLoggable(Object logger, Object level) { - return ((Logger) logger).isLoggable((Level) level); - } - - @Override - public void log(Object logger, Object level, String msg) { - ((Logger) logger).log((Level) level, msg); - } - - @Override - public void log(Object logger, Object level, String msg, Throwable t) { - ((Logger) logger).log((Level) level, msg, t); - } - - @Override - public void log(Object logger, Object level, String msg, Object... params) { - ((Logger) logger).log((Level) level, msg, params); - } - - @Override - public java.util.List getLoggerNames() { - return LogManager.getLoggingMXBean().getLoggerNames(); - } - - @Override - public String getLoggerLevel(String loggerName) { - return LogManager.getLoggingMXBean().getLoggerLevel(loggerName); - } - - @Override - public void setLoggerLevel(String loggerName, String levelName) { - LogManager.getLoggingMXBean().setLoggerLevel(loggerName, levelName); - } - - @Override - public String getParentLoggerName(String loggerName) { - return LogManager.getLoggingMXBean().getParentLoggerName(loggerName); - } - - @Override - public Object parseLevel(String levelName) { - Level level = Level.findLevel(levelName); - if (level == null) { - throw new IllegalArgumentException("Unknown level \"" + levelName + "\""); - } - return level; - } - - @Override - public String getLevelName(Object level) { - return ((Level) level).getLevelName(); - } - - @Override - public int getLevelValue(Object level) { - return ((Level) level).intValue(); - } - - @Override - public String getProperty(String key) { - return LogManager.getLogManager().getProperty(key); - } -} diff --git a/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java b/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java index 8beb465a59f..3f18d43ea21 100644 --- a/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java +++ b/jdk/src/java.logging/share/classes/java/util/logging/SimpleFormatter.java @@ -27,10 +27,9 @@ package java.util.logging; import java.io.*; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import sun.util.logging.LoggingSupport; +import jdk.internal.logger.SimpleConsoleLogger; /** * Print a brief summary of the {@code LogRecord} in a human readable @@ -60,8 +59,12 @@ import sun.util.logging.LoggingSupport; public class SimpleFormatter extends Formatter { // format string for printing the log record - private final String format = LoggingSupport.getSimpleFormat(); - private final ZoneId zoneId = ZoneId.systemDefault(); + static String getLoggingProperty(String name) { + return LogManager.getLogManager().getProperty(name); + } + + private final String format = + SimpleConsoleLogger.getSimpleFormat(SimpleFormatter::getLoggingProperty); /** * Format the given LogRecord. @@ -152,7 +155,7 @@ public class SimpleFormatter extends Formatter { @Override public synchronized String format(LogRecord record) { ZonedDateTime zdt = ZonedDateTime.ofInstant( - record.getInstant(), zoneId); + record.getInstant(), ZoneId.systemDefault()); String source; if (record.getSourceClassName() != null) { source = record.getSourceClassName(); diff --git a/jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java b/jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java new file mode 100644 index 00000000000..4fcf40d2daa --- /dev/null +++ b/jdk/src/java.logging/share/classes/sun/util/logging/internal/LoggingProviderImpl.java @@ -0,0 +1,466 @@ +/* + * 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.util.logging.internal; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.util.Objects; +import java.util.logging.LogManager; +import jdk.internal.logger.DefaultLoggerFinder; +import java.util.logging.LoggingPermission; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration; + +/** + * This {@code LoggingProviderImpl} is the JDK internal implementation of the + * {@link jdk.internal.logger.DefaultLoggerFinder} which is used by + * the default implementation of the {@link Logger} + * when no {@link LoggerFinder} is found + * and {@code java.util.logging} is present. + * When {@code java.util.logging} is present, the {@code LoggingProviderImpl} + * is {@linkplain java.util.ServiceLoader#loadInstalled(Class) installed} as + * an internal service provider, making it possible to use {@code java.util.logging} + * as the backend for loggers returned by the default LoggerFinder implementation. + *

    + * This implementation of {@link DefaultLoggerFinder} returns instances of + * {@link java.lang.System.Logger} which + * delegate to a wrapped instance of {@link java.util.logging.Logger + * java.util.logging.Logger}. + *
    + * Loggers returned by this class can therefore be configured by accessing + * their wrapped implementation through the regular {@code java.util.logging} + * APIs - such as {@link java.util.logging.LogManager java.util.logging.LogManager} + * and {@link java.util.logging.Logger java.util.logging.Logger}. + * + * @apiNote Programmers are not expected to call this class directly. + * Instead they should rely on the static methods defined by + * {@link java.lang.System java.lang.System}. + *

    + * To replace this default + * {@code java.util.logging} backend, an application is expected to install + * its own {@link java.lang.System.LoggerFinder}. + * + * @see java.lang.System.Logger + * @see java.lang.System.LoggerFinder + * @see sun.util.logging.PlatformLogger.Bridge + * @see java.lang.System + * @see jdk.internal.logger + * @see jdk.internal.logger + * + */ +public final class LoggingProviderImpl extends DefaultLoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + private static final LoggingPermission LOGGING_CONTROL_PERMISSION = + new LoggingPermission("control", null); + + /** + * Creates a new instance of LoggingProviderImpl. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")}. + */ + public LoggingProviderImpl() { + } + + /** + * A logger that delegates to a java.util.logging.Logger delegate. + */ + static final class JULWrapper extends LoggerConfiguration + implements System.Logger, PlatformLogger.Bridge, + PlatformLogger.ConfigurableBridge { + + + private static final java.util.logging.Level[] spi2JulLevelMapping = { + java.util.logging.Level.ALL, // mapped from ALL + java.util.logging.Level.FINER, // mapped from TRACE + java.util.logging.Level.FINE, // mapped from DEBUG + java.util.logging.Level.INFO, // mapped from INFO + java.util.logging.Level.WARNING, // mapped from WARNING + java.util.logging.Level.SEVERE, // mapped from ERROR + java.util.logging.Level.OFF // mapped from OFF + }; + + private static final java.util.logging.Level[] platform2JulLevelMapping = { + java.util.logging.Level.ALL, // mapped from ALL + java.util.logging.Level.FINEST, // mapped from FINEST + java.util.logging.Level.FINER, // mapped from FINER + java.util.logging.Level.FINE, // mapped from FINE + java.util.logging.Level.CONFIG, // mapped from CONFIG + java.util.logging.Level.INFO, // mapped from INFO + java.util.logging.Level.WARNING, // mapped from WARNING + java.util.logging.Level.SEVERE, // mapped from SEVERE + java.util.logging.Level.OFF // mapped from OFF + }; + + private final java.util.logging.Logger julLogger; + + + private JULWrapper(java.util.logging.Logger logger) { + this.julLogger = logger; + } + + @Override + public String getName() { + return julLogger.getName(); + } + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable throwable) { + julLogger.log(toJUL(level), msg, throwable); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) { + julLogger.log(toJUL(level), format, params); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + julLogger.log(toJUL(level), msg); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Supplier msgSuppier) { + julLogger.log(toJUL(level), msgSuppier); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, Supplier msgSuppier) { + julLogger.log(toJUL(level), thrown, msgSuppier); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Throwable throwable) { + julLogger.logrb(toJUL(level), bundle, key, throwable); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, String key, Object... params) { + julLogger.logrb(toJUL(level), bundle, key, params); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, String msg) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + Supplier msgSupplier) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msgSupplier); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Object... params) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, params); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Throwable thrown) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, msg, thrown); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + Throwable thrown, Supplier msgSupplier) { + julLogger.logp(toJUL(level), sourceClass, sourceMethod, + thrown, msgSupplier); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String key, Object... params) { + julLogger.logrb(toJUL(level), sourceClass, sourceMethod, + bundle, key, params); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + ResourceBundle bundle, String key, Throwable thrown) { + julLogger.logrb(toJUL(level), sourceClass, sourceMethod, + bundle, key, thrown); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return julLogger.isLoggable(toJUL(level)); + } + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + + @Override + public boolean isLoggable(Level level) { + return julLogger.isLoggable(toJUL(level)); + } + + @Override + public void log(Level level, String msg) { + julLogger.log(toJUL(level), msg); + } + + @Override + public void log(Level level, + Supplier msgSupplier) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(msgSupplier); + julLogger.log(toJUL(level), msgSupplier); + } + + @Override + public void log(Level level, Object obj) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(obj); + julLogger.log(toJUL(level), () -> obj.toString()); + } + + @Override + public void log(Level level, + String msg, Throwable thrown) { + julLogger.log(toJUL(level), msg, thrown); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + // We need to check for null here to satisfy the contract + // of System.Logger - because the underlying implementation + // of julLogger will check for it only if the level is + // loggable + Objects.requireNonNull(msgSupplier); + julLogger.log(toJUL(level), thrown, msgSupplier); + } + + @Override + public void log(Level level, + String format, Object... params) { + julLogger.log(toJUL(level), format, params); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + julLogger.logrb(toJUL(level), bundle, key, thrown); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + julLogger.logrb(toJUL(level), bundle, format, params); + } + + static java.util.logging.Level toJUL(Level level) { + if (level == null) return null; + assert level.ordinal() < spi2JulLevelMapping.length; + return spi2JulLevelMapping[level.ordinal()]; + } + + // --------------------------------------------------------- + // Methods from PlatformLogger.Bridge + // --------------------------------------------------------- + + @Override + public boolean isEnabled() { + return julLogger.getLevel() != java.util.logging.Level.OFF; + } + + @Override + public PlatformLogger.Level getPlatformLevel() { + final java.util.logging.Level javaLevel = julLogger.getLevel(); + if (javaLevel == null) return null; + try { + return PlatformLogger.Level.valueOf(javaLevel.getName()); + } catch (IllegalArgumentException e) { + return PlatformLogger.Level.valueOf(javaLevel.intValue()); + } + } + + @Override + public void setPlatformLevel(PlatformLogger.Level level) { + // null is allowed here + julLogger.setLevel(toJUL(level)); + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + return this; + } + + static java.util.logging.Level toJUL(PlatformLogger.Level level) { + // The caller will throw if null is invalid in its context. + // There's at least one case where a null level is valid. + if (level == null) return null; + assert level.ordinal() < platform2JulLevelMapping.length; + return platform2JulLevelMapping[level.ordinal()]; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JULWrapper) + && obj.getClass() == this.getClass() + && ((JULWrapper)obj).julLogger == this.julLogger; + } + + @Override + public int hashCode() { + return julLogger.hashCode(); + } + + // A JULWrapper is just a stateless thin shell over a JUL logger - so + // for a given JUL logger, we could always return the same wrapper. + // + // This is an optimization which may - or may not - be worth the + // trouble: if many classes use the same logger, and if each class + // keeps a reference to that logger, then caching the wrapper will + // be worthwhile. Otherwise, if each logger is only referred once, + // then the cache will eat up more memory than would be necessary... + // + // Here is an example of how we could implement JULWrapper.of(...) + // if we wanted to create at most one wrapper instance for each logger + // instance: + // + // static final WeakHashMap> + // wrappers = new WeakHashMap<>(); + // + // static JULWrapper of(java.util.logging.Logger logger) { + // + // // First access without synchronizing + // final JULWrapper candidate = new JULWrapper(logger); + // WeakReference ref = wrappers.get(candidate); + // JULWrapper found = ref.get(); + // + // // OK - we found it - lets return it. + // if (found != null) return found; + // + // // Not found. Need to synchronize. + // synchronized (wrappers) { + // ref = wrappers.get(candidate); + // found = ref.get(); + // if (found == null) { + // wrappers.put(candidate, new WeakReference<>(candidate)); + // found = candidate; + // } + // } + // assert found != null; + // return found; + // } + // + // But given that it may end up eating more memory in the nominal case + // (where each class that does logging has its own logger with the + // class name as logger name and stashes that logger away in a static + // field, thus making the cache redundant - as only one wrapper will + // ever be created anyway) - then we will simply return a new wrapper + // for each invocation of JULWrapper.of(...) - which may + // still prove more efficient in terms of memory consumption... + // + static JULWrapper of(java.util.logging.Logger logger) { + return new JULWrapper(logger); + } + + + } + + /** + * Creates a java.util.logging.Logger for the given caller. + * @param name the logger name. + * @param caller the caller for which the logger should be created. + * @return a Logger suitable for use in the given caller. + */ + private static java.util.logging.Logger demandJULLoggerFor(final String name, + /* Module */ + final Class caller) { + final LogManager manager = LogManager.getLogManager(); + final SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return logManagerAccess.demandLoggerFor(manager, name, caller); + } else { + final PrivilegedAction pa = + () -> logManagerAccess.demandLoggerFor(manager, name, caller); + return AccessController.doPrivileged(pa, null, LOGGING_CONTROL_PERMISSION); + } + } + + /** + * {@inheritDoc} + * + * @apiNote The logger returned by this method can be configured through + * its {@linkplain java.util.logging.LogManager#getLogger(String) + * corresponding java.util.logging.Logger backend}. + * + * @return {@inheritDoc} + * @throws SecurityException if the calling code doesn't have the + * {@code RuntimePermission("loggerFinder")}. + */ + @Override + protected Logger demandLoggerFor(String name, /* Module */ Class caller) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return JULWrapper.of(demandJULLoggerFor(name,caller)); + } + + public static interface LogManagerAccess { + java.util.logging.Logger demandLoggerFor(LogManager manager, + String name, /* Module */ Class caller); + } + + // Hook for tests + public static LogManagerAccess getLogManagerAccess() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGING_CONTROL_PERMISSION); + } + // Triggers initialization of accessJulLogger if not set. + if (logManagerAccess == null) LogManager.getLogManager(); + return logManagerAccess; + } + + + private static volatile LogManagerAccess logManagerAccess; + public static void setLogManagerAccess(LogManagerAccess accesLoggers) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGING_CONTROL_PERMISSION); + } + logManagerAccess = accesLoggers; + } + +} diff --git a/jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java b/jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java new file mode 100644 index 00000000000..22f45a70959 --- /dev/null +++ b/jdk/src/java.logging/share/classes/sun/util/logging/internal/package-info.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/** + *

    + * [JDK INTERNAL] + * The {@code sun.util.logging.internal} package defines an internal + * implementation of the {@link jdk.internal.logger.DefaultLoggerFinder} which + * provides an extension of the {@link java.lang.System.Logger System.Logger} + * interface making it easy to bridge from {@link java.util.logging}; + * the JDK default implementation of the LoggerFinder will return loggers + * implementing this extension when {@code java.util.logging} is present. + *

    + *

    + * When {@code java.util.logging} is present, Logger instances returned by + * the JDK default implementation of the LoggerFinder + * wrap an instance of {@link java.util.logging.Logger java.util.logging.Logger} + * and implement the {@link + * sun.util.logging.PlatformLogger.Bridge PlatformLogger.Bridge} + * extension, overriding all the methods defined in + * that extension in order to call the corresponding methods on their wrapped + * {@linkplain java.util.logging.Logger backend Logger} instance. + *

    + *
    + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + * @see sun.util.logging.PlatformLogger + * @see sun.util.logging.PlatformLogger.Bridge + * @see jdk.internal.logger + * + * @since 1.9 + */ +package sun.util.logging.internal; diff --git a/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java b/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java index f265f4c44fe..ba8661c5bcb 100644 --- a/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java +++ b/jdk/src/java.management/share/classes/java/lang/management/DefaultPlatformMBeanProvider.java @@ -355,36 +355,38 @@ class DefaultPlatformMBeanProvider extends PlatformMBeanProvider { } }); - /** - * Logging facility. - */ - initMBeanList.add(new PlatformComponent() { - private final Set platformLoggingMXBeanInterfaceNames + if (ManagementFactoryHelper.isPlatformLoggingMXBeanAvailable()) { + /** + * Logging facility. + */ + initMBeanList.add(new PlatformComponent() { + private final Set platformLoggingMXBeanInterfaceNames = Collections.unmodifiableSet(Collections.singleton( "java.lang.management.PlatformLoggingMXBean")); - @Override - public Set> mbeanInterfaces() { - return Collections.singleton(PlatformLoggingMXBean.class); - } + @Override + public Set> mbeanInterfaces() { + return Collections.singleton(PlatformLoggingMXBean.class); + } - @Override - public Set mbeanInterfaceNames() { - return platformLoggingMXBeanInterfaceNames; - } + @Override + public Set mbeanInterfaceNames() { + return platformLoggingMXBeanInterfaceNames; + } - @Override - public String getObjectNamePattern() { - return "java.util.logging:type=Logging"; - } + @Override + public String getObjectNamePattern() { + return "java.util.logging:type=Logging"; + } - @Override - public Map nameToMBeanMap() { - return Collections.singletonMap( + @Override + public Map nameToMBeanMap() { + return Collections.singletonMap( "java.util.logging:type=Logging", ManagementFactoryHelper.getPlatformLoggingMXBean()); - } - }); + } + }); + } /** * Buffer pools. diff --git a/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java b/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java index 3cc0acb8ffe..22b69401199 100644 --- a/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java +++ b/jdk/src/java.management/share/classes/sun/management/ManagementFactoryHelper.java @@ -39,10 +39,14 @@ import java.security.PrivilegedExceptionAction; import jdk.internal.misc.JavaNioAccess; import jdk.internal.misc.SharedSecrets; -import sun.util.logging.LoggingSupport; + import java.util.ArrayList; import java.util.List; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.PrivilegedAction; + /** * ManagementFactoryHelper provides static factory methods to create * instances of the management interface. @@ -141,13 +145,17 @@ public class ManagementFactoryHelper { } public static PlatformLoggingMXBean getPlatformLoggingMXBean() { - if (LoggingSupport.isAvailable()) { + if (LoggingMXBeanSupport.isAvailable()) { return PlatformLoggingImpl.instance; } else { return null; } } + public static boolean isPlatformLoggingMXBeanAvailable() { + return LoggingMXBeanSupport.isAvailable(); + } + /** * The logging MXBean object is an instance of * PlatformLoggingMXBean and java.util.logging.LoggingMXBean @@ -165,8 +173,44 @@ public class ManagementFactoryHelper { extends PlatformLoggingMXBean, java.util.logging.LoggingMXBean { } + // This is a trick: if java.util.logging is not present then + // attempting to access something that implements + // java.util.logging.LoggingMXBean will trigger a CNFE. + // So we cannot directly call any static method or access any static field + // on PlatformLoggingImpl, as we would risk raising a CNFE. + // Instead we use this intermediate LoggingMXBeanSupport class to determine + // whether java.util.logging is present, and load the actual LoggingMXBean + // implementation. + // + static final class LoggingMXBeanSupport { + final static Object loggingImpl = + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + try { + // create a LoggingProxyImpl instance when + // java.util.logging classes exist + Class c = Class.forName("java.util.logging.Logging", true, null); + Constructor cons = c.getDeclaredConstructor(); + cons.setAccessible(true); + return cons.newInstance(); + } catch (ClassNotFoundException cnf) { + return null; + } catch (NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + }}); + + static boolean isAvailable() { + return loggingImpl != null; + } + } + static class PlatformLoggingImpl implements LoggingMXBean { + final static java.util.logging.LoggingMXBean impl = + (java.util.logging.LoggingMXBean) LoggingMXBeanSupport.loggingImpl; final static PlatformLoggingMXBean instance = new PlatformLoggingImpl(); final static String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; @@ -188,22 +232,22 @@ public class ManagementFactoryHelper { @Override public java.util.List getLoggerNames() { - return LoggingSupport.getLoggerNames(); + return impl.getLoggerNames(); } @Override public String getLoggerLevel(String loggerName) { - return LoggingSupport.getLoggerLevel(loggerName); + return impl.getLoggerLevel(loggerName); } @Override public void setLoggerLevel(String loggerName, String levelName) { - LoggingSupport.setLoggerLevel(loggerName, levelName); + impl.setLoggerLevel(loggerName, levelName); } @Override public String getParentLoggerName(String loggerName) { - return LoggingSupport.getParentLoggerName(loggerName); + return impl.getParentLoggerName(loggerName); } } diff --git a/jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java b/jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java new file mode 100644 index 00000000000..d93d2ee94e0 --- /dev/null +++ b/jdk/test/java/lang/System/Logger/Level/LoggerLevelTest.java @@ -0,0 +1,86 @@ +/* + * 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. + * + * 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.System.Logger.Level; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; +/** + * @test + * @bug 8140364 + * @summary Tests System.Logger.Level names and severity. + * @author danielfuchs + */ +public class LoggerLevelTest { + public static void main(String[] args) { + Set untested = EnumSet.allOf(Level.class); + testLevel(untested, Level.ALL, java.util.logging.Level.ALL); + testLevel(untested, Level.TRACE, java.util.logging.Level.FINER); + testLevel(untested, Level.DEBUG, java.util.logging.Level.FINE); + testLevel(untested, Level.INFO, java.util.logging.Level.INFO); + testLevel(untested, Level.WARNING, java.util.logging.Level.WARNING); + testLevel(untested, Level.ERROR, java.util.logging.Level.SEVERE); + testLevel(untested, Level.OFF, java.util.logging.Level.OFF); + if (!untested.isEmpty()) { + throw new RuntimeException("Some level values were not tested: " + untested); + } + } + + private static void testLevel(Set untested, Level systemLevel, java.util.logging.Level julLevel) { + untested.remove(systemLevel); + assertEquals(systemLevel.getName(), systemLevel.name(), + "System.Logger.Level." + systemLevel.name() + ".getName()"); + assertEquals(systemLevel.getSeverity(), julLevel.intValue(), + "System.Logger.Level." + systemLevel.name() + ".getSeverity"); + } + + private static void assertEquals(Object actual, Object expected, String what) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad value for " + what + + "\n\t expected: " + expected + + "\n\t actual: " + actual); + } else { + System.out.println("Got expected value for " + what + ": " + actual); + } + } + + private static void assertEquals(int actual, int expected, String what) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad value for " + what + + "\n\t expected: " + toString(expected) + + "\n\t actual: " + toString(actual)); + } else { + System.out.println("Got expected value for " + what + ": " + toString(actual)); + } + } + + private static String toString(int value) { + switch (value) { + case Integer.MAX_VALUE: return "Integer.MAX_VALUE"; + case Integer.MIN_VALUE: return "Integer.MIN_VALUE"; + default: + return Integer.toString(value); + } + } + +} diff --git a/jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java b/jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java new file mode 100644 index 00000000000..b25947d64cd --- /dev/null +++ b/jdk/test/java/lang/System/Logger/custom/AccessSystemLogger.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} diff --git a/jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java b/jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java new file mode 100644 index 00000000000..2506905ec7c --- /dev/null +++ b/jdk/test/java/lang/System/Logger/custom/CustomLoggerTest.java @@ -0,0 +1,728 @@ +/* + * 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. + * + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests loggers returned by System.getLogger with a naive implementation + * of LoggerFinder, and in particular the default body of + * System.Logger methods. + * @build CustomLoggerTest AccessSystemLogger + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot CustomLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class CustomLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + + public static class BaseLoggerFinder extends LoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + // changing this to true requires changing the logic in the + // test in order to load this class with a protection domain + // that has the CONTROL_PERMISSION (e.g. by using a custom + // system class loader. + final boolean doChecks = false; + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + } + + @Override + public Logger getLogger(String name, Class caller) { + // We should check the permission to obey the API contract, but + // what happens if we don't? + // This is the main difference compared with what we test in + // java/lang/System/LoggerFinder/BaseLoggerFinderTest + SecurityManager sm = System.getSecurityManager(); + if (sm != null && doChecks) { + sm.checkPermission(SimplePolicy.LOGGERFINDER_PERMISSION); + } + + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl)); + System.setSecurityManager(new SecurityManager()); + } + } + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + // 1. Obtain destination loggers directly from the LoggerFinder + // - LoggerFinder.getLogger("foo", type) + BaseLoggerFinder provider = + BaseLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + BaseLoggerFinder.LoggerImpl appSink = + BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", CustomLoggerTest.class)); + BaseLoggerFinder.LoggerImpl sysSink = + BaseLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(provider, true, appSink, sysSink); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(BaseLoggerFinder provider, boolean hasRequiredPermissions, + BaseLoggerFinder.LoggerImpl appSink, BaseLoggerFinder.LoggerImpl sysSink) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by: + // - System.getLogger("foo") + // - and AccessSystemLogger.getLogger("foo") + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");"); + + Logger sysLogger1 = null; + try { + sysLogger1 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger1)) { + throw new RuntimeException("app logger in system map"); + } + if (provider.user.contains(sysLogger1)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (provider.system.contains(sysLogger1)) { + // sysLogger should be a a LazyLoggerWrapper + throw new RuntimeException("sys logger is in system map (should be wrapped)"); + } + + + // 2. Test loggers returned by: + // - System.getLogger(\"foo\", loggerBundle) + // - and AccessSystemLogger.getLogger(\"foo\", loggerBundle) + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appSink) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + if (provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying BaseLoggerFinder.LoggerImpl + // logger. + private static void testLogger(BaseLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + BaseLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + format, null, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + BaseLoggerFinder.LogEvent expected = + BaseLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + BaseLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowControl) { + this.allowControl = allowControl; + permissions = new Permissions(); + + // these are used for configuring the test itself... + allPermissions = new Permissions(); + allPermissions.add(LOGGERFINDER_PERMISSION); + + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (allowControl.get().get()) return allPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowControl.get().get() + ? allPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowControl.get().get() + ? allPermissions : permissions).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..94ab76c4c88 --- /dev/null +++ b/jdk/test/java/lang/System/Logger/custom/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +CustomLoggerTest$BaseLoggerFinder diff --git a/jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java b/jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java new file mode 100644 index 00000000000..263af31338c --- /dev/null +++ b/jdk/test/java/lang/System/Logger/default/AccessSystemLogger.java @@ -0,0 +1,83 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; +import java.util.logging.LogManager; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + public java.util.logging.Logger demandSystemLogger(String name) { + return java.util.logging.Logger.getLogger(name); + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} diff --git a/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java b/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java new file mode 100644 index 00000000000..128b90f0fc4 --- /dev/null +++ b/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java @@ -0,0 +1,726 @@ +/* + * 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. + * + * 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.security.AccessControlException; +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.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests default loggers returned by System.getLogger, and in + * particular the implementation of the the System.Logger method + * performed by the default binding. + * + * @build DefaultLoggerTest AccessSystemLogger + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class DefaultLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static java.util.logging.Level mapToJul(Level level) { + switch (level) { + case ALL: return java.util.logging.Level.ALL; + case TRACE: return java.util.logging.Level.FINER; + case DEBUG: return java.util.logging.Level.FINE; + case INFO: return java.util.logging.Level.INFO; + case WARNING: return java.util.logging.Level.WARNING; + case ERROR: return java.util.logging.Level.SEVERE; + case OFF: return java.util.logging.Level.OFF; + } + throw new InternalError("No such level: " + level); + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + // 1. Obtain destination loggers directly from the LoggerFinder + // - LoggerFinder.getLogger("foo", type) + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by: + // - System.getLogger("foo") + // - and AccessSystemLogger.getLogger("foo") + Logger sysLogger1 = null; + try { + sysLogger1 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");"); + + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + // 2. Test loggers returned by: + // - System.getLogger(\"foo\", loggerBundle) + // - and AccessSystemLogger.getLogger(\"foo\", loggerBundle) + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + throw new RuntimeException("unexpected exception: " + acx, acx); + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + + final java.util.logging.Logger appSink; + final java.util.logging.Logger sysSink; + final java.util.logging.Handler appHandler; + final java.util.logging.Handler sysHandler; + final LoggerFinder provider; + allowAll.get().set(true); + try { + appSink = java.util.logging.Logger.getLogger("foo"); + sysSink = accessSystemLogger.demandSystemLogger("foo"); + appSink.addHandler(appHandler = new MyHandler()); + sysSink.addHandler(sysHandler = new MyHandler()); + appSink.setUseParentHandlers(false); + sysSink.setUseParentHandlers(false); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowAll.get().set(false); + } + try { + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + } finally { + allowAll.get().set(true); + try { + appSink.removeHandler(appHandler); + sysSink.removeHandler(sysHandler); + sysSink.setLevel(null); + appSink.setLevel(null); + } finally { + allowAll.get().set(false); + } + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying BaseLoggerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + java.util.logging.Logger sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + msg, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooSupplier.get(), + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + format, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), loggerBundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, mapToJul(messageLevel), (ResourceBundle)null, + fooSupplier.get(), + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), bundle, + format, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + for (Level loggerLevel : Level.values()) { + setLevel(sink, mapToJul(loggerLevel)); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, mapToJul(messageLevel), bundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final Permissions permissions; + final Permissions allPermissions; + final Permissions controlPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAll = allowAll; + permissions = new Permissions(); + + // these are used for configuring the test itself... + controlPermissions = new Permissions(); + controlPermissions.add(LOGGERFINDER_PERMISSION); + 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); + if (allowControl.get().get()) return controlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : allowControl.get().get() + ? controlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(allowAll.get().get() + ? allPermissions : allowControl.get().get() + ? controlPermissions : permissions).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java b/jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java new file mode 100644 index 00000000000..727a10f54de --- /dev/null +++ b/jdk/test/java/lang/System/Logger/interface/LoggerInterfaceTest.java @@ -0,0 +1,592 @@ +/* + * 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. + * + * 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.ResourceBundle; +import java.util.function.Consumer; +import java.lang.System.Logger.Level; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Queue; +import java.util.function.Supplier; + +/** + * @test + * @bug 8140364 + * @summary Tests the default body of the System.Logger interface. + * @author danielfuchs + */ +public class LoggerInterfaceTest { + + public static class LoggerImpl implements System.Logger { + + public static class LogEvent implements Cloneable { + Level level; + ResourceBundle bundle; + String msg; + Throwable thrown; + Object[] params; + StackTraceElement[] callStack; + + @Override + protected LogEvent clone() { + try { + return (LogEvent)super.clone(); + } catch (CloneNotSupportedException x) { + throw new RuntimeException(x); + } + } + + + } + + public static class LogEventBuilder { + private LogEvent event = new LogEvent(); + public LogEventBuilder level(Level level) { + event.level = level; + return this; + } + public LogEventBuilder stack(StackTraceElement... stack) { + event.callStack = stack; + return this; + } + public LogEventBuilder bundle(ResourceBundle bundle) { + event.bundle = bundle; + return this; + } + public LogEventBuilder msg(String msg) { + event.msg = msg; + return this; + } + public LogEventBuilder thrown(Throwable thrown) { + event.thrown = thrown; + return this; + } + public LogEventBuilder params(Object... params) { + event.params = params; + return this; + } + public LogEvent build() { + return event.clone(); + } + + public LogEventBuilder clear() { + event = new LogEvent(); + return this; + } + + } + + Level level = Level.WARNING; + Consumer consumer; + final LogEventBuilder builder = new LogEventBuilder(); + + @Override + public String getName() { + return "noname"; + } + + @Override + public boolean isLoggable(Level level) { + return level.getSeverity() >= this.level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) { + builder.clear().level(level).bundle(bundle).msg(msg).thrown(thrown) + .stack(new Exception().getStackTrace()); + consumer.accept(builder.build()); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + builder.clear().level(level).bundle(bundle).msg(format).params(params) + .stack(new Exception().getStackTrace()); + consumer.accept(builder.build()); + } + + } + + static class Throwing { + @Override + public String toString() { + throw new RuntimeException("should not have been called"); + } + } + static class NotTrowing { + private final String toString; + private int count = 0; + public NotTrowing(String toString) { + this.toString = toString; + } + + @Override + public String toString() { + return toString + "[" + (++count) + "]"; + } + } + + public static void main(String[] args) { + final LoggerImpl loggerImpl = new LoggerImpl(); + final System.Logger logger = loggerImpl; + final Queue events = new LinkedList<>(); + loggerImpl.consumer = (x) -> events.add(x); + + System.out.println("\nlogger.isLoggable(Level)"); + assertTrue(logger.isLoggable(Level.WARNING), "logger.isLoggable(Level.WARNING)"," "); + assertFalse(logger.isLoggable(Level.INFO), "logger.isLoggable(Level.INFO)", " "); + + + System.out.println("\nlogger.log(Level, Object)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null}, {"baz"} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + String par1 = msg == null ? "(Object)null" + : logged ? "new NotTrowing(\""+ msg+"\")" : "new Throwing()"; + System.out.println(" logger.log(" + l + ", " + par1 + ")"); + try { + logger.log(l, obj); + if (obj == null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 + ")"); + } + } catch (NullPointerException x) { + if (obj == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + + "new NotThrowing(\"foobar\")" + ")"); + try { + logger.log(null, new NotTrowing("foobar")); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "new NotThrowing(\"foobar\")" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + + System.out.println("\nlogger.log(Level, String)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + String par = "bar"; + System.out.println(" logger.log(" + l + ", \"" + par +"\");"); + logger.log(l, par); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, "bar", "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + System.out.println(" logger.log(" + l + ", (String)null);"); + logger.log(l, (String)null); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, null, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + + System.out.println("\nlogger.log(Level, Supplier)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null}, {"baz"} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + final Supplier s = msg == null ? null : () -> obj.toString(); + String par1 = msg == null ? "(Supplier)null" + : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()"; + System.out.println(" logger.log(" + l + ", " + par1 + ")"); + try { + logger.log(l, s); + if (s == null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 + ")"); + } + } catch (NullPointerException x) { + if (s == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + "() -> \"biz\"" + ")"); + try { + logger.log(null, () -> "biz"); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "() -> \"biz\"" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + System.out.println("\nlogger.log(Level, String, Object...)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + String par = "bam"; + Object[] params = null; + System.out.println(" logger.log(" + l + ", \"" + par +"\", null);"); + logger.log(l, par, params); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, "bam", "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("one")}; + par = "bam {0}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new NotTrowing(\"one\"));"); + logger.log(l, par, params[0]); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("fisrt"), new NotTrowing("second")}; + par = "bam {0} {1}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new NotTrowing(\"fisrt\")," + + " new NotTrowing(\"second\"));"); + logger.log(l, par, params[0], params[1]); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + + params = new Object[] {new NotTrowing("third"), new NotTrowing("fourth")}; + par = "bam {2}"; + System.out.println(" logger.log(" + l + ", \"" + par + + "\", new Object[] {new NotTrowing(\"third\")," + + " new NotTrowing(\"fourth\")});"); + logger.log(l, par, params); + e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertEquals(e.msg, par, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertArrayEquals(e.params, params, "e.params", " "); + assertEquals(e.thrown, null, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + + System.out.println("\nlogger.log(Level, String, Throwable)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + Throwable thrown = (Throwable)p[1]; + String par1 = msg == null ? "(String)null" : "\"" + msg + "\""; + String par2 = thrown == null ? "(Throwable)null" : "new Throwable()"; + System.out.println(" logger.log(" + l + ", " + par1 +", " + par2 + ")"); + logger.log(l, msg, thrown); + LoggerImpl.LogEvent e = events.poll(); + assertNonNull(e, "e", " "); + assertEquals(e.level, l, "e.level", " "); + assertEquals(e.msg, msg, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, thrown, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), + "log", "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } + } + + System.out.println("\nlogger.log(Level, Supplier, Throwable)"); + for (Level l : Level.values()) { + boolean logged = l.compareTo(Level.WARNING) >= 0; + Object[][] cases = new Object[][] { + {null, null}, {null, new Throwable()}, {"biz", null}, {"boz", new Throwable()} + }; + for (Object[] p : cases) { + String msg = (String)p[0]; + Throwable thrown = (Throwable)p[1]; + final Object obj = msg == null ? null : logged ? new NotTrowing(msg) : new Throwing(); + final Supplier s = msg == null ? null : () -> obj.toString(); + String par1 = msg == null ? "(Supplier)null" + : logged ? "() -> new NotTrowing(\""+ msg+"\").toString()" : "new Throwing()"; + String par2 = thrown == null ? "(Throwable)null" : "new Throwable()"; + System.out.println(" logger.log(" + l + ", " + par1 +", " + par2 + ")"); + try { + logger.log(l, s, thrown); + if (s== null) { + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + l + ", " + par1 +", " + par2 + ")"); + } + } catch (NullPointerException x) { + if (s == null) { + System.out.println(" Got expected exception: " + x); + continue; + } else { + throw x; + } + } + LoggerImpl.LogEvent e = events.poll(); + if (logged) { + assertNonNull(e, "e", " "); + assertEquals(l, e.level, "e.level", " "); + assertToString(e.msg, msg, 1, "e.msg", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.params, null, "e.params", " "); + assertEquals(e.thrown, thrown, "e.thrown", " "); + assertEquals(e.bundle, null, "e.bundle", " "); + assertEquals(e.callStack[0].getMethodName(), "log", + "e.callStack[0].getMethodName()", " "); + assertEquals(e.callStack[0].getClassName(), + logger.getClass().getName(), + "e.callStack[0].getClassName() ", " "); + assertEquals(e.callStack[1].getMethodName(), "log", + "e.callStack[1].getMethodName()", " "); + assertEquals(e.callStack[1].getClassName(), + System.Logger.class.getName(), + "e.callStack[1].getClassName() ", " "); + assertEquals(e.callStack[2].getMethodName(), "main", + "e.callStack[2].getMethodName()", " "); + } else { + assertEquals(e, null, "e", " "); + } + } + } + System.out.println(" logger.log(" + null + ", " + "() -> \"biz\"" + + ", " + "new Throwable()" + ")"); + try { + logger.log(null, () -> "biz", new Throwable()); + throw new RuntimeException("Expected NullPointerException not thrown for" + + " logger.log(" + null + ", " + + "() -> \"biz\"" + ", " + + "new Throwable()" + ")"); + } catch (NullPointerException x) { + System.out.println(" Got expected exception: " + x); + } + + System.out.println("Checking that we have no spurious events in the queue"); + assertEquals(events.poll(), null, "events.poll()", " "); + } + + static void assertTrue(boolean test, String what, String prefix) { + if (!test) { + throw new RuntimeException("Expected true for " + what); + } + System.out.println(prefix + "Got expected " + what + ": " + test); + } + static void assertFalse(boolean test, String what, String prefix) { + if (test) { + throw new RuntimeException("Expected false for " + what); + } + System.out.println(prefix + "Got expected " + what + ": " + test); + } + static void assertToString(String actual, String expected, int count, String what, String prefix) { + assertEquals(actual, expected + "["+count+"]", what, prefix); + } + static void assertEquals(Object actual, Object expected, String what, String prefix) { + if (!Objects.equals(actual, expected)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: " + expected + + "\n\t actual: " + actual); + } + System.out.println(prefix + "Got expected " + what + ": " + actual); + } + static void assertArrayEquals(Object[] actual, Object[] expected, String what, String prefix) { + if (!Objects.deepEquals(actual, expected)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: " + expected == null ? "null" : Arrays.deepToString(expected) + + "\n\t actual: " + actual == null ? "null" : Arrays.deepToString(actual)); + } + System.out.println(prefix + "Got expected " + what + ": " + Arrays.deepToString(actual)); + } + static void assertNonNull(Object actual, String what, String prefix) { + if (Objects.equals(actual, null)) { + throw new RuntimeException("Bad " + what + ":" + + "\n\t expected: non null" + + "\n\t actual: " + actual); + } + System.out.println(prefix + "Got expected " + what + ": " + "non null"); + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java new file mode 100644 index 00000000000..b25947d64cd --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/AccessSystemLogger.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java new file mode 100644 index 00000000000..30daa232248 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinder.java @@ -0,0 +1,47 @@ +/* + * 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. + * + * 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.security.AccessController; +import java.security.PrivilegedAction; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; + +public class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java new file mode 100644 index 00000000000..2cb57d78559 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/BaseLoggerFinderTest.java @@ -0,0 +1,694 @@ +/* + * 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. + * + * 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.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * @test + * @bug 8140364 + * @summary Tests a naive implementation of LoggerFinder, and in particular + * the default body of System.Logger methods. + * @build AccessSystemLogger BaseLoggerFinderTest CustomSystemClassLoader BaseLoggerFinder TestLoggerFinder + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + System.out.println("Using provider class: " + providerClass + "[" + providerClass.getClassLoader() + "]"); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + // 1. Test loggers returned by LoggerFinder, both for system callers + // and not system callers. + TestLoggerFinder.LoggerImpl appLogger1 = null; + try { + appLogger1 = + TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class)); + loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger1 = + TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerFinderTest.class)); + loggerDescMap.put(appLogger1, "provider.getLogger(\"foo\", BaseLoggerFinderTest.class)"); + } finally { + allowControl.get().set(old); + } + } + + TestLoggerFinder.LoggerImpl sysLogger1 = null; + try { + sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger1 = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + loggerDescMap.put(sysLogger1, "provider.getLogger(\"foo\", Thread.class)"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger1)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appLogger1)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (provider.user.contains(sysLogger1)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (!provider.system.contains(sysLogger1)) { + throw new RuntimeException("sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appLogger1); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysLogger1); + + // 2. Test localized loggers returned LoggerFinder, both for system + // callers and non system callers + Logger appLogger2 = null; + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, BaseLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, BaseLoggerFinderTest.class)"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + final boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class))"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + if (provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appLogger1); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysLogger1); + + // 3 Test loggers returned by: + // 3.1: System.getLogger("foo") + Logger appLogger3 = System.getLogger("foo"); + loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")"); + testLogger(provider, loggerDescMap, "foo", null, appLogger3, appLogger1); + + // 3.2: System.getLogger("foo") + // Emulate what System.getLogger() does when the caller is a + // platform classes + Logger sysLogger3 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")"); + + if (appLogger3 == sysLogger3) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysLogger1); + + // 4. Test loggers returned by: + // 4.1 System.getLogger("foo", loggerBundle) + Logger appLogger4 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)"); + if (appLogger4 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appLogger1); + + // 4.2: System.getLogger("foo", loggerBundle) + // Emulate what System.getLogger() does when the caller is a + // platform classes + Logger sysLogger4 = accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(sysLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + if (appLogger4 == sysLogger4) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysLogger1); + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + TestLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, format, foo, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, loggerBundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0, + name, messageLevel, (ResourceBundle)null, + fooSupplier.get(), null, + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (provider.eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + format, null, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + messageLevel.compareTo(loggerLevel) >= 0 && loggerLevel != Level.OFF, + name, messageLevel, bundle, + msg, null, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..711771f9690 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/CustomSystemClassLoader.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.equals("BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.equals("BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..fecd6622497 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +BaseLoggerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java new file mode 100644 index 00000000000..716f8b8faec --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/BaseLoggerFinderTest/TestLoggerFinder.java @@ -0,0 +1,181 @@ +/* + * 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. + * + * 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.Arrays; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.Logger; + +/** + * What our test provider needs to implement. + * @author danielfuchs + */ +public interface TestLoggerFinder { + public final static AtomicLong sequencer = new AtomicLong(); + public final ConcurrentHashMap system = new ConcurrentHashMap<>(); + public final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Logger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + + public static LogEvent of(boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Logger.Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + final String name; + Logger.Level level = Logger.Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Logger.Level level) { + return this.level != Logger.Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); +} diff --git a/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java new file mode 100644 index 00000000000..45d3f9d1a0e --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/AccessSystemLogger.java @@ -0,0 +1,82 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + public java.util.logging.Logger demandSystemLogger(String name) { + return java.util.logging.Logger.getLogger(name); + } + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + Path thisClass = Paths.get(classes.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + Path dest = Paths.get(bootDir.toString(), + AccessSystemLogger.class.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java new file mode 100644 index 00000000000..0f7524ec242 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java @@ -0,0 +1,887 @@ +/* + * 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. + * + * 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.security.AccessControlException; +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.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary Tests the default implementation of System.Logger, when + * JUL is the default backend. + * @build AccessSystemLogger DefaultLoggerFinderTest + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class DefaultLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerFinderTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static java.util.logging.Level mapToJul(Level level) { + switch (level) { + case ALL: return java.util.logging.Level.ALL; + case TRACE: return java.util.logging.Level.FINER; + case DEBUG: return java.util.logging.Level.FINE; + case INFO: return java.util.logging.Level.INFO; + case WARNING: return java.util.logging.Level.WARNING; + case ERROR: return java.util.logging.Level.SEVERE; + case OFF: return java.util.logging.Level.OFF; + } + throw new InternalError("No such level: " + level); + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {}, + java.util.logging.Level.FINEST, + new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {}, + java.util.logging.Level.FINER, + new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {}, + java.util.logging.Level.FINE, + new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {}, + java.util.logging.Level.CONFIG, + new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {}, + java.util.logging.Level.INFO, + new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {}, + java.util.logging.Level.WARNING, + new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {}, + java.util.logging.Level.SEVERE, + new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {}, + java.util.logging.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.DEBUG, // FINER_THAN_FINEST + Level.DEBUG, // FINEST + Level.DEBUG, // FINER_THAN_FINER + Level.TRACE, // FINER + Level.TRACE, // FINER_THAN_FINE + Level.DEBUG, // FINE + Level.DEBUG, // FINER_THAN_CONFIG + Level.DEBUG, // CONFIG + Level.DEBUG, // FINER_THAN_INFO + Level.INFO, // INFO + Level.INFO, // FINER_THAN_WARNING + Level.WARNING, // WARNING + Level.WARNING, // FINER_THAN_SEVERE + Level.ERROR, // SEVERE + Level.ERROR, // FATAL + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowAll, allowControl)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + final java.util.logging.Logger appSink = java.util.logging.Logger.getLogger("foo"); + final java.util.logging.Logger sysSink = accessSystemLogger.demandSystemLogger("foo"); + appSink.addHandler(new MyHandler()); + sysSink.addHandler(new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true, appSink, sysSink); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, + boolean hasRequiredPermissions, + java.util.logging.Logger appSink, + java.util.logging.Logger sysSink) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + Logger appLogger1 = null; + try { + appLogger1 = provider.getLogger("foo", DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger1 =provider.getLogger("foo", DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger1, "provider.getApplicationLogger(\"foo\")"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger1 = null; + try { + sysLogger1 = provider.getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger1 = provider.getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, "provider.getSystemLogger(\"foo\")"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger1 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + Logger appLogger2 = null; + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appLogger2 = provider.getLocalizedLogger("foo", loggerBundle, DefaultLoggerFinderTest.class); + loggerDescMap.put(appLogger2, "provider.getLocalizedApplicationLogger(\"foo\", loggerBundle)"); + } finally { + allowControl.get().set(old); + } + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedSystemLogger(\"foo\", loggerBundle)"); + } finally { + allowControl.get().set(old); + } + } + if (appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + + testLogger(provider, loggerDescMap, "foo", null, appLogger1, appSink); + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger2, appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger2, sysSink); + + + Logger appLogger3 = System.getLogger("foo"); + loggerDescMap.put(appLogger3, "System.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, appLogger3, appSink); + + Logger appLogger4 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger4 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, appLogger4, appSink); + + Logger sysLogger3 = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger3, "AccessSystemLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger3, sysSink); + + Logger sysLogger4 = + accessSystemLogger.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger4, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)"); + + if (sysLogger4 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, sysLogger4, sysSink); + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying Logger Impl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + java.util.logging.Logger sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]"); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, foo); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String msg = "blah"; + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + msg, (Throwable)null, (Object[])null); + logger.log(messageLevel, msg); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooSupplier.get(), + (Throwable)null, (Object[])null); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + format, (Throwable)null, new Object[] {arg1, arg2}); + logger.log(messageLevel, format, arg1, arg2); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, loggerBundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, (ResourceBundle)null, + fooSupplier.get(), + (Throwable)thrown, (Object[])null); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, bundle, + format, (Throwable)null, new Object[] {foo, msg}); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (Level messageLevel : Level.values()) { + java.util.logging.Level julLevel = mapToJul(messageLevel); + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + julLevel.intValue() >= loggerLevel.intValue(), + name, julLevel, bundle, + msg, thrown, (Object[]) null); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == OFF || julLevel.intValue() < loggerLevel.intValue()) { + if (eventQueue.poll() != null) { + throw new RuntimeException("unexpected event in queue for " + desc); + } + } else { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + + final Permissions permissions; + final Permissions withControlPermissions; + final Permissions allPermissions; + final ThreadLocal allowAll; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowAll, + ThreadLocal allowControl) { + this.allowAll = allowAll; + this.allowControl = allowControl; + permissions = new Permissions(); + + withControlPermissions = new Permissions(); + withControlPermissions.add(LOGGERFINDER_PERMISSION); + + // 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); + if (allowControl.get().get()) return withControlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java new file mode 100644 index 00000000000..928dc97987e --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/AccessSystemLogger.java @@ -0,0 +1,82 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + static final Class[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class }; + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + for (Class c : toCopy) { + Path thisClass = Paths.get(classes.toString(), + c.getSimpleName()+".class"); + Path dest = Paths.get(bootDir.toString(), + c.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java new file mode 100644 index 00000000000..28deedf6231 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java @@ -0,0 +1,768 @@ +/* + * 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. + * + * 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.PrintStream; +import java.io.UncheckedIOException; +import java.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; +import jdk.internal.logger.DefaultLoggerFinder; +import jdk.internal.logger.SimpleConsoleLogger; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for the base DefaultLoggerFinder. + * Tests the behavior of DefaultLoggerFinder and SimpleConsoleLogger + * implementation. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @build AccessSystemLogger BaseDefaultLoggerFinderTest CustomSystemClassLoader + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseDefaultLoggerFinderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class[] providerClass; + static { + try { + providerClass = new Class[] { + ClassLoader.getSystemClassLoader().loadClass("BaseDefaultLoggerFinderTest$BaseLoggerFinder"), + }; + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + /** + * What our test provider needs to implement. + */ + public static interface TestLoggerFinder { + public final static AtomicBoolean fails = new AtomicBoolean(); + public final static AtomicReference conf = new AtomicReference<>(""); + public final static AtomicLong sequencer = new AtomicLong(); + + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + void setLevel(Logger logger, Level level, Class caller); + void setLevel(Logger logger, PlatformLogger.Level level, Class caller); + PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger); + } + + public static class BaseLoggerFinder extends DefaultLoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder() { + if (fails.get()) { + throw new RuntimeException("Simulate exception while loading provider"); + } + } + + @Override + public void setLevel(Logger logger, Level level, Class caller) { + PrivilegedAction pa = () -> { + setLevel(logger, PlatformLogger.toPlatformLevel(level), caller); + return null; + }; + AccessController.doPrivileged(pa); + } + + @Override + public void setLevel(Logger logger, PlatformLogger.Level level, Class caller) { + PrivilegedAction pa = () -> demandLoggerFor(logger.getName(), caller); + Logger impl = AccessController.doPrivileged(pa); + SimpleConsoleLogger.class.cast(impl) + .getLoggerConfiguration() + .setPlatformLevel(level); + } + + @Override + public PlatformLogger.Bridge asPlatformLoggerBridge(Logger logger) { + PrivilegedAction pa = () -> + PlatformLogger.Bridge.convert(logger); + return AccessController.doPrivileged(pa); + } + + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + static TestLoggerFinder getLoggerFinder(Class expectedClass) { + LoggerFinder provider = null; + try { + TestLoggerFinder.sequencer.incrementAndGet(); + provider = LoggerFinder.getLoggerFinder(); + } catch(AccessControlException a) { + throw a; + } + ErrorStream.errorStream.store(); + System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName()); + expectedClass.cast(provider); + return TestLoggerFinder.class.cast(provider); + } + + + static class ErrorStream extends PrintStream { + + static AtomicBoolean forward = new AtomicBoolean(); + ByteArrayOutputStream out; + String saved = ""; + public ErrorStream(ByteArrayOutputStream out) { + super(out); + this.out = out; + } + + @Override + public void write(int b) { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + if (forward.get()) err.write(buf, off, len); + } + + public String peek() { + flush(); + return out.toString(); + } + + public String drain() { + flush(); + String res = out.toString(); + out.reset(); + return res; + } + + public void store() { + flush(); + saved = out.toString(); + out.reset(); + } + + public void restore() { + out.reset(); + try { + out.write(saved.getBytes()); + } catch(IOException io) { + throw new UncheckedIOException(io); + } + } + + static final PrintStream err = System.err; + static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream()); + } + + private static StringBuilder appendProperty(StringBuilder b, String name) { + String value = System.getProperty(name); + if (value == null) return b; + return b.append(name).append("=").append(value).append('\n'); + } + + public static void main(String[] args) { + if (args.length == 0) { + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + } + Locale.setDefault(Locale.ENGLISH); + System.setErr(ErrorStream.errorStream); + //System.setProperty("jdk.logger.finder.error", "ERROR"); + //System.setProperty("jdk.logger.finder.singleton", "true"); + //System.setProperty("test.fails", "true"); + TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails")); + StringBuilder c = new StringBuilder(); + appendProperty(c, "jdk.logger.packages"); + appendProperty(c, "jdk.logger.finder.error"); + appendProperty(c, "jdk.logger.finder.singleton"); + appendProperty(c, "test.fails"); + TestLoggerFinder.conf.set(c.toString()); + try { + test(args); + } finally { + try { + System.setErr(ErrorStream.err); + } catch (Error | RuntimeException x) { + x.printStackTrace(ErrorStream.err); + } + } + } + + + public static void test(String[] args) { + + final Class expectedClass = jdk.internal.logger.DefaultLoggerFinder.class; + + System.out.println("Declared provider class: " + providerClass[0] + + "[" + providerClass[0].getClassLoader() + "]"); + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + ErrorStream.errorStream.restore(); + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + System.out.println(TestLoggerFinder.conf.get()); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + try { + provider = getLoggerFinder(expectedClass); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass); + if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + provider.getClass().getName()); + } + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + System.Logger sysLogger = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")"); + System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle); + loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)"); + System.Logger appLogger = System.getLogger("bar"); + loggerDescMap.put(appLogger,"System.getLogger(\"bar\")"); + System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle); + loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger, accessSystemLogger.getClass()); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger, accessSystemLogger.getClass()); + testLogger(provider, loggerDescMap, "foo", null, appLogger, BaseDefaultLoggerFinderTest.class); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger, BaseDefaultLoggerFinderTest.class); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger, + Class caller) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + ErrorStream.errorStream.drain(); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooMsg)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooMsg + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + msgText + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format); + String text = java.text.MessageFormat.format(msgFormat, foo, msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + msgText +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get()) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + for (Level loggerLevel : Level.values()) { + provider.setLevel(logger, loggerLevel, caller); + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String textMsg = bundle.getString(msg); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("BaseDefaultLoggerFinderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + textMsg) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] BaseDefaultLoggerFinderTest testLogger\n" + + messageLevel.getName() + " " + textMsg +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + permissions.add(new RuntimePermission("setIO")); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..c948b0b58b1 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/CustomSystemClassLoader.java @@ -0,0 +1,117 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. The CustomSystemClassLoader class must be + * in the BCL, otherwise when system classes - such as + * ZoneDateTime try to load their resource bundle a MissingResourceBundle + * caused by a SecurityException may be thrown, as the CustomSystemClassLoader + * code base will be found in the stack called by doPrivileged. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + final List finderClassNames = + Arrays.asList("BaseDefaultLoggerFinderTest$BaseLoggerFinder"); + final Map> finderClasses = new HashMap<>(); + Class testLoggerFinderClass; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClasses.get(name) != null) return finderClasses.get(name); + if (testLoggerFinderClass == null) { + // Hack: we load testLoggerFinderClass to get its code source. + // we can't use this.getClass() since we are in the boot. + testLoggerFinderClass = super.loadClass("BaseDefaultLoggerFinderTest$TestLoggerFinder"); + } + URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + Class finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + finderClasses.put(name, finderClass); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..843cae617f1 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +BaseDefaultLoggerFinderTest$BaseLoggerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java new file mode 100644 index 00000000000..5319f0e5fbf --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/BaseLoggerBridgeTest.java @@ -0,0 +1,1058 @@ +/* + * 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. + * + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import sun.util.logging.PlatformLogger; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests a naive implementation of System.Logger, and in particular + * the default mapping provided by PlatformLogger.Bridge. + * @modules java.base/sun.util.logging java.base/jdk.internal.logger + * @build CustomSystemClassLoader BaseLoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BaseLoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BaseLoggerBridgeTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + // whether the implementation of Logger try to do a best + // effort for logp... Our base logger finder stub doesn't + // support logp, and thus the logp() implementation comes from + // LoggerWrapper - which does a best effort. + static final boolean BEST_EFFORT_FOR_LOGP = true; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BaseLoggerBridgeTest$BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.TRACE, // FINEST + Level.TRACE, // FINER + Level.DEBUG, // FINE + Level.DEBUG, // CONFIG + Level.INFO, // INFO + Level.WARNING, // WARNING + Level.ERROR, // SEVERE + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + public static interface TestLoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + boolean callSupplier = false; + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray(boolean callSupplier) { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + callSupplier && supplier != null ? supplier.get() : supplier, + msg, + }; + } + + boolean callSupplier(Object obj) { + return callSupplier || ((LogEvent)obj).callSupplier; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray(false)); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(toArray(callSupplier(obj)), ((LogEvent)obj).toArray(callSupplier(obj))); + } + + @Override + public int hashCode() { + return Objects.hash(toArray(true)); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, Throwable thrown, Supplier supplier) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = null; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = null; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + public static LogEvent ofp(boolean callSupplier, LogEvent evt) { + evt.callSupplier = callSupplier; + return evt; + } + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier)); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier)); + } + + + + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static PlatformLogger.Bridge convert(Logger logger) { + boolean old = allowAll.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.Bridge.convert(logger); + } finally { + allowAccess.get().set(old); + } + } + + static Logger getLogger(String name, Class caller) { + boolean old = allowAll.get().get(); + allowAccess.get().set(true); + try { + return jdk.internal.logger.LazyLoggers.getLogger(name, caller); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + // Ugly test hack: preload the resources needed by String.format + // We need to do that before setting the security manager + // because our implementation of CustomSystemClassLoader + // doesn't have the required permission. + System.out.println(String.format("debug: %s", "Setting security manager")); + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + TestLoggerFinder.LoggerImpl appSink = null; + try { + appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class)); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for logger: " + acx); + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", BaseLoggerBridgeTest.class)); + } finally { + allowControl.get().set(old); + } + } + + + TestLoggerFinder.LoggerImpl sysSink = null; + try { + sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + if (hasRequiredPermissions && appSink == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appSink)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appSink)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (hasRequiredPermissions && provider.user.contains(sysSink)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (hasRequiredPermissions && !provider.system.contains(sysSink)) { + throw new RuntimeException("sys logger not in system map"); + } + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")"); + PlatformLogger.Bridge bridge = convert(appLogger1); + loggerDescMap.putIfAbsent(bridge, "PlatformLogger.Bridge.convert(System.getLogger(\"foo\"))"); + testLogger(provider, loggerDescMap, "foo", null, bridge, appSink); + + Logger sysLogger1 = null; + try { + sysLogger1 = getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1, + "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)"); + + if (!hasRequiredPermissions) { + // check that the provider would have thrown an exception + provider.getLogger("foo", Thread.class); + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + if (hasRequiredPermissions) { + // if we don't have permissions sysSink will be null. + testLogger(provider, loggerDescMap, "foo", null, + PlatformLogger.Bridge.convert(sysLogger1), sysSink); + } + + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appLogger2)) { + throw new RuntimeException("localized app logger in system map"); + } + if (provider.user.contains(appLogger2)) { + throw new RuntimeException("localized app logger in appplication map"); + } + + testLogger(provider, loggerDescMap, "foo", loggerBundle, + PlatformLogger.Bridge.convert(appLogger2), appSink); + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && provider.user.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger in appplication map"); + } + if (hasRequiredPermissions && provider.system.contains(sysLogger2)) { + throw new RuntimeException("localized sys logger not in system map"); + } + + if (hasRequiredPermissions) { + // if we don't have permissions sysSink will be null. + testLogger(provider, loggerDescMap, "foo", loggerBundle, + PlatformLogger.Bridge.convert(sysLogger2), sysSink); + } + + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!Objects.equals(expected, actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected, boolean expectNotNull) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static Supplier logpMessage(ResourceBundle bundle, + String className, String methodName, Supplier msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return () -> String.format("[%s %s] %s", cName, mName, msg.get()); + } else { + return msg; + } + } + + static String logpMessage(ResourceBundle bundle, + String className, String methodName, String msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return String.format("[%s %s] %s", cName, mName, msg == null ? "" : msg); + } else { + return msg; + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying TestLoggerFinder.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + TestLoggerFinder.LoggerImpl sink) { + + if (loggerDescMap.get(logger) == null) { + throw new RuntimeException("Test bug: Missing description"); + } + System.out.println("Testing " + loggerDescMap.get(logger) +" [" + logger + "]"); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, (Throwable)null, (Object[])null); + logger.log(messageLevel, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + null, supplier, (Throwable)null, (Object[])null); + logger.log(messageLevel, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + format, null, (Throwable)null, arg1, arg2); + logger.log(messageLevel, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, thrown, (Object[])null); + logger.log(messageLevel, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle)null, + null, supplier, thrown, (Object[])null); + logger.log(messageLevel, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + isLoggable, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg), + null, (Throwable)null, (Object[]) null) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = isLoggable ? + TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP, + TestLoggerFinder.LogEvent.of( + sequencer.get(), + isLoggable, + name, expectedMessageLevel, null, null, + logpMessage(null, sourceClass, sourceMethod, supplier), + (Throwable)null, (Object[]) null)) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, format), + null, (Throwable)null, arg1, arg2) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = + isLoggable || loggerBundle != null && BEST_EFFORT_FOR_LOGP ? + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + logpMessage(loggerBundle, sourceClass, sourceMethod, fooMsg), + null, thrown, (Object[])null) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + boolean isLoggable = loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0; + TestLoggerFinder.LogEvent expected = isLoggable ? + TestLoggerFinder.LogEvent.ofp(BEST_EFFORT_FOR_LOGP, + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, null, null, + logpMessage(null, sourceClass, sourceMethod, supplier), + thrown, (Object[])null)) : null; + logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + format, null, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + fooMsg, null, thrown, (Object[])null); + logger.logrb(messageLevel, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + format, null, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, bundle, + fooMsg, null, thrown, (Object[])null); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..f903b43dba2 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/CustomSystemClassLoader.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..4fc04019eb7 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BaseLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +BaseLoggerBridgeTest$BaseLoggerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java new file mode 100644 index 00000000000..da8c2d73b16 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/BasePlatformLoggerTest.java @@ -0,0 +1,732 @@ +/* + * 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. + * + * 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.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessControlException; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal API. + * Tests a naive implementation of System.Logger, and in particular + * the default mapping provided by PlatformLogger. + * @modules java.base/sun.util.logging + * @build CustomSystemClassLoader BasePlatformLoggerTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader BasePlatformLoggerTest WITHPERMISSIONS + * @author danielfuchs + */ +public class BasePlatformLoggerTest { + + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + providerClass = ClassLoader.getSystemClassLoader().loadClass("BasePlatformLoggerTest$BaseLoggerFinder"); + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static final PlatformLogger.Level[] julLevels = { + PlatformLogger.Level.ALL, + PlatformLogger.Level.FINEST, + PlatformLogger.Level.FINER, + PlatformLogger.Level.FINE, + PlatformLogger.Level.CONFIG, + PlatformLogger.Level.INFO, + PlatformLogger.Level.WARNING, + PlatformLogger.Level.SEVERE, + PlatformLogger.Level.OFF, + }; + + static final Level[] mappedLevels = { + Level.ALL, // ALL + Level.TRACE, // FINEST + Level.TRACE, // FINER + Level.DEBUG, // FINE + Level.DEBUG, // CONFIG + Level.INFO, // INFO + Level.WARNING, // WARNING + Level.ERROR, // SEVERE + Level.OFF, // OFF + }; + + final static Map julToSpiMap; + static { + Map map = new HashMap<>(); + if (mappedLevels.length != julLevels.length) { + throw new ExceptionInInitializerError("Array lengths differ" + + "\n\tjulLevels=" + Arrays.deepToString(julLevels) + + "\n\tmappedLevels=" + Arrays.deepToString(mappedLevels)); + } + for (int i=0; i map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + + public static interface TestLoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + public Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + Supplier supplier; + String msg; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + supplier, + msg, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Throwable thrown) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, Throwable thrown, Supplier supplier) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = null; + evt.bundle = null; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = null; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Object... params) { + LogEvent evt = new LogEvent(); + evt.isLoggable = isLoggable; + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = null; + evt.supplier = null; + evt.msg = key; + return evt; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + Level level, ResourceBundle bundle, + String key, Supplier supplier, + Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.msg = key; + evt.isLoggable = isLoggable; + return evt; + } + + } + + public class LoggerImpl implements Logger { + private final String name; + private Level level = Level.INFO; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != Level.OFF && this.level.getSeverity() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + log(LogEvent.of(isLoggable(level), this.name, level, bundle, key, thrown)); + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + log(LogEvent.of(isLoggable(level), name, level, bundle, format, params)); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, level, null, msgSupplier)); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, level, thrown, msgSupplier)); + } + } + + public Logger getLogger(String name, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static PlatformLogger getPlatformLogger(String name) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.getLogger(name); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + TestLoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = TestLoggerFinder.class.cast(LoggerFinder.getLoggerFinder()); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) { + + final Map loggerDescMap = new HashMap<>(); + + TestLoggerFinder.LoggerImpl appSink; + boolean before = allowControl.get().get(); + try { + allowControl.get().set(true); + appSink = TestLoggerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", BasePlatformLoggerTest.class)); + } finally { + allowControl.get().set(before); + } + + TestLoggerFinder.LoggerImpl sysSink = null; + before = allowControl.get().get(); + try { + allowControl.get().set(true); + sysSink = TestLoggerFinder.LoggerImpl.class.cast(provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(before); + } + + if (hasRequiredPermissions && appSink == sysSink) { + throw new RuntimeException("identical loggers"); + } + + if (provider.system.contains(appSink)) { + throw new RuntimeException("app logger in system map"); + } + if (!provider.user.contains(appSink)) { + throw new RuntimeException("app logger not in appplication map"); + } + if (hasRequiredPermissions && provider.user.contains(sysSink)) { + throw new RuntimeException("sys logger in appplication map"); + } + if (hasRequiredPermissions && !provider.system.contains(sysSink)) { + throw new RuntimeException("sys logger not in system map"); + } + + PlatformLogger platform = getPlatformLogger("foo"); + loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, platform, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(TestLoggerFinder provider, String desc, + TestLoggerFinder.LogEvent expected, boolean expectNotNull) { + TestLoggerFinder.LogEvent actual = provider.eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying TestLoggerFinder.LoggerImpl + // logger. + private static void testLogger(TestLoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + TestLoggerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger)); + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.(fooMsg)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, loggerBundle, + fooMsg, null, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.(msg, thrown)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + fooMsg, null, (Throwable)thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.(format, arg1, arg2)"); + for (Level loggerLevel : Level.values()) { + sink.level = loggerLevel; + for (PlatformLogger.Level messageLevel :julLevels) { + Level expectedMessageLevel = julToSpiMap.get(messageLevel); + TestLoggerFinder.LogEvent expected = + TestLoggerFinder.LogEvent.of( + sequencer.get(), + loggerLevel != Level.OFF && expectedMessageLevel.compareTo(loggerLevel) >= 0, + name, expectedMessageLevel, (ResourceBundle) null, + format, null, (Throwable)null, foo, fooMsg); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == PlatformLogger.Level.FINEST) { + logger.finest(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINER) { + logger.finer(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.FINE) { + logger.fine(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.CONFIG) { + logger.config(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.INFO) { + logger.info(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.WARNING) { + logger.warning(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == PlatformLogger.Level.SEVERE) { + logger.severe(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..f903b43dba2 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/CustomSystemClassLoader.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class finderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClass != null) return finderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$BaseLoggerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..7ce6e966935 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BasePlatformLoggerTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +BasePlatformLoggerTest$BaseLoggerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java new file mode 100644 index 00000000000..4bbfe1e1f10 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/BootstrapLogger/BootstrapLoggerTest.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * 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.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AllPermission; +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.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.internal.logger.BootstrapLogger; +import jdk.internal.logger.LazyLoggers; + +/* + * @test + * @bug 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + Tests the behavior of bootstrap loggers (and SimpleConsoleLoggers + * too). + * @modules java.base/jdk.internal.logger + * @run main/othervm BootstrapLoggerTest NO_SECURITY + * @run main/othervm BootstrapLoggerTest SECURE + * @run main/othervm/timeout=120 BootstrapLoggerTest SECURE_AND_WAIT + */ +public class BootstrapLoggerTest { + + static final Method awaitPending; + static final Method isAlive; + static final Field isBooted; + static final Field logManagerInitialized; + static { + try { + isBooted = BootstrapLogger.class.getDeclaredField("isBooted"); + isBooted.setAccessible(true); + // private reflection hook that allows us to test wait until all + // the tasks pending in the BootstrapExecutor are finished. + awaitPending = BootstrapLogger.class + .getDeclaredMethod("awaitPendingTasks"); + awaitPending.setAccessible(true); + // private reflection hook that allows us to test whether + // the BootstrapExecutor is alive. + isAlive = BootstrapLogger.class + .getDeclaredMethod("isAlive"); + isAlive.setAccessible(true); + // private reflection hook that allows us to test whether the LogManager + // has initialized and registered with the BootstrapLogger class + logManagerInitialized = BootstrapLogger.class + .getDeclaredField("logManagerConfigured"); + logManagerInitialized.setAccessible(true); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static void awaitPending() { + try { + awaitPending.invoke(null); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + ex.printStackTrace(LogStream.err); + } + } + + /** + * We use an instance of this class to check what the logging system has + * printed on System.err. + */ + public static class LogStream extends OutputStream { + + final static PrintStream err = System.err; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + public LogStream() { + super(); + } + + @Override + public synchronized void write(int b) { + baos.write(b); + err.write(b); + } + + public String drain() { + awaitPending(); + synchronized(this) { + String txt = baos.toString(); + baos.reset(); + return txt; + } + } + } + + static enum TestCase { + NO_SECURITY, SECURE, SECURE_AND_WAIT + } + + public static void main(String[] args) throws Exception { + if (args == null || args.length == 0) { + args = new String[] { TestCase.SECURE_AND_WAIT.name() }; + } + if (args.length > 1) throw new RuntimeException("Only one argument allowed"); + TestCase test = TestCase.valueOf(args[0]); + System.err.println("Testing: " + test); + + + // private reflection hook that allows us to simulate a non booted VM + final AtomicBoolean vmBooted = new AtomicBoolean(false); + isBooted.set(null,(BooleanSupplier) () -> vmBooted.get()); + + // We replace System.err to check the messages that have been logged + // by the JUL ConsoleHandler and default SimpleConsoleLogger + // implementaion + final LogStream err = new LogStream(); + System.setErr(new PrintStream(err)); + + if (BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should not be booted!"); + } + Logger logger = LazyLoggers.getLogger("foo.bar", Thread.class); + + if (test != TestCase.NO_SECURITY) { + LogStream.err.println("Setting security manager"); + Policy.setPolicy(new SimplePolicy()); + System.setSecurityManager(new SecurityManager()); + } + + Level[] levels = {Level.INFO, Level.WARNING, Level.INFO}; + int index = 0; + logger.log(levels[index], "Early message #" + (index+1)); index++; + logger.log(levels[index], "Early message #" + (index+1)); index++; + LogStream.err.println("VM Booted: " + vmBooted.get()); + LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null)); + logger.log(levels[index], "Early message #" + (index+1)); index++; + if (err.drain().contains("Early message")) { + // We're expecting that logger will be a LazyLogger wrapping a + // BootstrapLogger. The Bootstrap logger will stack the log messages + // it receives until the VM is booted. + // Since our private hook pretend that the VM is not booted yet, + // the logged messages shouldn't have reached System.err yet. + throw new RuntimeException("Early message logged while VM is not booted!"); + } + + // Now pretend that the VM is booted. Nothing should happen yet, until + // we try to log a new message. + vmBooted.getAndSet(true); + LogStream.err.println("VM Booted: " + vmBooted.get()); + LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null)); + if (!BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should now be booted!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + + // Logging a message should cause the BootstrapLogger to replace itself + // by a 'real' logger in the LazyLogger. But since the LogManager isn't + // initialized yet, this should be a SimpleConsoleLogger... + logger.log(Level.INFO, "LOG#4: VM now booted: {0}", vmBooted.get()); + logger.log(Level.DEBUG, "LOG#5: hi!"); + SimplePolicy.allowAll.set(Boolean.TRUE); + WeakReference threadRef = null; + ReferenceQueue queue = new ReferenceQueue<>(); + try { + Set set = Thread.getAllStackTraces().keySet().stream() + .filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-")) + .collect(Collectors.toSet()); + set.stream().forEach(t -> LogStream.err.println("Found: " + t)); + if (set.size() > 1) { + throw new RuntimeException("Too many bootsrap threads found"); + } + Optional t = set.stream().findFirst(); + if (t.isPresent()) { + threadRef = new WeakReference<>(t.get(), queue); + } + } finally{ + SimplePolicy.allowAll.set(Boolean.FALSE); + } + if (!BootstrapLogger.isBooted()) { + throw new RuntimeException("VM should still be booted!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + + // Now check that the early messages we had printed before the VM was + // booted have appeared on System.err... + String afterBoot = err.drain(); + for (int i=0; i loggerClass = Class.forName("java.util.logging.Logger"); + Class levelClass = Class.forName("java.util.logging.Level"); + Class handlerClass = Class.forName("java.util.logging.Handler"); + + // java.util.logging.Logger.getLogger("foo") + // .setLevel(java.util.logging.Level.FINEST); + Object fooLogger = loggerClass.getMethod("getLogger", String.class) + .invoke(null, "foo"); + loggerClass.getMethod("setLevel", levelClass) + .invoke(fooLogger, levelClass.getField("FINEST").get(null)); + + // java.util.logging.Logger.getLogger("").getHandlers()[0] + // .setLevel(java.util.logging.Level.ALL); + Object rootLogger = loggerClass.getMethod("getLogger", String.class) + .invoke(null, ""); + Object handlers = loggerClass.getMethod("getHandlers"). + invoke(rootLogger); + handlerClass.getMethod("setLevel", levelClass) + .invoke(Array.get(handlers, 0), levelClass.getField("ALL") + .get(null)); + + hasJUL = true; + } catch (ClassNotFoundException x) { + LogStream.err.println("JUL is not present: class " + x.getMessage() + + " not found"); + hasJUL = false; + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + + logger.log(Level.DEBUG, "hi now!"); + String debug = err.drain(); + if (hasJUL) { + if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager should be initialized now!"); + } + if (!debug.contains("FINE: hi now!")) { + throw new RuntimeException("System.err does not contain: " + + "FINE: hi now!"); + } + } else { + if (debug.contains("hi now!")) { + throw new RuntimeException("System.err contains: " + "hi now!"); + } + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + Logger baz = System.getLogger("foo.bar.baz"); + if (((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager shouldn't be initialized yet!"); + } + } + Logger bazbaz = null; + SimplePolicy.allowAll.set(Boolean.TRUE); + try { + bazbaz = java.lang.System.LoggerFinder + .getLoggerFinder().getLogger("foo.bar.baz.baz", BootstrapLoggerTest.class); + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) { + throw new RuntimeException("LogManager should be initialized now!"); + } + Logger bazbaz2 = System.getLogger("foo.bar.baz.baz"); + if (bazbaz2.getClass() != bazbaz.getClass()) { + throw new RuntimeException("bazbaz2.class != bazbaz.class [" + + bazbaz2.getClass() + " != " + + bazbaz.getClass() + "]"); + } + if (hasJUL != bazbaz2.getClass().getName() + .equals("sun.util.logging.internal.LoggingProviderImpl$JULWrapper")) { + throw new RuntimeException("Unexpected class for bazbaz: " + + bazbaz.getClass().getName() + + "\n\t expected: " + + "sun.util.logging.internal.LoggingProviderImpl$JULWrapper"); + } + + // Now we're going to check that the thread of the BootstrapLogger + // executor terminates, and that the Executor is GC'ed after that. + // This will involve a bit of waiting, hence the timeout=120 in + // the @run line. + // If this test fails in timeout - we could envisage skipping this part, + // or adding some System property to configure the keep alive delay + // of the executor. + SimplePolicy.allowAll.set(Boolean.TRUE); + try { + Stream stream = Thread.getAllStackTraces().keySet().stream(); + stream.filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-")) + .forEach(t -> LogStream.err.println(t)); + stream = null; + if (threadRef != null && test == TestCase.SECURE_AND_WAIT) { + Thread t = threadRef.get(); + if (t != null) { + if (!(Boolean)isAlive.invoke(null)) { + throw new RuntimeException("Executor already terminated"); + } else { + LogStream.err.println("Executor still alive as expected."); + } + LogStream.err.println("Waiting for " + t.getName() + " to terminate (join)"); + t.join(60_000); + t = null; + } + LogStream.err.println("Calling System.gc()"); + System.gc(); + LogStream.err.println("Waiting for BootstrapMessageLoggerTask to be gc'ed"); + while (queue.remove(1000) == null) { + LogStream.err.println("Calling System.gc()"); + System.gc(); + } + + // Call the reference here to make sure threadRef will not be + // eagerly garbage collected before the thread it references. + // otherwise, it might not be enqueued, resulting in the + // queue.remove() call above to always return null.... + if (threadRef.get() != null) { + throw new RuntimeException("Reference should have been cleared"); + } + + LogStream.err.println("BootstrapMessageLoggerTask has been gc'ed"); + // Wait for the executor to be gc'ed... + for (int i=0; i<10; i++) { + LogStream.err.println("Calling System.gc()"); + System.gc(); + if (!(Boolean)isAlive.invoke(null)) break; + // It would be unexpected that we reach here... + Thread.sleep(1000); + } + + if ((Boolean)isAlive.invoke(null)) { + throw new RuntimeException("Executor still alive"); + } else { + LogStream.err.println("Executor terminated as expected."); + } + } else { + LogStream.err.println("Not checking executor termination for " + test); + } + } finally { + SimplePolicy.allowAll.set(Boolean.FALSE); + } + LogStream.err.println(test.name() + ": PASSED"); + } + + final static class SimplePolicy extends Policy { + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + Permissions getPermissions() { + Permissions perms = new Permissions(); + if (allowAll.get()) { + perms.add(new AllPermission()); + } + return perms; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions(domain).implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return getPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return getPermissions(); + } + + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..0c68ff89e93 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/CustomSystemClassLoader.java @@ -0,0 +1,141 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class loggerFinderClass = null; +// Class loggerImplClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (loggerFinderClass != null) return loggerFinderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + loggerFinderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return loggerFinderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } +// private Class defineLoggerImplClass(String name) +// throws ClassNotFoundException { +// final Object obj = getClassLoadingLock(name); +// synchronized(obj) { +// if (loggerImplClass != null) return loggerImplClass; +// +// URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); +// File file = new File(url.getPath(), name+".class"); +// if (file.canRead()) { +// try { +// byte[] b = Files.readAllBytes(file.toPath()); +// Permissions perms = new Permissions(); +// perms.add(new AllPermission()); +// loggerImplClass = defineClass( +// name, b, 0, b.length, new ProtectionDomain( +// this.getClass().getProtectionDomain().getCodeSource(), +// perms)); +// System.out.println("Loaded " + name); +// return loggerImplClass; +// } catch (Throwable ex) { +// ex.printStackTrace(); +// throw new ClassNotFoundException(name, ex); +// } +// } else { +// throw new ClassNotFoundException(name, +// new IOException(file.toPath() + ": can't read")); +// } +// } +// } +// + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$LogProducerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } +// if (name.endsWith("$LogProducerFinder$LoggerImpl")) { +// Class c = defineLoggerImplClass(name); +// if (resolve) { +// resolveClass(c); +// } +// return c; +// } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { +// if (name.endsWith("$LogProducerFinder$LoggerImpl")) { +// return defineLoggerImplClass(name); +// } + if (name.endsWith("$$LogProducerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java new file mode 100644 index 00000000000..10e480fc363 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/LoggerBridgeTest.java @@ -0,0 +1,1087 @@ +/* + * 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. + * + * 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.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all bridge methods with the a custom backend whose + * loggers implement PlatformLogger.Bridge. + * @modules java.base/sun.util.logging java.base/jdk.internal.logger + * @build CustomSystemClassLoader LoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader LoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class LoggerBridgeTest { + + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + sun.util.logging.PlatformLogger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + Supplier supplier; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + loggerName, + level, + isLoggable, + bundle, + msg, + supplier, + thrown, + args, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, supplier, + thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, key, thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, supplier, thrown, params); + } + + } + static final Class providerClass; + static { + try { + // Preload classes before the security manager is on. + providerClass = ClassLoader.getSystemClassLoader().loadClass("LoggerBridgeTest$LogProducerFinder"); + ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static class LogProducerFinder extends LoggerFinder { + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements Logger, PlatformLogger.Bridge { + private final String name; + private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE; + private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER; + private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST; + private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG; + private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING; + private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != OFF && this.level.intValue() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + throw new UnsupportedOperationException(); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, params)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, params)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return this.level != OFF && level.intValue() + >= this.level.intValue(); + } + + @Override + public boolean isEnabled() { + return this.level != OFF; + } + + } + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + PlatformLogger.Level.valueOf(record.getLevel().getName()), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + final static Method lazyGetLogger; + static { + // jdk.internal.logging.LoggerBridge.getLogger(name, caller) + try { + Class bridgeClass = Class.forName("jdk.internal.logger.LazyLoggers"); + lazyGetLogger = bridgeClass.getDeclaredMethod("getLogger", + String.class, Class.class); + lazyGetLogger.setAccessible(true); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static Logger getLogger(LoggerFinder provider, String name, Class caller) { + Logger logger; + try { + logger = Logger.class.cast(lazyGetLogger.invoke(null, name, caller)); + } catch (Throwable x) { + Throwable t = (x instanceof InvocationTargetException) ? + ((InvocationTargetException)x).getTargetException() : x; + if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } else if (t instanceof Exception) { + throw new RuntimeException(t); + } else { + throw (Error)t; + } + } + // The method above does not throw exception... + // call the provider here to verify that an exception would have + // been thrown by the provider. + if (logger != null && caller == Thread.class) { + Logger log = provider.getLogger(name, caller); + } + return logger; + } + + static Logger getLogger(LoggerFinder provider, String name, ResourceBundle bundle, Class caller) { + if (caller.getClassLoader() != null) { + return System.getLogger(name,bundle); + } else { + return provider.getLocalizedLogger(name, bundle, caller); + } + } + + static PlatformLogger.Bridge convert(Logger logger) { + return PlatformLogger.Bridge.convert(logger); + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "LogProducer.getApplicationLogger(\"foo\")"); + + Logger sysLogger1 = null; + try { + sysLogger1 = getLogger(provider, "foo", Thread.class); + loggerDescMap.put(sysLogger1, "LogProducer.getSystemLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + + Logger appLogger2 = + System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "LogProducer.getApplicationLogger(\"foo\", loggerBundle)"); + + Logger sysLogger2 = null; + try { + sysLogger2 = getLogger(provider, "foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getSystemLogger(\"foo\", loggerBundle)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1) { + throw new RuntimeException("identical loggers"); + } + + + final LogProducerFinder.LoggerImpl appSink; + final LogProducerFinder.LoggerImpl sysSink; + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + appSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", LoggerBridgeTest.class)); + sysSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(old); + } + + testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink); + if (hasRequiredPermissions) { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1), sysSink); + } + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink); + if (hasRequiredPermissions) { + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink); + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel( LogProducerFinder.LoggerImpl sink, + sun.util.logging.PlatformLogger.Level loggerLevel) { + sink.level = loggerLevel; + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying LogProducerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + LogProducerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + "[" + logger + "]"); + final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.log(messageLevel, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier, (Throwable)null, (Object[])null); + logger.log(messageLevel, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.log(messageLevel, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.log(messageLevel, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier, thrown, (Object[])null); + logger.log(messageLevel, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier, (Throwable)null, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier, thrown, (Object[])null); + logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(messageLevel, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected); + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..8abf57e2bac --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +LoggerBridgeTest$LogProducerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java new file mode 100644 index 00000000000..928dc97987e --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/AccessSystemLogger.java @@ -0,0 +1,82 @@ +/* + * 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. + * + * 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.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; + +/** + * + * @author danielfuchs + */ +public final class AccessSystemLogger { + + public AccessSystemLogger() { + this(check()); + } + + private AccessSystemLogger(Void unused) { + } + + private static Void check() { + if (AccessSystemLogger.class.getClassLoader() != null) { + throw new RuntimeException("AccessSystemLogger should be loaded by the null classloader"); + } + return null; + } + + public Logger getLogger(String name) { + Logger logger = System.getLogger(name); + System.out.println("System.getLogger(\"" + name + "\"): " + logger); + return logger; + } + + public Logger getLogger(String name, ResourceBundle bundle) { + Logger logger = System.getLogger(name, bundle); + System.out.println("System.getLogger(\"" + name + "\", bundle): " + logger); + return logger; + } + + static final Class[] toCopy = { AccessSystemLogger.class, CustomSystemClassLoader.class }; + + // copy AccessSystemLogger.class to ./boot + public static void main(String[] args) throws IOException { + Path testDir = Paths.get(System.getProperty("user.dir", ".")); + Path bootDir = Paths.get(testDir.toString(), "boot"); + Path classes = Paths.get(System.getProperty("test.classes", "build/classes")); + if (Files.notExists(bootDir)) { + Files.createDirectory(bootDir); + } + for (Class c : toCopy) { + Path thisClass = Paths.get(classes.toString(), + c.getSimpleName()+".class"); + Path dest = Paths.get(bootDir.toString(), + c.getSimpleName()+".class"); + Files.copy(thisClass, dest, StandardCopyOption.REPLACE_EXISTING); + } + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..7e3db8b7dab --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/CustomSystemClassLoader.java @@ -0,0 +1,118 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. The CustomSystemClassLoader class must be + * in the BCL, otherwise when system classes - such as + * ZoneDateTime try to load their resource bundle a MissingResourceBundle + * caused by a SecurityException may be thrown, as the CustomSystemClassLoader + * code base will be found in the stack called by doPrivileged. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + final List finderClassNames = + Arrays.asList("LoggerFinderLoaderTest$BaseLoggerFinder", + "LoggerFinderLoaderTest$BaseLoggerFinder2"); + final Map> finderClasses = new HashMap<>(); + Class testLoggerFinderClass; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (finderClasses.get(name) != null) return finderClasses.get(name); + if (testLoggerFinderClass == null) { + // Hack: we load testLoggerFinderClass to get its code source. + // we can't use this.getClass() since we are in the boot. + testLoggerFinderClass = super.loadClass("LoggerFinderLoaderTest$TestLoggerFinder"); + } + URL url = testLoggerFinderClass.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + Class finderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + finderClasses.put(name, finderClass); + return finderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (finderClassNames.contains(name)) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java new file mode 100644 index 00000000000..3756956edb8 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/LoggerFinderLoaderTest.java @@ -0,0 +1,882 @@ +/* + * 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. + * + * 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.PrintStream; +import java.io.UncheckedIOException; +import java.security.AccessControlException; +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.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.stream.Stream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import jdk.internal.logger.SimpleConsoleLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for LoggerFinderLoader. + * Tests the behavior of LoggerFinderLoader with respect to the + * value of the internal diagnosability switches. Also test the + * DefaultLoggerFinder and SimpleConsoleLogger implementation. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @build AccessSystemLogger LoggerFinderLoaderTest CustomSystemClassLoader + * @run driver AccessSystemLogger + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Dtest.fails=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=ERROR LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=DEBUG LoggerFinderLoaderTest WITHPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOSECURITY + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest NOPERMISSIONS + * @run main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader -Djdk.logger.finder.singleton=true -Djdk.logger.finder.error=QUIET LoggerFinderLoaderTest WITHPERMISSIONS + * @author danielfuchs + */ +public class LoggerFinderLoaderTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + final static AccessSystemLogger accessSystemLogger = new AccessSystemLogger(); + static final Class[] providerClass; + static { + try { + providerClass = new Class[] { + ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder"), + ClassLoader.getSystemClassLoader().loadClass("LoggerFinderLoaderTest$BaseLoggerFinder2") + }; + } catch (ClassNotFoundException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + /** + * What our test provider needs to implement. + */ + public static interface TestLoggerFinder { + public final static AtomicBoolean fails = new AtomicBoolean(); + public final static AtomicReference conf = new AtomicReference<>(""); + public final static AtomicLong sequencer = new AtomicLong(); + public final ConcurrentHashMap system = new ConcurrentHashMap<>(); + public final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements System.Logger { + final String name; + final Logger logger; + + public LoggerImpl(String name, Logger logger) { + this.name = name; + this.logger = logger; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Logger.Level level) { + return logger.isLoggable(level); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + logger.log(level, bundle, key, thrown); + } + + @Override + public void log(Logger.Level level, ResourceBundle bundle, String format, Object... params) { + logger.log(level, bundle, format, params); + } + + } + + public Logger getLogger(String name, Class caller); + public Logger getLocalizedLogger(String name, ResourceBundle bundle, Class caller); + } + + public static class BaseLoggerFinder extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder() { + if (fails.get()) { + throw new RuntimeException("Simulate exception while loading provider"); + } + } + + System.Logger createSimpleLogger(String name) { + PrivilegedAction pa = () -> SimpleConsoleLogger.makeSimpleLogger(name, false); + return AccessController.doPrivileged(pa); + } + + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name))); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n, createSimpleLogger(name))); + } + } + } + + public static class BaseLoggerFinder2 extends LoggerFinder implements TestLoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + public BaseLoggerFinder2() { + throw new ServiceConfigurationError("Should not come here"); + } + @Override + public Logger getLogger(String name, Class caller) { + throw new ServiceConfigurationError("Should not come here"); + } + } + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess)); + System.setSecurityManager(new SecurityManager()); + } + } + + static LoggerFinder getLoggerFinder(Class expectedClass, + String errorPolicy, boolean singleton) { + LoggerFinder provider = null; + try { + TestLoggerFinder.sequencer.incrementAndGet(); + provider = LoggerFinder.getLoggerFinder(); + if (TestLoggerFinder.fails.get() || singleton) { + if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + throw new RuntimeException("Expected exception not thrown"); + } else if ("WARNING".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + String warning = ErrorStream.errorStream.peek(); + if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } else if ("DEBUG".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + String warning = ErrorStream.errorStream.peek(); + if (!warning.contains("WARNING: Failed to instantiate LoggerFinder provider; Using default.")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + if (!warning.contains("WARNING: Exception raised trying to instantiate LoggerFinder")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + if (TestLoggerFinder.fails.get()) { + if (!warning.contains("java.util.ServiceConfigurationError: java.lang.System$LoggerFinder: Provider LoggerFinderLoaderTest$BaseLoggerFinder could not be instantiated")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } else if (singleton) { + if (!warning.contains("java.util.ServiceConfigurationError: More than on LoggerFinder implementation")) { + throw new RuntimeException("Expected message not found. Error stream contained: " + warning); + } + } + } else if ("QUIET".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("Unexpected error message found: " + + ErrorStream.errorStream.peek()); + } + } + } + } catch(AccessControlException a) { + throw a; + } catch(Throwable t) { + if (TestLoggerFinder.fails.get() || singleton) { + // must check System.err + if ("ERROR".equals(errorPolicy.toUpperCase(Locale.ROOT))) { + provider = LoggerFinder.getLoggerFinder(); + } else { + Throwable orig = t.getCause(); + while (orig != null && orig.getCause() != null) orig = orig.getCause(); + if (orig != null) orig.printStackTrace(ErrorStream.err); + throw new RuntimeException("Unexpected exception: " + t, t); + } + } else { + throw new RuntimeException("Unexpected exception: " + t, t); + } + } + expectedClass.cast(provider); + ErrorStream.errorStream.store(); + System.out.println("*** Actual LoggerFinder class is: " + provider.getClass().getName()); + return provider; + } + + + static class ErrorStream extends PrintStream { + + static AtomicBoolean forward = new AtomicBoolean(); + ByteArrayOutputStream out; + String saved = ""; + public ErrorStream(ByteArrayOutputStream out) { + super(out); + this.out = out; + } + + @Override + public void write(int b) { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + super.write(b); + if (forward.get()) err.write(b); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + if (forward.get()) err.write(buf, off, len); + } + + public String peek() { + flush(); + return out.toString(); + } + + public String drain() { + flush(); + String res = out.toString(); + out.reset(); + return res; + } + + public void store() { + flush(); + saved = out.toString(); + out.reset(); + } + + public void restore() { + out.reset(); + try { + out.write(saved.getBytes()); + } catch(IOException io) { + throw new UncheckedIOException(io); + } + } + + static final PrintStream err = System.err; + static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream()); + } + + private static StringBuilder appendProperty(StringBuilder b, String name) { + String value = System.getProperty(name); + if (value == null) return b; + return b.append(name).append("=").append(value).append('\n'); + } + + public static void main(String[] args) { + if (args.length == 0) { + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + } + Locale.setDefault(Locale.ENGLISH); + System.setErr(ErrorStream.errorStream); + System.setProperty("jdk.logger.packages", TestLoggerFinder.LoggerImpl.class.getName()); + //System.setProperty("jdk.logger.finder.error", "ERROR"); + //System.setProperty("jdk.logger.finder.singleton", "true"); + //System.setProperty("test.fails", "true"); + TestLoggerFinder.fails.set(Boolean.getBoolean("test.fails")); + StringBuilder c = new StringBuilder(); + appendProperty(c, "jdk.logger.packages"); + appendProperty(c, "jdk.logger.finder.error"); + appendProperty(c, "jdk.logger.finder.singleton"); + appendProperty(c, "test.fails"); + TestLoggerFinder.conf.set(c.toString()); + try { + test(args); + } finally { + try { + System.setErr(ErrorStream.err); + } catch (Error | RuntimeException x) { + x.printStackTrace(ErrorStream.err); + } + } + } + + + public static void test(String[] args) { + + final String errorPolicy = System.getProperty("jdk.logger.finder.error", "WARNING"); + final Boolean ensureSingleton = Boolean.getBoolean("jdk.logger.finder.singleton"); + + final Class expectedClass = + TestLoggerFinder.fails.get() || ensureSingleton + ? jdk.internal.logger.DefaultLoggerFinder.class + : TestLoggerFinder.class; + + System.out.println("Declared provider class: " + providerClass[0] + + "[" + providerClass[0].getClassLoader() + "]"); + + if (!TestLoggerFinder.fails.get()) { + ServiceLoader serviceLoader = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()); + Iterator iterator = serviceLoader.iterator(); + Object firstProvider = iterator.next(); + if (!firstProvider.getClass().getName().equals("LoggerFinderLoaderTest$BaseLoggerFinder")) { + throw new RuntimeException("Unexpected provider: " + firstProvider.getClass().getName()); + } + if (!iterator.hasNext()) { + throw new RuntimeException("Expected two providers"); + } + } + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + ErrorStream.errorStream.restore(); + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + System.out.println(TestLoggerFinder.conf.get()); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + test(provider, true); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + try { + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + TestLoggerFinder.sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + System.out.println(TestLoggerFinder.conf.get()); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = getLoggerFinder(expectedClass, errorPolicy, ensureSingleton); + test(provider, true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + TestLoggerFinder.sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + System.Logger sysLogger = accessSystemLogger.getLogger("foo"); + loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")"); + System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle); + loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)"); + System.Logger appLogger = System.getLogger("bar"); + loggerDescMap.put(appLogger,"System.getLogger(\"bar\")"); + System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle); + loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)"); + + testLogger(provider, loggerDescMap, "foo", null, sysLogger); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedSysLogger); + testLogger(provider, loggerDescMap, "foo", null, appLogger); + testLogger(provider, loggerDescMap, "foo", loggerBundle, localizedAppLogger); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + // Calls the 8 methods defined on Logger and verify the + // parameters received by the underlying TestProvider.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + Logger logger) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + AtomicLong sequencer = TestLoggerFinder.sequencer; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + ErrorStream.errorStream.drain(); + String desc = "logger.log(messageLevel, foo): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, foo); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooMsg)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooMsg + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + String msg = "blah"; + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\"): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + msgText + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Supplier fooSupplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get())) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = msg; + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String msgFormat = loggerBundle == null ? format : loggerBundle.getString(format); + String text = java.text.MessageFormat.format(msgFormat, foo, msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + String msgText = loggerBundle == null ? msg : loggerBundle.getString(msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + msgText) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + msgText +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, fooSupplier, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + fooSupplier.get()) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + fooSupplier.get() +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, format, foo, msg); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + text + + "\n>>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + for (Level loggerLevel : EnumSet.of(Level.INFO)) { + for (Level messageLevel : Level.values()) { + String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + sequencer.incrementAndGet(); + logger.log(messageLevel, bundle, msg, thrown); + if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) { + if (!ErrorStream.errorStream.peek().isEmpty()) { + throw new RuntimeException("unexpected event in queue for " + + desc +": " + "\n\t" + ErrorStream.errorStream.drain()); + } + } else { + String logged = ErrorStream.errorStream.drain(); + String textMsg = bundle.getString(msg); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thrown.printStackTrace(new PrintStream(baos)); + String text = baos.toString(); + if (!logged.contains("LoggerFinderLoaderTest testLogger") + || !logged.contains(messageLevel.getName() + ": " + textMsg) + || !logged.contains(text)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected:" + "\n<<<<\n" + + "[date] LoggerFinderLoaderTest testLogger\n" + + messageLevel.getName() + " " + textMsg +"\n" + + text + + ">>>>" + + "\n\t actual:" + + "\n<<<<\n" + logged + ">>>>\n"); + } else { + verbose("Got expected results for " + + desc + "\n<<<<\n" + logged + ">>>>\n"); + } + } + } + } + + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + + final Permissions permissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + public SimplePolicy(ThreadLocal allowControl, ThreadLocal allowAccess) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + permissions = new Permissions(); + permissions.add(new RuntimePermission("setIO")); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..39a1084640c --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/LoggerFinderLoaderTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1,3 @@ +LoggerFinderLoaderTest$BaseLoggerFinder +LoggerFinderLoaderTest$BaseLoggerFinder2 + diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java new file mode 100644 index 00000000000..b0730fb0828 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/CustomSystemClassLoader.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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 java.net.URL; +import java.nio.file.Files; +import java.security.AllPermission; +import java.security.Permissions; +import java.security.ProtectionDomain; + + +/** + * A custom ClassLoader to load the concrete LoggerFinder class + * with all permissions. + * + * @author danielfuchs + */ +public class CustomSystemClassLoader extends ClassLoader { + + + Class loggerFinderClass = null; + + public CustomSystemClassLoader() { + super(); + } + public CustomSystemClassLoader(ClassLoader parent) { + super(parent); + } + + private Class defineFinderClass(String name) + throws ClassNotFoundException { + final Object obj = getClassLoadingLock(name); + synchronized(obj) { + if (loggerFinderClass != null) return loggerFinderClass; + + URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation(); + File file = new File(url.getPath(), name+".class"); + if (file.canRead()) { + try { + byte[] b = Files.readAllBytes(file.toPath()); + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + loggerFinderClass = defineClass( + name, b, 0, b.length, new ProtectionDomain( + this.getClass().getProtectionDomain().getCodeSource(), + perms)); + System.out.println("Loaded " + name); + return loggerFinderClass; + } catch (Throwable ex) { + ex.printStackTrace(); + throw new ClassNotFoundException(name, ex); + } + } else { + throw new ClassNotFoundException(name, + new IOException(file.toPath() + ": can't read")); + } + } + } + + @Override + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.endsWith("$LogProducerFinder")) { + Class c = defineFinderClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("$$LogProducerFinder")) { + return defineFinderClass(name); + } + return super.findClass(name); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..a686921e3be --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1 @@ +PlatformLoggerBridgeTest$LogProducerFinder diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java new file mode 100644 index 00000000000..f9b8eabba40 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/PlatformLoggerBridgeTest/PlatformLoggerBridgeTest.java @@ -0,0 +1,876 @@ +/* + * 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. + * + * 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.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all bridge methods from PlatformLogger with the a custom + * backend whose loggers implement PlatformLogger.Bridge. + * @modules java.base/sun.util.logging + * @build CustomSystemClassLoader PlatformLoggerBridgeTest + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOSECURITY + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest NOPERMISSIONS + * @run main/othervm -Djava.system.class.loader=CustomSystemClassLoader PlatformLoggerBridgeTest WITHPERMISSIONS + * @author danielfuchs + */ +public class PlatformLoggerBridgeTest { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + static final Class providerClass; + static { + try { + // Preload classes before the security manager is on. + providerClass = ClassLoader.getSystemClassLoader().loadClass("PlatformLoggerBridgeTest$LogProducerFinder"); + ((LoggerFinder)providerClass.newInstance()).getLogger("foo", providerClass); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + sun.util.logging.PlatformLogger.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + Supplier supplier; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + loggerName, + level, + isLoggable, + bundle, + msg, + supplier, + thrown, + args, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + null, null, level, bundle, supplier, + thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, key, thrown, params); + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.supplier = supplier; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + public static LogEvent of(boolean isLoggable, String name, + String className, String methodName, + sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + Supplier supplier, Throwable thrown, Object... params) { + return LogEvent.of(sequencer.getAndIncrement(), isLoggable, name, + className, methodName, level, bundle, supplier, thrown, params); + } + + } + + public static class LogProducerFinder extends LoggerFinder { + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final ConcurrentHashMap system = new ConcurrentHashMap<>(); + final ConcurrentHashMap user = new ConcurrentHashMap<>(); + + public class LoggerImpl implements Logger, PlatformLogger.Bridge { + private final String name; + private sun.util.logging.PlatformLogger.Level level = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + private sun.util.logging.PlatformLogger.Level FINE = sun.util.logging.PlatformLogger.Level.FINE; + private sun.util.logging.PlatformLogger.Level FINER = sun.util.logging.PlatformLogger.Level.FINER; + private sun.util.logging.PlatformLogger.Level FINEST = sun.util.logging.PlatformLogger.Level.FINEST; + private sun.util.logging.PlatformLogger.Level CONFIG = sun.util.logging.PlatformLogger.Level.CONFIG; + private sun.util.logging.PlatformLogger.Level INFO = sun.util.logging.PlatformLogger.Level.INFO; + private sun.util.logging.PlatformLogger.Level WARNING = sun.util.logging.PlatformLogger.Level.WARNING; + private sun.util.logging.PlatformLogger.Level SEVERE = sun.util.logging.PlatformLogger.Level.SEVERE; + + public LoggerImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLoggable(Level level) { + return this.level != OFF && this.level.intValue() <= level.getSeverity(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + throw new UnsupportedOperationException(); + } + + void log(LogEvent event) { + eventQueue.add(event); + } + + @Override + public void log(Level level, Supplier msgSupplier) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(Level level, Supplier msgSupplier, + Throwable thrown) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, null, params)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void log(sun.util.logging.PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, null, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, null, params)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msg, thrown, (Object[])null)); + } + + @Override + public void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, null, msgSupplier, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, null, params)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, + sourceClass, sourceMethod, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public void logrb(sun.util.logging.PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + log(LogEvent.of(isLoggable(level), name, null, null, + level, bundle, msg, thrown, (Object[])null)); + } + + @Override + public boolean isLoggable(sun.util.logging.PlatformLogger.Level level) { + return this.level != OFF && level.intValue() + >= this.level.intValue(); + } + + @Override + public boolean isEnabled() { + return this.level != OFF; + } + + + } + + @Override + public Logger getLogger(String name, Class caller) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + PrivilegedAction pa = () -> caller.getClassLoader(); + ClassLoader callerLoader = AccessController.doPrivileged(pa); + if (callerLoader == null) { + return system.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } else { + return user.computeIfAbsent(name, (n) -> new LoggerImpl(n)); + } + } + } + + static final sun.util.logging.PlatformLogger.Level[] julLevels = { + sun.util.logging.PlatformLogger.Level.ALL, + sun.util.logging.PlatformLogger.Level.FINEST, + sun.util.logging.PlatformLogger.Level.FINER, + sun.util.logging.PlatformLogger.Level.FINE, + sun.util.logging.PlatformLogger.Level.CONFIG, + sun.util.logging.PlatformLogger.Level.INFO, + sun.util.logging.PlatformLogger.Level.WARNING, + sun.util.logging.PlatformLogger.Level.SEVERE, + sun.util.logging.PlatformLogger.Level.OFF, + }; + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + PlatformLogger.Level.valueOf(record.getLevel().getName()), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + //"NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + provider = LoggerFinder.getLoggerFinder(); + test(provider, true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + try { + provider = LoggerFinder.getLoggerFinder(); + throw new RuntimeException("Expected exception not raised"); + } catch (AccessControlException x) { + if (!LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission check", x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + test(provider, false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with access permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + final boolean access = allowAccess.get().get(); + try { + allowAccess.get().set(true); + test(provider, true); + } finally { + allowAccess.get().set(access); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions) { + + final Map loggerDescMap = new HashMap<>(); + + PlatformLogger sysLogger1 = null; + try { + sysLogger1 = PlatformLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.ACCESS_LOGGING)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + final boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + sysLogger1 = PlatformLogger.getLogger("foo"); + loggerDescMap.put(sysLogger1, "PlatformLogger.getLogger(\"foo\")"); + } finally { + allowAccess.get().set(old); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + final LogProducerFinder.LoggerImpl sysSink; + boolean old = allowControl.get().get(); + allowControl.get().set(true); + try { + sysSink = LogProducerFinder.LoggerImpl.class.cast( + provider.getLogger("foo", Thread.class)); + } finally { + allowControl.get().set(old); + } + + testLogger(provider, loggerDescMap, "foo", null, sysLogger1, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel( LogProducerFinder.LoggerImpl sink, + sun.util.logging.PlatformLogger.Level loggerLevel) { + sink.level = loggerLevel; + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying LogProducerFinder.LoggerImpl + // logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + LogProducerFinder.LoggerImpl sink) { + + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger +"]"); + final sun.util.logging.PlatformLogger.Level OFF = sun.util.logging.PlatformLogger.Level.OFF; + final sun.util.logging.PlatformLogger.Level ALL = sun.util.logging.PlatformLogger.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(format, arg1, arg2); + checkLogEvent(provider, desc2, expected); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (sun.util.logging.PlatformLogger.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (sun.util.logging.PlatformLogger.Level messageLevel :julLevels) { + if (messageLevel == ALL || messageLevel == OFF) continue; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == sun.util.logging.PlatformLogger.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } else if (messageLevel == sun.util.logging.PlatformLogger.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected); + } + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + final static RuntimePermission CONTROL = LOGGERFINDER_PERMISSION; + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(CONTROL); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java b/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java new file mode 100644 index 00000000000..b616ec81944 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java @@ -0,0 +1,497 @@ +/* + * 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. + * + * 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 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests the consistency of the LoggerFinder and JDK extensions. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * @run main LoggerFinderAPITest + */ + + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; + +public class LoggerFinderAPITest { + + static final Class spiLoggerClass + = java.lang.System.Logger.class; + static final Class jdkLoggerClass + = java.lang.System.Logger.class; + static final Class bridgeLoggerClass + = sun.util.logging.PlatformLogger.Bridge.class; + static final Class julLoggerClass + = java.util.logging.Logger.class; + static final Class julLogProducerClass + = PlatformLogger.Bridge.class; + static final Pattern julLogNames = Pattern.compile( + "^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$"); + static final Collection julLoggerIgnores; + static { + List ignores = new ArrayList<>(); + try { + ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class)); + } catch (NoSuchMethodException | SecurityException ex) { + throw new ExceptionInInitializerError(ex); + } + julLoggerIgnores = Collections.unmodifiableList(ignores); + } + + + + // Don't require LoggerBridge to have a body for those methods + interface LoggerBridgeMethodsWithNoBody extends + PlatformLogger.Bridge, java.lang.System.Logger { + + @Override + public default String getName() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default boolean isLoggable(PlatformLogger.Level level) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String msg, Throwable thrown) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Throwable thrown, Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, String msg) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String format, Object... params) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String key, Throwable thrown) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String format, Object... params) { + } + + @Override + public default void logrb(PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + } + + @Override + public default void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + } + + static boolean requiresDefaultBodyFor(Method m) { + try { + Method m2 = LoggerBridgeMethodsWithNoBody.class + .getDeclaredMethod(m.getName(), + m.getParameterTypes()); + return !m2.isDefault(); + } catch (NoSuchMethodException x) { + return true; + } + } + } + + final boolean warnDuplicateMappings; + public LoggerFinderAPITest(boolean verbose) { + this.warnDuplicateMappings = verbose; + for (Handler h : Logger.getLogger("").getHandlers()) { + if (h instanceof ConsoleHandler) { + Logger.getLogger("").removeHandler(h); + } + } + Logger.getLogger("").addHandler( new Handler() { + @Override + public void publish(LogRecord record) { + StringBuilder builder = new StringBuilder(); + builder.append("GOT LogRecord: ") + .append(record.getLevel().getLocalizedName()) + .append(": [").append(record.getLoggerName()) + .append("] ").append(record.getSourceClassName()) + .append('.') + .append(record.getSourceMethodName()).append(" -> ") + .append(record.getMessage()) + .append(' ') + .append(record.getParameters() == null ? "" + : Arrays.toString(record.getParameters())) + ; + System.out.println(builder); + if (record.getThrown() != null) { + record.getThrown().printStackTrace(System.out); + } + } + @Override public void flush() {} + @Override public void close() {} + }); + } + + public Stream getJulLogMethodStream(Class loggerClass) { + + return Stream.of(loggerClass.getMethods()).filter((x) -> { + final Matcher m = julLogNames.matcher(x.getName()); + return m.matches() ? x.getAnnotation(Deprecated.class) == null : false; + }); + } + + /** + * Tells whether a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + * This method only look at the parameter signatures, it doesn't look at + * the name, nor does it look at the return types. + *

    + * Example: + *

      + *
    • java.util.logging.Logger.log(Level, String, Object) can be invoked as
      + java.util.logging.spi.Logger.log(Level, String, Object...) because the + last parameter in 'target' is a varargs.
    • + *
    • java.util.logging.Logger.log(Level, String) can also be invoked as
      + java.util.logging.spi.Logger.log(Level, String, Object...) for the + same reason.
    • + *
    + *

    + * The algorithm is tailored for our needs: when the last parameter in the + * target is a vararg, and when origin & target have the same number of + * parameters, then we consider that the types of the last parameter *must* + * match. + *

    + * Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y) + * although strictly speaking, it should... + * + * @param origin The method in the original class + * @param target The correspondent candidate in the target class + * @return true if a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + */ + public boolean canBeInvokedAs(Method origin, Method target, + Map,Class> substitutes) { + final Class[] xParams = target.getParameterTypes(); + final Class[] mParams = Stream.of(origin.getParameterTypes()) + .map((x) -> substitutes.getOrDefault(x, x)) + .collect(Collectors.toList()).toArray(new Class[0]); + if (Arrays.deepEquals(xParams, mParams)) return true; + if (target.isVarArgs()) { + if (xParams.length == mParams.length) { + if (xParams[xParams.length-1].isArray()) { + return mParams[mParams.length -1].equals( + xParams[xParams.length -1].getComponentType()); + } + } else if (xParams.length == mParams.length + 1) { + return Arrays.deepEquals( + Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams); + } + } + return false; + } + + /** + * Look whether {@code otherClass} has a public method similar to m + * @param m + * @param otherClass + * @return + */ + public Stream findInvokable(Method m, Class otherClass) { + final Map,Class> substitues = + Collections.singletonMap(java.util.logging.Level.class, + sun.util.logging.PlatformLogger.Level.class); + return Stream.of(otherClass.getMethods()) + .filter((x) -> m.getName().equals(x.getName())) + .filter((x) -> canBeInvokedAs(m, x, substitues)); + } + + /** + * Test that the concrete Logger implementation passed as parameter + * overrides all the methods defined by its interface. + * @param julLogger A concrete implementation of System.Logger + * whose backend is a JUL Logger. + */ + StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) { + final StringBuilder errors = new StringBuilder(); + if (!bridgeLoggerClass.isInstance(julLogger)) { + final String errorMsg = + "Logger returned by LoggerFactory.getLogger(\"foo\") is not a " + + bridgeLoggerClass + "\n\t" + julLogger; + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + } + final Class xClass = julLogger.getClass(); + List notOverridden = + Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> { + try { + Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return !Modifier.isStatic(m.getModifiers()); + } + }).collect(Collectors.toList()); + notOverridden.stream().filter((x) -> { + boolean shouldOverride = true; + try { + final Method m = xClass.getMethod(x.getName(), x.getParameterTypes()); + Method m2 = null; + try { + m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes()); + } catch (Exception e) { + + } + shouldOverride = m.isDefault() || m2 == null; + } catch (Exception e) { + // should override. + } + return shouldOverride; + }).forEach(x -> { + final String errorMsg = xClass.getName() + " should override\n\t" + x.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (notOverridden.isEmpty()) { + System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass); + } + return errors; + } + + public static class ResourceBundeParam extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final ResourceBundle bundleParam = + ResourceBundle.getBundle(ResourceBundeParam.class.getName()); + + public static class ResourceBundeLocalized extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "Localized:${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final static ResourceBundle bundleLocalized = + ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); + + final Map, Object> params = new HashMap<>(); + { + params.put(String.class, "TestString"); + params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING); + params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING); + params.put(ResourceBundle.class, bundleParam); + params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)")); + params.put(Object[].class, new Object[] {"One", "Two"}); + params.put(Object.class, new Object() { + @Override public String toString() { return "I am an object!"; } + }); + } + + public Object[] getParamsFor(Method m) { + final Object[] res = new Object[m.getParameterCount()]; + final Class[] sig = m.getParameterTypes(); + if (res.length == 0) { + return res; + } + for (int i=0; i) () -> msg; + } + if (p instanceof String) { + res[i] = String.valueOf(p)+"["+i+"]"; + } else { + res[i] = p; + } + } + return res; + } + + public void invokeOn(java.lang.System.Logger logger, Method m) { + Object[] p = getParamsFor(m); + try { + m.invoke(logger, p); + } catch (Exception e) { + throw new RuntimeException("Failed to invoke "+m.toString(), e); + } + } + + public void testAllJdkExtensionMethods(java.lang.System.Logger logger) { + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllAPIMethods(java.lang.System.Logger logger) { + Stream.of(spiLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllBridgeMethods(java.lang.System.Logger logger) { + Stream.of(bridgeLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllLogProducerMethods(java.lang.System.Logger logger) { + Stream.of(julLogProducerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public StringBuilder testGetLoggerOverriddenOnSpi() { + final StringBuilder errors = new StringBuilder(); + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers())) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .filter(m -> !m.getName().equals("getLoggerFinder")) + .filter(m -> { + try { + final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return true; + } + }).forEach(m -> { + final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (errors.length() == 0) { + System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass); + } else { + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + } + return errors; + } + + public static void main(String argv[]) throws Exception { + final LoggerFinderAPITest test = new LoggerFinderAPITest(false); + final StringBuilder errors = new StringBuilder(); + errors.append(test.testGetLoggerOverriddenOnSpi()); + java.lang.System.Logger julLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("foo", LoggerFinderAPITest.class); + errors.append(test.testDefaultJULLogger(julLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("bar", Thread.class); + errors.append(test.testDefaultJULLogger(julSystemLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julLocalizedLogger = + (java.lang.System.Logger) + System.getLogger("baz", bundleLocalized); + java.lang.System.Logger julLocalizedSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("oof", bundleLocalized, Thread.class); + final String error = errors.toString(); + if (!error.isEmpty()) throw new RuntimeException(error); + for (java.lang.System.Logger logger : new java.lang.System.Logger[] { + julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger + }) { + test.testAllJdkExtensionMethods(logger); + test.testAllAPIMethods(logger); + test.testAllBridgeMethods(logger); + test.testAllLogProducerMethods(logger); + } + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java b/jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java new file mode 100644 index 00000000000..226f2f7cf30 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/LoggerFinderBackendTest.java @@ -0,0 +1,2316 @@ +/* + * 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. + * + * 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 8140364 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + * This test tests all the public API methods defined in the {@link + * java.lang.System.Logger} interface, as well as all the JDK + * internal methods defined in the + * {@link sun.util.logging.PlatformLogger.Bridge} + * interface, with loggers returned by {@link + * java.lang.System.LoggerFinder#getLogger(java.lang.String, java.lang.Class)} + * and {@link java.lang.System.LoggerFinder#getLocalizedLogger(java.lang.String, + * java.util.ResourceBundle, java.lang.Class)} + * (using both a null resource bundle and a non null resource bundle). + * It calls both the {@link java.lang.System} factory methods and + * {@link jdk.internal.logger.LazyLoggers} to obtains those loggers, + * and configure them with all possible known levels. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * java.logging/sun.util.logging.internal + * @build LoggerFinderBackendTest SystemClassLoader + * @run main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=true LoggerFinderBackendTest + * @run main/othervm -Djava.system.class.loader=SystemClassLoader -Dtest.logger.hidesProvider=false LoggerFinderBackendTest + */ + + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import sun.util.logging.PlatformLogger.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @author danielfuchs + */ +public class LoggerFinderBackendTest { + + // whether the implementation of Logger try to do a best + // effort for logp... If the provider is not hidden, then + // the logp() implementation comes from LoggerWrapper - which does a + // best effort. Otherwise, it comes from the default provider + // which does support logp. + static final boolean BEST_EFFORT_FOR_LOGP = + !Boolean.getBoolean("test.logger.hidesProvider"); + static final boolean VERBOSE = false; + + static final Class spiLoggerClass = + java.lang.System.Logger.class; + static final Class jdkLoggerClass = + java.lang.System.Logger.class; + static final Class bridgeLoggerClass = + sun.util.logging.PlatformLogger.Bridge.class; + + /** Use to retrieve the log records that were produced by the JUL backend */ + static class LoggerTesterHandler extends Handler { + public final List records = + Collections.synchronizedList(new ArrayList<>()); + + @Override + public void publish(LogRecord record) { + record.getSourceClassName(); record.getSourceMethodName(); + records.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + records.clear(); + } + + public void reset() { + records.clear(); + } + } + + /** The {@link LoggerTesterHandler} handler is added to the root logger. */ + static final LoggerTesterHandler handler = new LoggerTesterHandler(); + static { + for (Handler h : Logger.getLogger("").getHandlers()) { + if (h instanceof ConsoleHandler) { + Logger.getLogger("").removeHandler(h); + } + } + Logger.getLogger("").addHandler(handler); + } + + /** + * A resource handler parameter that will be used when calling out the + * logrb-like methods - as well as when calling the level-specific + * methods that take a ResourceBundle parameter. + */ + public static class ResourceBundeParam extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final static ResourceBundle bundleParam = + ResourceBundle.getBundle(ResourceBundeParam.class.getName()); + + /** + * A resource handler parameter that will be used when creating localized + * loggers by calling {@link + * LoggerFinder#getLocalizedLogger(java.lang.String, java.util.ResourceBundle, java.lang.Class)}. + */ + public static class ResourceBundeLocalized extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "Localized:${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + /** + * The Levels enum is used to call all the level-specific methods on + * a logger instance. To minimize the amount of code it uses reflection + * to do so. + */ + static Lookup lookup = MethodHandles.lookup(); + public enum Levels { + /** Used to call all forms of Logger.log?(SEVERE, ...) */ + SEVERE("severe", bridgeLoggerClass, Level.SEVERE, null, "error", false), + /** Used to call all forms of Logger.log?(WARNING,...) */ + WARNING("warning", bridgeLoggerClass, Level.WARNING, "warning", "warning", false), + /** Used to call all forms of Logger.log?(INFO,...) */ + INFO("info", bridgeLoggerClass, Level.INFO, "info", "info", false), + /** Used to call all forms of Logger.log?(CONFIG,...) */ + CONFIG("config", bridgeLoggerClass, Level.CONFIG, null, "debug", false), + /** Used to call all forms of Logger.log?(FINE,...) */ + FINE("fine", bridgeLoggerClass, Level.FINE, null, "debug", false), + /** Used to call all forms of Logger.log?(FINER,...) */ + FINER("finer", bridgeLoggerClass, Level.FINER, null, "trace", false), + /** Used to call all forms of Logger.log?(FINEST,...) */ + FINEST("finest", bridgeLoggerClass, Level.FINEST, null, "trace", false), + ; + public final String method; // The name of the level-specific method to call + public final Class definingClass; // which interface j.u.logger.Logger or j.u.logging.spi.Logger defines it + public final Level platformLevel; // The platform Level it will be mapped to in Jul when Jul is the backend + public final String jdkExtensionToJUL; // The name of the method called on the JUL logger when JUL is the backend + public final String julToJdkExtension; // The name of the method called in the jdk extension by the default impl in jdk.internal.logging.Logger + public final String enableMethod; // The name of the isXxxxEnabled method + public final boolean hasSpecificIsEnabled; + Levels(String method, Class definingClass, Level defaultMapping, + String jdkExtensionToJUL, String julToJdkExtension, + boolean hasSpecificIsEnabled) { + this.method = method; + this.definingClass = definingClass; + this.platformLevel = defaultMapping; + this.jdkExtensionToJUL = jdkExtensionToJUL; + this.julToJdkExtension = julToJdkExtension; + this.hasSpecificIsEnabled = hasSpecificIsEnabled; + if (hasSpecificIsEnabled) { + this.enableMethod = "is" + method.substring(0,1).toUpperCase() + + method.substring(1) + "Enabled"; + } else { + this.enableMethod = "isLoggable"; + } + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg); + */ + public void level(Object logger, String msg) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class); + invoke("log", logger, mt, platformLevel, msg); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier); + */ + public void level(Object logger, Supplier msgSupplier) { + MethodType mt = MethodType.methodType(void.class, Level.class, Supplier.class); + invoke("log", logger, mt, platformLevel, msgSupplier); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg, params); + */ + public void level(Object logger, String msg, Object... params) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class, + Object[].class); + invoke("log", logger, mt, platformLevel, msg, params); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msg, thrown); + */ + public void level(Object logger, String msg, Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, String.class, + Throwable.class); + invoke("log", logger, mt, platformLevel, msg, thrown); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(msgSupplier, thrown); + */ + public void level(Object logger, Supplier msgSupplier, Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, + Throwable.class, Supplier.class); + invoke("log", logger, mt, platformLevel, thrown, msgSupplier); + } + + /* + * calls this level specific method - e.g. if this==INFO: logger.info(bundle, msg); + */ + public void level(Object logger, String msg, ResourceBundle bundle) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Object[].class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, null); + } + + public void level(Object logger, String msg, ResourceBundle bundle, + Object... params) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Object[].class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, params); + } + + public void level(Object logger, String msg, ResourceBundle bundle, + Throwable thrown) { + MethodType mt = MethodType.methodType(void.class, Level.class, + ResourceBundle.class, String.class, Throwable.class); + invoke("logrb", logger, mt, platformLevel, bundle, msg, thrown); + } + + public boolean isEnabled(Object logger) { + try { + if (hasSpecificIsEnabled) { + MethodType mt = MethodType.methodType(boolean.class); + final MethodHandle handle = lookup.findVirtual(definingClass, + enableMethod, mt).bindTo(logger); + return Boolean.class.cast(handle.invoke()); + } else { + MethodType mt = MethodType.methodType(boolean.class, + Level.class); + final MethodHandle handle = lookup.findVirtual(definingClass, + enableMethod, mt).bindTo(logger); + return Boolean.class.cast(handle.invoke(platformLevel)); + } + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + private void invoke(String method, Object logger, MethodType mt, Object... args) { + try { + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + final MethodHandle handle = lookup.findVirtual(definingClass, + method, mt).bindTo(logger); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) { + System.out.println(builder); + } + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + + }; + + static interface Checker extends BiFunction {} + static interface JdkLogTester + extends BiFunction {} + static interface SpiLogTester + extends BiFunction {} + + static interface MethodInvoker { + public void logX(LOGGER logger, LEVEL level, Object... args); + } + + public enum JdkLogMethodInvoker + implements MethodInvoker { + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Object...)}; + **/ + LOG_STRING_PARAMS("log", MethodType.methodType(void.class, + Level.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Throwable)}; + **/ + LOG_STRING_THROWN("log", MethodType.methodType(void.class, + Level.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_SUPPLIER("log", MethodType.methodType(void.class, + Level.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Throwable, Supplier)}; + **/ + LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class, + Level.class, Throwable.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String)}; + **/ + LOGP_STRING("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String, Object...)}; + **/ + LOGP_STRING_PARAMS("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, String, Throwable)}; + **/ + LOGP_STRING_THROWN("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, Supplier)}; + **/ + LOGP_SUPPLIER("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logp(Level, String, String, Throwable, Supplier)}; + **/ + LOGP_SUPPLIER_THROWN("logp", MethodType.methodType(void.class, + Level.class, String.class, String.class, + Throwable.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)}; + **/ + LOGRB_STRING_PARAMS("logrb", MethodType.methodType(void.class, + Level.class, ResourceBundle.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)}; + **/ + LOGRB_STRING_THROWN("logrb", MethodType.methodType(void.class, + Level.class, ResourceBundle.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Object...)}; + **/ + LOGRBP_STRING_PARAMS("logrb", MethodType.methodType(void.class, + Level.class, String.class, String.class, ResourceBundle.class, + String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, String, String, ResourceBundle, String, Throwable)}; + **/ + LOGRBP_STRING_THROWN("logrb", MethodType.methodType(void.class, + Level.class, String.class, String.class, ResourceBundle.class, + String.class, Throwable.class)), + ; + final MethodType mt; + final String method; + JdkLogMethodInvoker(String method, MethodType mt) { + this.mt = mt; + this.method = method; + } + Object[] makeArgs(Level level, Object... rest) { + List list = new ArrayList<>(rest == null ? 1 : rest.length + 1); + list.add(level); + if (rest != null) { + list.addAll(Arrays.asList(rest)); + } + return list.toArray(new Object[list.size()]); + } + + @Override + public void logX(sun.util.logging.PlatformLogger.Bridge logger, Level level, Object... args) { + try { + MethodHandle handle = lookup.findVirtual(bridgeLoggerClass, + method, mt).bindTo(logger); + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + + args = makeArgs(level, args); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(this.method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + p = p instanceof Level ? "Level."+p : p; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) System.out.println(builder); + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + } + + + public enum SpiLogMethodInvoker implements MethodInvoker { + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Object...)}; + **/ + LOG_STRING_PARAMS("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, String, Throwable)}; + **/ + LOG_STRING_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, String.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_SUPPLIER("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Supplier.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Throwable, Supplier)}; + **/ + LOG_SUPPLIER_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Supplier.class, Throwable.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#log(Level, Supplier)}; + **/ + LOG_OBJECT("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, Object.class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Object...)}; + **/ + LOGRB_STRING_PARAMS("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, ResourceBundle.class, + String.class, Object[].class)), + /** + * Tests {@link + * jdk.internal.logging.Logger#logrb(Level, ResourceBundle, String, Throwable)}; + **/ + LOGRB_STRING_THROWN("log", MethodType.methodType(void.class, + java.lang.System.Logger.Level.class, ResourceBundle.class, + String.class, Throwable.class)), + ; + final MethodType mt; + final String method; + SpiLogMethodInvoker(String method, MethodType mt) { + this.mt = mt; + this.method = method; + } + Object[] makeArgs(java.lang.System.Logger.Level level, Object... rest) { + List list = new ArrayList<>(rest == null ? 1 : rest.length + 1); + list.add(level); + if (rest != null) { + list.addAll(Arrays.asList(rest)); + } + return list.toArray(new Object[list.size()]); + } + + @Override + public void logX(java.lang.System.Logger logger, + java.lang.System.Logger.Level level, Object... args) { + try { + MethodHandle handle = lookup.findVirtual(spiLoggerClass, + method, mt).bindTo(logger); + final int last = mt.parameterCount()-1; + boolean isVarargs = mt.parameterType(last).isArray(); + + args = makeArgs(level, args); + + final StringBuilder builder = new StringBuilder(); + builder.append(logger.getClass().getSimpleName()).append('.') + .append(this.method).append('('); + String sep = ""; + int offset = 0; + Object[] params = args; + for (int i=0; (i-offset) < params.length; i++) { + if (isVarargs && i == last) { + offset = last; + params = (Object[])args[i]; + if (params == null) break; + } + Object p = params[i - offset]; + String quote = (p instanceof String) ? "\"" : ""; + p = p instanceof Level ? "Level."+p : p; + builder.append(sep).append(quote).append(p).append(quote); + sep = ", "; + } + builder.append(')'); + if (verbose) System.out.println(builder); + handle.invokeWithArguments(args); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + } + + + public abstract static class BackendTester { + static final Level[] levelMap = {Level.ALL, Level.FINER, Level.FINE, + Level.INFO, Level.WARNING, Level.SEVERE, Level.OFF}; + + abstract class BackendAdaptor { + public abstract String getLoggerName(BackendRecord res); + public abstract Object getLevel(BackendRecord res); + public abstract String getMessage(BackendRecord res); + public abstract String getSourceClassName(BackendRecord res); + public abstract String getSourceMethodName(BackendRecord res); + public abstract Throwable getThrown(BackendRecord res); + public abstract ResourceBundle getResourceBundle(BackendRecord res); + public abstract void setLevel(java.lang.System.Logger logger, + Level level); + public abstract void setLevel(java.lang.System.Logger logger, + java.lang.System.Logger.Level level); + public abstract List getBackendRecords(); + public abstract void resetBackendRecords(); + public boolean shouldBeLoggable(Levels level, Level loggerLevel) { + final Level logLevel = level.platformLevel; + return shouldBeLoggable(logLevel, loggerLevel); + } + public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) { + return loggerLevel.intValue() != Level.OFF.intValue() + && logLevel.intValue() >= loggerLevel.intValue(); + } + public boolean shouldBeLoggable(java.lang.System.Logger.Level logLevel, + java.lang.System.Logger.Level loggerLevel) { + return loggerLevel != java.lang.System.Logger.Level.OFF + && logLevel.ordinal() >= loggerLevel.ordinal(); + } + public boolean isLoggable(java.lang.System.Logger logger, Level l) { + return bridgeLoggerClass.cast(logger).isLoggable(l); + } + public String getCallerClassName(Levels level, String clazz) { + return clazz != null ? clazz : Levels.class.getName(); + } + public String getCallerClassName(MethodInvoker logMethod, + String clazz) { + return clazz != null ? clazz : logMethod.getClass().getName(); + } + public String getCallerMethodName(Levels level, String method) { + return method != null ? method : "invoke"; + } + public String getCallerMethodName(MethodInvoker logMethod, + String method) { + return method != null ? method : "logX"; + } + public Object getMappedLevel(Object level) { + return level; + } + + public Level toJUL(java.lang.System.Logger.Level level) { + return levelMap[level.ordinal()]; + } + } + + public final boolean isSystem; + public final Class restrictedTo; + public final ResourceBundle localized; + public BackendTester(boolean isSystem) { + this(isSystem,null,null); + } + public BackendTester(boolean isSystem, ResourceBundle localized) { + this(isSystem,null,localized); + } + public BackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + public BackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + this.isSystem = isSystem; + this.restrictedTo = restrictedTo; + this.localized = localized; + } + + public java.lang.System.Logger convert(java.lang.System.Logger logger) { + return logger; + } + + public static Level[] LEVELS = { + Level.OFF, + Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, + Level.FINE, Level.FINER, Level.FINEST, + Level.ALL + }; + + abstract BackendAdaptor adaptor(); + + protected void checkRecord(Levels test, BackendRecord res, String loggerName, + Level level, String msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + checkRecord(test, res, loggerName, level, ()->msg, className, + methodName, thrown, bundle, params); + + } + protected void checkRecord(Levels test, BackendRecord res, String loggerName, + Level level, Supplier msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + checkRecord(test.method, res, loggerName, level, msg, + className, methodName, thrown, bundle, params); + } + protected void checkRecord(String logMethod, BackendRecord res, String loggerName, + L level, Supplier msg, String className, String methodName, + Throwable thrown, ResourceBundle bundle, Object... params) { + final BackendAdaptor analyzer = adaptor(); + if (! Objects.equals(analyzer.getLoggerName(res), loggerName)) { + throw new RuntimeException(logMethod+": expected logger name " + + loggerName + " got " + analyzer.getLoggerName(res)); + } + if (!Objects.equals(analyzer.getLevel(res), analyzer.getMappedLevel(level))) { + throw new RuntimeException(logMethod+": expected level " + + analyzer.getMappedLevel(level) + " got " + analyzer.getLevel(res)); + } + if (!Objects.equals(analyzer.getMessage(res), msg.get())) { + throw new RuntimeException(logMethod+": expected message \"" + + msg.get() + "\" got \"" + analyzer.getMessage(res) +"\""); + } + if (!Objects.equals(analyzer.getSourceClassName(res), className)) { + throw new RuntimeException(logMethod + + ": expected class name \"" + className + + "\" got \"" + analyzer.getSourceClassName(res) +"\""); + } + if (!Objects.equals(analyzer.getSourceMethodName(res), methodName)) { + throw new RuntimeException(logMethod + + ": expected method name \"" + methodName + + "\" got \"" + analyzer.getSourceMethodName(res) +"\""); + } + final Throwable thrownRes = analyzer.getThrown(res); + if (!Objects.equals(thrownRes, thrown)) { + throw new RuntimeException(logMethod + + ": expected throwable \"" + thrown + + "\" got \"" + thrownRes + "\""); + } + if (!Objects.equals(analyzer.getResourceBundle(res), bundle)) { + throw new RuntimeException(logMethod + + ": expected bundle \"" + bundle + + "\" got \"" + analyzer.getResourceBundle(res) +"\""); + } + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg) { + Runnable test = () -> level.level(logger, msg); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, localized); + return null; + }; + test("msg", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, Object... params) { + Runnable test = () -> level.level(logger, msg, (Object[])params); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, localized, (Object[])params); + return null; + }; + test("msg, params", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, localized); + return null; + }; + test("msg, thrown", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + Supplier msg) { + Runnable test = () -> level.level(logger, msg); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, null); + return null; + }; + test("msgSupplier", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + Supplier msg, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, null); + return null; + }; + test("throw, msgSupplier", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle) { + Runnable test = () -> level.level(logger, msg, bundle); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, bundle); + return null; + }; + test("bundle, msg", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle, Object... params) { + Runnable test = () -> level.level(logger, msg, bundle, (Object[])params); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + null, bundle, (Object[])params); + return null; + }; + test("bundle, msg, params", level, logger, test, check); + } + + public void testLevel(Levels level, java.lang.System.Logger logger, + String msg, ResourceBundle bundle, Throwable thrown) { + Runnable test = () -> level.level(logger, msg, bundle, thrown); + Checker check = (res, l) -> { + checkRecord(level, res, logger.getName(), l, msg, + adaptor().getCallerClassName(level, Levels.class.getName()), + adaptor().getCallerMethodName(level, "invoke"), + thrown, bundle); + return null; + }; + test("bundle, msg, throwable", level, logger, test, check); + } + + // System.Logger + public void testSpiLog(java.lang.System.Logger logger, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized, params); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle, params); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\", params...)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_STRING_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_STRING_THROWN, + "logX"), thrown, localized); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", \"" + msg + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOGRB_STRING_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOGRB_STRING_THROWN, + "logX"), thrown, bundle); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", bundle, \"" + msg + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_SUPPLIER, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_SUPPLIER, + "logX"), null, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\")"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Object obj) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> obj.toString(), + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_OBJECT, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_OBJECT, + "logX"), null, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_OBJECT.logX(x, level, obj); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", new "+obj.getClass().getSimpleName()+"(\"" + + obj.toString() + "\"))"; + testSpiLog(logger, tester, check, nameProducer); + } + + public void testSpiLog(java.lang.System.Logger logger, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN, + SpiLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN, + "logX"), thrown, null); + return null; + }; + SpiLogTester tester = (x, level) -> { + SpiLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)"; + testSpiLog(logger, tester, check, nameProducer); + } + + + // JDK + + public void testLog(java.lang.System.Logger logger, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> "logrb(Level." + l + + ", bundle, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_PARAMS, + "logX"), null, localized, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_PARAMS.logX(x, level, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + ", \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_PARAMS, + "logX"), null, bundle, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_PARAMS.logX(x, level, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> "log(Level." + l + + ", bundle, \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_STRING_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_STRING_THROWN, + "logX"), thrown, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_STRING_THROWN.logX(x, level, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, + ResourceBundle bundle, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRB_STRING_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOGRB_STRING_THROWN, + "logX"), thrown, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRB_STRING_THROWN.logX(x, level, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", bundle, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_SUPPLIER, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_SUPPLIER, + "logX"), null, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_SUPPLIER.logX(x, level, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLog(java.lang.System.Logger logger, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN, + JdkLogMethodInvoker.class.getName()), + adaptor().getCallerMethodName( + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN, + "logX"), thrown, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOG_SUPPLIER_THROWN.logX(x, level, thrown, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", () -> \"" + msg.get() + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + static Supplier logpMessage(ResourceBundle bundle, + String className, String methodName, Supplier msg) { + if (BEST_EFFORT_FOR_LOGP && bundle == null + && (className != null || methodName != null)) { + final String cName = className == null ? "" : className; + final String mName = methodName == null ? "" : methodName; + return () -> { + String m = msg.get(); + return String.format("[%s %s] %s", cName, mName, m == null ? "" : m); + }; + } else { + return msg; + } + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING, methodName), + null, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING.logX(x, level, + className, methodName, msg); + return null; + }; + Function nameProducer = (l) -> + "logp(Level." + l + ", class, method, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, String msg) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName), + null, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level, + className, methodName, bundle, msg, (Object[])null); + return null; + }; + Function nameProducer = (l) -> + "logp(Level." + l + ", class, method, bundle, \"" + msg + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg, Object... params) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_PARAMS, methodName), + null, localized, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING_PARAMS.logX(x, level, + className, methodName, msg, params); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, \"" + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, String msg, + Object... params) { + Checker check = (res, l) -> { + checkRecord("logp", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS, methodName), + null, bundle, params); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_PARAMS.logX(x, level, + className, methodName, bundle, msg, params); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, bundle, \"" + + msg + "\", params...)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(localized, className, methodName, () -> msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_STRING_THROWN, methodName), + thrown, localized); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_STRING_THROWN.logX(x, level, + className, methodName, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogrb(java.lang.System.Logger logger, String className, + String methodName, ResourceBundle bundle, + String msg, Throwable thrown) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, () -> msg, + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGRBP_STRING_THROWN, methodName), + thrown, bundle); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGRBP_STRING_THROWN.logX(x, level, + className, methodName, bundle, msg, thrown); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, bundle, \"" + msg + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(null, className, methodName, msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER, methodName), + null, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_SUPPLIER.logX(x, level, + className, methodName, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\")"; + testJdkLog(logger, tester, check, nameProducer); + } + + public void testLogp(java.lang.System.Logger logger, String className, + String methodName, Throwable thrown, Supplier msg) { + Checker check = (res, l) -> { + checkRecord("log", res, logger.getName(), l, + logpMessage(null, className, methodName, msg), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, className), + adaptor().getCallerClassName( + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN, methodName), + thrown, null); + return null; + }; + JdkLogTester tester = (x, level) -> { + JdkLogMethodInvoker.LOGP_SUPPLIER_THROWN.logX(x, level, + className, methodName, thrown, msg); + return null; + }; + Function nameProducer = (l) -> + "log(Level." + l + ", class, method, () -> \"" + msg.get() + "\", thrown)"; + testJdkLog(logger, tester, check, nameProducer); + } + + private void testJdkLog(java.lang.System.Logger logger, + JdkLogTester log, Checker check, + Function nameProducer) { + if (restrictedTo != null) { + if (!bridgeLoggerClass.isAssignableFrom(restrictedTo)) { + if (VERBOSE) { + System.out.println("Skipping method from " + + bridgeLoggerClass); + } + return; + } + } + System.out.println("Testing Logger." + nameProducer.apply("*") + + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (Level loggerLevel : LEVELS) { + adaptor.setLevel(logger, loggerLevel); + for (Level l : LEVELS) { + check(logger, () -> log.apply(bridgeLoggerClass.cast(logger), l), + check, () -> adaptor.isLoggable(logger, l), + () -> adaptor.shouldBeLoggable(l, loggerLevel), + l, loggerLevel, nameProducer.apply(l.toString())); + } + } + } + + private void testSpiLog(java.lang.System.Logger logger, + SpiLogTester log, Checker check, + Function nameProducer) { + System.out.println("Testing System.Logger." + nameProducer.apply("*") + + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (java.lang.System.Logger.Level loggerLevel : java.lang.System.Logger.Level.values()) { + + adaptor.setLevel(logger, loggerLevel); + for (java.lang.System.Logger.Level l : java.lang.System.Logger.Level.values()) { + check(logger, () -> log.apply(logger, l), + check, () -> logger.isLoggable(l), + () -> adaptor.shouldBeLoggable(l, loggerLevel), + l, loggerLevel, nameProducer.apply(l.toString())); + } + } + } + + private void test(String args, Levels level, java.lang.System.Logger logger, + Runnable test, Checker check) { + if (restrictedTo != null) { + if (!level.definingClass.isAssignableFrom(restrictedTo)) { + if (VERBOSE) { + System.out.println("Skipping method from " + + level.definingClass); + } + return; + } + } + String method = args.contains("bundle") ? "logrb" : "log"; + System.out.println("Testing Logger." + + method + "(Level." + level.platformLevel + + ", "+ args + ")" + " on " + logger); + final BackendAdaptor adaptor = adaptor(); + for (Level loggerLevel : LEVELS) { + adaptor.setLevel(logger, loggerLevel); + check(logger, test, check, + () -> level.isEnabled(logger), + () -> adaptor.shouldBeLoggable(level, loggerLevel), + level.platformLevel, loggerLevel, level.method); + } + } + + private void check(java.lang.System.Logger logger, + Runnable test, Checker check, + BooleanSupplier checkLevelEnabled, + BooleanSupplier shouldBeLoggable, + L logLevel, L loggerLevel, String logMethod) { + final BackendAdaptor adaptor = adaptor(); + adaptor.resetBackendRecords(); + test.run(); + final List records = adaptor.getBackendRecords(); + if (shouldBeLoggable.getAsBoolean()) { + if (!checkLevelEnabled.getAsBoolean()) { + throw new RuntimeException("Logger is not enabled for " + + logMethod + + " although logger level is " + loggerLevel); + } + if (records.size() != 1) { + throw new RuntimeException(loggerLevel + " [" + + logLevel + "] : Unexpected record sizes: " + + records.toString()); + } + BackendRecord res = records.get(0); + check.apply(res, logLevel); + } else { + if (checkLevelEnabled.getAsBoolean()) { + throw new RuntimeException("Logger is enabled for " + + logMethod + + " although logger level is " + loggerLevel); + } + if (!records.isEmpty()) { + throw new RuntimeException(loggerLevel + " [" + + logLevel + "] : Unexpected record sizes: " + + records.toString()); + } + } + } + } + + public static class JULBackendTester extends BackendTester{ + + public JULBackendTester(boolean isSystem) { + this(isSystem,null,null); + } + public JULBackendTester(boolean isSystem, ResourceBundle localized) { + this(isSystem,null,localized); + } + public JULBackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + public JULBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + super(isSystem, restrictedTo, localized); + } + + Logger getBackendLogger(String name) { + if (isSystem) { + return LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), name, Thread.class); + } else { + return Logger.getLogger(name); + } + } + + class JULBackendAdaptor extends BackendAdaptor { + @Override + public String getLoggerName(LogRecord res) { + return res.getLoggerName(); + } + @Override + public Level getLevel(LogRecord res) { + return Level.valueOf(res.getLevel().getName()); + } + @Override + public String getMessage(LogRecord res) { + return res.getMessage(); + } + @Override + public String getSourceClassName(LogRecord res) { + return res.getSourceClassName(); + } + @Override + public String getSourceMethodName(LogRecord res) { + return res.getSourceMethodName(); + } + @Override + public Throwable getThrown(LogRecord res) { + return res.getThrown(); + } + @Override + public ResourceBundle getResourceBundle(LogRecord res) { + return res.getResourceBundle(); + } + @Override + public void setLevel(java.lang.System.Logger logger, Level level) { + Logger backend = getBackendLogger(logger.getName()); + backend.setLevel(java.util.logging.Level.parse(level.name())); + } + @Override + public void setLevel(java.lang.System.Logger logger, java.lang.System.Logger.Level level) { + setLevel(logger, toJUL(level)); + } + @Override + public List getBackendRecords() { + return handler.records; + } + @Override + public void resetBackendRecords() { + handler.reset(); + } + @Override + public Level getMappedLevel(Object level) { + if (level instanceof java.lang.System.Logger.Level) { + return toJUL((java.lang.System.Logger.Level)level); + } + return (Level)level; + } + } + + final JULBackendAdaptor julAdaptor = new JULBackendAdaptor(); + + @Override + BackendAdaptor adaptor() { + return julAdaptor; + } + + } + + public abstract static class BackendTesterFactory { + public abstract BackendTester createBackendTester(boolean isSystem); + public abstract BackendTester createBackendTester(boolean isSystem, + Class restrictedTo); + public abstract BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle); + public abstract BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle); + } + + public static class JULBackendTesterFactory extends BackendTesterFactory { + + @Override + public BackendTester createBackendTester(boolean isSystem) { + return new JULBackendTester(isSystem); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo) { + return new JULBackendTester(isSystem, restrictedTo); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle) { + return new JULBackendTester(isSystem, restrictedTo, bundle); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle) { + return new JULBackendTester(isSystem, bundle); + } + } + + public static class CustomLoggerFinder extends LoggerFinder { + + static enum CustomLevel { OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL }; + static CustomLevel[] customLevelMap = { CustomLevel.ALL, + CustomLevel.TRACE, CustomLevel.DEBUG, CustomLevel.INFO, + CustomLevel.WARN, CustomLevel.ERROR, CustomLevel.OFF + }; + static class CustomLogRecord { + public final CustomLevel logLevel; + public final java.lang.System.Logger logger; + public final String msg; + public final Object[] params; + public final Throwable thrown; + public final ResourceBundle bundle; + + CustomLogRecord(java.lang.System.Logger producer, + CustomLevel level, String msg) { + this(producer, level, msg, (ResourceBundle)null, (Throwable)null, (Object[])null); + } + + CustomLogRecord(java.lang.System.Logger producer, + CustomLevel level, String msg, ResourceBundle bundle, + Throwable thrown, Object... params) { + this.logger = producer; + this.logLevel = level; + this.msg = msg; + this.params = params; + this.thrown = thrown; + this.bundle = bundle; + } + } + + static final List records = + Collections.synchronizedList(new ArrayList<>()); + + static class CustomLogger implements java.lang.System.Logger { + + final String name; + volatile CustomLevel level; + CustomLogger(String name) { + this.name = name; + this.level = CustomLevel.INFO; + } + + @Override + public String getName() { + return name; + } + + public void setLevel(CustomLevel level) { + this.level = level; + } + + + @Override + public boolean isLoggable(java.lang.System.Logger.Level level) { + + return this.level != CustomLevel.OFF && this.level.ordinal() + >= customLevelMap[level.ordinal()].ordinal(); + } + + @Override + public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()], + key, bundle, thrown)); + } + } + + @Override + public void log(java.lang.System.Logger.Level level, ResourceBundle bundle, String format, Object... params) { + if (isLoggable(level)) { + records.add(new CustomLogRecord(this, customLevelMap[level.ordinal()], + format, bundle, null, params)); + } + } + + } + + final Map applicationLoggers = + Collections.synchronizedMap(new HashMap<>()); + final Map systemLoggers = + Collections.synchronizedMap(new HashMap<>()); + + @Override + public java.lang.System.Logger getLogger(String name, Class caller) { + ClassLoader callerLoader = caller.getClassLoader(); + if (callerLoader == null) { + systemLoggers.putIfAbsent(name, new CustomLogger(name)); + return systemLoggers.get(name); + } else { + applicationLoggers.putIfAbsent(name, new CustomLogger(name)); + return applicationLoggers.get(name); + } + } + + CustomLevel fromJul(Level level) { + if (level.intValue() == Level.OFF.intValue()) { + return CustomLevel.OFF; + } else if (level.intValue() > Level.SEVERE.intValue()) { + return CustomLevel.ERROR; + } else if (level.intValue() > Level.WARNING.intValue()) { + return CustomLevel.ERROR; + } else if (level.intValue() > Level.INFO.intValue()) { + return CustomLevel.WARN; + } else if (level.intValue() > Level.CONFIG.intValue()) { + return CustomLevel.INFO; + } else if (level.intValue() > Level.FINER.intValue()) { + return CustomLevel.DEBUG; + } else if (level.intValue() > Level.FINEST.intValue()) { + return CustomLevel.TRACE; + } else if (level.intValue() == Level.ALL.intValue()) { + return CustomLevel.ALL; + } else { + return CustomLevel.TRACE; + } + } + + Level toJul(CustomLevel level) { + switch(level) { + case OFF: return Level.OFF; + case FATAL: return Level.SEVERE; + case ERROR: return Level.SEVERE; + case WARN: return Level.WARNING; + case INFO: return Level.INFO; + case DEBUG: return Level.FINE; + case TRACE: return Level.FINER; + case ALL: return Level.ALL; + default: throw new InternalError("No such level: "+level); + } + } + + } + + public static class CustomBackendTester extends + BackendTester { + + public final CustomLoggerFinder provider; + + public CustomBackendTester(boolean isSystem) { + this(isSystem, null, null); + } + + public CustomBackendTester(boolean isSystem, + Class restrictedTo) { + this(isSystem, restrictedTo, null); + } + + public CustomBackendTester(boolean isSystem, + ResourceBundle localized) { + this(isSystem, null, localized); + } + + public CustomBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle localized) { + super(isSystem, restrictedTo, localized); + provider = (CustomLoggerFinder)java.lang.System.LoggerFinder.getLoggerFinder(); + } + + @Override + public java.lang.System.Logger convert(java.lang.System.Logger logger) { + if (restrictedTo != null && restrictedTo.isInstance(logger)) { + return logger; + } else if (restrictedTo == jdkLoggerClass) { + return logger; + } else { + return java.lang.System.Logger.class.cast( + sun.util.logging.PlatformLogger.Bridge.convert(logger)); + } + } + + class CustomBackendAdaptor extends BackendAdaptor { + + @Override + public String getLoggerName(CustomLoggerFinder.CustomLogRecord res) { + return res.logger.getName(); + } + + @Override + public CustomLoggerFinder.CustomLevel getLevel(CustomLoggerFinder.CustomLogRecord res) { + return res.logLevel; + } + + @Override + public String getMessage(CustomLoggerFinder.CustomLogRecord res) { + return res.msg; + } + + @Override // we don't support source class name in our custom provider implementation + public String getSourceClassName(CustomLoggerFinder.CustomLogRecord res) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getSourceMethodName(CustomLoggerFinder.CustomLogRecord res) { + return null; + } + + @Override + public Throwable getThrown(CustomLoggerFinder.CustomLogRecord res) { + return res.thrown; + } + + @Override + public ResourceBundle getResourceBundle(CustomLoggerFinder.CustomLogRecord res) { + return res.bundle; + } + + @Override + public void setLevel(java.lang.System.Logger logger, Level level) { + final CustomLoggerFinder.CustomLogger l = + (CustomLoggerFinder.CustomLogger) + (isSystem ? provider.getLogger(logger.getName(), Thread.class) : + provider.getLogger(logger.getName(), LoggerFinderBackendTest.class)); + l.setLevel(provider.fromJul(level)); + } + @Override + public void setLevel(java.lang.System.Logger logger, + java.lang.System.Logger.Level level) { + setLevel(logger, toJUL(level)); + } + + CustomLoggerFinder.CustomLevel getLevel(java.lang.System.Logger logger) { + final CustomLoggerFinder.CustomLogger l = + (CustomLoggerFinder.CustomLogger) + (isSystem ? provider.getLogger(logger.getName(), Thread.class) : + provider.getLogger(logger.getName(), LoggerFinderBackendTest.class)); + return l.level; + } + + @Override + public List getBackendRecords() { + return CustomLoggerFinder.records; + } + + @Override + public void resetBackendRecords() { + CustomLoggerFinder.records.clear(); + } + + @Override + public boolean shouldBeLoggable(Levels level, Level loggerLevel) { + return loggerLevel != Level.OFF && + fromLevels(level).ordinal() <= provider.fromJul(loggerLevel).ordinal(); + } + + @Override + public boolean isLoggable(java.lang.System.Logger logger, Level l) { + return super.isLoggable(logger, l); + } + + @Override + public boolean shouldBeLoggable(Level logLevel, Level loggerLevel) { + return loggerLevel != Level.OFF && + provider.fromJul(logLevel).ordinal() <= provider.fromJul(loggerLevel).ordinal(); + } + + @Override // we don't support source class name in our custom provider implementation + public String getCallerClassName(Levels level, String clazz) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getCallerMethodName(Levels level, String method) { + return null; + } + + @Override // we don't support source class name in our custom provider implementation + public String getCallerClassName(MethodInvoker logMethod, String clazz) { + return null; + } + + @Override // we don't support source method name in our custom provider implementation + public String getCallerMethodName(MethodInvoker logMethod, String method) { + return null; + } + + @Override + public CustomLoggerFinder.CustomLevel getMappedLevel(Object level) { + if (level instanceof java.lang.System.Logger.Level) { + final int index = ((java.lang.System.Logger.Level)level).ordinal(); + return CustomLoggerFinder.customLevelMap[index]; + } else if (level instanceof Level) { + return provider.fromJul((Level)level); + } + return (CustomLoggerFinder.CustomLevel) level; + } + + CustomLoggerFinder.CustomLevel fromLevels(Levels level) { + switch(level) { + case SEVERE: + return CustomLoggerFinder.CustomLevel.ERROR; + case WARNING: + return CustomLoggerFinder.CustomLevel.WARN; + case INFO: + return CustomLoggerFinder.CustomLevel.INFO; + case CONFIG: case FINE: + return CustomLoggerFinder.CustomLevel.DEBUG; + case FINER: case FINEST: + return CustomLoggerFinder.CustomLevel.TRACE; + } + throw new InternalError("No such level "+level); + } + + } + + @Override + BackendAdaptor adaptor() { + return new CustomBackendAdaptor(); + } + + } + + public static class CustomBackendTesterFactory extends BackendTesterFactory { + + @Override + public BackendTester createBackendTester(boolean isSystem) { + return new CustomBackendTester(isSystem); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo) { + return new CustomBackendTester(isSystem, restrictedTo); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + Class restrictedTo, + ResourceBundle bundle) { + return new CustomBackendTester(isSystem, restrictedTo, bundle); + } + + @Override + public BackendTester createBackendTester(boolean isSystem, + ResourceBundle bundle) { + return new CustomBackendTester(isSystem, bundle); + } + } + + static final Method getLazyLogger; + static final Method accessLoggerFinder; + static { + // jdk.internal.logger.LazyLoggers.getLazyLogger(name, caller); + try { + Class lazyLoggers = jdk.internal.logger.LazyLoggers.class; + getLazyLogger = lazyLoggers.getMethod("getLazyLogger", + String.class, Class.class); + getLazyLogger.setAccessible(true); + Class loggerFinderLoader = + Class.forName("java.lang.System$LoggerFinder"); + accessLoggerFinder = loggerFinderLoader.getDeclaredMethod("accessProvider"); + accessLoggerFinder.setAccessible(true); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static java.lang.System.Logger getSystemLogger(String name, Class caller) throws Exception { + try { + return java.lang.System.Logger.class.cast(getLazyLogger.invoke(null, name, caller)); + } catch (InvocationTargetException x) { + Throwable t = x.getTargetException(); + if (t instanceof Exception) { + throw (Exception)t; + } else { + throw (Error)t; + } + } + } + static java.lang.System.Logger getSystemLogger(String name, + ResourceBundle bundle, Class caller) throws Exception { + try { + LoggerFinder provider = LoggerFinder.class.cast(accessLoggerFinder.invoke(null)); + return provider.getLocalizedLogger(name, bundle, caller); + } catch (InvocationTargetException x) { + Throwable t = x.getTargetException(); + if (t instanceof Exception) { + throw (Exception)t; + } else { + throw (Error)t; + } + } + } + + // Change this to 'true' to get more traces... + public static boolean verbose = false; + + public static void main(String[] argv) throws Exception { + + final AtomicInteger nb = new AtomicInteger(0); + final boolean hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + System.out.println(ClassLoader.getSystemClassLoader()); + final BackendTesterFactory factory; + if (java.lang.System.LoggerFinder.getLoggerFinder() instanceof CustomLoggerFinder) { + if (hidesProvider) { + System.err.println("Custom backend " + + java.lang.System.LoggerFinder.getLoggerFinder() + + " should have been hidden!"); + throw new RuntimeException( + "Custom backend should have been hidden: " + + "check value of java.system.class.loader property"); + } + System.out.println("Using custom backend"); + factory = new CustomBackendTesterFactory(); + } else { + if (!hidesProvider) { + System.err.println("Default JUL backend " + + java.lang.System.LoggerFinder.getLoggerFinder() + + " should have been hidden!"); + throw new RuntimeException( + "Default JUL backend should have been hidden: " + + "check value of java.system.class.loader property"); + } + System.out.println("Using JUL backend"); + factory = new JULBackendTesterFactory(); + } + + testBackend(nb, factory); + } + + public static void testBackend(AtomicInteger nb, BackendTesterFactory factory) throws Exception { + + // Tests all level specifics methods with loggers configured with + // all possible levels and loggers obtained with all possible + // entry points from LoggerFactory and JdkLoggerFactory, with + // JUL as backend. + + // Test a simple application logger with JUL backend + final BackendTester tester = factory.createBackendTester(false); + final java.lang.System.Logger logger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("foo", LoggerFinderBackendTest.class); + + testLogger(tester, logger, nb); + + // Test a simple system logger with JUL backend + final java.lang.System.Logger system = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("bar", Thread.class); + final BackendTester systemTester = factory.createBackendTester(true); + testLogger(systemTester, system, nb); + + // Test a localized application logger with null resource bundle and + // JUL backend + final java.lang.System.Logger noBundleLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("baz", null, LoggerFinderBackendTest.class); + final BackendTester noBundleTester = + factory.createBackendTester(false, spiLoggerClass); + testLogger(noBundleTester, noBundleLogger, nb); + + // Test a localized system logger with null resource bundle and JUL + // backend + final java.lang.System.Logger noBundleSysLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("oof", null, Thread.class); + final BackendTester noBundleSysTester = + factory.createBackendTester(true, spiLoggerClass); + testLogger(noBundleSysTester, noBundleSysLogger, nb); + + // Test a localized application logger with null resource bundle and + // JUL backend + try { + System.getLogger("baz", null); + throw new RuntimeException("Expected NullPointerException not thrown"); + } catch (NullPointerException x) { + System.out.println("System.Loggers.getLogger(\"baz\", null): got expected " + x); + } + final java.lang.System.Logger noBundleExtensionLogger = + getSystemLogger("baz", null, LoggerFinderBackendTest.class); + final BackendTester noBundleExtensionTester = + factory.createBackendTester(false, jdkLoggerClass); + testLogger(noBundleExtensionTester, noBundleExtensionLogger, nb); + + // Test a simple system logger with JUL backend + final java.lang.System.Logger sysExtensionLogger = + getSystemLogger("oof", Thread.class); + final BackendTester sysExtensionTester = + factory.createBackendTester(true, jdkLoggerClass); + testLogger(sysExtensionTester, sysExtensionLogger, nb); + + // Test a localized system logger with null resource bundle and JUL + // backend + final java.lang.System.Logger noBundleSysExtensionLogger = + getSystemLogger("oof", null, Thread.class); + final BackendTester noBundleSysExtensionTester = + factory.createBackendTester(true, jdkLoggerClass); + testLogger(noBundleSysExtensionTester, noBundleSysExtensionLogger, nb); + + // Test a localized application logger converted to JDK with null + // resource bundle and JUL backend + final java.lang.System.Logger noBundleConvertedLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(noBundleLogger); + final BackendTester noBundleJdkTester = factory.createBackendTester(false); + testLogger(noBundleJdkTester, noBundleConvertedLogger, nb); + + // Test a localized system logger converted to JDK with null resource + // bundle and JUL backend + final java.lang.System.Logger noBundleConvertedSysLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(noBundleSysLogger); + final BackendTester noBundleJdkSysTester = factory.createBackendTester(true); + testLogger(noBundleJdkSysTester, noBundleConvertedSysLogger, nb); + + // Test a localized application logger with resource bundle and JUL + // backend + final ResourceBundle bundle = + ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); + final java.lang.System.Logger bundleLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("toto", bundle, LoggerFinderBackendTest.class); + final BackendTester bundleTester = + factory.createBackendTester(false, spiLoggerClass, bundle); + testLogger(bundleTester, bundleLogger, nb); + + // Test a localized system logger with resource bundle and JUL backend + final java.lang.System.Logger bundleSysLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("titi", bundle, Thread.class); + final BackendTester bundleSysTester = + factory.createBackendTester(true, spiLoggerClass, bundle); + testLogger(bundleSysTester, bundleSysLogger, nb); + + // Test a localized Jdk application logger with resource bundle and JUL + // backend + final java.lang.System.Logger bundleExtensionLogger = + System.getLogger("tita", bundle); + final BackendTester bundleExtensionTester = + factory.createBackendTester(false, jdkLoggerClass, bundle); + testLogger(bundleExtensionTester, bundleExtensionLogger, nb); + + // Test a localized Jdk system logger with resource bundle and JUL + // backend + final java.lang.System.Logger bundleExtensionSysLogger = + getSystemLogger("titu", bundle, Thread.class); + final BackendTester bundleExtensionSysTester = + factory.createBackendTester(true, jdkLoggerClass, bundle); + testLogger(bundleExtensionSysTester, bundleExtensionSysLogger, nb); + + // Test a localized application logger converted to JDK with resource + // bundle and JUL backend + final BackendTester bundleJdkTester = + factory.createBackendTester(false, bundle); + final java.lang.System.Logger bundleConvertedLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(bundleLogger); + testLogger(bundleJdkTester, bundleConvertedLogger, nb); + + // Test a localized Jdk system logger converted to JDK with resource + // bundle and JUL backend + final BackendTester bundleJdkSysTester = + factory.createBackendTester(true, bundle); + final java.lang.System.Logger bundleConvertedSysLogger = + (java.lang.System.Logger) + sun.util.logging.PlatformLogger.Bridge.convert(bundleSysLogger); + testLogger(bundleJdkSysTester, bundleConvertedSysLogger, nb); + + // Now need to add tests for all the log/logp/logrb methods... + + } + + private static class FooObj { + final String s; + FooObj(String s) { + this.s = s; + } + + @Override + public String toString() { + return super.toString() +": "+s; + } + + } + + public static void testLogger(BackendTester tester, + java.lang.System.Logger spiLogger, AtomicInteger nb) { + + // Test all level-specific method forms: + // fatal(...) error(...) severe(...) etc... + java.lang.System.Logger jdkLogger = tester.convert(spiLogger); + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + bundleParam); + final int nbb = nb.incrementAndGet(); + tester.testLevel(l, logger, () -> l.method + "[" + logger.getName() + + "]-" + nbb); + } + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, + l.method + "[" + logger.getName()+ "]({0},{1})-" + + nb.incrementAndGet(), + "One", "Two"); + tester.testLevel(l, logger, + l.method + "[" + logger.getName()+ "]({0},{1})-" + + nb.incrementAndGet(), + bundleParam, "One", "Two"); + } + final Throwable thrown = new RuntimeException("Test"); + for (Levels l : Levels.values()) { + java.lang.System.Logger logger = + l.definingClass.equals(spiLoggerClass) ? spiLogger : jdkLogger; + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + thrown); + tester.testLevel(l, logger, l.method + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), + bundleParam, thrown); + final int nbb = nb.incrementAndGet(); + tester.testLevel(l, logger, ()->l.method + "[" + logger.getName()+ "]-" + + nbb, thrown); + } + + java.lang.System.Logger logger = jdkLogger; + + // test System.Logger methods + tester.testSpiLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testSpiLog(logger, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testSpiLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testSpiLog(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb01 = nb.incrementAndGet(); + tester.testSpiLog(logger, () -> "[" + logger.getName()+ "]-" + nbb01); + final int nbb02 = nb.incrementAndGet(); + tester.testSpiLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb02); + final int nbb03 = nb.incrementAndGet(); + tester.testSpiLog(logger, new FooObj("[" + logger.getName()+ "]-" + nbb03)); + + // Test all log method forms: + // jdk.internal.logging.Logger.log(...) + tester.testLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLog(logger, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLog(logger, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testLogrb(logger, bundleParam, "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb1 = nb.incrementAndGet(); + tester.testLog(logger, () -> "[" + logger.getName()+ "]-" + nbb1); + final int nbb2 = nb.incrementAndGet(); + tester.testLog(logger, thrown, () -> "[" + logger.getName()+ "]-" + nbb2); + + // Test all logp method forms + // jdk.internal.logging.Logger.logp(...) + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-" + + nb.incrementAndGet()); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-({0},{1})" + + nb.incrementAndGet(), "One", "Two"); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + tester.testLogrb(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), bundleParam, + "[" + logger.getName()+ "]-" + + nb.incrementAndGet(), thrown); + final int nbb3 = nb.incrementAndGet(); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + () -> "[" + logger.getName()+ "]-" + nbb3); + final int nbb4 = nb.incrementAndGet(); + tester.testLogp(logger, "clazz" + nb.incrementAndGet(), + "method" + nb.incrementAndGet(), + thrown, () -> "[" + logger.getName()+ "]-" + nbb4); + } + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder b/jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 00000000000..c4068ecdd4b --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1,2 @@ +LoggerFinderBackendTest$CustomLoggerFinder + diff --git a/jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java b/jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java new file mode 100644 index 00000000000..54a85911032 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/internal/backend/SystemClassLoader.java @@ -0,0 +1,98 @@ +/* + * 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. + * + * 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.URL; +import java.util.Enumeration; +import java.lang.System.LoggerFinder; + +/** + * A custom class loader which can hide the registered LoggerProvider + * depending on the value of a test.logger.hidesProvider system property. + * @author danielfuchs + */ +public class SystemClassLoader extends ClassLoader { + + final public boolean hidesProvider; + + public SystemClassLoader() { + hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + } + public SystemClassLoader(ClassLoader parent) { + super(parent); + hidesProvider = Boolean.getBoolean("test.logger.hidesProvider"); + } + + boolean accept(String name) { + final boolean res = !name.endsWith(LoggerFinder.class.getName()); + if (res == false) { + System.out.println("Hiding " + name); + } + return res; + } + + @Override + public URL getResource(String name) { + if (hidesProvider && !accept(name)) { + return null; + } else { + return super.getResource(name); + } + } + + class Enumerator implements Enumeration { + final Enumeration enumeration; + volatile URL next; + Enumerator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + @Override + public boolean hasMoreElements() { + if (next != null) return true; + if (!enumeration.hasMoreElements()) return false; + if (hidesProvider == false) return true; + next = enumeration.nextElement(); + if (accept(next.getPath())) return true; + next = null; + return hasMoreElements(); + } + + @Override + public URL nextElement() { + final URL res = next == null ? enumeration.nextElement() : next; + next = null; + if (hidesProvider == false || accept(res.getPath())) return res; + return nextElement(); + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + final Enumeration enumeration = super.getResources(name); + return hidesProvider ? new Enumerator(enumeration) : enumeration; + } + + + +} diff --git a/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java new file mode 100644 index 00000000000..6f15819fcd2 --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java @@ -0,0 +1,850 @@ +/* + * 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. + * + * 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.security.AccessControlException; +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.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.logging.Handler; +import java.util.logging.LogManager; +import sun.util.logging.PlatformLogger; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.util.stream.Stream; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @test + * @bug 8140364 + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests all internal bridge methods with the default LoggerFinder + * JUL backend. + * @modules java.base/sun.util.logging + * java.base/jdk.internal.logger + * java.logging/sun.util.logging.internal + * @run main/othervm DefaultLoggerBridgeTest + * @author danielfuchs + */ +public class DefaultLoggerBridgeTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAccess = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultLoggerBridgeTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + java.util.logging.Level.FINEST, + java.util.logging.Level.FINER, + java.util.logging.Level.FINE, + java.util.logging.Level.CONFIG, + java.util.logging.Level.INFO, + java.util.logging.Level.WARNING, + java.util.logging.Level.SEVERE, + java.util.logging.Level.OFF, + }; + + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + static PlatformLogger.Bridge convert(Logger logger) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return PlatformLogger.Bridge.convert(logger); + } finally { + allowAccess.get().set(old); + } + } + + static Logger getLogger(String name, Class caller) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return jdk.internal.logger.LazyLoggers.getLogger(name, caller); + } finally { + allowAccess.get().set(old); + } + } + + static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS}; + + static void setSecurityManager() { + if (System.getSecurityManager() == null) { + Policy.setPolicy(new SimplePolicy(allowControl, allowAccess, allowAll)); + System.setSecurityManager(new SecurityManager()); + } + } + + public static void main(String[] args) { + if (args.length == 0) + args = new String[] { + "NOSECURITY", + "NOPERMISSIONS", + "WITHPERMISSIONS" + }; + + Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> { + LoggerFinder provider; + switch (testCase) { + case NOSECURITY: + System.out.println("\n*** Without Security Manager\n"); + test(true); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case NOPERMISSIONS: + System.out.println("\n*** With Security Manager, without permissions\n"); + setSecurityManager(); + test(false); + System.out.println("Tetscase count: " + sequencer.get()); + break; + case WITHPERMISSIONS: + System.out.println("\n*** With Security Manager, with control permission\n"); + setSecurityManager(); + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + test(true); + } finally { + allowControl.get().set(control); + } + break; + default: + throw new RuntimeException("Unknown test case: " + testCase); + } + }); + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(boolean hasRequiredPermissions) { + + ResourceBundle loggerBundle = + ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + Logger sysLogger1a = getLogger("foo", Thread.class); + loggerDescMap.put(sysLogger1a, "jdk.internal.logger.LazyLoggers.getLogger(\"foo\", Thread.class)"); + + Logger appLogger1 = System.getLogger("foo"); + loggerDescMap.put(appLogger1, "System.getLogger(\"foo\")"); + + LoggerFinder provider; + try { + provider = LoggerFinder.getLoggerFinder(); + if (!hasRequiredPermissions) { + throw new RuntimeException("Expected exception not raised"); + } + } catch (AccessControlException x) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected permission check", x); + } + if (!SimplePolicy.LOGGERFINDER_PERMISSION.equals(x.getPermission())) { + throw new RuntimeException("Unexpected permission in exception: " + x, x); + } + final boolean control = allowControl.get().get(); + try { + allowControl.get().set(true); + provider = LoggerFinder.getLoggerFinder(); + } finally { + allowControl.get().set(control); + } + } + + Logger sysLogger1b = null; + try { + sysLogger1b = provider.getLogger("foo", Thread.class); + if (sysLogger1b != sysLogger1a) { + loggerDescMap.put(sysLogger1b, "provider.getLogger(\"foo\", Thread.class)"); + } + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for system logger: " + acx); + } + + Logger appLogger2 = System.getLogger("foo", loggerBundle); + loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)"); + + if (appLogger2 == appLogger1) { + throw new RuntimeException("identical loggers"); + } + + Logger sysLogger2 = null; + try { + sysLogger2 = provider.getLocalizedLogger("foo", loggerBundle, Thread.class); + loggerDescMap.put(sysLogger2, "provider.getLocalizedLogger(\"foo\", loggerBundle, Thread.class)"); + if (!hasRequiredPermissions) { + throw new RuntimeException("Managed to obtain a system logger without permission"); + } + } catch (AccessControlException acx) { + if (hasRequiredPermissions) { + throw new RuntimeException("Unexpected security exception: ", acx); + } + if (!acx.getPermission().equals(SimplePolicy.LOGGERFINDER_PERMISSION)) { + throw new RuntimeException("Unexpected permission in exception: " + acx, acx); + } + System.out.println("Got expected exception for localized system logger: " + acx); + } + if (hasRequiredPermissions && appLogger2 == sysLogger2) { + throw new RuntimeException("identical loggers"); + } + if (hasRequiredPermissions && sysLogger2 == sysLogger1a) { + throw new RuntimeException("identical loggers"); + } + + final java.util.logging.Logger appSink; + final java.util.logging.Logger sysSink; + final MyHandler appHandler; + final MyHandler sysHandler; + final boolean old = allowAll.get().get(); + allowAll.get().set(true); + try { + sysSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), "foo", Thread.class); + appSink = LoggingProviderImpl.getLogManagerAccess().demandLoggerFor( + LogManager.getLogManager(), "foo", DefaultLoggerBridgeTest.class); + if (appSink == sysSink) { + throw new RuntimeException("identical backend loggers"); + } + appSink.addHandler(appHandler = new MyHandler()); + sysSink.addHandler(sysHandler = new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + } finally { + allowAll.get().set(old); + } + + try { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1a), sysSink); + testLogger(provider, loggerDescMap, "foo", null, convert(appLogger1), appSink); + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(appLogger2), appSink); + if (sysLogger1b != null && sysLogger1b != sysLogger1a) { + testLogger(provider, loggerDescMap, "foo", null, convert(sysLogger1b), sysSink); + } + if (sysLogger2 != null) { + testLogger(provider, loggerDescMap, "foo", loggerBundle, convert(sysLogger2), sysSink); + } + } finally { + allowAll.get().set(true); + try { + appSink.removeHandler(appHandler); + sysSink.removeHandler(sysHandler); + } finally { + allowAll.get().set(old); + } + } + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + static sun.util.logging.PlatformLogger.Level toPlatformLevel(java.util.logging.Level level) { + boolean old = allowAccess.get().get(); + allowAccess.get().set(true); + try { + return sun.util.logging.PlatformLogger.Level.valueOf(level.getName()); + } finally { + allowAccess.get().set(old); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger.Bridge logger, + java.util.logging.Logger sink) { + + if (loggerDescMap.get(logger) == null) { + throw new RuntimeException("Missing description for " + logger); + } + System.out.println("Testing " + loggerDescMap.get(logger) + " [" + logger + "]"); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.log(messageLevel, fooMsg)"); + System.out.println("\tlogger.(fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.log(toPlatformLevel(messageLevel), fooMsg); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + Supplier supplier = new Supplier() { + @Override + public String get() { + return this.toString(); + } + }; + System.out.println("\tlogger.log(messageLevel, supplier)"); + System.out.println("\tlogger.(supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier.get(), (Throwable)null, (Object[])null); + logger.log(toPlatformLevel(messageLevel), supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.log(messageLevel, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.log(toPlatformLevel(messageLevel), format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.log(messageLevel, fooMsg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.log(toPlatformLevel(messageLevel), fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.log(messageLevel, thrown, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.log(messageLevel, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, null, + supplier.get(), thrown, (Object[])null); + logger.log(toPlatformLevel(messageLevel), thrown, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + String sourceClass = "blah.Blah"; + String sourceMethod = "blih"; + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier.get(), (Throwable)null, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + format, (Throwable)null, arg1, arg2); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, fooMsg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logp(messageLevel, sourceClass, sourceMethod, thrown, supplier): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, null, + supplier.get(), thrown, (Object[])null); + logger.logp(toPlatformLevel(messageLevel), sourceClass, sourceMethod, thrown, supplier); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName()); + System.out.println("\tlogger.logrb(messageLevel, bundle, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(toPlatformLevel(messageLevel), bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, bundle, msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(toPlatformLevel(messageLevel), bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, format, arg1, arg2): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + format, (Throwable)null, arg1, arg2); + logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, format, arg1, arg2); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + + System.out.println("\tlogger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julLevels) { + String desc = "logger.logrb(messageLevel, sourceClass, sourceMethod, bundle, msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, sourceClass, sourceMethod, messageLevel, bundle, + fooMsg, thrown, (Object[])null); + logger.logrb(toPlatformLevel(messageLevel), sourceClass, sourceMethod, bundle, fooMsg, thrown); + checkLogEvent(provider, desc, expected, expected.isLoggable); + } + } + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + final static RuntimePermission ACCESS_LOGGER = new RuntimePermission("accessClassInPackage.jdk.internal.logger"); + final static RuntimePermission ACCESS_LOGGING = new RuntimePermission("accessClassInPackage.sun.util.logging"); + + final Permissions permissions; + final Permissions allPermissions; + final ThreadLocal allowControl; + final ThreadLocal allowAccess; + final ThreadLocal allowAll; + public SimplePolicy(ThreadLocal allowControl, + ThreadLocal allowAccess, + ThreadLocal allowAll) { + this.allowControl = allowControl; + this.allowAccess = allowAccess; + this.allowAll = allowAll; + permissions = new Permissions(); + allPermissions = new PermissionsBuilder() + .add(new java.security.AllPermission()) + .toPermissions(); + } + + Permissions getPermissions() { + if (allowControl.get().get() || allowAccess.get().get() || allowAll.get().get()) { + PermissionsBuilder builder = new PermissionsBuilder() + .addAll(permissions); + if (allowControl.get().get()) { + builder.add(LOGGERFINDER_PERMISSION); + } + if (allowAccess.get().get()) { + builder.add(ACCESS_LOGGER); + builder.add(ACCESS_LOGGING); + } + if (allowAll.get().get()) { + builder.addAll(allPermissions); + } + return builder.toPermissions(); + } + return permissions; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + return getPermissions().implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll(getPermissions()).toPermissions(); + } + } +} diff --git a/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java new file mode 100644 index 00000000000..2eb64d0743d --- /dev/null +++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java @@ -0,0 +1,544 @@ +/* + * 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. + * + * 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.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.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Handler; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.lang.System.LoggerFinder; +import sun.util.logging.PlatformLogger; +import sun.util.logging.internal.LoggingProviderImpl; + +/** + * @test + * @bug 8140364 + * @summary Tests all PlatformLogger methods with the default LoggerFinder JUL backend. + * @modules java.base/sun.util.logging java.logging/sun.util.logging.internal + * @run main/othervm DefaultPlatformLoggerTest + * @author danielfuchs + */ +public class DefaultPlatformLoggerTest { + + final static AtomicLong sequencer = new AtomicLong(); + final static boolean VERBOSE = false; + static final ThreadLocal allowControl = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + static final ThreadLocal allowAll = new ThreadLocal() { + @Override + protected AtomicBoolean initialValue() { + return new AtomicBoolean(false); + } + }; + + public static final Queue eventQueue = new ArrayBlockingQueue<>(128); + + public static final class LogEvent implements Cloneable { + + public LogEvent() { + this(sequencer.getAndIncrement()); + } + + LogEvent(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + long sequenceNumber; + boolean isLoggable; + String loggerName; + java.util.logging.Level level; + ResourceBundle bundle; + Throwable thrown; + Object[] args; + String msg; + String className; + String methodName; + + Object[] toArray() { + return new Object[] { + sequenceNumber, + isLoggable, + loggerName, + level, + bundle, + thrown, + args, + msg, + className, + methodName, + }; + } + + @Override + public String toString() { + return Arrays.deepToString(toArray()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof LogEvent + && Objects.deepEquals(this.toArray(), ((LogEvent)obj).toArray()); + } + + @Override + public int hashCode() { + return Objects.hash(toArray()); + } + + public LogEvent cloneWith(long sequenceNumber) + throws CloneNotSupportedException { + LogEvent cloned = (LogEvent)super.clone(); + cloned.sequenceNumber = sequenceNumber; + return cloned; + } + + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + return LogEvent.of(sequenceNumber, isLoggable, name, + DefaultPlatformLoggerTest.class.getName(), + "testLogger", level, bundle, key, + thrown, params); + } + public static LogEvent of(long sequenceNumber, + boolean isLoggable, String name, + String className, String methodName, + java.util.logging.Level level, ResourceBundle bundle, + String key, Throwable thrown, Object... params) { + LogEvent evt = new LogEvent(sequenceNumber); + evt.loggerName = name; + evt.level = level; + evt.args = params; + evt.bundle = bundle; + evt.thrown = thrown; + evt.msg = key; + evt.isLoggable = isLoggable; + evt.className = className; + evt.methodName = methodName; + return evt; + } + + } + + static final java.util.logging.Level[] julLevels = { + java.util.logging.Level.ALL, + new java.util.logging.Level("FINER_THAN_FINEST", java.util.logging.Level.FINEST.intValue() - 10) {}, + java.util.logging.Level.FINEST, + new java.util.logging.Level("FINER_THAN_FINER", java.util.logging.Level.FINER.intValue() - 10) {}, + java.util.logging.Level.FINER, + new java.util.logging.Level("FINER_THAN_FINE", java.util.logging.Level.FINE.intValue() - 10) {}, + java.util.logging.Level.FINE, + new java.util.logging.Level("FINER_THAN_CONFIG", java.util.logging.Level.FINE.intValue() + 10) {}, + java.util.logging.Level.CONFIG, + new java.util.logging.Level("FINER_THAN_INFO", java.util.logging.Level.INFO.intValue() - 10) {}, + java.util.logging.Level.INFO, + new java.util.logging.Level("FINER_THAN_WARNING", java.util.logging.Level.INFO.intValue() + 10) {}, + java.util.logging.Level.WARNING, + new java.util.logging.Level("FINER_THAN_SEVERE", java.util.logging.Level.SEVERE.intValue() - 10) {}, + java.util.logging.Level.SEVERE, + new java.util.logging.Level("FATAL", java.util.logging.Level.SEVERE.intValue() + 10) {}, + java.util.logging.Level.OFF, + }; + + static final java.util.logging.Level[] julPlatformLevels = { + java.util.logging.Level.FINEST, + java.util.logging.Level.FINER, + java.util.logging.Level.FINE, + java.util.logging.Level.CONFIG, + java.util.logging.Level.INFO, + java.util.logging.Level.WARNING, + java.util.logging.Level.SEVERE, + }; + + + public static class MyBundle extends ResourceBundle { + + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Override + protected Object handleGetObject(String key) { + if (key.contains(" (translated)")) { + throw new RuntimeException("Unexpected key: " + key); + } + return map.computeIfAbsent(key, k -> k + " (translated)"); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(map.keySet()); + } + + } + + public static class MyHandler extends Handler { + + @Override + public java.util.logging.Level getLevel() { + return java.util.logging.Level.ALL; + } + + @Override + public void publish(LogRecord record) { + eventQueue.add(LogEvent.of(sequencer.getAndIncrement(), + true, record.getLoggerName(), + record.getSourceClassName(), + record.getSourceMethodName(), + record.getLevel(), + record.getResourceBundle(), record.getMessage(), + record.getThrown(), record.getParameters())); + } + @Override + public void flush() { + } + @Override + public void close() throws SecurityException { + } + + } + + public static class MyLoggerBundle extends MyBundle { + + } + + public static void main(String[] args) throws Exception { + LoggerFinder provider = LoggerFinder.getLoggerFinder(); + java.util.logging.Logger appSink = LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(), "foo", + DefaultPlatformLoggerTest.class); + java.util.logging.Logger sysSink = LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(),"foo", Thread.class); + appSink.addHandler(new MyHandler()); + sysSink.addHandler(new MyHandler()); + appSink.setUseParentHandlers(VERBOSE); + sysSink.setUseParentHandlers(VERBOSE); + + System.out.println("\n*** Without Security Manager\n"); + test(provider, true, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + + Policy.setPolicy(new SimplePolicy(allowAll, allowControl)); + System.setSecurityManager(new SecurityManager()); + + System.out.println("\n*** With Security Manager, without permissions\n"); + test(provider, false, appSink, sysSink); + System.out.println("Tetscase count: " + sequencer.get()); + + System.out.println("\n*** With Security Manager, with control permission\n"); + allowControl.get().set(true); + test(provider, true, appSink, sysSink); + + System.out.println("\nPASSED: Tested " + sequencer.get() + " cases."); + } + + public static void test(LoggerFinder provider, boolean hasRequiredPermissions, + java.util.logging.Logger appSink, java.util.logging.Logger sysSink) throws Exception { + + // No way to giva a resource bundle to a platform logger. + // ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName()); + final Map loggerDescMap = new HashMap<>(); + + PlatformLogger platform = PlatformLogger.getLogger("foo"); + loggerDescMap.put(platform, "PlatformLogger.getLogger(\"foo\")"); + + testLogger(provider, loggerDescMap, "foo", null, platform, sysSink); + } + + public static class Foo { + + } + + static void verbose(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected) { + LogEvent actual = eventQueue.poll(); + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void checkLogEvent(LoggerFinder provider, String desc, + LogEvent expected, boolean expectNotNull) { + LogEvent actual = eventQueue.poll(); + if (actual == null && !expectNotNull) return; + if (actual != null && !expectNotNull) { + throw new RuntimeException("Unexpected log event found for " + desc + + "\n\tgot: " + actual); + } + if (!expected.equals(actual)) { + throw new RuntimeException("mismatch for " + desc + + "\n\texpected=" + expected + + "\n\t actual=" + actual); + } else { + verbose("Got expected results for " + + desc + "\n\t" + expected); + } + } + + static void setLevel(java.util.logging.Logger sink, java.util.logging.Level loggerLevel) { + boolean before = allowAll.get().get(); + try { + allowAll.get().set(true); + sink.setLevel(loggerLevel); + } finally { + allowAll.get().set(before); + } + } + + // Calls the methods defined on LogProducer and verify the + // parameters received by the underlying logger. + private static void testLogger(LoggerFinder provider, + Map loggerDescMap, + String name, + ResourceBundle loggerBundle, + PlatformLogger logger, + java.util.logging.Logger sink) throws Exception { + + System.out.println("Testing " + loggerDescMap.get(logger)); + final java.util.logging.Level OFF = java.util.logging.Level.OFF; + + Foo foo = new Foo(); + String fooMsg = foo.toString(); + System.out.println("\tlogger.(fooMsg)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel : julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, (Throwable)null, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + Throwable thrown = new Exception("OK: log me!"); + System.out.println("\tlogger.(msg, thrown)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel :julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + fooMsg, thrown, (Object[])null); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(msg, thrown): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(fooMsg, thrown); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + String format = "two params [{1} {2}]"; + Object arg1 = foo; + Object arg2 = fooMsg; + System.out.println("\tlogger.(format, arg1, arg2)"); + for (java.util.logging.Level loggerLevel : julLevels) { + setLevel(sink, loggerLevel); + for (java.util.logging.Level messageLevel : julPlatformLevels) { + LogEvent expected = + LogEvent.of( + sequencer.get(), + loggerLevel != OFF && messageLevel.intValue() >= loggerLevel.intValue(), + name, messageLevel, loggerBundle, + format, (Throwable)null, foo, fooMsg); + String desc2 = "logger." + messageLevel.toString().toLowerCase() + + "(format, foo, fooMsg): loggerLevel=" + + loggerLevel+", messageLevel="+messageLevel; + if (messageLevel == java.util.logging.Level.FINEST) { + logger.finest(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINER) { + logger.finer(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.FINE) { + logger.fine(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.CONFIG) { + logger.config(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.INFO) { + logger.info(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.WARNING) { + logger.warning(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } else if (messageLevel == java.util.logging.Level.SEVERE) { + logger.severe(format, foo, fooMsg); + checkLogEvent(provider, desc2, expected, expected.isLoggable); + } + } + } + + } + + final static 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; + } + } + + public static class SimplePolicy extends Policy { + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + final Permissions permissions; + final Permissions withControlPermissions; + final Permissions allPermissions; + final ThreadLocal allowAll; + final ThreadLocal allowControl; + public SimplePolicy(ThreadLocal allowAll, + ThreadLocal allowControl) { + this.allowAll = allowAll; + this.allowControl = allowControl; + permissions = new Permissions(); + + withControlPermissions = new Permissions(); + withControlPermissions.add(LOGGERFINDER_PERMISSION); + + // 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); + if (allowControl.get().get()) return withControlPermissions.implies(permission); + return permissions.implies(permission); + } + + @Override + public PermissionCollection getPermissions(CodeSource codesource) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + return new PermissionsBuilder().addAll( + allowAll.get().get() ? allPermissions : + allowControl.get().get() + ? withControlPermissions : permissions).toPermissions(); + } + } +} diff --git a/jdk/test/java/util/logging/LoggerSubclass.java b/jdk/test/java/util/logging/LoggerSubclass.java index ae3d4af0db0..e1a989ee6f9 100644 --- a/jdk/test/java/util/logging/LoggerSubclass.java +++ b/jdk/test/java/util/logging/LoggerSubclass.java @@ -92,7 +92,7 @@ public class LoggerSubclass { else fail(x + " not equal to " + y);} public static void main(String[] args) throws Throwable { try {new LoggerSubclass().instanceMain(args);} - catch (Throwable e) {throw e.getCause();}} + catch (Throwable e) {throw e.getCause() == null ? e : e.getCause();}} public void instanceMain(String[] args) throws Throwable { try {test(args);} catch (Throwable t) {unexpected(t);} System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); diff --git a/jdk/test/sun/util/logging/PlatformLoggerTest.java b/jdk/test/sun/util/logging/PlatformLoggerTest.java index 968f35c6d64..c3719c96eb5 100644 --- a/jdk/test/sun/util/logging/PlatformLoggerTest.java +++ b/jdk/test/sun/util/logging/PlatformLoggerTest.java @@ -38,7 +38,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.logging.*; import sun.util.logging.PlatformLogger; -import sun.util.logging.LoggingSupport; import static sun.util.logging.PlatformLogger.Level.*; public class PlatformLoggerTest { @@ -195,7 +194,9 @@ public class PlatformLoggerTest { System.out.println("Testing Java Level with: " + level.getName()); // create a brand new java logger - Logger javaLogger = (Logger) LoggingSupport.getLogger(logger.getName()+"."+level.getName()); + Logger javaLogger = sun.util.logging.internal.LoggingProviderImpl.getLogManagerAccess() + .demandLoggerFor(LogManager.getLogManager(), + logger.getName()+"."+level.getName(), Thread.class); // Set a non standard java.util.logging.Level on the java logger // (except for OFF & ALL - which will remain unchanged) From 138d45b4769669eeb5a6d39f7312ffea890df205 Mon Sep 17 00:00:00 2001 From: Kumar Srinivasan Date: Mon, 28 Sep 2015 08:42:06 -0700 Subject: [PATCH 10/12] 8066272: pack200 must support Multi-Release Jars Reviewed-by: jrose, sdrach --- .../sun/java/util/jar/pack/PackerImpl.java | 47 ++-- jdk/test/tools/pack200/MultiRelease.java | 257 ++++++++++++++++++ 2 files changed, 284 insertions(+), 20 deletions(-) create mode 100644 jdk/test/tools/pack200/MultiRelease.java diff --git a/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java b/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java index 5e7da4bb6d4..203fde39335 100644 --- a/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java +++ b/jdk/src/java.base/share/classes/com/sun/java/util/jar/pack/PackerImpl.java @@ -272,22 +272,6 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { // (Done collecting options from props.) - boolean isClassFile(String name) { - if (!name.endsWith(".class")) return false; - for (String prefix = name; ; ) { - if (passFiles.contains(prefix)) return false; - int chop = prefix.lastIndexOf('/'); - if (chop < 0) break; - prefix = prefix.substring(0, chop); - } - return true; - } - - boolean isMetaInfFile(String name) { - return name.startsWith("/" + Utils.METAINF) || - name.startsWith(Utils.METAINF); - } - // Get a new package, based on the old one. private void makeNextPackage() { pkg.reset(); @@ -332,6 +316,29 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { InFile(JarEntry je) { this(null, je); } + boolean isClassFile() { + if (!name.endsWith(".class")) { + return false; + } + for (String prefix = name;;) { + if (passFiles.contains(prefix)) { + return false; + } + int chop = prefix.lastIndexOf('/'); + if (chop < 0) { + break; + } + prefix = prefix.substring(0, chop); + } + return true; + } + boolean isMetaInfFile() { + return name.startsWith("/" + Utils.METAINF) + || name.startsWith(Utils.METAINF); + } + boolean mustProcess() { + return !isMetaInfFile() && isClassFile(); + } long getInputLength() { long len = (je != null)? je.getSize(): f.length(); assert(len >= 0) : this+".len="+len; @@ -391,7 +398,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { Package.File file = null; // (5078608) : discount the resource files in META-INF // from segment computation. - long inflen = (isMetaInfFile(name)) + long inflen = (inFile.isMetaInfFile()) ? 0L : inFile.getInputLength(); @@ -406,7 +413,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { assert(je.isDirectory() == name.endsWith("/")); - if (isClassFile(name)) { + if (inFile.mustProcess()) { file = readClass(name, bits.getInputStream()); } if (file == null) { @@ -429,7 +436,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { for (InFile inFile : inFiles) { String name = inFile.name; // (5078608) : discount the resource files completely from segmenting - long inflen = (isMetaInfFile(name)) + long inflen = (inFile.isMetaInfFile()) ? 0L : inFile.getInputLength() ; if ((segmentSize += inflen) > segmentLimit) { @@ -447,7 +454,7 @@ public class PackerImpl extends TLGlobals implements Pack200.Packer { if (verbose > 1) Utils.log.fine("Reading " + name); Package.File file = null; - if (isClassFile(name)) { + if (inFile.mustProcess()) { file = readClass(name, strm); if (file == null) { strm.close(); diff --git a/jdk/test/tools/pack200/MultiRelease.java b/jdk/test/tools/pack200/MultiRelease.java new file mode 100644 index 00000000000..7b3f8a3b91a --- /dev/null +++ b/jdk/test/tools/pack200/MultiRelease.java @@ -0,0 +1,257 @@ +/* + * 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. + * + * 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). + * + r 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 java.util.ArrayList; +import java.util.List; + +/* + * @test + * @bug 8066272 + * @summary tests a simple multi-versioned jar file + * @compile -XDignore.symbol.file Utils.java MultiRelease.java + * @run main MultiRelease + * @author ksrini + */ + +public class MultiRelease { + private static final File cwd = new File("."); + private static int pass = 0; + private static int fail = 0; + // specify alternate name via arguments to verify + // if permanent fix works + + private static final String PropKey = "pack200.MultiRelease.META-INF"; + private static final String MetaInfName = System.getProperty(PropKey, "META-INF"); + + public static void main(String... args) throws Exception { + new MultiRelease().run(); + } + + void run() throws Exception { + List testCases = new ArrayList<>(); + testCases.add(new TestCase1()); + testCases.add(new TestCase2()); + for (TestCase tc : testCases) { + tc.run(); + } + if (fail > 0) { + throw new Exception(fail + "/" + testCases.size() + " tests fails"); + } else { + System.out.println("All tests(" + pass + ") passes"); + } + } + + /* + * An abstract class to eliminate test boiler plating. + */ + static abstract class TestCase { + final File tcwd; + final File metaInfDir; + final File versionsDir; + final File manifestFile; + + TestCase(String directory) throws IOException { + System.out.println("initializing directories"); + tcwd = new File(cwd, directory); + metaInfDir = mkdir(new File(tcwd, MetaInfName)); + versionsDir = mkdir(new File(metaInfDir, "versions")); + manifestFile = new File(tcwd, "manifest.tmp"); + List scratch = new ArrayList<>(); + scratch.add("Multi-Release: true"); + Utils.createFile(manifestFile, scratch); + } + + File mkdir(File f) throws IOException { + if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite()) { + return f; + } + if (!f.mkdirs()) { + throw new IOException("mkdirs failed: " + f.getAbsolutePath()); + } + return f; + } + + abstract void emitClassFiles() throws Exception; + + void run() { + try { + emitClassFiles(); + // jar the file up + File testFile = new File(tcwd, "test" + Utils.JAR_FILE_EXT); + Utils.jar("cvfm", + testFile.getAbsolutePath(), + manifestFile.getAbsolutePath(), + "-C", + tcwd.getAbsolutePath(), + "."); + File outFile = new File(tcwd, "test-repacked" + Utils.JAR_FILE_EXT); + List cmdsList = new ArrayList<>(); + + cmdsList.add(Utils.getPack200Cmd()); + cmdsList.add("-J-ea"); + cmdsList.add("-J-esa"); + cmdsList.add("-v"); + cmdsList.add("--repack"); + cmdsList.add(outFile.getAbsolutePath()); + cmdsList.add(testFile.getAbsolutePath()); + List output = Utils.runExec(cmdsList); + Utils.doCompareVerify(testFile.getAbsoluteFile(), outFile.getAbsoluteFile()); + pass++; + } catch (Exception e) { + e.printStackTrace(System.err); + fail++; + } + } + } + + static class TestCase1 extends TestCase { + private TestCase1(String directory) throws IOException { + super(directory); + } + + public TestCase1() throws Exception { + this("case1"); + } + + @Override + void emitClassFiles() throws Exception { + emitClassFile(""); + emitClassFile("7"); + emitClassFile("8"); + emitClassFile("9"); + } + + /* + * Adds different variants of types + */ + void emitClassFile(String version) throws IOException { + final File outDir = mkdir(version.isEmpty() + ? tcwd + : new File(versionsDir, version)); + + final File srcDir = mkdir(version.isEmpty() + ? new File(tcwd, "src") + : new File(new File(versionsDir, version), "src")); + + final String fname = "Foo"; + final File srcFile = new File(srcDir, fname + Utils.JAVA_FILE_EXT); + List scratch = new ArrayList<>(); + + scratch.add("package pkg;"); + switch (version) { + case "7": + scratch.add("public class Foo {"); + scratch.add("public static final class Bar {}"); + break; + case "8": + scratch.add("public abstract class Foo {"); + scratch.add("public final class Bar {}"); + break; + case "9": + scratch.add("public interface Foo {"); + scratch.add("public final class Bar {}"); + break; + default: + scratch.add("public class Foo {"); + scratch.add("public final class Bar {}"); + break; + } + scratch.add("}"); + + Utils.createFile(srcFile, scratch); + Utils.compiler("-d", + outDir.getAbsolutePath(), + srcFile.getAbsolutePath()); + } + } + + static class TestCase2 extends TestCase { + private TestCase2(String directory) throws IOException { + super(directory); + } + + TestCase2() throws Exception { + this("case2"); + } + + @Override + void emitClassFiles() throws Exception { + emitClassFile(""); + emitClassFile("8"); + } + + /* + * Adds different variants of types and tries to invoke an + * interface or concrete method defined by them. + */ + void emitClassFile(String version) throws IOException { + + final File outDir = mkdir(version.isEmpty() + ? tcwd + : new File(versionsDir, version)); + + final File srcDir = mkdir(version.isEmpty() + ? new File(tcwd, "src") + : new File(new File(versionsDir, version), "src")); + + List scratch = new ArrayList<>(); + final String fname1 = "Ab"; + final File srcFile1 = new File(srcDir, fname1 + Utils.JAVA_FILE_EXT); + + final String fname2 = "AbNormal"; + final File srcFile2 = new File(srcDir, fname2 + Utils.JAVA_FILE_EXT); + switch (version) { + case "8": + scratch.clear(); + scratch.add("import java.io.IOException;"); + scratch.add("public interface " + fname1 + "{"); + scratch.add(" public abstract void close() throws IOException ;"); + scratch.add("}"); + Utils.createFile(srcFile1, scratch); + break; + default: + scratch.clear(); + scratch.add("import java.io.IOException;"); + scratch.add("public abstract class " + fname1 + "{"); + scratch.add(" public abstract void close() throws IOException ;"); + scratch.add("}"); + Utils.createFile(srcFile1, scratch); + } + + scratch.clear(); + scratch.add("import java.io.IOException;"); + scratch.add("public class " + fname2 + "{"); + scratch.add(" public void doSomething(Ab ab) throws IOException {"); + scratch.add(" ab.close();"); + scratch.add(" }"); + scratch.add("}"); + + Utils.createFile(srcFile2, scratch); + Utils.compiler("-d", + outDir.getAbsolutePath(), + srcFile1.getAbsolutePath(), + srcFile2.getAbsolutePath()); + } + } +} From dad26e258cae0bbeab893b90b6eb552065f8da30 Mon Sep 17 00:00:00 2001 From: Amy Lu Date: Mon, 23 Nov 2015 16:14:33 +0800 Subject: [PATCH 11/12] 8143583: Several tests don't work with latest jtreg due to non-existing files in @build Reviewed-by: alanb, sla --- jdk/test/com/sun/jdi/DoubleAgentTest.java | 2 +- jdk/test/com/sun/jdi/SuspendNoFlagTest.java | 2 +- .../com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java | 4 ++-- jdk/test/sun/tools/jmap/BasicJMapTest.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jdk/test/com/sun/jdi/DoubleAgentTest.java b/jdk/test/com/sun/jdi/DoubleAgentTest.java index 91babd72e26..be3b8d8ffdc 100644 --- a/jdk/test/com/sun/jdi/DoubleAgentTest.java +++ b/jdk/test/com/sun/jdi/DoubleAgentTest.java @@ -31,7 +31,7 @@ import jdk.testlibrary.Utils; * * @library /lib/testlibrary * @modules java.management - * @build jdk.testlibarary.* + * @build jdk.testlibrary.* * @build DoubleAgentTest Exit0 * @run driver DoubleAgentTest */ diff --git a/jdk/test/com/sun/jdi/SuspendNoFlagTest.java b/jdk/test/com/sun/jdi/SuspendNoFlagTest.java index 8f91db2bac5..346dcca8923 100644 --- a/jdk/test/com/sun/jdi/SuspendNoFlagTest.java +++ b/jdk/test/com/sun/jdi/SuspendNoFlagTest.java @@ -29,7 +29,7 @@ import jdk.testlibrary.ProcessTools; * @summary Test for JDWP: -agentlib:jdwp=suspend=n hanging * @library /lib/testlibrary * @modules java.management - * @build jdk.testlibarary.* + * @build jdk.testlibrary.* * @compile -g HelloWorld.java * @run driver SuspendNoFlagTest */ diff --git a/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java b/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java index 1a64ae0d0a5..8a2c224f461 100644 --- a/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java +++ b/jdk/test/com/sun/management/HotSpotDiagnosticMXBean/DumpHeap.java @@ -41,9 +41,9 @@ import com.sun.management.HotSpotDiagnosticMXBean; * @library /test/lib/share/classes * @build jdk.testlibrary.* * @build jdk.test.lib.hprof.* - * @build jdk.test.lib.hprof.module.* + * @build jdk.test.lib.hprof.model.* * @build jdk.test.lib.hprof.parser.* - * @build jdk.test.lib.hprof.utils.* + * @build jdk.test.lib.hprof.util.* * @run main DumpHeap */ public class DumpHeap { diff --git a/jdk/test/sun/tools/jmap/BasicJMapTest.java b/jdk/test/sun/tools/jmap/BasicJMapTest.java index 03efac77cf0..1ddb903a099 100644 --- a/jdk/test/sun/tools/jmap/BasicJMapTest.java +++ b/jdk/test/sun/tools/jmap/BasicJMapTest.java @@ -42,9 +42,9 @@ import jdk.testlibrary.ProcessTools; * @modules java.management * @build jdk.testlibrary.* * @build jdk.test.lib.hprof.* - * @build jdk.test.lib.hprof.module.* + * @build jdk.test.lib.hprof.model.* * @build jdk.test.lib.hprof.parser.* - * @build jdk.test.lib.hprof.utils.* + * @build jdk.test.lib.hprof.util.* * @run main/timeout=240 BasicJMapTest */ public class BasicJMapTest { From bcdeeca8008f72527bd307ea4971f1226c860339 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Mon, 23 Nov 2015 08:11:25 -0800 Subject: [PATCH 12/12] 8143813: Problem list PKCS8Test.java Reviewed-by: mullan --- jdk/test/ProblemList.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jdk/test/ProblemList.txt b/jdk/test/ProblemList.txt index cb33d6e7eae..d68562e043e 100644 --- a/jdk/test/ProblemList.txt +++ b/jdk/test/ProblemList.txt @@ -217,6 +217,9 @@ java/rmi/activation/Activatable/extLoadedImpl/ext.sh generic-all # jdk_security +# 8143377 +sun/security/pkcs/pkcs8/PKCS8Test.java solaris-all + # 7157786 sun/security/pkcs11/ec/TestKeyFactory.java generic-all