changeset 8311:97d5cc1e7586

8001105: findVirtual of Object[].clone produces internal error Summary: Replicate JVM logic for access control that makes Object.clone appear public when applied to an array type. Reviewed-by: twisti
author jrose
date Sat, 05 Oct 2013 05:30:38 -0700
parents 44da760eed4b
children 91535ade7349
files src/share/classes/java/lang/invoke/MethodHandles.java test/java/lang/invoke/MethodHandlesTest.java
diffstat 2 files changed, 74 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/lang/invoke/MethodHandles.java	Sat Oct 05 05:30:38 2013 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandles.java	Sat Oct 05 05:30:38 2013 -0700
@@ -1204,6 +1204,26 @@
             int allowedModes = this.allowedModes;
             if (allowedModes == TRUSTED)  return;
             int mods = m.getModifiers();
+            if (Modifier.isProtected(mods) &&
+                    refKind == REF_invokeVirtual &&
+                    m.getDeclaringClass() == Object.class &&
+                    m.getName().equals("clone") &&
+                    refc.isArray()) {
+                // The JVM does this hack also.
+                // (See ClassVerifier::verify_invoke_instructions
+                // and LinkResolver::check_method_accessability.)
+                // Because the JVM does not allow separate methods on array types,
+                // there is no separate method for int[].clone.
+                // All arrays simply inherit Object.clone.
+                // But for access checking logic, we make Object.clone
+                // (normally protected) appear to be public.
+                // Later on, when the DirectMethodHandle is created,
+                // its leading argument will be restricted to the
+                // requested array type.
+                // N.B. The return type is not adjusted, because
+                // that is *not* the bytecode behavior.
+                mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
+            }
             if (Modifier.isFinal(mods) &&
                     MethodHandleNatives.refKindIsSetter(refKind))
                 throw m.makeAccessException("unexpected set of a final field", this);
--- a/test/java/lang/invoke/MethodHandlesTest.java	Sat Oct 05 05:30:38 2013 -0700
+++ b/test/java/lang/invoke/MethodHandlesTest.java	Sat Oct 05 05:30:38 2013 -0700
@@ -140,7 +140,7 @@
         Object actual   = calledLog.get(calledLog.size() - 1);
         if (expected.equals(actual) && verbosity < 9)  return;
         System.out.println("assertCalled "+name+":");
-        System.out.println("expected:   "+expected);
+        System.out.println("expected:   "+deepToString(expected));
         System.out.println("actual:     "+actual);
         System.out.println("ex. types:  "+getClasses(expected));
         System.out.println("act. types: "+getClasses(actual));
@@ -148,7 +148,25 @@
     }
     static void printCalled(MethodHandle target, String name, Object... args) {
         if (verbosity >= 3)
-            System.out.println("calling MH="+target+" to "+name+Arrays.toString(args));
+            System.out.println("calling MH="+target+" to "+name+deepToString(args));
+    }
+    static String deepToString(Object x) {
+        if (x == null)  return "null";
+        if (x instanceof Collection)
+            x = ((Collection)x).toArray();
+        if (x instanceof Object[]) {
+            Object[] ax = (Object[]) x;
+            ax = Arrays.copyOf(ax, ax.length, Object[].class);
+            for (int i = 0; i < ax.length; i++)
+                ax[i] = deepToString(ax[i]);
+            x = Arrays.deepToString(ax);
+        }
+        if (x.getClass().isArray())
+            try {
+                x = Arrays.class.getMethod("toString", x.getClass()).invoke(null, x);
+            } catch (ReflectiveOperationException ex) { throw new Error(ex); }
+        assert(!(x instanceof Object[]));
+        return x.toString();
     }
 
     static Object castToWrapper(Object value, Class<?> dst) {
@@ -230,6 +248,12 @@
                     { param = c; break; }
             }
         }
+        if (param.isArray()) {
+            Class<?> ctype = param.getComponentType();
+            Object arg = Array.newInstance(ctype, 2);
+            Array.set(arg, 0, randomArg(ctype));
+            return arg;
+        }
         if (param.isInterface() && param.isAssignableFrom(List.class))
             return Arrays.asList("#"+nextArg());
         if (param.isInterface() || param.isAssignableFrom(String.class))
@@ -568,6 +592,16 @@
         testFindVirtual(IntExample.Impl.class, IntExample.class, void.class, "Int/v0");
     }
 
+    @Test
+    public void testFindVirtualClone() throws Throwable {
+        // test some ad hoc system methods
+        testFindVirtual(false, PUBLIC, Object.class, Object.class, "clone");
+        testFindVirtual(true, PUBLIC, Object[].class, Object.class, "clone");
+        testFindVirtual(true, PUBLIC, int[].class, Object.class, "clone");
+        for (Class<?> cls : new Class<?>[]{ boolean[].class, long[].class, float[].class, char[].class })
+            testFindVirtual(true, PUBLIC, cls, Object.class, "clone");
+    }
+
     void testFindVirtual(Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
         Class<?> rcvc = defc;
         testFindVirtual(rcvc, defc, ret, name, params);
@@ -580,6 +614,9 @@
     void testFindVirtual(Lookup lookup, Class<?> rcvc, Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
         testFindVirtual(true, lookup, rcvc, defc, ret, name, params);
     }
+    void testFindVirtual(boolean positive, Lookup lookup, Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
+        testFindVirtual(positive, lookup, defc, defc, ret, name, params);
+    }
     void testFindVirtual(boolean positive, Lookup lookup, Class<?> rcvc, Class<?> defc, Class<?> ret, String name, Class<?>... params) throws Throwable {
         countTest(positive);
         String methodName = name.substring(1 + name.indexOf('/'));  // foo/bar => foo
@@ -618,8 +655,21 @@
         Object[] argsWithSelf = randomArgs(paramsWithSelf);
         if (selfc.isAssignableFrom(rcvc) && rcvc != selfc)  argsWithSelf[0] = randomArg(rcvc);
         printCalled(target, name, argsWithSelf);
-        target.invokeWithArguments(argsWithSelf);
-        assertCalled(name, argsWithSelf);
+        Object res = target.invokeWithArguments(argsWithSelf);
+        if (Example.class.isAssignableFrom(defc) || IntExample.class.isAssignableFrom(defc)) {
+            assertCalled(name, argsWithSelf);
+        } else if (name.equals("clone")) {
+            // Ad hoc method call outside Example.  For Object[].clone.
+            printCalled(target, name, argsWithSelf);
+            assertEquals(MethodType.methodType(Object.class, rcvc), target.type());
+            Object orig = argsWithSelf[0];
+            assertEquals(orig.getClass(), res.getClass());
+            if (res instanceof Object[])
+                assertArrayEquals((Object[])res, (Object[])argsWithSelf[0]);
+            assert(Arrays.deepEquals(new Object[]{res}, new Object[]{argsWithSelf[0]}));
+        } else {
+            assert(false) : Arrays.asList(positive, lookup, rcvc, defc, ret, name, deepToString(params));
+        }
         if (verbosity >= 1)
             System.out.print(':');
     }