diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java index a4f217f5a51c3b1c66b083fedbf3eb03df8bece8..730c30e716258be0d6711ce2ce8d0917e6989dc9 100644 --- a/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/share/classes/java/lang/invoke/MethodHandles.java @@ -1204,6 +1204,26 @@ return mh1; 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); diff --git a/test/java/lang/invoke/MethodHandlesTest.java b/test/java/lang/invoke/MethodHandlesTest.java index d7e3626d55ca619834f611985bbca36c343ec7a7..092eeb4eedaacef6112eb600add593613f8933da 100644 --- a/test/java/lang/invoke/MethodHandlesTest.java +++ b/test/java/lang/invoke/MethodHandlesTest.java @@ -140,7 +140,7 @@ public class MethodHandlesTest { 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 @@ public class MethodHandlesTest { } 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 @@ public class MethodHandlesTest { { 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 @@ public class MethodHandlesTest { 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 @@ public class MethodHandlesTest { 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 @@ public class MethodHandlesTest { 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(':'); }