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 extends Certificate> engineGenerateCertificates(InputStream inStream) throws CertificateException {
+ throw new CertificateException("unimplemented");
+ }
+
+ @Override
+ public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
+ throw new CRLException("unimplemented");
+ }
+
+ @Override
+ public Collection extends CRL> 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 extends Certificate> 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 extends ZipEntry> 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 extends ZipEntry> 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 extends ZipEntry> 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 extends Certificate> 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 extends ZipEntry> 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 extends ZipEntry> 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 extends ZipEntry> 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.
+ *
+ * @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.
+ *
+ * @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:
*
*
*
lookup expression
@@ -235,6 +234,10 @@ public class MethodHandles {
*
*
* 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.
+ *
The clause array (of type {@code MethodHandle[][]} must be non-{@code null} and contain at least one element.
+ *
The clause array may not contain {@code null}s or sub-arrays longer than four elements.
+ *
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.
+ *
Clauses with all {@code null}s are disregarded.
+ *
Each clause is treated as a four-tuple of functions, called "init", "step", "pred", and "fini".
+ *
+ *
+ * Step 1A: Determine iteration variables.
+ *
Examine init and step function return types, pairwise, to determine each clause's iteration variable type.
+ *
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).
+ *
Form the list of return types (in clause order), omitting all occurrences of {@code void}.
+ *
This list of types is called the "common prefix".
+ *
+ *
+ * Step 1B: Determine loop parameters.
+ *
Examine init function parameter lists.
+ *
Omitted init functions are deemed to have {@code null} parameter lists.
+ *
All init function parameter lists must be effectively identical.
+ *
The longest parameter list (which is necessarily unique) is called the "common suffix".
+ *
+ *
+ * Step 1C: Determine loop return type.
+ *
Examine fini function return types, disregarding omitted fini functions.
+ *
If there are no fini functions, use {@code void} as the loop return type.
+ *
Otherwise, use the common return type of the fini functions; they must all be identical.
+ *
+ *
+ * Step 1D: Check other types.
+ *
There must be at least one non-omitted pred function.
+ *
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.
+ *
The parameter list for the resulting loop handle will be the "common suffix".
+ *
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.)
+ *
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".
+ *
Every non-init, non-omitted function parameter list must be effectively identical to the common parameter
+ * sequence.
+ *
+ *
+ * Step 3: Fill in omitted functions.
+ *
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}.)
+ *
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.)
+ *
If a pred function is omitted, the corresponding fini function must also be omitted.
+ *
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.)
+ *
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.
+ *
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}.
+ *
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.
+ *
After these steps, all clauses have been adjusted by supplying omitted functions and arguments.
+ *
All init functions have a common parameter type list, which the final loop handle will also have.
+ *
All fini functions have a common return type, which the final loop handle will also have.
+ *
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.
+ *
Each pair of init and step functions agrees in their return types.
+ *
Each non-init function will be able to observe the current values of all iteration variables, by means of the
+ * common prefix.
+ *
+ *
+ * Loop execution.
+ *
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.
+ *
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).
+ *
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).
+ *
The step and pred functions are then executed, in clause order (step before pred), until a pred function
+ * returns {@code false}.
+ *
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.
+ *
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
+ * // 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:
+ *
+ *
+ * @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:
+ *
+ *
+ *
+ * @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:
+ *
+ *
+ * @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:
+ *
+ *
+ * @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.
+ *
{@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 extends Object[]> cls = (Class extends Object[]>) cls0;
//Class extends Object[]> 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 extends Object[]> cls = (Class extends Object[]>) 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 extends Object[]> 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