• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

raphw / byte-buddy / #792

05 Oct 2025 08:51PM UTC coverage: 85.115% (-0.01%) from 85.128%
#792

push

raphw
Add missing checksum.

29562 of 34732 relevant lines covered (85.11%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.94
/byte-buddy-dep/src/main/java/net/bytebuddy/utility/dispatcher/JavaDispatcher.java
1
/*
2
 * Copyright 2014 - Present Rafael Winterhalter
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.bytebuddy.utility.dispatcher;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.ClassFileVersion;
20
import net.bytebuddy.asm.AsmVisitorWrapper;
21
import net.bytebuddy.build.AccessControllerPlugin;
22
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
23
import net.bytebuddy.description.method.MethodDescription;
24
import net.bytebuddy.dynamic.scaffold.TypeWriter;
25
import net.bytebuddy.utility.GraalImageCode;
26
import net.bytebuddy.utility.Invoker;
27
import net.bytebuddy.utility.MethodComparator;
28
import net.bytebuddy.utility.nullability.MaybeNull;
29
import net.bytebuddy.utility.privilege.GetSystemPropertyAction;
30
import org.objectweb.asm.ClassWriter;
31
import org.objectweb.asm.MethodVisitor;
32
import org.objectweb.asm.Opcodes;
33
import org.objectweb.asm.Type;
34

35
import java.io.File;
36
import java.io.FileOutputStream;
37
import java.io.OutputStream;
38
import java.lang.annotation.*;
39
import java.lang.reflect.*;
40
import java.security.Permission;
41
import java.security.PrivilegedAction;
42
import java.util.HashMap;
43
import java.util.LinkedHashMap;
44
import java.util.Map;
45

46
/**
47
 * <p>
48
 * A dispatcher for creating a proxy that invokes methods of a type that is possibly unknown on the current VM. Dispatchers do not
49
 * use any of Byte Buddy's regular infrastructure, to avoid bootstrapping issues as these dispatchers are used by Byte Buddy itself.
50
 * </p>
51
 * <p>
52
 * By default, this dispatcher uses the Java {@link Proxy} for creating dispatchers. By setting {@code net.bytebuddy.generate} to
53
 * {@code true}, Byte Buddy can generate proxies manually as byte code to mostly avoid reflection and boxing of arguments as arrays.
54
 * </p>
55
 * <p>
56
 * If a security manager is active, the <i>net.bytebuddy.createJavaDispatcher</i> runtime permission is required. Any dispatching
57
 * will be executed from a separate class loader and an unnamed module but with the {@link java.security.ProtectionDomain} of
58
 * the {@link JavaDispatcher} class. It is not permitted to invoke methods of the {@code java.security.AccessController} class or
59
 * to resolve a {@code java.lang.invoke.MethodHandle$Lookup}.
60
 * </p>
61
 *
62
 * @param <T> The resolved type.
63
 */
64
@HashCodeAndEqualsPlugin.Enhance
65
public class JavaDispatcher<T> implements PrivilegedAction<T> {
66

67
    /**
68
     * A property to determine, that if {@code true}, dispatcher classes will be generated natively and not by using a {@link Proxy}.
69
     */
70
    public static final String GENERATE_PROPERTY = "net.bytebuddy.generate";
71

72
    /**
73
     * If {@code true}, dispatcher classes will be generated natively and not by using a {@link Proxy}.
74
     */
75
    private static final boolean GENERATE = Boolean.parseBoolean(doPrivileged(new GetSystemPropertyAction(GENERATE_PROPERTY)));
1✔
76

77
    /**
78
     * A resolver to assure that a type's package and module are exported to the created class loader.
79
     * This should normally always be the case, but if another library is shading Byte Buddy or otherwise
80
     * manipulates the module graph, this might become necessary.
81
     */
82
    private static final DynamicClassLoader.Resolver RESOLVER = doPrivileged(DynamicClassLoader.Resolver.CreationAction.INSTANCE);
1✔
83

84
    /**
85
     * Contains an invoker that makes sure that reflective dispatchers make invocations from an isolated {@link ClassLoader} and
86
     * not from within Byte Buddy's context. This way, no privilege context can be leaked by accident.
87
     */
88
    private static final Invoker INVOKER = doPrivileged(new InvokerCreationAction());
1✔
89

90
    /**
91
     * The proxy type.
92
     */
93
    private final Class<T> proxy;
94

95
    /**
96
     * The class loader to resolve the proxied type from or {@code null} if the bootstrap loader should be used.
97
     */
98
    @MaybeNull
99
    @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
100
    private final ClassLoader classLoader;
101

102
    /**
103
     * {@code true} if a proxy class should be manually generated.
104
     */
105
    private final boolean generate;
106

107
    /**
108
     * Creates a new dispatcher.
109
     *
110
     * @param proxy       The proxy type.
111
     * @param classLoader The class loader to resolve the proxied type from or {@code null} if the bootstrap loader should be used.
112
     * @param generate    {@code true} if a proxy class should be manually generated.
113
     */
114
    protected JavaDispatcher(Class<T> proxy, @MaybeNull ClassLoader classLoader, boolean generate) {
1✔
115
        this.proxy = proxy;
1✔
116
        this.classLoader = classLoader;
1✔
117
        this.generate = generate;
1✔
118
    }
1✔
119

120
    /**
121
     * A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
122
     *
123
     * @param action The action to execute from a privileged context.
124
     * @param <T>    The type of the action's resolved value.
125
     * @return The action's resolved value.
126
     */
127
    @AccessControllerPlugin.Enhance
128
    private static <T> T doPrivileged(PrivilegedAction<T> action) {
129
        return action.run();
×
130
    }
131

132
    /**
133
     * Resolves an action for creating a dispatcher for the provided type where the proxied type is resolved from the bootstrap loader.
134
     *
135
     * @param type The type for which a dispatcher should be resolved.
136
     * @param <T>  The resolved type.
137
     * @return An action for creating an appropriate dispatcher.
138
     */
139
    public static <T> PrivilegedAction<T> of(Class<T> type) {
140
        return of(type, null);
1✔
141
    }
142

143
    /**
144
     * Resolves an action for creating a dispatcher for the provided type.
145
     *
146
     * @param type        The type for which a dispatcher should be resolved.
147
     * @param classLoader The class loader to resolve the proxied type from.
148
     * @param <T>         The resolved type.
149
     * @return An action for creating an appropriate dispatcher.
150
     */
151
    public static <T> PrivilegedAction<T> of(Class<T> type, @MaybeNull ClassLoader classLoader) {
152
        return of(type, classLoader, GENERATE);
1✔
153
    }
154

155
    /**
156
     * Resolves an action for creating a dispatcher for the provided type.
157
     *
158
     * @param type        The type for which a dispatcher should be resolved.
159
     * @param classLoader The class loader to resolve the proxied type from.
160
     * @param generate    {@code true} if a proxy class should be manually generated.
161
     * @param <T>         The resolved type.
162
     * @return An action for creating an appropriate dispatcher.
163
     */
164
    protected static <T> PrivilegedAction<T> of(Class<T> type, @MaybeNull ClassLoader classLoader, boolean generate) {
165
        if (!type.isInterface()) {
1✔
166
            throw new IllegalArgumentException("Expected an interface instead of " + type);
1✔
167
        } else if (!type.isAnnotationPresent(Proxied.class)) {
1✔
168
            throw new IllegalArgumentException("Expected " + type.getName() + " to be annotated with " + Proxied.class.getName());
1✔
169
        } else if (type.getAnnotation(Proxied.class).value().startsWith("java.security.")) {
1✔
170
            throw new IllegalArgumentException("Classes related to Java security cannot be proxied: " + type.getName());
×
171
        } else {
172
            return new JavaDispatcher<T>(type, classLoader, generate);
1✔
173
        }
174
    }
175

176
    /**
177
     * {@inheritDoc}
178
     */
179
    @SuppressWarnings("unchecked")
180
    public T run() {
181
        try {
182
            Object securityManager = System.class.getMethod("getSecurityManager").invoke(null);
1✔
183
            if (securityManager != null) {
1✔
184
                Object permission = Class.forName("java.lang.RuntimePermission")
×
185
                        .getConstructor(String.class)
×
186
                        .newInstance("net.bytebuddy.createJavaDispatcher");
×
187
                Class.forName("java.lang.SecurityManager")
×
188
                        .getMethod("checkPermission", Permission.class)
×
189
                        .invoke(securityManager, permission);
×
190
            }
191
        } catch (NoSuchMethodException ignored) {
×
192
            /* security manager not available on current VM */
193
        } catch (ClassNotFoundException ignored) {
×
194
            /* security manager not available on current VM */
195
        } catch (InvocationTargetException exception) {
×
196
            Throwable cause = exception.getTargetException();
×
197
            if (cause instanceof RuntimeException) {
×
198
                throw (RuntimeException) cause;
×
199
            } else {
200
                throw new IllegalStateException("Failed to assert access rights using security manager", cause);
×
201
            }
202
        } catch (IllegalAccessException exception) {
×
203
            throw new IllegalStateException("Failed to access security manager", exception);
×
204
        } catch (InstantiationException exception) {
×
205
            throw new IllegalStateException("Failed to instantiate runtime permission", exception);
×
206
        }
1✔
207
        Map<Method, Dispatcher> dispatchers = generate
1✔
208
                ? new LinkedHashMap<Method, Dispatcher>()
209
                : new HashMap<Method, Dispatcher>();
210
        boolean defaults = proxy.isAnnotationPresent(Defaults.class);
1✔
211
        String name = proxy.getAnnotation(Proxied.class).value();
1✔
212
        Class<?> target;
213
        try {
214
            target = Class.forName(name, false, classLoader);
1✔
215
        } catch (ClassNotFoundException exception) {
1✔
216
            for (Method method : generate
1✔
217
                    ? GraalImageCode.getCurrent().sorted(proxy.getMethods(), MethodComparator.INSTANCE)
1✔
218
                    : proxy.getMethods()) {
1✔
219
                if (method.getDeclaringClass() == Object.class) {
1✔
220
                    continue;
×
221
                }
222
                if (method.isAnnotationPresent(Instance.class)) {
1✔
223
                    if (method.getParameterTypes().length != 1 || method.getParameterTypes()[0].isPrimitive() || method.getParameterTypes()[0].isArray()) {
1✔
224
                        throw new IllegalStateException("Instance check requires a single regular-typed argument: " + method);
×
225
                    } else if (method.getReturnType() != boolean.class) {
1✔
226
                        throw new IllegalStateException("Instance check requires a boolean return type: " + method);
×
227
                    } else {
228
                        dispatchers.put(method, Dispatcher.ForDefaultValue.BOOLEAN);
1✔
229
                    }
230
                } else {
231
                    dispatchers.put(method, defaults || method.isAnnotationPresent(Defaults.class)
1✔
232
                            ? Dispatcher.ForDefaultValue.of(method.getReturnType())
1✔
233
                            : new Dispatcher.ForUnresolvedMethod("Type not available on current VM: " + exception.getMessage()));
1✔
234
                }
235
            }
236
            if (generate) {
1✔
237
                return (T) DynamicClassLoader.proxy(proxy, dispatchers);
1✔
238
            } else {
239
                return (T) Proxy.newProxyInstance(proxy.getClassLoader(),
1✔
240
                        new Class<?>[]{proxy},
241
                        new ProxiedInvocationHandler(name, dispatchers));
242
            }
243
        }
1✔
244
        boolean generate = this.generate;
1✔
245
        for (Method method : generate
1✔
246
                ? GraalImageCode.getCurrent().sorted(proxy.getMethods(), MethodComparator.INSTANCE)
1✔
247
                : proxy.getMethods()) {
1✔
248
            if (method.getDeclaringClass() == Object.class) {
1✔
249
                continue;
×
250
            }
251
            if (method.isAnnotationPresent(Instance.class)) {
1✔
252
                if (method.getParameterTypes().length != 1 || !method.getParameterTypes()[0].isAssignableFrom(target)) {
1✔
253
                    throw new IllegalStateException("Instance check requires a single regular-typed argument: " + method);
1✔
254
                } else if (method.getReturnType() != boolean.class) {
1✔
255
                    throw new IllegalStateException("Instance check requires a boolean return type: " + method);
×
256
                } else {
257
                    dispatchers.put(method, new Dispatcher.ForInstanceCheck(target));
1✔
258
                }
259
            } else if (method.isAnnotationPresent(Container.class)) {
1✔
260
                if (method.getParameterTypes().length != 1 || method.getParameterTypes()[0] != int.class) {
1✔
261
                    throw new IllegalStateException("Container creation requires a single int-typed argument: " + method);
1✔
262
                } else if (!method.getReturnType().isArray() || !method.getReturnType().getComponentType().isAssignableFrom(target)) {
1✔
263
                    throw new IllegalStateException("Container creation requires an assignable array as return value: " + method);
×
264
                } else {
265
                    dispatchers.put(method, new Dispatcher.ForContainerCreation(target));
1✔
266
                }
267
            } else if (target.getName().equals("java.lang.invoke.MethodHandles") && method.getName().equals("lookup")) {
1✔
268
                throw new UnsupportedOperationException("Cannot resolve Byte Buddy lookup via dispatcher");
×
269
            } else {
270
                try {
271
                    Class<?>[] parameterType = method.getParameterTypes();
1✔
272
                    int offset;
273
                    if (method.isAnnotationPresent(IsStatic.class) || method.isAnnotationPresent(IsConstructor.class)) {
1✔
274
                        offset = 0;
1✔
275
                    } else {
276
                        offset = 1;
1✔
277
                        if (parameterType.length == 0) {
1✔
278
                            throw new IllegalStateException("Expected self type: " + method);
1✔
279
                        } else if (!parameterType[0].isAssignableFrom(target)) {
1✔
280
                            throw new IllegalStateException("Cannot assign self type: " + target + " on " + method);
×
281
                        }
282
                        Class<?>[] adjusted = new Class<?>[parameterType.length - 1];
1✔
283
                        System.arraycopy(parameterType, 1, adjusted, 0, adjusted.length);
1✔
284
                        parameterType = adjusted;
1✔
285
                    }
286
                    Annotation[][] parameterAnnotation = method.getParameterAnnotations();
1✔
287
                    for (int index = 0; index < parameterType.length; index++) {
1✔
288
                        for (Annotation annotation : parameterAnnotation[index + offset]) {
1✔
289
                            if (annotation instanceof Proxied) {
1✔
290
                                int arity = 0;
1✔
291
                                while (parameterType[index].isArray()) {
1✔
292
                                    arity += 1;
1✔
293
                                    parameterType[index] = parameterType[index].getComponentType();
1✔
294
                                }
295
                                if (arity > 0) {
1✔
296
                                    if (parameterType[index].isPrimitive()) {
1✔
297
                                        throw new IllegalStateException("Primitive values are not supposed to be proxied: " + index + " of " + method);
×
298
                                    } else if (!parameterType[index].isAssignableFrom(Class.forName(((Proxied) annotation).value(), false, classLoader))) {
1✔
299
                                        throw new IllegalStateException("Cannot resolve to component type: " + ((Proxied) annotation).value() + " at " + index + " of " + method);
1✔
300
                                    }
301
                                    StringBuilder stringBuilder = new StringBuilder();
1✔
302
                                    while (arity-- > 0) {
1✔
303
                                        stringBuilder.append('[');
1✔
304
                                    }
305
                                    parameterType[index] = Class.forName(stringBuilder.append('L')
1✔
306
                                            .append(((Proxied) annotation).value())
1✔
307
                                            .append(';')
1✔
308
                                            .toString(), false, classLoader);
1✔
309
                                } else {
1✔
310
                                    Class<?> resolved = Class.forName(((Proxied) annotation).value(), false, classLoader);
1✔
311
                                    if (!parameterType[index].isAssignableFrom(resolved)) {
1✔
312
                                        throw new IllegalStateException("Cannot resolve to type: " + resolved.getName() + " at " + index + " of " + method);
1✔
313
                                    }
314
                                    parameterType[index] = resolved;
1✔
315
                                }
316
                                break;
1✔
317
                            }
318
                        }
319
                    }
320
                    if (method.isAnnotationPresent(IsConstructor.class)) {
1✔
321
                        Constructor<?> resolved = target.getConstructor(parameterType);
1✔
322
                        if (!method.getReturnType().isAssignableFrom(target)) {
1✔
323
                            throw new IllegalStateException("Cannot assign " + resolved.getDeclaringClass().getName() + " to " + method);
×
324
                        }
325
                        if ((resolved.getModifiers() & Opcodes.ACC_PUBLIC) == 0 || (target.getModifiers() & Opcodes.ACC_PUBLIC) == 0) {
1✔
326
                            resolved.setAccessible(true);
×
327
                            generate = false;
×
328
                        }
329
                        dispatchers.put(method, new Dispatcher.ForConstructor(resolved));
1✔
330
                    } else {
1✔
331
                        Proxied proxied = method.getAnnotation(Proxied.class);
1✔
332
                        Method resolved = target.getMethod(proxied == null ? method.getName() : proxied.value(), parameterType);
1✔
333
                        if (!method.getReturnType().isAssignableFrom(resolved.getReturnType())) {
1✔
334
                            throw new IllegalStateException("Cannot assign " + resolved.getReturnType().getName() + " to " + method);
1✔
335
                        }
336
                        exceptions:
337
                        for (Class<?> type : resolved.getExceptionTypes()) {
1✔
338
                            if (RuntimeException.class.isAssignableFrom(type) || Error.class.isAssignableFrom(type)) {
1✔
339
                                continue;
×
340
                            }
341
                            for (Class<?> exception : method.getExceptionTypes()) {
1✔
342
                                if (exception.isAssignableFrom(type)) {
1✔
343
                                    continue exceptions;
1✔
344
                                }
345
                            }
346
                            throw new IllegalStateException("Resolved method for " + method + " throws undeclared checked exception " + type.getName());
1✔
347
                        }
348
                        if ((resolved.getModifiers() & Opcodes.ACC_PUBLIC) == 0 || (resolved.getDeclaringClass().getModifiers() & Opcodes.ACC_PUBLIC) == 0) {
1✔
349
                            resolved.setAccessible(true);
×
350
                            generate = false;
×
351
                        }
352
                        if (Modifier.isStatic(resolved.getModifiers())) {
1✔
353
                            if (!method.isAnnotationPresent(IsStatic.class)) {
1✔
354
                                throw new IllegalStateException("Resolved method for " + method + " was expected to be static: " + resolved);
×
355
                            }
356
                            dispatchers.put(method, new Dispatcher.ForStaticMethod(resolved));
1✔
357
                        } else {
358
                            if (method.isAnnotationPresent(IsStatic.class)) {
1✔
359
                                throw new IllegalStateException("Resolved method for " + method + " was expected to be virtual: " + resolved);
×
360
                            }
361
                            dispatchers.put(method, new Dispatcher.ForNonStaticMethod(resolved));
1✔
362
                        }
363
                    }
364
                } catch (ClassNotFoundException exception) {
1✔
365
                    dispatchers.put(method, defaults || method.isAnnotationPresent(Defaults.class)
1✔
366
                            ? Dispatcher.ForDefaultValue.of(method.getReturnType())
1✔
367
                            : new Dispatcher.ForUnresolvedMethod("Class not available on current VM: " + exception.getMessage()));
1✔
368
                } catch (NoSuchMethodException exception) {
1✔
369
                    dispatchers.put(method, defaults || method.isAnnotationPresent(Defaults.class)
1✔
370
                            ? Dispatcher.ForDefaultValue.of(method.getReturnType())
1✔
371
                            : new Dispatcher.ForUnresolvedMethod("Method not available on current VM: " + exception.getMessage()));
1✔
372
                } catch (Throwable throwable) {
1✔
373
                    dispatchers.put(method, new Dispatcher.ForUnresolvedMethod("Unexpected error: " + throwable.getMessage()));
1✔
374
                }
1✔
375
            }
376
        }
377
        if (generate) {
1✔
378
            return (T) DynamicClassLoader.proxy(proxy, dispatchers);
1✔
379
        } else {
380
            return (T) Proxy.newProxyInstance(proxy.getClassLoader(),
1✔
381
                    new Class<?>[]{proxy},
382
                    new ProxiedInvocationHandler(target.getName(), dispatchers));
1✔
383
        }
384
    }
385

386
    /**
387
     * Indicates a proxied type's name. This annotation is mandatory for proxied types but can also be used on method's
388
     * to describe the actual name of the proxied method or on parameters to indicate the parameter's (component) type.
389
     */
390
    @Documented
391
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
392
    @Retention(RetentionPolicy.RUNTIME)
393
    public @interface Proxied {
394

395
        /**
396
         * Returns the binary name of the proxied type.
397
         *
398
         * @return The binary name of the proxied type.
399
         */
400
        String value();
401
    }
402

403
    /**
404
     * Indicates that a proxied method is static.
405
     */
406
    @Documented
407
    @Target(ElementType.METHOD)
408
    @Retention(RetentionPolicy.RUNTIME)
409
    public @interface IsStatic {
410
        /* empty */
411
    }
412

413
    /**
414
     * Indicates that a proxied method is a constructor.
415
     */
416
    @Documented
417
    @Target(ElementType.METHOD)
418
    @Retention(RetentionPolicy.RUNTIME)
419
    public @interface IsConstructor {
420
        /* empty */
421
    }
422

423
    /**
424
     * Indicates that a method is supposed to perform an instance check. The annotated method must declare a single argument
425
     * with a type that is assignable from the proxied type.
426
     */
427
    @Documented
428
    @Target(ElementType.METHOD)
429
    @Retention(RetentionPolicy.RUNTIME)
430
    public @interface Instance {
431
        /* empty */
432
    }
433

434
    /**
435
     * Indicates that the method is supposed to return an array of the proxied type. The annotated method must declare a single,
436
     * {@code int}-typed argument that represents the array's dimension.
437
     */
438
    @Documented
439
    @Target(ElementType.METHOD)
440
    @Retention(RetentionPolicy.RUNTIME)
441
    public @interface Container {
442
        /* empty */
443
    }
444

445
    /**
446
     * Indicates that a method is supposed to return a default value if a method or type could not be resolved.
447
     */
448
    @Documented
449
    @Target({ElementType.TYPE, ElementType.METHOD})
450
    @Retention(RetentionPolicy.RUNTIME)
451
    public @interface Defaults {
452
        /* empty */
453
    }
454

455
    /**
456
     * A privileged action for creating an {@link Invoker}.
457
     */
458
    @HashCodeAndEqualsPlugin.Enhance
459
    private static class InvokerCreationAction implements PrivilegedAction<Invoker> {
460

461
        /**
462
         * {@inheritDoc}
463
         */
464
        public Invoker run() {
465
            return DynamicClassLoader.invoker();
1✔
466
        }
467
    }
468

469
    /**
470
     * An {@link Invoker} that uses Byte Buddy's invocation context to use if dynamic class loading is not supported, for example on Android,
471
     * and that do not use secured contexts, where this security measure is obsolete to begin with.
472
     */
473
    @HashCodeAndEqualsPlugin.Enhance
474
    private static class DirectInvoker implements Invoker {
475

476
        /**
477
         * {@inheritDoc}
478
         */
479
        public Object newInstance(Constructor<?> constructor, Object[] argument) throws InstantiationException, IllegalAccessException, InvocationTargetException {
480
            return constructor.newInstance(argument);
×
481
        }
482

483
        /**
484
         * {@inheritDoc}
485
         */
486
        public Object invoke(Method method, @MaybeNull Object instance, @MaybeNull Object[] argument) throws IllegalAccessException, InvocationTargetException {
487
            return method.invoke(instance, argument);
×
488
        }
489
    }
490

491
    /**
492
     * A dispatcher for handling a proxied method.
493
     */
494
    protected interface Dispatcher {
495

496
        /**
497
         * Invokes the proxied action.
498
         *
499
         * @param argument The arguments provided.
500
         * @return The return value.
501
         * @throws Throwable If any error occurs.
502
         */
503
        @MaybeNull
504
        Object invoke(Object[] argument) throws Throwable;
505

506
        /**
507
         * Implements this dispatcher in a generated proxy.
508
         *
509
         * @param methodVisitor The method visitor to implement the method with.
510
         * @param method        The method being implemented.
511
         * @return The maximal size of the operand stack.
512
         */
513
        int apply(MethodVisitor methodVisitor, Method method);
514

515
        /**
516
         * A dispatcher that performs an instance check.
517
         */
518
        @HashCodeAndEqualsPlugin.Enhance
519
        class ForInstanceCheck implements Dispatcher {
520

521
            /**
522
             * The checked type.
523
             */
524
            private final Class<?> target;
525

526
            /**
527
             * Creates a dispatcher for an instance check.
528
             *
529
             * @param target The checked type.
530
             */
531
            protected ForInstanceCheck(Class<?> target) {
1✔
532
                this.target = target;
1✔
533
            }
1✔
534

535
            /**
536
             * {@inheritDoc}
537
             */
538
            public Object invoke(Object[] argument) {
539
                return target.isInstance(argument[0]);
1✔
540
            }
541

542
            /**
543
             * {@inheritDoc}
544
             */
545
            public int apply(MethodVisitor methodVisitor, Method method) {
546
                methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
1✔
547
                methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(target));
1✔
548
                methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
549
                return 1;
1✔
550
            }
551
        }
552

553
        /**
554
         * A dispatcher that creates an array.
555
         */
556
        @HashCodeAndEqualsPlugin.Enhance
557
        class ForContainerCreation implements Dispatcher {
558

559
            /**
560
             * The component type.
561
             */
562
            private final Class<?> target;
563

564
            /**
565
             * Creates a dispatcher for an array creation.
566
             *
567
             * @param target The component type.
568
             */
569
            protected ForContainerCreation(Class<?> target) {
1✔
570
                this.target = target;
1✔
571
            }
1✔
572

573
            /**
574
             * {@inheritDoc}
575
             */
576
            public Object invoke(Object[] argument) {
577
                return Array.newInstance(target, (Integer) argument[0]);
1✔
578
            }
579

580
            /**
581
             * {@inheritDoc}
582
             */
583
            public int apply(MethodVisitor methodVisitor, Method method) {
584
                methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);
1✔
585
                methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(target));
1✔
586
                methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
587
                return 1;
1✔
588
            }
589
        }
590

591
        /**
592
         * A dispatcher that returns a fixed value.
593
         */
594
        enum ForDefaultValue implements Dispatcher {
1✔
595

596
            /**
597
             * A dispatcher for a {@code void} type.
598
             */
599
            VOID(null, Opcodes.NOP, Opcodes.RETURN, 0),
1✔
600

601
            /**
602
             * A dispatcher for a {@code boolean} type.
603
             */
604
            BOOLEAN(false, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
605

606
            /**
607
             * A dispatcher for a {@code boolean} type that returns {@code true}.
608
             */
609
            BOOLEAN_REVERSE(true, Opcodes.ICONST_1, Opcodes.IRETURN, 1),
1✔
610

611
            /**
612
             * A dispatcher for a {@code byte} type.
613
             */
614
            BYTE((byte) 0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
615

616
            /**
617
             * A dispatcher for a {@code short} type.
618
             */
619
            SHORT((short) 0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
620

621
            /**
622
             * A dispatcher for a {@code char} type.
623
             */
624
            CHARACTER((char) 0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
625

626
            /**
627
             * A dispatcher for an {@code int} type.
628
             */
629
            INTEGER(0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
630

631
            /**
632
             * A dispatcher for a {@code long} type.
633
             */
634
            LONG(0L, Opcodes.LCONST_0, Opcodes.LRETURN, 2),
1✔
635

636
            /**
637
             * A dispatcher for a {@code float} type.
638
             */
639
            FLOAT(0f, Opcodes.FCONST_0, Opcodes.FRETURN, 1),
1✔
640

641
            /**
642
             * A dispatcher for a {@code double} type.
643
             */
644
            DOUBLE(0d, Opcodes.DCONST_0, Opcodes.DRETURN, 2),
1✔
645

646
            /**
647
             * A dispatcher for a reference type.
648
             */
649
            REFERENCE(null, Opcodes.ACONST_NULL, Opcodes.ARETURN, 1);
1✔
650

651
            /**
652
             * The default value.
653
             */
654
            @MaybeNull
655
            private final Object value;
656

657
            /**
658
             * The opcode to load the default value.
659
             */
660
            private final int load;
661

662
            /**
663
             * The opcode to return the default value.
664
             */
665
            private final int returned;
666

667
            /**
668
             * The operand stack size of default value.
669
             */
670
            private final int size;
671

672
            /**
673
             * Creates a new default value dispatcher.
674
             *
675
             * @param value    The default value.
676
             * @param load     The opcode to load the default value.
677
             * @param returned The opcode to return the default value.
678
             * @param size     The operand stack size of default value.
679
             */
680
            ForDefaultValue(@MaybeNull Object value, int load, int returned, int size) {
1✔
681
                this.value = value;
1✔
682
                this.load = load;
1✔
683
                this.returned = returned;
1✔
684
                this.size = size;
1✔
685
            }
1✔
686

687
            /**
688
             * Resolves a fixed value for a given type.
689
             *
690
             * @param type The type to resolve.
691
             * @return An appropriate dispatcher.
692
             */
693
            protected static Dispatcher of(Class<?> type) {
694
                if (type == void.class) {
1✔
695
                    return VOID;
1✔
696
                } else if (type == boolean.class) {
1✔
697
                    return BOOLEAN;
1✔
698
                } else if (type == byte.class) {
1✔
699
                    return BYTE;
1✔
700
                } else if (type == short.class) {
1✔
701
                    return SHORT;
1✔
702
                } else if (type == char.class) {
1✔
703
                    return CHARACTER;
1✔
704
                } else if (type == int.class) {
1✔
705
                    return INTEGER;
1✔
706
                } else if (type == long.class) {
1✔
707
                    return LONG;
1✔
708
                } else if (type == float.class) {
1✔
709
                    return FLOAT;
1✔
710
                } else if (type == double.class) {
1✔
711
                    return DOUBLE;
1✔
712
                } else if (type.isArray()) {
1✔
713
                    if (type.getComponentType() == boolean.class) {
1✔
714
                        return OfPrimitiveArray.BOOLEAN;
1✔
715
                    } else if (type.getComponentType() == byte.class) {
1✔
716
                        return OfPrimitiveArray.BYTE;
1✔
717
                    } else if (type.getComponentType() == short.class) {
1✔
718
                        return OfPrimitiveArray.SHORT;
1✔
719
                    } else if (type.getComponentType() == char.class) {
1✔
720
                        return OfPrimitiveArray.CHARACTER;
1✔
721
                    } else if (type.getComponentType() == int.class) {
1✔
722
                        return OfPrimitiveArray.INTEGER;
1✔
723
                    } else if (type.getComponentType() == long.class) {
1✔
724
                        return OfPrimitiveArray.LONG;
1✔
725
                    } else if (type.getComponentType() == float.class) {
1✔
726
                        return OfPrimitiveArray.FLOAT;
1✔
727
                    } else if (type.getComponentType() == double.class) {
1✔
728
                        return OfPrimitiveArray.DOUBLE;
1✔
729
                    } else {
730
                        return OfNonPrimitiveArray.of(type.getComponentType());
1✔
731
                    }
732
                } else {
733
                    return REFERENCE;
1✔
734
                }
735
            }
736

737
            /**
738
             * {@inheritDoc}
739
             */
740
            @MaybeNull
741
            public Object invoke(Object[] argument) {
742
                return value;
1✔
743
            }
744

745
            /**
746
             * {@inheritDoc}
747
             */
748
            public int apply(MethodVisitor methodVisitor, Method method) {
749
                if (load != Opcodes.NOP) {
1✔
750
                    methodVisitor.visitInsn(load);
1✔
751
                }
752
                methodVisitor.visitInsn(returned);
1✔
753
                return size;
1✔
754
            }
755

756
            /**
757
             * A dispatcher for returning a default value for a primitive array.
758
             */
759
            protected enum OfPrimitiveArray implements Dispatcher {
1✔
760

761
                /**
762
                 * A dispatcher for a {@code boolean} array.
763
                 */
764
                BOOLEAN(new boolean[0], Opcodes.T_BOOLEAN),
1✔
765

766
                /**
767
                 * A dispatcher for a {@code byte} array.
768
                 */
769
                BYTE(new byte[0], Opcodes.T_BYTE),
1✔
770

771
                /**
772
                 * A dispatcher for a {@code short} array.
773
                 */
774
                SHORT(new short[0], Opcodes.T_SHORT),
1✔
775

776
                /**
777
                 * A dispatcher for a {@code char} array.
778
                 */
779
                CHARACTER(new char[0], Opcodes.T_CHAR),
1✔
780

781
                /**
782
                 * A dispatcher for a {@code int} array.
783
                 */
784
                INTEGER(new int[0], Opcodes.T_INT),
1✔
785

786
                /**
787
                 * A dispatcher for a {@code long} array.
788
                 */
789
                LONG(new long[0], Opcodes.T_LONG),
1✔
790

791
                /**
792
                 * A dispatcher for a {@code float} array.
793
                 */
794
                FLOAT(new float[0], Opcodes.T_FLOAT),
1✔
795

796
                /**
797
                 * A dispatcher for a {@code double} array.
798
                 */
799
                DOUBLE(new double[0], Opcodes.T_DOUBLE);
1✔
800

801
                /**
802
                 * The default value.
803
                 */
804
                private final Object value;
805

806
                /**
807
                 * The operand for creating an array of the represented type.
808
                 */
809
                private final int operand;
810

811
                /**
812
                 * Creates a new dispatcher for a primitive array.
813
                 *
814
                 * @param value   The default value.
815
                 * @param operand The operand for creating an array of the represented type.
816
                 */
817
                OfPrimitiveArray(Object value, int operand) {
1✔
818
                    this.value = value;
1✔
819
                    this.operand = operand;
1✔
820
                }
1✔
821

822
                /**
823
                 * {@inheritDoc}
824
                 */
825
                public Object invoke(Object[] argument) {
826
                    return value;
1✔
827
                }
828

829
                /**
830
                 * {@inheritDoc}
831
                 */
832
                public int apply(MethodVisitor methodVisitor, Method method) {
833
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
834
                    methodVisitor.visitIntInsn(Opcodes.NEWARRAY, operand);
1✔
835
                    methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
836
                    return 1;
1✔
837
                }
838
            }
839

840
            /**
841
             * A dispatcher for a non-primitive array type.
842
             */
843
            @HashCodeAndEqualsPlugin.Enhance
844
            protected static class OfNonPrimitiveArray implements Dispatcher {
845

846
                /**
847
                 * The default value.
848
                 */
849
                @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
850
                private final Object value;
851

852
                /**
853
                 * The represented component type.
854
                 */
855
                private final Class<?> componentType;
856

857
                /**
858
                 * Creates a new dispatcher for the default value of a non-primitive array.
859
                 *
860
                 * @param value         The default value.
861
                 * @param componentType The represented component type.
862
                 */
863
                protected OfNonPrimitiveArray(Object value, Class<?> componentType) {
1✔
864
                    this.value = value;
1✔
865
                    this.componentType = componentType;
1✔
866
                }
1✔
867

868
                /**
869
                 * Creates a new dispatcher.
870
                 *
871
                 * @param componentType The represented component type.
872
                 * @return A dispatcher for the supplied component type.
873
                 */
874
                protected static Dispatcher of(Class<?> componentType) {
875
                    return new OfNonPrimitiveArray(Array.newInstance(componentType, 0), componentType);
1✔
876
                }
877

878
                /**
879
                 * {@inheritDoc}
880
                 */
881
                public Object invoke(Object[] argument) {
882
                    return value;
1✔
883
                }
884

885
                /**
886
                 * {@inheritDoc}
887
                 */
888
                public int apply(MethodVisitor methodVisitor, Method method) {
889
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
890
                    methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(componentType));
1✔
891
                    methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
892
                    return 1;
1✔
893
                }
894
            }
895
        }
896

897
        /**
898
         * A dispatcher for invoking a constructor.
899
         */
900
        @HashCodeAndEqualsPlugin.Enhance
901
        class ForConstructor implements Dispatcher {
902

903
            /**
904
             * The proxied constructor.
905
             */
906
            private final Constructor<?> constructor;
907

908
            /**
909
             * Creates a dispatcher for invoking a constructor.
910
             *
911
             * @param constructor The proxied constructor.
912
             */
913
            protected ForConstructor(Constructor<?> constructor) {
1✔
914
                this.constructor = constructor;
1✔
915
            }
1✔
916

917
            /**
918
             * {@inheritDoc}
919
             */
920
            public Object invoke(Object[] argument) throws Throwable {
921
                return INVOKER.newInstance(constructor, argument);
1✔
922
            }
923

924
            /**
925
             * {@inheritDoc}
926
             */
927
            public int apply(MethodVisitor methodVisitor, Method method) {
928
                Class<?>[] source = method.getParameterTypes(), target = constructor.getParameterTypes();
1✔
929
                methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(constructor.getDeclaringClass()));
1✔
930
                methodVisitor.visitInsn(Opcodes.DUP);
1✔
931
                int offset = 1;
1✔
932
                for (int index = 0; index < source.length; index++) {
1✔
933
                    Type type = Type.getType(source[index]);
×
934
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
×
935
                    if (source[index] != target[index]) {
×
936
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(target[index]));
×
937
                    }
938
                    offset += type.getSize();
×
939
                }
940
                methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
941
                        Type.getInternalName(constructor.getDeclaringClass()),
1✔
942
                        MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
943
                        Type.getConstructorDescriptor(constructor),
1✔
944
                        false);
945
                methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
946
                return offset + 1;
1✔
947
            }
948
        }
949

950
        /**
951
         * A dispatcher for invoking a static proxied method.
952
         */
953
        @HashCodeAndEqualsPlugin.Enhance
954
        class ForStaticMethod implements Dispatcher {
955

956
            /**
957
             * The proxied method.
958
             */
959
            private final Method method;
960

961
            /**
962
             * Creates a dispatcher for invoking a static method.
963
             *
964
             * @param method The proxied method.
965
             */
966
            protected ForStaticMethod(Method method) {
1✔
967
                this.method = method;
1✔
968
            }
1✔
969

970
            /**
971
             * {@inheritDoc}
972
             */
973
            @MaybeNull
974
            public Object invoke(Object[] argument) throws Throwable {
975
                return INVOKER.invoke(method, null, argument);
1✔
976
            }
977

978
            /**
979
             * {@inheritDoc}
980
             */
981
            public int apply(MethodVisitor methodVisitor, Method method) {
982
                Class<?>[] source = method.getParameterTypes(), target = this.method.getParameterTypes();
1✔
983
                int offset = 1;
1✔
984
                for (int index = 0; index < source.length; index++) {
1✔
985
                    Type type = Type.getType(source[index]);
1✔
986
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
1✔
987
                    if (source[index] != target[index]) {
1✔
988
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(target[index]));
1✔
989
                    }
990
                    offset += type.getSize();
1✔
991
                }
992
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
1✔
993
                        Type.getInternalName(this.method.getDeclaringClass()),
1✔
994
                        this.method.getName(),
1✔
995
                        Type.getMethodDescriptor(this.method),
1✔
996
                        this.method.getDeclaringClass().isInterface());
1✔
997
                methodVisitor.visitInsn(Type.getReturnType(this.method).getOpcode(Opcodes.IRETURN));
1✔
998
                return Math.max(offset - 1, Type.getReturnType(this.method).getSize());
1✔
999
            }
1000
        }
1001

1002
        /**
1003
         * A dispatcher for invoking a non-static proxied method.
1004
         */
1005
        @HashCodeAndEqualsPlugin.Enhance
1006
        class ForNonStaticMethod implements Dispatcher {
1007

1008
            /**
1009
             * Indicates a call without arguments.
1010
             */
1011
            private static final Object[] NO_ARGUMENTS = new Object[0];
1✔
1012

1013
            /**
1014
             * The proxied method.
1015
             */
1016
            private final Method method;
1017

1018
            /**
1019
             * Creates a dispatcher for invoking a non-static method.
1020
             *
1021
             * @param method The proxied method.
1022
             */
1023
            protected ForNonStaticMethod(Method method) {
1✔
1024
                this.method = method;
1✔
1025
            }
1✔
1026

1027
            /**
1028
             * {@inheritDoc}
1029
             */
1030
            public Object invoke(Object[] argument) throws Throwable {
1031
                Object[] reduced;
1032
                if (argument.length == 1) {
1✔
1033
                    reduced = NO_ARGUMENTS;
1✔
1034
                } else {
1035
                    reduced = new Object[argument.length - 1];
1✔
1036
                    System.arraycopy(argument, 1, reduced, 0, reduced.length);
1✔
1037
                }
1038
                return INVOKER.invoke(method, argument[0], reduced);
1✔
1039
            }
1040

1041
            /**
1042
             * {@inheritDoc}
1043
             */
1044
            public int apply(MethodVisitor methodVisitor, Method method) {
1045
                Class<?>[] source = method.getParameterTypes(), target = this.method.getParameterTypes();
1✔
1046
                int offset = 1;
1✔
1047
                for (int index = 0; index < source.length; index++) {
1✔
1048
                    Type type = Type.getType(source[index]);
1✔
1049
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
1✔
1050
                    if (source[index] != (index == 0 ? this.method.getDeclaringClass() : target[index - 1])) {
1✔
1051
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(index == 0
1✔
1052
                                ? this.method.getDeclaringClass()
1✔
1053
                                : target[index - 1]));
1054
                    }
1055
                    offset += type.getSize();
1✔
1056
                }
1057
                methodVisitor.visitMethodInsn(this.method.getDeclaringClass().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
1✔
1058
                        Type.getInternalName(this.method.getDeclaringClass()),
1✔
1059
                        this.method.getName(),
1✔
1060
                        Type.getMethodDescriptor(this.method),
1✔
1061
                        this.method.getDeclaringClass().isInterface());
1✔
1062
                methodVisitor.visitInsn(Type.getReturnType(this.method).getOpcode(Opcodes.IRETURN));
1✔
1063
                return Math.max(offset - 1, Type.getReturnType(this.method).getSize());
1✔
1064
            }
1065
        }
1066

1067
        /**
1068
         * A dispatcher for an unresolved method.
1069
         */
1070
        @HashCodeAndEqualsPlugin.Enhance
1071
        class ForUnresolvedMethod implements Dispatcher {
1072

1073
            /**
1074
             * The message for describing the reason why the method could not be resolved.
1075
             */
1076
            private final String message;
1077

1078
            /**
1079
             * Creates a dispatcher for an unresolved method.
1080
             *
1081
             * @param message The message for describing the reason why the method could not be resolved.
1082
             */
1083
            protected ForUnresolvedMethod(String message) {
1✔
1084
                this.message = message;
1✔
1085
            }
1✔
1086

1087
            /**
1088
             * {@inheritDoc}
1089
             */
1090
            public Object invoke(Object[] argument) throws Throwable {
1091
                throw new IllegalStateException("Could not invoke proxy: " + message);
1✔
1092
            }
1093

1094
            /**
1095
             * {@inheritDoc}
1096
             */
1097
            public int apply(MethodVisitor methodVisitor, Method method) {
1098
                methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalStateException.class));
1✔
1099
                methodVisitor.visitInsn(Opcodes.DUP);
1✔
1100
                methodVisitor.visitLdcInsn(message);
1✔
1101
                methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
1102
                        Type.getInternalName(IllegalStateException.class),
1✔
1103
                        MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1104
                        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)),
1✔
1105
                        false);
1106
                methodVisitor.visitInsn(Opcodes.ATHROW);
1✔
1107
                return 3;
1✔
1108
            }
1109
        }
1110
    }
1111

1112
    /**
1113
     * An invocation handler that invokes given dispatchers.
1114
     */
1115
    @HashCodeAndEqualsPlugin.Enhance
1116
    protected static class ProxiedInvocationHandler implements InvocationHandler {
1117

1118
        /**
1119
         * Indicates that an invocation handler does not provide any arguments.
1120
         */
1121
        private static final Object[] NO_ARGUMENTS = new Object[0];
1✔
1122

1123
        /**
1124
         * The proxied type's name.
1125
         */
1126
        private final String name;
1127

1128
        /**
1129
         * A mapping of proxy type methods to their proxied dispatchers.
1130
         */
1131
        private final Map<Method, Dispatcher> targets;
1132

1133
        /**
1134
         * Creates a new invocation handler for proxying a type.
1135
         *
1136
         * @param name    The proxied type's name.
1137
         * @param targets A mapping of proxy type methods to their proxied dispatchers.
1138
         */
1139
        protected ProxiedInvocationHandler(String name, Map<Method, Dispatcher> targets) {
1✔
1140
            this.name = name;
1✔
1141
            this.targets = targets;
1✔
1142
        }
1✔
1143

1144
        /**
1145
         * {@inheritDoc}
1146
         */
1147
        @MaybeNull
1148
        public Object invoke(Object proxy, Method method, @MaybeNull Object[] argument) throws Throwable {
1149
            if (method.getDeclaringClass() == Object.class) {
1✔
1150
                if (method.getName().equals("hashCode")) {
×
1151
                    return hashCode();
×
1152
                } else if (method.getName().equals("equals")) {
×
1153
                    return argument[0] != null
×
1154
                            && Proxy.isProxyClass(argument[0].getClass())
×
1155
                            && Proxy.getInvocationHandler(argument[0]).equals(this);
×
1156
                } else if (method.getName().equals("toString")) {
×
1157
                    return "Call proxy for " + name;
×
1158
                } else {
1159
                    throw new IllegalStateException("Unexpected object method: " + method);
×
1160
                }
1161
            }
1162
            Dispatcher dispatcher = targets.get(method);
1✔
1163
            try {
1164
                try {
1165
                    if (dispatcher == null) {
1✔
1166
                        throw new IllegalStateException("No proxy target found for " + method);
×
1167
                    } else {
1168
                        return dispatcher.invoke(argument == null
1✔
1169
                                ? NO_ARGUMENTS
1170
                                : argument);
1171
                    }
1172
                } catch (InvocationTargetException exception) {
1✔
1173
                    throw exception.getTargetException();
1✔
1174
                }
1175
            } catch (RuntimeException exception) {
1✔
1176
                throw exception;
1✔
1177
            } catch (Error error) {
×
1178
                throw error;
×
1179
            } catch (Throwable throwable) {
1✔
1180
                for (Class<?> type : method.getExceptionTypes()) {
1✔
1181
                    if (type.isInstance(throwable)) {
1✔
1182
                        throw throwable;
1✔
1183
                    }
1184
                }
1185
                throw new IllegalStateException("Failed to invoke proxy for " + method, throwable);
×
1186
            }
1187
        }
1188
    }
1189

1190
    /**
1191
     * A class loader for loading synthetic classes for implementing a {@link JavaDispatcher}.
1192
     */
1193
    protected static class DynamicClassLoader extends ClassLoader {
1194

1195
        /**
1196
         * The dump folder that is defined by the {@link TypeWriter#DUMP_PROPERTY} property or {@code null} if not set.
1197
         */
1198
        @MaybeNull
1199
        private static final String DUMP_FOLDER;
1200

1201
        /**
1202
         * Indicates that a constructor does not declare any parameters.
1203
         */
1204
        private static final Class<?>[] NO_PARAMETER = new Class<?>[0];
1✔
1205

1206
        /**
1207
         * Indicates that a constructor does not require any arguments.
1208
         */
1209
        private static final Object[] NO_ARGUMENT = new Object[0];
1✔
1210

1211
        /*
1212
         * Resolves the currently set dump folder.
1213
         */
1214
        static {
1215
            String dumpFolder;
1216
            try {
1217
                dumpFolder = doPrivileged(new GetSystemPropertyAction(TypeWriter.DUMP_PROPERTY));
1✔
1218
            } catch (Throwable ignored) {
×
1219
                dumpFolder = null;
×
1220
            }
1✔
1221
            DUMP_FOLDER = dumpFolder;
1✔
1222
        }
1✔
1223

1224
        /**
1225
         * Creates a new dynamic class loader.
1226
         *
1227
         * @param target The proxied type.
1228
         */
1229
        protected DynamicClassLoader(Class<?> target) {
1230
            super(target.getClassLoader());
1✔
1231
            RESOLVER.accept(this, target);
1✔
1232
        }
1✔
1233

1234
        /**
1235
         * Creates a new proxied type.
1236
         *
1237
         * @param proxy       The proxy type interface.
1238
         * @param dispatchers The dispatchers to implement.
1239
         * @return An instance of the proxied type.
1240
         */
1241
        @SuppressFBWarnings(value = {"REC_CATCH_EXCEPTION", "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED"}, justification = "Expected internal invocation.")
1242
        protected static Object proxy(Class<?> proxy, Map<Method, Dispatcher> dispatchers) {
1243
            ClassWriter classWriter = new ClassWriter(AsmVisitorWrapper.NO_FLAGS);
1✔
1244
            classWriter.visit(ClassFileVersion.JAVA_V5.getMinorMajorVersion(),
1✔
1245
                    Opcodes.ACC_PUBLIC,
1246
                    Type.getInternalName(proxy) + "$Proxy",
1✔
1247
                    null,
1248
                    Type.getInternalName(Object.class),
1✔
1249
                    new String[]{Type.getInternalName(proxy)});
1✔
1250
            for (Map.Entry<Method, Dispatcher> entry : dispatchers.entrySet()) {
1✔
1251
                Class<?>[] exceptionType = entry.getKey().getExceptionTypes();
1✔
1252
                String[] exceptionTypeName = new String[exceptionType.length];
1✔
1253
                for (int index = 0; index < exceptionType.length; index++) {
1✔
1254
                    exceptionTypeName[index] = Type.getInternalName(exceptionType[index]);
1✔
1255
                }
1256
                MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
1✔
1257
                        entry.getKey().getName(),
1✔
1258
                        Type.getMethodDescriptor(entry.getKey()),
1✔
1259
                        null,
1260
                        exceptionTypeName);
1261
                methodVisitor.visitCode();
1✔
1262
                int offset = (entry.getKey().getModifiers() & Opcodes.ACC_STATIC) == 0 ? 1 : 0;
1✔
1263
                for (Class<?> type : entry.getKey().getParameterTypes()) {
1✔
1264
                    offset += Type.getType(type).getSize();
1✔
1265
                }
1266
                methodVisitor.visitMaxs(entry.getValue().apply(methodVisitor, entry.getKey()), offset);
1✔
1267
                methodVisitor.visitEnd();
1✔
1268
            }
1✔
1269
            MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
1✔
1270
                    MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1271
                    Type.getMethodDescriptor(Type.VOID_TYPE),
1✔
1272
                    null,
1273
                    null);
1274
            methodVisitor.visitCode();
1✔
1275
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
1✔
1276
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
1277
                    Type.getInternalName(Object.class),
1✔
1278
                    MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1279
                    Type.getMethodDescriptor(Type.VOID_TYPE),
1✔
1280
                    false);
1281
            methodVisitor.visitInsn(Opcodes.RETURN);
1✔
1282
            methodVisitor.visitMaxs(1, 1);
1✔
1283
            methodVisitor.visitEnd();
1✔
1284
            classWriter.visitEnd();
1✔
1285
            byte[] binaryRepresentation = classWriter.toByteArray();
1✔
1286
            if (DUMP_FOLDER != null) {
1✔
1287
                try {
1288
                    OutputStream outputStream = new FileOutputStream(new File(DUMP_FOLDER, proxy.getName() + "$Proxy.class"));
×
1289
                    try {
1290
                        outputStream.write(binaryRepresentation);
×
1291
                    } finally {
1292
                        outputStream.close();
×
1293
                    }
1294
                } catch (Throwable ignored) {
×
1295
                    /* do nothing */
1296
                }
×
1297
            }
1298
            try {
1299
                return new DynamicClassLoader(proxy)
1✔
1300
                        .defineClass(proxy.getName() + "$Proxy",
1✔
1301
                                binaryRepresentation,
1302
                                0,
1303
                                binaryRepresentation.length,
1304
                                JavaDispatcher.class.getProtectionDomain())
1✔
1305
                        .getConstructor(NO_PARAMETER)
1✔
1306
                        .newInstance(NO_ARGUMENT);
1✔
1307
            } catch (Exception exception) {
×
1308
                throw new IllegalStateException("Failed to create proxy for " + proxy.getName(), exception);
×
1309
            }
1310
        }
1311

1312
        /**
1313
         * Resolves a {@link Invoker} for a separate class loader.
1314
         *
1315
         * @return The created {@link Invoker}.
1316
         */
1317
        @SuppressFBWarnings(value = {"REC_CATCH_EXCEPTION", "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED"}, justification = "Expected internal invocation.")
1318
        protected static Invoker invoker() {
1319
            ClassWriter classWriter = new ClassWriter(AsmVisitorWrapper.NO_FLAGS);
1✔
1320
            classWriter.visit(ClassFileVersion.ofThisVm().getMinorMajorVersion(),
1✔
1321
                    Opcodes.ACC_PUBLIC,
1322
                    Type.getInternalName(Invoker.class) + "$Dispatcher",
1✔
1323
                    null,
1324
                    Type.getInternalName(Object.class),
1✔
1325
                    new String[]{Type.getInternalName(Invoker.class)});
1✔
1326
            for (Method method : GraalImageCode.getCurrent().sorted(Invoker.class.getMethods(), MethodComparator.INSTANCE)) {
1✔
1327
                Class<?>[] exceptionType = method.getExceptionTypes();
1✔
1328
                String[] exceptionTypeName = new String[exceptionType.length];
1✔
1329
                for (int index = 0; index < exceptionType.length; index++) {
1✔
1330
                    exceptionTypeName[index] = Type.getInternalName(exceptionType[index]);
1✔
1331
                }
1332
                MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
1✔
1333
                        method.getName(),
1✔
1334
                        Type.getMethodDescriptor(method),
1✔
1335
                        null,
1336
                        exceptionTypeName);
1337
                methodVisitor.visitCode();
1✔
1338
                int offset = 1;
1✔
1339
                Type[] parameter = new Type[method.getParameterTypes().length - 1];
1✔
1340
                for (int index = 0; index < method.getParameterTypes().length; index++) {
1✔
1341
                    Type type = Type.getType(method.getParameterTypes()[index]);
1✔
1342
                    if (index > 0) {
1✔
1343
                        parameter[index - 1] = type;
1✔
1344
                    }
1345
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
1✔
1346
                    offset += type.getSize();
1✔
1347
                }
1348
                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
1✔
1349
                        Type.getInternalName(method.getParameterTypes()[0]),
1✔
1350
                        method.getName(),
1✔
1351
                        Type.getMethodDescriptor(Type.getReturnType(method), parameter),
1✔
1352
                        false);
1353
                methodVisitor.visitInsn(Type.getReturnType(method).getOpcode(Opcodes.IRETURN));
1✔
1354
                methodVisitor.visitMaxs(Math.max(offset - 1, Type.getReturnType(method).getSize()), offset);
1✔
1355
                methodVisitor.visitEnd();
1✔
1356
            }
1357
            MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC,
1✔
1358
                    MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1359
                    Type.getMethodDescriptor(Type.VOID_TYPE),
1✔
1360
                    null,
1361
                    null);
1362
            methodVisitor.visitCode();
1✔
1363
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
1✔
1364
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
1365
                    Type.getInternalName(Object.class),
1✔
1366
                    MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1367
                    Type.getMethodDescriptor(Type.VOID_TYPE),
1✔
1368
                    false);
1369
            methodVisitor.visitInsn(Opcodes.RETURN);
1✔
1370
            methodVisitor.visitMaxs(1, 1);
1✔
1371
            methodVisitor.visitEnd();
1✔
1372
            classWriter.visitEnd();
1✔
1373
            byte[] binaryRepresentation = classWriter.toByteArray();
1✔
1374
            try {
1375
                String dumpFolder = System.getProperty(TypeWriter.DUMP_PROPERTY);
1✔
1376
                if (dumpFolder != null) {
1✔
1377
                    OutputStream outputStream = new FileOutputStream(new File(dumpFolder, Invoker.class.getName() + "$Dispatcher.class"));
×
1378
                    try {
1379
                        outputStream.write(binaryRepresentation);
×
1380
                    } finally {
1381
                        outputStream.close();
×
1382
                    }
1383
                }
1384
            } catch (Throwable ignored) {
×
1385
                /* do nothing */
1386
            }
1✔
1387
            try {
1388
                return (Invoker) new DynamicClassLoader(Invoker.class)
1✔
1389
                        .defineClass(Invoker.class.getName() + "$Dispatcher",
1✔
1390
                                binaryRepresentation,
1391
                                0,
1392
                                binaryRepresentation.length,
1393
                                JavaDispatcher.class.getProtectionDomain())
1✔
1394
                        .getConstructor(NO_PARAMETER)
1✔
1395
                        .newInstance(NO_ARGUMENT);
1✔
1396
            } catch (UnsupportedOperationException ignored) {
×
1397
                return new DirectInvoker();
×
1398
            } catch (Exception exception) {
×
1399
                throw new IllegalStateException("Failed to create invoker for " + Invoker.class.getName(), exception);
×
1400
            }
1401
        }
1402

1403
        /**
1404
         * A resolver to make adjustments that are possibly necessary to withhold module graph guarantees.
1405
         */
1406
        protected interface Resolver {
1407

1408
            /**
1409
             * Adjusts a module graph if necessary.
1410
             *
1411
             * @param classLoader The class loader to adjust.
1412
             * @param target      The targeted class for which a proxy is created.
1413
             */
1414
            void accept(@MaybeNull ClassLoader classLoader, Class<?> target);
1415

1416
            /**
1417
             * An action to create a resolver.
1418
             */
1419
            enum CreationAction implements PrivilegedAction<Resolver> {
1✔
1420

1421
                /**
1422
                 * The singleton instance.
1423
                 */
1424
                INSTANCE;
1✔
1425

1426
                /**
1427
                 * {@inheritDoc}
1428
                 */
1429
                @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
1430
                public Resolver run() {
1431
                    try {
1432
                        Class<?> module = Class.forName("java.lang.Module", false, null);
×
1433
                        return new ForModuleSystem(Class.class.getMethod("getModule"),
×
1434
                                module.getMethod("isExported", String.class),
×
1435
                                module.getMethod("addExports", String.class, module),
×
1436
                                ClassLoader.class.getMethod("getUnnamedModule"));
×
1437
                    } catch (Exception ignored) {
1✔
1438
                        return NoOp.INSTANCE;
1✔
1439
                    }
1440
                }
1441
            }
1442

1443
            /**
1444
             * A non-operational resolver for VMs that do not support the module system.
1445
             */
1446
            enum NoOp implements Resolver {
1✔
1447

1448
                /**
1449
                 * The singleton instance.
1450
                 */
1451
                INSTANCE;
1✔
1452

1453
                /**
1454
                 * {@inheritDoc}
1455
                 */
1456
                public void accept(@MaybeNull ClassLoader classLoader, Class<?> target) {
1457
                    /* do nothing */
1458
                }
1✔
1459
            }
1460

1461
            /**
1462
             * A resolver for VMs that do support the module system.
1463
             */
1464
            @HashCodeAndEqualsPlugin.Enhance
1465
            class ForModuleSystem implements Resolver {
1466

1467
                /**
1468
                 * The {@code java.lang.Class#getModule} method.
1469
                 */
1470
                private final Method getModule;
1471

1472
                /**
1473
                 * The {@code java.lang.Module#isExported} method.
1474
                 */
1475
                private final Method isExported;
1476

1477
                /**
1478
                 * The {@code java.lang.Module#addExports} method.
1479
                 */
1480
                private final Method addExports;
1481

1482
                /**
1483
                 * The {@code java.lang.ClassLoader#getUnnamedModule} method.
1484
                 */
1485
                private final Method getUnnamedModule;
1486

1487
                /**
1488
                 * Creates a new resolver for a VM that supports the module system.
1489
                 *
1490
                 * @param getModule        The {@code java.lang.Class#getModule} method.
1491
                 * @param isExported       The {@code java.lang.Module#isExported} method.
1492
                 * @param addExports       The {@code java.lang.Module#addExports} method.
1493
                 * @param getUnnamedModule The {@code java.lang.ClassLoader#getUnnamedModule} method.
1494
                 */
1495
                protected ForModuleSystem(Method getModule,
1496
                                          Method isExported,
1497
                                          Method addExports,
1498
                                          Method getUnnamedModule) {
×
1499
                    this.getModule = getModule;
×
1500
                    this.isExported = isExported;
×
1501
                    this.addExports = addExports;
×
1502
                    this.getUnnamedModule = getUnnamedModule;
×
1503
                }
×
1504

1505
                /**
1506
                 * {@inheritDoc}
1507
                 */
1508
                @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should always be wrapped for clarity.")
1509
                public void accept(@MaybeNull ClassLoader classLoader, Class<?> target) {
1510
                    Package location = target.getPackage();
×
1511
                    if (location != null) {
×
1512
                        try {
1513
                            Object module = getModule.invoke(target);
×
1514
                            if (!(Boolean) isExported.invoke(module, location.getName())) {
×
1515
                                addExports.invoke(module, location.getName(), getUnnamedModule.invoke(classLoader));
×
1516
                            }
1517
                        } catch (Exception exception) {
×
1518
                            throw new IllegalStateException("Failed to adjust module graph for dispatcher", exception);
×
1519
                        }
×
1520
                    }
1521
                }
×
1522
            }
1523
        }
1524
    }
1525
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc