From d50a168dede4254c18f7d337e18b2bb4bef9db2e Mon Sep 17 00:00:00 2001 From: John R Rose Date: Wed, 1 Jun 2011 23:56:43 -0700 Subject: [PATCH 1/4] 7049415: Failure of resolution of sym.reference to the c.s.s. should be wrapped in BootstrapMethodError Wrap invokedynamic linkage errors in BootstrapMethodError, as needed. Reviewed-by: never --- .../share/classes/java/lang/invoke/MethodHandleNatives.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java index daf64224cb8..0d17f4a5cb8 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -359,6 +359,12 @@ class MethodHandleNatives { required = Object[].class; // should have been an array code = 192; // checkcast break; + case 191: // athrow + // JVM is asking us to wrap an exception which happened during resolving + if (required == BootstrapMethodError.class) { + throw new BootstrapMethodError((Throwable) actual); + } + break; } // disregard the identity of the actual object, if it is not a class: if (message == null) { From f32795386e332acf39af334119304144808ea2bf Mon Sep 17 00:00:00 2001 From: John R Rose Date: Wed, 1 Jun 2011 23:56:47 -0700 Subject: [PATCH 2/4] 7050328: (jsr-292) findConstructor throws ExceptionInInitializerError if run under SecurityManager Wrap system property and reflection accesses under doPrivileged. Ensure constant pool linkage bypasses the SM as specified. Reviewed-by: kvn, never --- .../java/lang/invoke/MethodHandleImpl.java | 26 ++++--- .../java/lang/invoke/MethodHandleNatives.java | 13 +--- .../java/lang/invoke/MethodHandleStatics.java | 15 +++- .../java/lang/invoke/MethodHandles.java | 72 ++++++++++++++++++- .../sun/invoke/util/ValueConversions.java | 14 +++- .../lang/invoke/InvokeDynamicPrintArgs.java | 47 +++++++++++- 6 files changed, 160 insertions(+), 27 deletions(-) diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java index 06811feaf42..5437e85a34d 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -26,6 +26,8 @@ package java.lang.invoke; import sun.invoke.util.VerifyType; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -336,18 +338,20 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; void setFieldL(C obj, V x) { unsafe.putObject(obj, offset, x); } // cast (V) is OK here, since we wrap convertArguments around the MH. - static Object staticBase(MemberName field) { + static Object staticBase(final MemberName field) { if (!field.isStatic()) return null; - Class c = field.getDeclaringClass(); - java.lang.reflect.Field f; - try { - // FIXME: Should not have to create 'f' to get this value. - f = c.getDeclaredField(field.getName()); - // Note: Previous line might invalidly throw SecurityException (7042829) - return unsafe.staticFieldBase(f); - } catch (NoSuchFieldException ee) { - throw uncaughtException(ee); - } + return AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Class c = field.getDeclaringClass(); + // FIXME: Should not have to create 'f' to get this value. + java.lang.reflect.Field f = c.getDeclaredField(field.getName()); + return unsafe.staticFieldBase(f); + } catch (NoSuchFieldException ee) { + throw uncaughtException(ee); + } + } + }); } int getStaticI() { return unsafe.getInt(base, offset); } diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java index 0d17f4a5cb8..ef3df63a3e4 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -395,18 +395,7 @@ class MethodHandleNatives { Class defc, String name, Object type) { try { Lookup lookup = IMPL_LOOKUP.in(callerClass); - switch (refKind) { - case REF_getField: return lookup.findGetter( defc, name, (Class) type ); - case REF_getStatic: return lookup.findStaticGetter( defc, name, (Class) type ); - case REF_putField: return lookup.findSetter( defc, name, (Class) type ); - case REF_putStatic: return lookup.findStaticSetter( defc, name, (Class) type ); - case REF_invokeVirtual: return lookup.findVirtual( defc, name, (MethodType) type ); - case REF_invokeStatic: return lookup.findStatic( defc, name, (MethodType) type ); - case REF_invokeSpecial: return lookup.findSpecial( defc, name, (MethodType) type, callerClass ); - case REF_newInvokeSpecial: return lookup.findConstructor( defc, (MethodType) type ); - case REF_invokeInterface: return lookup.findVirtual( defc, name, (MethodType) type ); - } - throw new InternalError("bad MethodHandle constant "+name+" : "+type); + return lookup.linkMethodHandleConstant(refKind, defc, name, type); } catch (ReflectiveOperationException ex) { Error err = new IncompatibleClassChangeError(); err.initCause(ex); diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java b/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java index 61c44277874..e812d245bbc 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleStatics.java @@ -25,6 +25,9 @@ package java.lang.invoke; +import java.security.AccessController; +import java.security.PrivilegedAction; + /** * This class consists exclusively of static names internal to the * method handle implementation. @@ -35,7 +38,17 @@ package java.lang.invoke; private MethodHandleStatics() { } // do not instantiate - static final boolean DEBUG_METHOD_HANDLE_NAMES = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); + static final boolean DEBUG_METHOD_HANDLE_NAMES; + static { + final Object[] values = { false }; + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + values[0] = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); + return null; + } + }); + DEBUG_METHOD_HANDLE_NAMES = (Boolean) values[0]; + } /*non-public*/ static String getNameString(MethodHandle target, MethodType type) { if (type == null) diff --git a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java index 42de213459a..9e18b4b1aef 100644 --- a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; import sun.reflect.Reflection; import static java.lang.invoke.MethodHandleStatics.*; +import static java.lang.invoke.MethodHandleNatives.Constants.*; /** * This class consists exclusively of static methods that operate on or return @@ -579,9 +580,18 @@ public class MethodHandles { MethodHandle findStatic(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { MemberName method = resolveOrFail(refc, name, type, true); checkSecurityManager(refc, method); // stack walk magic: do not refactor + return accessStatic(refc, method); + } + private + MethodHandle accessStatic(Class refc, MemberName method) throws IllegalAccessException { checkMethod(refc, method, true); return MethodHandleImpl.findMethod(method, false, lookupClassOrNull()); } + private + MethodHandle resolveStatic(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + MemberName method = resolveOrFail(refc, name, type, true); + return accessStatic(refc, method); + } /** * Produces a method handle for a virtual method. @@ -624,6 +634,13 @@ public class MethodHandles { public MethodHandle findVirtual(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { MemberName method = resolveOrFail(refc, name, type, false); checkSecurityManager(refc, method); // stack walk magic: do not refactor + return accessVirtual(refc, method); + } + private MethodHandle resolveVirtual(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + MemberName method = resolveOrFail(refc, name, type, false); + return accessVirtual(refc, method); + } + private MethodHandle accessVirtual(Class refc, MemberName method) throws IllegalAccessException { checkMethod(refc, method, false); MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull()); return restrictProtectedReceiver(method, mh); @@ -658,13 +675,21 @@ public class MethodHandles { public MethodHandle findConstructor(Class refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { String name = ""; MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull()); - assert(ctor.isConstructor()); checkSecurityManager(refc, ctor); // stack walk magic: do not refactor + return accessConstructor(refc, ctor); + } + private MethodHandle accessConstructor(Class refc, MemberName ctor) throws IllegalAccessException { + assert(ctor.isConstructor()); checkAccess(refc, ctor); MethodHandle rawMH = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull()); MethodHandle allocMH = MethodHandleImpl.makeAllocator(rawMH); return fixVarargs(allocMH, rawMH); } + private MethodHandle resolveConstructor(Class refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { + String name = ""; + MemberName ctor = resolveOrFail(refc, name, type, false, false, lookupClassOrNull()); + return accessConstructor(refc, ctor); + } /** Return a version of MH which matches matchMH w.r.t. isVarargsCollector. */ private static MethodHandle fixVarargs(MethodHandle mh, MethodHandle matchMH) { @@ -720,10 +745,20 @@ public class MethodHandles { checkSpecialCaller(specialCaller); MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller); checkSecurityManager(refc, method); // stack walk magic: do not refactor + return accessSpecial(refc, method, specialCaller); + } + private MethodHandle accessSpecial(Class refc, MemberName method, + Class specialCaller) throws NoSuchMethodException, IllegalAccessException { checkMethod(refc, method, false); MethodHandle mh = MethodHandleImpl.findMethod(method, false, specialCaller); return restrictReceiver(method, mh, specialCaller); } + private MethodHandle resolveSpecial(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + Class specialCaller = lookupClass(); + checkSpecialCaller(specialCaller); + MemberName method = resolveOrFail(refc, name, type, false, false, specialCaller); + return accessSpecial(refc, method, specialCaller); + } /** * Produces a method handle giving read access to a non-static field. @@ -747,6 +782,10 @@ public class MethodHandles { checkSecurityManager(refc, field); // stack walk magic: do not refactor return makeAccessor(refc, field, false, false, 0); } + private MethodHandle resolveGetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(refc, name, type, false); + return makeAccessor(refc, field, false, false, 0); + } /** * Produces a method handle giving write access to a non-static field. @@ -770,6 +809,10 @@ public class MethodHandles { checkSecurityManager(refc, field); // stack walk magic: do not refactor return makeAccessor(refc, field, false, true, 0); } + private MethodHandle resolveSetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(refc, name, type, false); + return makeAccessor(refc, field, false, true, 0); + } /** * Produces a method handle giving read access to a static field. @@ -792,6 +835,10 @@ public class MethodHandles { checkSecurityManager(refc, field); // stack walk magic: do not refactor return makeAccessor(refc, field, false, false, 1); } + private MethodHandle resolveStaticGetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(refc, name, type, true); + return makeAccessor(refc, field, false, false, 1); + } /** * Produces a method handle giving write access to a static field. @@ -814,6 +861,10 @@ public class MethodHandles { checkSecurityManager(refc, field); // stack walk magic: do not refactor return makeAccessor(refc, field, false, true, 1); } + private MethodHandle resolveStaticSetter(Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + MemberName field = resolveOrFail(refc, name, type, true); + return makeAccessor(refc, field, false, true, 1); + } /** * Produces an early-bound method handle for a non-static method. @@ -1179,6 +1230,25 @@ return mh1; MethodHandle mh = MethodHandleImpl.accessField(field, isSetter, lookupClassOrNull()); return restrictProtectedReceiver(field, mh); } + + /** Hook called from the JVM (via MethodHandleNatives) to link MH constants: + */ + /*non-public*/ + MethodHandle linkMethodHandleConstant(int refKind, Class defc, String name, Object type) throws ReflectiveOperationException { + switch (refKind) { + case REF_getField: return resolveGetter( defc, name, (Class) type ); + case REF_getStatic: return resolveStaticGetter( defc, name, (Class) type ); + case REF_putField: return resolveSetter( defc, name, (Class) type ); + case REF_putStatic: return resolveStaticSetter( defc, name, (Class) type ); + case REF_invokeVirtual: return resolveVirtual( defc, name, (MethodType) type ); + case REF_invokeStatic: return resolveStatic( defc, name, (MethodType) type ); + case REF_invokeSpecial: return resolveSpecial( defc, name, (MethodType) type ); + case REF_newInvokeSpecial: return resolveConstructor( defc, (MethodType) type ); + case REF_invokeInterface: return resolveVirtual( defc, name, (MethodType) type ); + } + // oops + throw new ReflectiveOperationException("bad MethodHandle constant #"+refKind+" "+name+" : "+type); + } } /** diff --git a/jdk/src/share/classes/sun/invoke/util/ValueConversions.java b/jdk/src/share/classes/sun/invoke/util/ValueConversions.java index f3e2f85f843..9179785981b 100644 --- a/jdk/src/share/classes/sun/invoke/util/ValueConversions.java +++ b/jdk/src/share/classes/sun/invoke/util/ValueConversions.java @@ -29,6 +29,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -38,7 +40,17 @@ import java.util.List; public class ValueConversions { private static final Class THIS_CLASS = ValueConversions.class; // Do not adjust this except for special platforms: - private static final int MAX_ARITY = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255); + private static final int MAX_ARITY; + static { + final Object[] values = { 255 }; + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + values[0] = Integer.getInteger(THIS_CLASS.getName()+".MAX_ARITY", 255); + return null; + } + }); + MAX_ARITY = (Integer) values[0]; + } private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); diff --git a/jdk/test/java/lang/invoke/InvokeDynamicPrintArgs.java b/jdk/test/java/lang/invoke/InvokeDynamicPrintArgs.java index 9c3db429170..89d2fdb4c0c 100644 --- a/jdk/test/java/lang/invoke/InvokeDynamicPrintArgs.java +++ b/jdk/test/java/lang/invoke/InvokeDynamicPrintArgs.java @@ -30,6 +30,10 @@ * --verify-specifier-count=3 * --expand-properties --classpath ${test.classes} * --java test.java.lang.invoke.InvokeDynamicPrintArgs --check-output + * @run main/othervm + * indify.Indify + * --expand-properties --classpath ${test.classes} + * --java test.java.lang.invoke.InvokeDynamicPrintArgs --security-manager */ package test.java.lang.invoke; @@ -45,7 +49,8 @@ import static java.lang.invoke.MethodType.*; public class InvokeDynamicPrintArgs { public static void main(String... av) throws Throwable { - if (av.length > 0) openBuf(); // --check-output mode + if (av.length > 0 && av[0].equals("--check-output")) openBuf(); + if (av.length > 0 && av[0].equals("--security-manager")) setSM(); System.out.println("Printing some argument lists, starting with a empty one:"); INDY_nothing().invokeExact(); // BSM specifier #0 = {bsm} INDY_bar().invokeExact("bar arg", 1); // BSM specifier #1 = {bsm2, Void.class, "void type"} @@ -55,6 +60,43 @@ public class InvokeDynamicPrintArgs { // Hence, BSM specifier count should be 3. See "--verify-specifier-count=3" above. System.out.println("Done printing argument lists."); closeBuf(); + checkConstantRefs(); + } + + private static void checkConstantRefs() throws Throwable { + // check some constant references: + assertEquals(MT_bsm(), MH_bsm().type()); + assertEquals(MT_bsm2(), MH_bsm2().type()); + try { + assertEquals(MT_bsm(), non_MH_bsm().type()); + // if SM is installed, must throw before this point + assertEquals(false, System.getSecurityManager() != null); + } catch (SecurityException ex) { + // if SM is installed, must throw to this point + assertEquals(true, System.getSecurityManager() != null); + } + } + private static void assertEquals(Object exp, Object act) { + if (exp == act || (exp != null && exp.equals(act))) return; + throw new AssertionError("not equal: "+exp+", "+act); + } + + private static void setSM() { + // Test for severe security manager interactions (7050328). + class SM extends SecurityManager { + public void checkPackageAccess(String pkg) { + if (pkg.startsWith("test.")) + throw new SecurityException("checkPackageAccess "+pkg); + } + public void checkMemberAccess(Class clazz, int which) { + if (clazz == InvokeDynamicPrintArgs.class) + throw new SecurityException("checkMemberAccess "+clazz.getName()+" #"+which); + } + // allow these others: + public void checkPermission(java.security.Permission perm) { + } + } + System.setSecurityManager(new SM()); } @Test @@ -130,6 +172,9 @@ public class InvokeDynamicPrintArgs { shouldNotCallThis(); return lookup().findStatic(lookup().lookupClass(), "bsm", MT_bsm()); } + private static MethodHandle non_MH_bsm() throws ReflectiveOperationException { + return lookup().findStatic(lookup().lookupClass(), "bsm", MT_bsm()); + } /* Example of a constant call site with user-data. * In this case, the user data is exactly the BSM data. From c0d9c39aa24897fa1f109b2b14b277122dc12986 Mon Sep 17 00:00:00 2001 From: John R Rose Date: Wed, 1 Jun 2011 23:56:51 -0700 Subject: [PATCH 3/4] 7049122: java/lang/invoke/RicochetTest.java with MAX_ARITY=255 in -Xcomp mode overflows code cache Reduce the scope of the unit test (mark high water mark of testing with @ignore tags) Reviewed-by: never --- jdk/test/java/lang/invoke/RicochetTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jdk/test/java/lang/invoke/RicochetTest.java b/jdk/test/java/lang/invoke/RicochetTest.java index 310b576ddc8..1ac7290f3c6 100644 --- a/jdk/test/java/lang/invoke/RicochetTest.java +++ b/jdk/test/java/lang/invoke/RicochetTest.java @@ -25,6 +25,10 @@ /* @test * @summary unit tests for recursive method handles + * @run junit/othervm -DRicochetTest.MAX_ARITY=50 test.java.lang.invoke.RicochetTest + */ +/* + * @ignore The following test creates an unreasonable number of adapters in -Xcomp mode (7049122) * @run junit/othervm -DRicochetTest.MAX_ARITY=255 test.java.lang.invoke.RicochetTest */ From ad33af1a1ca1ce716b5ccbb4dab993a4ace01c14 Mon Sep 17 00:00:00 2001 From: John R Rose Date: Fri, 3 Jun 2011 11:20:20 -0700 Subject: [PATCH 4/4] 7051206: JSR 292 method name SwitchPoint.isValid is misleading to unwary users; should be hasBeenInvalidated Reviewed-by: kvn, never, ysr --- .../classes/java/lang/invoke/SwitchPoint.java | 34 +++++++++++++------ .../java/lang/invoke/JavaDocExamplesTest.java | 4 +-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/jdk/src/share/classes/java/lang/invoke/SwitchPoint.java b/jdk/src/share/classes/java/lang/invoke/SwitchPoint.java index 11d8018c076..7469e408dd6 100644 --- a/jdk/src/share/classes/java/lang/invoke/SwitchPoint.java +++ b/jdk/src/share/classes/java/lang/invoke/SwitchPoint.java @@ -59,14 +59,14 @@ package java.lang.invoke; MethodHandle MH_strcat = MethodHandles.lookup() .findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class)); SwitchPoint spt = new SwitchPoint(); -assert(spt.isValid()); +assert(!spt.hasBeenInvalidated()); // the following steps may be repeated to re-use the same switch point: MethodHandle worker1 = MH_strcat; MethodHandle worker2 = MethodHandles.permuteArguments(MH_strcat, MH_strcat.type(), 1, 0); MethodHandle worker = spt.guardWithTest(worker1, worker2); assertEquals("method", (String) worker.invokeExact("met", "hod")); SwitchPoint.invalidateAll(new SwitchPoint[]{ spt }); -assert(!spt.isValid()); +assert(spt.hasBeenInvalidated()); assertEquals("hodmet", (String) worker.invokeExact("met", "hod")); * *

@@ -126,16 +126,30 @@ public class SwitchPoint { } /** - * Determines if this switch point is still valid. - *

+ * Determines if this switch point has been invalidated yet. + * + *

+ * Discussion: + * Because of the one-way nature of invalidation, once a switch point begins + * to return true for {@code hasBeenInvalidated}, + * it will always do so in the future. + * On the other hand, a valid switch point visible to other threads may + * invalidated at any moment, due to a request by another thread. + *

* Since invalidation is a global and immediate operation, - * this query must be sequenced with any - * other threads that could invalidate this switch point. - * It may therefore be expensive. - * @return true if this switch point has never been invalidated + * the execution of this query, on a valid switchpoint, + * must be internally sequenced with any + * other threads that could cause invalidation. + * This query may therefore be expensive. + * The recommended way to build a boolean-valued method handle + * which queries the invalidation state of a switch point {@code s} is + * to call {@code s.guardWithTest} on + * {@link MethodHandles#constant constant} true and false method handles. + * + * @return true if this switch point has been invalidated */ - public boolean isValid() { - return (mcs.getTarget() == K_true); + public boolean hasBeenInvalidated() { + return (mcs.getTarget() != K_true); } /** diff --git a/jdk/test/java/lang/invoke/JavaDocExamplesTest.java b/jdk/test/java/lang/invoke/JavaDocExamplesTest.java index 0e373c1e773..ba1eed5e757 100644 --- a/jdk/test/java/lang/invoke/JavaDocExamplesTest.java +++ b/jdk/test/java/lang/invoke/JavaDocExamplesTest.java @@ -477,14 +477,14 @@ assertEquals("Wilma, dear?", (String) worker2.invokeExact()); MethodHandle MH_strcat = MethodHandles.lookup() .findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class)); SwitchPoint spt = new SwitchPoint(); -assert(spt.isValid()); +assert(!spt.hasBeenInvalidated()); // the following steps may be repeated to re-use the same switch point: MethodHandle worker1 = MH_strcat; MethodHandle worker2 = MethodHandles.permuteArguments(MH_strcat, MH_strcat.type(), 1, 0); MethodHandle worker = spt.guardWithTest(worker1, worker2); assertEquals("method", (String) worker.invokeExact("met", "hod")); SwitchPoint.invalidateAll(new SwitchPoint[]{ spt }); -assert(!spt.isValid()); +assert(spt.hasBeenInvalidated()); assertEquals("hodmet", (String) worker.invokeExact("met", "hod")); {} }}