@@ -69,7 +69,26 @@ protected Class<?> keyOf(T target) {
6969 */
7070 @ Nullable
7171 public final R tryApplyOrNull (@ Nullable T target ) throws E {
72- return target == null || isLatched (target ) ? null : apply (target );
72+ if (target == null || isLatched (target )) {
73+ return null ;
74+ }
75+ try {
76+ return apply (target );
77+ } catch (NoSuchMethodError e ) {
78+ // a method reference passed to handleNoSuchMethod resolves where it is written, in apply's
79+ // own frame, so a missing target method surfaces here, outside the helper's own try, as the
80+ // invokedynamic call site's own linkage failure; HotSpot reports this directly as
81+ // NoSuchMethodError on some JVM versions
82+ latch (target );
83+ return null ;
84+ } catch (BootstrapMethodError e ) {
85+ // on other JVM versions the same linkage failure is wrapped instead
86+ if (e .getCause () instanceof NoSuchMethodError ) {
87+ latch (target );
88+ return null ;
89+ }
90+ throw e ;
91+ }
7392 }
7493
7594 /**
@@ -103,12 +122,14 @@ protected final void unlatch(T target) {
103122 }
104123
105124 /**
106- * Latches the target's key if the error's message names that class as the receiver that lacks the
107- * method. Returns whether it latched. An error that does not name the key, for example one thrown
108- * inside a wrapper's delegate, is left alone.
125+ * Latches the target's key if the error's message names that class as the receiver that lacks
126+ * {@code methodName}. Returns whether it latched. An error that does not name the key, for
127+ * example one thrown inside a wrapper's delegate, is left alone — and so is one that names the
128+ * key but for a different method, for example one the guarded method's own implementation calls
129+ * internally.
109130 */
110- protected final boolean latchIfNamed (T target , AbstractMethodError error ) {
111- if (isNamedIn (error , keyOf (target ))) {
131+ protected final boolean latchIfNamed (T target , String methodName , AbstractMethodError error ) {
132+ if (isNamedIn (error , keyOf (target ), methodName )) {
112133 latch (target );
113134 return true ;
114135 }
@@ -124,7 +145,7 @@ protected final boolean latchIfNamed(T target, AbstractMethodError error) {
124145 *
125146 * <pre>{@code
126147 * protected Properties apply(Connection c) throws SQLException {
127- * return handleAbstractMethod(c, Connection::getClientInfo);
148+ * return handleAbstractMethod(c, "getClientInfo", Connection::getClientInfo);
128149 * }
129150 * }</pre>
130151 *
@@ -133,18 +154,23 @@ protected final boolean latchIfNamed(T target, AbstractMethodError error) {
133154 * handle; see {@link #handleNoSuchMethod} and {@link #handleNoSuchOrAbstractMethod}, and prefer
134155 * the latter when unsure which a call site can see.
135156 *
157+ * <p>{@code methodName} must be {@code call}'s own method, not merely some method of {@code T}:
158+ * the error's message can name the key class while blaming a different method that the guarded
159+ * method's implementation happens to call internally, and only a method name check tells the two
160+ * apart.
161+ *
136162 * <p>Pass a method reference or a non-capturing lambda, and keep this method small so it inlines:
137163 * that is what lets the JIT see the exact function at each call site (see {@link Strategy}).
138164 * Compose {@link #latchIfNamed} and {@link #latch} directly for anything more involved.
139165 */
140166 @ Nullable
141167 @ StrategyConsumer
142- protected final R handleAbstractMethod (T target , @ Strategy ThrowingFunction < T , R , E > call )
143- throws E {
168+ protected final R handleAbstractMethod (
169+ T target , String methodName , @ Strategy ThrowingFunction < T , R , E > call ) throws E {
144170 try {
145171 return call .apply (target );
146172 } catch (AbstractMethodError e ) {
147- latchIfNamed (target , e );
173+ latchIfNamed (target , methodName , e );
148174 return null ;
149175 } catch (UnsupportedOperationException e ) {
150176 // no class to attribute it to, and it may come from a delegate: never latched
@@ -194,16 +220,16 @@ protected final R handleNoSuchMethod(T target, @Strategy ThrowingFunction<T, R,
194220 *
195221 * <pre>{@code
196222 * protected Properties apply(Connection c) throws SQLException {
197- * return handleNoSuchOrAbstractMethod(c, Connection::getClientInfo);
223+ * return handleNoSuchOrAbstractMethod(c, "getClientInfo", Connection::getClientInfo);
198224 * }
199225 * }</pre>
200226 */
201227 @ Nullable
202228 @ StrategyConsumer
203- protected final R handleNoSuchOrAbstractMethod (T target , @ Strategy ThrowingFunction < T , R , E > call )
204- throws E {
229+ protected final R handleNoSuchOrAbstractMethod (
230+ T target , String methodName , @ Strategy ThrowingFunction < T , R , E > call ) throws E {
205231 try {
206- return handleAbstractMethod (target , call );
232+ return handleAbstractMethod (target , methodName , call );
207233 } catch (NoSuchMethodError e ) {
208234 latch (target );
209235 return null ;
@@ -220,9 +246,12 @@ protected final R handleNoSuchOrAbstractMethod(T target, @Strategy ThrowingFunct
220246 * </ul>
221247 *
222248 * A concrete object's class is never the abstract method's declaring class or an interface, so an
223- * exact match can only be the receiver. An unparseable message never matches.
249+ * exact match can only be the receiver. The message also names the resolved method itself: that
250+ * method's implementation can call a different, unimplemented method on the very same receiver,
251+ * raising an error that names the key class but blames a method other than {@code methodName}, so
252+ * the method name is checked too. An unparseable message never matches.
224253 */
225- static boolean isNamedIn (AbstractMethodError e , Class <?> type ) {
254+ static boolean isNamedIn (AbstractMethodError e , Class <?> type , String methodName ) {
226255 final String message = e .getMessage ();
227256 if (message == null ) {
228257 return false ;
@@ -232,7 +261,8 @@ static boolean isNamedIn(AbstractMethodError e, Class<?> type) {
232261 final int end = RECEIVER_PREFIX .length () + name .length ();
233262 return message .startsWith (name , RECEIVER_PREFIX .length ())
234263 && message .length () > end
235- && message .charAt (end ) == ' ' ;
264+ && message .charAt (end ) == ' '
265+ && message .indexOf (methodName + "(" , end ) > end ;
236266 }
237267 if (message .startsWith (name ) && message .length () > name .length () + 1 ) {
238268 // JDK 8: the method name follows the class name and runs up to the descriptor, so it
@@ -241,7 +271,9 @@ static boolean isNamedIn(AbstractMethodError e, Class<?> type) {
241271 final int paren = message .indexOf ('(' , methodStart );
242272 return message .charAt (name .length ()) == '.'
243273 && paren > methodStart
244- && message .indexOf ('.' , methodStart ) < 0 ;
274+ && message .indexOf ('.' , methodStart ) < 0
275+ && paren == methodStart + methodName .length ()
276+ && message .regionMatches (methodStart , methodName , 0 , methodName .length ());
245277 }
246278 return false ;
247279 }
0 commit comments