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

raphw / byte-buddy / #801

27 Oct 2025 09:37AM UTC coverage: 84.715% (-0.4%) from 85.118%
#801

push

raphw
Fix imports.

29586 of 34924 relevant lines covered (84.72%)

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.Annotation;
39
import java.lang.annotation.Documented;
40
import java.lang.annotation.ElementType;
41
import java.lang.annotation.Retention;
42
import java.lang.annotation.RetentionPolicy;
43
import java.lang.annotation.Target;
44
import java.lang.reflect.Array;
45
import java.lang.reflect.Constructor;
46
import java.lang.reflect.InvocationHandler;
47
import java.lang.reflect.InvocationTargetException;
48
import java.lang.reflect.Method;
49
import java.lang.reflect.Modifier;
50
import java.lang.reflect.Proxy;
51
import java.security.Permission;
52
import java.security.PrivilegedAction;
53
import java.util.HashMap;
54
import java.util.LinkedHashMap;
55
import java.util.Map;
56

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

78
    /**
79
     * A property to determine, that if {@code true}, dispatcher classes will be generated natively and not by using a {@link Proxy}.
80
     */
81
    public static final String GENERATE_PROPERTY = "net.bytebuddy.generate";
82

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

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

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

101
    /**
102
     * The proxy type.
103
     */
104
    private final Class<T> proxy;
105

106
    /**
107
     * The class loader to resolve the proxied type from or {@code null} if the bootstrap loader should be used.
108
     */
109
    @MaybeNull
110
    @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
111
    private final ClassLoader classLoader;
112

113
    /**
114
     * {@code true} if a proxy class should be manually generated.
115
     */
116
    private final boolean generate;
117

118
    /**
119
     * Creates a new dispatcher.
120
     *
121
     * @param proxy       The proxy type.
122
     * @param classLoader The class loader to resolve the proxied type from or {@code null} if the bootstrap loader should be used.
123
     * @param generate    {@code true} if a proxy class should be manually generated.
124
     */
125
    protected JavaDispatcher(Class<T> proxy, @MaybeNull ClassLoader classLoader, boolean generate) {
1✔
126
        this.proxy = proxy;
1✔
127
        this.classLoader = classLoader;
1✔
128
        this.generate = generate;
1✔
129
    }
1✔
130

131
    /**
132
     * A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
133
     *
134
     * @param action The action to execute from a privileged context.
135
     * @param <T>    The type of the action's resolved value.
136
     * @return The action's resolved value.
137
     */
138
    @AccessControllerPlugin.Enhance
139
    private static <T> T doPrivileged(PrivilegedAction<T> action) {
140
        return action.run();
×
141
    }
142

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

154
    /**
155
     * Resolves an action for creating a dispatcher for the provided type.
156
     *
157
     * @param type        The type for which a dispatcher should be resolved.
158
     * @param classLoader The class loader to resolve the proxied type from.
159
     * @param <T>         The resolved type.
160
     * @return An action for creating an appropriate dispatcher.
161
     */
162
    public static <T> PrivilegedAction<T> of(Class<T> type, @MaybeNull ClassLoader classLoader) {
163
        return of(type, classLoader, GENERATE);
1✔
164
    }
165

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

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

397
    /**
398
     * Indicates a proxied type's name. This annotation is mandatory for proxied types but can also be used on method's
399
     * to describe the actual name of the proxied method or on parameters to indicate the parameter's (component) type.
400
     */
401
    @Documented
402
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
403
    @Retention(RetentionPolicy.RUNTIME)
404
    public @interface Proxied {
405

406
        /**
407
         * Returns the binary name of the proxied type.
408
         *
409
         * @return The binary name of the proxied type.
410
         */
411
        String value();
412
    }
413

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

424
    /**
425
     * Indicates that a proxied method is a constructor.
426
     */
427
    @Documented
428
    @Target(ElementType.METHOD)
429
    @Retention(RetentionPolicy.RUNTIME)
430
    public @interface IsConstructor {
431
        /* empty */
432
    }
433

434
    /**
435
     * Indicates that a method is supposed to perform an instance check. The annotated method must declare a single argument
436
     * with a type that is assignable from the proxied type.
437
     */
438
    @Documented
439
    @Target(ElementType.METHOD)
440
    @Retention(RetentionPolicy.RUNTIME)
441
    public @interface Instance {
442
        /* empty */
443
    }
444

445
    /**
446
     * Indicates that the method is supposed to return an array of the proxied type. The annotated method must declare a single,
447
     * {@code int}-typed argument that represents the array's dimension.
448
     */
449
    @Documented
450
    @Target(ElementType.METHOD)
451
    @Retention(RetentionPolicy.RUNTIME)
452
    public @interface Container {
453
        /* empty */
454
    }
455

456
    /**
457
     * Indicates that a method is supposed to return a default value if a method or type could not be resolved.
458
     */
459
    @Documented
460
    @Target({ElementType.TYPE, ElementType.METHOD})
461
    @Retention(RetentionPolicy.RUNTIME)
462
    public @interface Defaults {
463
        /* empty */
464
    }
465

466
    /**
467
     * A privileged action for creating an {@link Invoker}.
468
     */
469
    @HashCodeAndEqualsPlugin.Enhance
470
    private static class InvokerCreationAction implements PrivilegedAction<Invoker> {
471

472
        /**
473
         * {@inheritDoc}
474
         */
475
        public Invoker run() {
476
            return DynamicClassLoader.invoker();
1✔
477
        }
478
    }
479

480
    /**
481
     * An {@link Invoker} that uses Byte Buddy's invocation context to use if dynamic class loading is not supported, for example on Android,
482
     * and that do not use secured contexts, where this security measure is obsolete to begin with.
483
     */
484
    @HashCodeAndEqualsPlugin.Enhance
485
    private static class DirectInvoker implements Invoker {
486

487
        /**
488
         * {@inheritDoc}
489
         */
490
        public Object newInstance(Constructor<?> constructor, Object[] argument) throws InstantiationException, IllegalAccessException, InvocationTargetException {
491
            return constructor.newInstance(argument);
×
492
        }
493

494
        /**
495
         * {@inheritDoc}
496
         */
497
        public Object invoke(Method method, @MaybeNull Object instance, @MaybeNull Object[] argument) throws IllegalAccessException, InvocationTargetException {
498
            return method.invoke(instance, argument);
×
499
        }
500
    }
501

502
    /**
503
     * A dispatcher for handling a proxied method.
504
     */
505
    protected interface Dispatcher {
506

507
        /**
508
         * Invokes the proxied action.
509
         *
510
         * @param argument The arguments provided.
511
         * @return The return value.
512
         * @throws Throwable If any error occurs.
513
         */
514
        @MaybeNull
515
        Object invoke(Object[] argument) throws Throwable;
516

517
        /**
518
         * Implements this dispatcher in a generated proxy.
519
         *
520
         * @param methodVisitor The method visitor to implement the method with.
521
         * @param method        The method being implemented.
522
         * @return The maximal size of the operand stack.
523
         */
524
        int apply(MethodVisitor methodVisitor, Method method);
525

526
        /**
527
         * A dispatcher that performs an instance check.
528
         */
529
        @HashCodeAndEqualsPlugin.Enhance
530
        class ForInstanceCheck implements Dispatcher {
531

532
            /**
533
             * The checked type.
534
             */
535
            private final Class<?> target;
536

537
            /**
538
             * Creates a dispatcher for an instance check.
539
             *
540
             * @param target The checked type.
541
             */
542
            protected ForInstanceCheck(Class<?> target) {
1✔
543
                this.target = target;
1✔
544
            }
1✔
545

546
            /**
547
             * {@inheritDoc}
548
             */
549
            public Object invoke(Object[] argument) {
550
                return target.isInstance(argument[0]);
1✔
551
            }
552

553
            /**
554
             * {@inheritDoc}
555
             */
556
            public int apply(MethodVisitor methodVisitor, Method method) {
557
                methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
1✔
558
                methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(target));
1✔
559
                methodVisitor.visitInsn(Opcodes.IRETURN);
1✔
560
                return 1;
1✔
561
            }
562
        }
563

564
        /**
565
         * A dispatcher that creates an array.
566
         */
567
        @HashCodeAndEqualsPlugin.Enhance
568
        class ForContainerCreation implements Dispatcher {
569

570
            /**
571
             * The component type.
572
             */
573
            private final Class<?> target;
574

575
            /**
576
             * Creates a dispatcher for an array creation.
577
             *
578
             * @param target The component type.
579
             */
580
            protected ForContainerCreation(Class<?> target) {
1✔
581
                this.target = target;
1✔
582
            }
1✔
583

584
            /**
585
             * {@inheritDoc}
586
             */
587
            public Object invoke(Object[] argument) {
588
                return Array.newInstance(target, (Integer) argument[0]);
1✔
589
            }
590

591
            /**
592
             * {@inheritDoc}
593
             */
594
            public int apply(MethodVisitor methodVisitor, Method method) {
595
                methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);
1✔
596
                methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(target));
1✔
597
                methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
598
                return 1;
1✔
599
            }
600
        }
601

602
        /**
603
         * A dispatcher that returns a fixed value.
604
         */
605
        enum ForDefaultValue implements Dispatcher {
1✔
606

607
            /**
608
             * A dispatcher for a {@code void} type.
609
             */
610
            VOID(null, Opcodes.NOP, Opcodes.RETURN, 0),
1✔
611

612
            /**
613
             * A dispatcher for a {@code boolean} type.
614
             */
615
            BOOLEAN(false, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
616

617
            /**
618
             * A dispatcher for a {@code boolean} type that returns {@code true}.
619
             */
620
            BOOLEAN_REVERSE(true, Opcodes.ICONST_1, Opcodes.IRETURN, 1),
1✔
621

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

627
            /**
628
             * A dispatcher for a {@code short} type.
629
             */
630
            SHORT((short) 0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
631

632
            /**
633
             * A dispatcher for a {@code char} type.
634
             */
635
            CHARACTER((char) 0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
636

637
            /**
638
             * A dispatcher for an {@code int} type.
639
             */
640
            INTEGER(0, Opcodes.ICONST_0, Opcodes.IRETURN, 1),
1✔
641

642
            /**
643
             * A dispatcher for a {@code long} type.
644
             */
645
            LONG(0L, Opcodes.LCONST_0, Opcodes.LRETURN, 2),
1✔
646

647
            /**
648
             * A dispatcher for a {@code float} type.
649
             */
650
            FLOAT(0f, Opcodes.FCONST_0, Opcodes.FRETURN, 1),
1✔
651

652
            /**
653
             * A dispatcher for a {@code double} type.
654
             */
655
            DOUBLE(0d, Opcodes.DCONST_0, Opcodes.DRETURN, 2),
1✔
656

657
            /**
658
             * A dispatcher for a reference type.
659
             */
660
            REFERENCE(null, Opcodes.ACONST_NULL, Opcodes.ARETURN, 1);
1✔
661

662
            /**
663
             * The default value.
664
             */
665
            @MaybeNull
666
            private final Object value;
667

668
            /**
669
             * The opcode to load the default value.
670
             */
671
            private final int load;
672

673
            /**
674
             * The opcode to return the default value.
675
             */
676
            private final int returned;
677

678
            /**
679
             * The operand stack size of default value.
680
             */
681
            private final int size;
682

683
            /**
684
             * Creates a new default value dispatcher.
685
             *
686
             * @param value    The default value.
687
             * @param load     The opcode to load the default value.
688
             * @param returned The opcode to return the default value.
689
             * @param size     The operand stack size of default value.
690
             */
691
            ForDefaultValue(@MaybeNull Object value, int load, int returned, int size) {
1✔
692
                this.value = value;
1✔
693
                this.load = load;
1✔
694
                this.returned = returned;
1✔
695
                this.size = size;
1✔
696
            }
1✔
697

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

748
            /**
749
             * {@inheritDoc}
750
             */
751
            @MaybeNull
752
            public Object invoke(Object[] argument) {
753
                return value;
1✔
754
            }
755

756
            /**
757
             * {@inheritDoc}
758
             */
759
            public int apply(MethodVisitor methodVisitor, Method method) {
760
                if (load != Opcodes.NOP) {
1✔
761
                    methodVisitor.visitInsn(load);
1✔
762
                }
763
                methodVisitor.visitInsn(returned);
1✔
764
                return size;
1✔
765
            }
766

767
            /**
768
             * A dispatcher for returning a default value for a primitive array.
769
             */
770
            protected enum OfPrimitiveArray implements Dispatcher {
1✔
771

772
                /**
773
                 * A dispatcher for a {@code boolean} array.
774
                 */
775
                BOOLEAN(new boolean[0], Opcodes.T_BOOLEAN),
1✔
776

777
                /**
778
                 * A dispatcher for a {@code byte} array.
779
                 */
780
                BYTE(new byte[0], Opcodes.T_BYTE),
1✔
781

782
                /**
783
                 * A dispatcher for a {@code short} array.
784
                 */
785
                SHORT(new short[0], Opcodes.T_SHORT),
1✔
786

787
                /**
788
                 * A dispatcher for a {@code char} array.
789
                 */
790
                CHARACTER(new char[0], Opcodes.T_CHAR),
1✔
791

792
                /**
793
                 * A dispatcher for a {@code int} array.
794
                 */
795
                INTEGER(new int[0], Opcodes.T_INT),
1✔
796

797
                /**
798
                 * A dispatcher for a {@code long} array.
799
                 */
800
                LONG(new long[0], Opcodes.T_LONG),
1✔
801

802
                /**
803
                 * A dispatcher for a {@code float} array.
804
                 */
805
                FLOAT(new float[0], Opcodes.T_FLOAT),
1✔
806

807
                /**
808
                 * A dispatcher for a {@code double} array.
809
                 */
810
                DOUBLE(new double[0], Opcodes.T_DOUBLE);
1✔
811

812
                /**
813
                 * The default value.
814
                 */
815
                private final Object value;
816

817
                /**
818
                 * The operand for creating an array of the represented type.
819
                 */
820
                private final int operand;
821

822
                /**
823
                 * Creates a new dispatcher for a primitive array.
824
                 *
825
                 * @param value   The default value.
826
                 * @param operand The operand for creating an array of the represented type.
827
                 */
828
                OfPrimitiveArray(Object value, int operand) {
1✔
829
                    this.value = value;
1✔
830
                    this.operand = operand;
1✔
831
                }
1✔
832

833
                /**
834
                 * {@inheritDoc}
835
                 */
836
                public Object invoke(Object[] argument) {
837
                    return value;
1✔
838
                }
839

840
                /**
841
                 * {@inheritDoc}
842
                 */
843
                public int apply(MethodVisitor methodVisitor, Method method) {
844
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
845
                    methodVisitor.visitIntInsn(Opcodes.NEWARRAY, operand);
1✔
846
                    methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
847
                    return 1;
1✔
848
                }
849
            }
850

851
            /**
852
             * A dispatcher for a non-primitive array type.
853
             */
854
            @HashCodeAndEqualsPlugin.Enhance
855
            protected static class OfNonPrimitiveArray implements Dispatcher {
856

857
                /**
858
                 * The default value.
859
                 */
860
                @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
861
                private final Object value;
862

863
                /**
864
                 * The represented component type.
865
                 */
866
                private final Class<?> componentType;
867

868
                /**
869
                 * Creates a new dispatcher for the default value of a non-primitive array.
870
                 *
871
                 * @param value         The default value.
872
                 * @param componentType The represented component type.
873
                 */
874
                protected OfNonPrimitiveArray(Object value, Class<?> componentType) {
1✔
875
                    this.value = value;
1✔
876
                    this.componentType = componentType;
1✔
877
                }
1✔
878

879
                /**
880
                 * Creates a new dispatcher.
881
                 *
882
                 * @param componentType The represented component type.
883
                 * @return A dispatcher for the supplied component type.
884
                 */
885
                protected static Dispatcher of(Class<?> componentType) {
886
                    return new OfNonPrimitiveArray(Array.newInstance(componentType, 0), componentType);
1✔
887
                }
888

889
                /**
890
                 * {@inheritDoc}
891
                 */
892
                public Object invoke(Object[] argument) {
893
                    return value;
1✔
894
                }
895

896
                /**
897
                 * {@inheritDoc}
898
                 */
899
                public int apply(MethodVisitor methodVisitor, Method method) {
900
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
1✔
901
                    methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(componentType));
1✔
902
                    methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
903
                    return 1;
1✔
904
                }
905
            }
906
        }
907

908
        /**
909
         * A dispatcher for invoking a constructor.
910
         */
911
        @HashCodeAndEqualsPlugin.Enhance
912
        class ForConstructor implements Dispatcher {
913

914
            /**
915
             * The proxied constructor.
916
             */
917
            private final Constructor<?> constructor;
918

919
            /**
920
             * Creates a dispatcher for invoking a constructor.
921
             *
922
             * @param constructor The proxied constructor.
923
             */
924
            protected ForConstructor(Constructor<?> constructor) {
1✔
925
                this.constructor = constructor;
1✔
926
            }
1✔
927

928
            /**
929
             * {@inheritDoc}
930
             */
931
            public Object invoke(Object[] argument) throws Throwable {
932
                return INVOKER.newInstance(constructor, argument);
1✔
933
            }
934

935
            /**
936
             * {@inheritDoc}
937
             */
938
            public int apply(MethodVisitor methodVisitor, Method method) {
939
                Class<?>[] source = method.getParameterTypes(), target = constructor.getParameterTypes();
1✔
940
                methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(constructor.getDeclaringClass()));
1✔
941
                methodVisitor.visitInsn(Opcodes.DUP);
1✔
942
                int offset = 1;
1✔
943
                for (int index = 0; index < source.length; index++) {
1✔
944
                    Type type = Type.getType(source[index]);
×
945
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
×
946
                    if (source[index] != target[index]) {
×
947
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(target[index]));
×
948
                    }
949
                    offset += type.getSize();
×
950
                }
951
                methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
952
                        Type.getInternalName(constructor.getDeclaringClass()),
1✔
953
                        MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
954
                        Type.getConstructorDescriptor(constructor),
1✔
955
                        false);
956
                methodVisitor.visitInsn(Opcodes.ARETURN);
1✔
957
                return offset + 1;
1✔
958
            }
959
        }
960

961
        /**
962
         * A dispatcher for invoking a static proxied method.
963
         */
964
        @HashCodeAndEqualsPlugin.Enhance
965
        class ForStaticMethod implements Dispatcher {
966

967
            /**
968
             * The proxied method.
969
             */
970
            private final Method method;
971

972
            /**
973
             * Creates a dispatcher for invoking a static method.
974
             *
975
             * @param method The proxied method.
976
             */
977
            protected ForStaticMethod(Method method) {
1✔
978
                this.method = method;
1✔
979
            }
1✔
980

981
            /**
982
             * {@inheritDoc}
983
             */
984
            @MaybeNull
985
            public Object invoke(Object[] argument) throws Throwable {
986
                return INVOKER.invoke(method, null, argument);
1✔
987
            }
988

989
            /**
990
             * {@inheritDoc}
991
             */
992
            public int apply(MethodVisitor methodVisitor, Method method) {
993
                Class<?>[] source = method.getParameterTypes(), target = this.method.getParameterTypes();
1✔
994
                int offset = 1;
1✔
995
                for (int index = 0; index < source.length; index++) {
1✔
996
                    Type type = Type.getType(source[index]);
1✔
997
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
1✔
998
                    if (source[index] != target[index]) {
1✔
999
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(target[index]));
1✔
1000
                    }
1001
                    offset += type.getSize();
1✔
1002
                }
1003
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
1✔
1004
                        Type.getInternalName(this.method.getDeclaringClass()),
1✔
1005
                        this.method.getName(),
1✔
1006
                        Type.getMethodDescriptor(this.method),
1✔
1007
                        this.method.getDeclaringClass().isInterface());
1✔
1008
                methodVisitor.visitInsn(Type.getReturnType(this.method).getOpcode(Opcodes.IRETURN));
1✔
1009
                return Math.max(offset - 1, Type.getReturnType(this.method).getSize());
1✔
1010
            }
1011
        }
1012

1013
        /**
1014
         * A dispatcher for invoking a non-static proxied method.
1015
         */
1016
        @HashCodeAndEqualsPlugin.Enhance
1017
        class ForNonStaticMethod implements Dispatcher {
1018

1019
            /**
1020
             * Indicates a call without arguments.
1021
             */
1022
            private static final Object[] NO_ARGUMENTS = new Object[0];
1✔
1023

1024
            /**
1025
             * The proxied method.
1026
             */
1027
            private final Method method;
1028

1029
            /**
1030
             * Creates a dispatcher for invoking a non-static method.
1031
             *
1032
             * @param method The proxied method.
1033
             */
1034
            protected ForNonStaticMethod(Method method) {
1✔
1035
                this.method = method;
1✔
1036
            }
1✔
1037

1038
            /**
1039
             * {@inheritDoc}
1040
             */
1041
            public Object invoke(Object[] argument) throws Throwable {
1042
                Object[] reduced;
1043
                if (argument.length == 1) {
1✔
1044
                    reduced = NO_ARGUMENTS;
1✔
1045
                } else {
1046
                    reduced = new Object[argument.length - 1];
1✔
1047
                    System.arraycopy(argument, 1, reduced, 0, reduced.length);
1✔
1048
                }
1049
                return INVOKER.invoke(method, argument[0], reduced);
1✔
1050
            }
1051

1052
            /**
1053
             * {@inheritDoc}
1054
             */
1055
            public int apply(MethodVisitor methodVisitor, Method method) {
1056
                Class<?>[] source = method.getParameterTypes(), target = this.method.getParameterTypes();
1✔
1057
                int offset = 1;
1✔
1058
                for (int index = 0; index < source.length; index++) {
1✔
1059
                    Type type = Type.getType(source[index]);
1✔
1060
                    methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
1✔
1061
                    if (source[index] != (index == 0 ? this.method.getDeclaringClass() : target[index - 1])) {
1✔
1062
                        methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(index == 0
1✔
1063
                                ? this.method.getDeclaringClass()
1✔
1064
                                : target[index - 1]));
1065
                    }
1066
                    offset += type.getSize();
1✔
1067
                }
1068
                methodVisitor.visitMethodInsn(this.method.getDeclaringClass().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
1✔
1069
                        Type.getInternalName(this.method.getDeclaringClass()),
1✔
1070
                        this.method.getName(),
1✔
1071
                        Type.getMethodDescriptor(this.method),
1✔
1072
                        this.method.getDeclaringClass().isInterface());
1✔
1073
                methodVisitor.visitInsn(Type.getReturnType(this.method).getOpcode(Opcodes.IRETURN));
1✔
1074
                return Math.max(offset - 1, Type.getReturnType(this.method).getSize());
1✔
1075
            }
1076
        }
1077

1078
        /**
1079
         * A dispatcher for an unresolved method.
1080
         */
1081
        @HashCodeAndEqualsPlugin.Enhance
1082
        class ForUnresolvedMethod implements Dispatcher {
1083

1084
            /**
1085
             * The message for describing the reason why the method could not be resolved.
1086
             */
1087
            private final String message;
1088

1089
            /**
1090
             * Creates a dispatcher for an unresolved method.
1091
             *
1092
             * @param message The message for describing the reason why the method could not be resolved.
1093
             */
1094
            protected ForUnresolvedMethod(String message) {
1✔
1095
                this.message = message;
1✔
1096
            }
1✔
1097

1098
            /**
1099
             * {@inheritDoc}
1100
             */
1101
            public Object invoke(Object[] argument) throws Throwable {
1102
                throw new IllegalStateException("Could not invoke proxy: " + message);
1✔
1103
            }
1104

1105
            /**
1106
             * {@inheritDoc}
1107
             */
1108
            public int apply(MethodVisitor methodVisitor, Method method) {
1109
                methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalStateException.class));
1✔
1110
                methodVisitor.visitInsn(Opcodes.DUP);
1✔
1111
                methodVisitor.visitLdcInsn(message);
1✔
1112
                methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,
1✔
1113
                        Type.getInternalName(IllegalStateException.class),
1✔
1114
                        MethodDescription.CONSTRUCTOR_INTERNAL_NAME,
1115
                        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)),
1✔
1116
                        false);
1117
                methodVisitor.visitInsn(Opcodes.ATHROW);
1✔
1118
                return 3;
1✔
1119
            }
1120
        }
1121
    }
1122

1123
    /**
1124
     * An invocation handler that invokes given dispatchers.
1125
     */
1126
    @HashCodeAndEqualsPlugin.Enhance
1127
    protected static class ProxiedInvocationHandler implements InvocationHandler {
1128

1129
        /**
1130
         * Indicates that an invocation handler does not provide any arguments.
1131
         */
1132
        private static final Object[] NO_ARGUMENTS = new Object[0];
1✔
1133

1134
        /**
1135
         * The proxied type's name.
1136
         */
1137
        private final String name;
1138

1139
        /**
1140
         * A mapping of proxy type methods to their proxied dispatchers.
1141
         */
1142
        private final Map<Method, Dispatcher> targets;
1143

1144
        /**
1145
         * Creates a new invocation handler for proxying a type.
1146
         *
1147
         * @param name    The proxied type's name.
1148
         * @param targets A mapping of proxy type methods to their proxied dispatchers.
1149
         */
1150
        protected ProxiedInvocationHandler(String name, Map<Method, Dispatcher> targets) {
1✔
1151
            this.name = name;
1✔
1152
            this.targets = targets;
1✔
1153
        }
1✔
1154

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

1201
    /**
1202
     * A class loader for loading synthetic classes for implementing a {@link JavaDispatcher}.
1203
     */
1204
    protected static class DynamicClassLoader extends ClassLoader {
1205

1206
        /**
1207
         * The dump folder that is defined by the {@link TypeWriter#DUMP_PROPERTY} property or {@code null} if not set.
1208
         */
1209
        @MaybeNull
1210
        private static final String DUMP_FOLDER;
1211

1212
        /**
1213
         * Indicates that a constructor does not declare any parameters.
1214
         */
1215
        private static final Class<?>[] NO_PARAMETER = new Class<?>[0];
1✔
1216

1217
        /**
1218
         * Indicates that a constructor does not require any arguments.
1219
         */
1220
        private static final Object[] NO_ARGUMENT = new Object[0];
1✔
1221

1222
        /*
1223
         * Resolves the currently set dump folder.
1224
         */
1225
        static {
1226
            String dumpFolder;
1227
            try {
1228
                dumpFolder = doPrivileged(new GetSystemPropertyAction(TypeWriter.DUMP_PROPERTY));
1✔
1229
            } catch (Throwable ignored) {
×
1230
                dumpFolder = null;
×
1231
            }
1✔
1232
            DUMP_FOLDER = dumpFolder;
1✔
1233
        }
1✔
1234

1235
        /**
1236
         * Creates a new dynamic class loader.
1237
         *
1238
         * @param target The proxied type.
1239
         */
1240
        protected DynamicClassLoader(Class<?> target) {
1241
            super(target.getClassLoader());
1✔
1242
            RESOLVER.accept(this, target);
1✔
1243
        }
1✔
1244

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

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

1414
        /**
1415
         * A resolver to make adjustments that are possibly necessary to withhold module graph guarantees.
1416
         */
1417
        protected interface Resolver {
1418

1419
            /**
1420
             * Adjusts a module graph if necessary.
1421
             *
1422
             * @param classLoader The class loader to adjust.
1423
             * @param target      The targeted class for which a proxy is created.
1424
             */
1425
            void accept(@MaybeNull ClassLoader classLoader, Class<?> target);
1426

1427
            /**
1428
             * An action to create a resolver.
1429
             */
1430
            enum CreationAction implements PrivilegedAction<Resolver> {
1✔
1431

1432
                /**
1433
                 * The singleton instance.
1434
                 */
1435
                INSTANCE;
1✔
1436

1437
                /**
1438
                 * {@inheritDoc}
1439
                 */
1440
                @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
1441
                public Resolver run() {
1442
                    try {
1443
                        Class<?> module = Class.forName("java.lang.Module", false, null);
×
1444
                        return new ForModuleSystem(Class.class.getMethod("getModule"),
×
1445
                                module.getMethod("isExported", String.class),
×
1446
                                module.getMethod("addExports", String.class, module),
×
1447
                                ClassLoader.class.getMethod("getUnnamedModule"));
×
1448
                    } catch (Exception ignored) {
1✔
1449
                        return NoOp.INSTANCE;
1✔
1450
                    }
1451
                }
1452
            }
1453

1454
            /**
1455
             * A non-operational resolver for VMs that do not support the module system.
1456
             */
1457
            enum NoOp implements Resolver {
1✔
1458

1459
                /**
1460
                 * The singleton instance.
1461
                 */
1462
                INSTANCE;
1✔
1463

1464
                /**
1465
                 * {@inheritDoc}
1466
                 */
1467
                public void accept(@MaybeNull ClassLoader classLoader, Class<?> target) {
1468
                    /* do nothing */
1469
                }
1✔
1470
            }
1471

1472
            /**
1473
             * A resolver for VMs that do support the module system.
1474
             */
1475
            @HashCodeAndEqualsPlugin.Enhance
1476
            class ForModuleSystem implements Resolver {
1477

1478
                /**
1479
                 * The {@code java.lang.Class#getModule} method.
1480
                 */
1481
                private final Method getModule;
1482

1483
                /**
1484
                 * The {@code java.lang.Module#isExported} method.
1485
                 */
1486
                private final Method isExported;
1487

1488
                /**
1489
                 * The {@code java.lang.Module#addExports} method.
1490
                 */
1491
                private final Method addExports;
1492

1493
                /**
1494
                 * The {@code java.lang.ClassLoader#getUnnamedModule} method.
1495
                 */
1496
                private final Method getUnnamedModule;
1497

1498
                /**
1499
                 * Creates a new resolver for a VM that supports the module system.
1500
                 *
1501
                 * @param getModule        The {@code java.lang.Class#getModule} method.
1502
                 * @param isExported       The {@code java.lang.Module#isExported} method.
1503
                 * @param addExports       The {@code java.lang.Module#addExports} method.
1504
                 * @param getUnnamedModule The {@code java.lang.ClassLoader#getUnnamedModule} method.
1505
                 */
1506
                protected ForModuleSystem(Method getModule,
1507
                                          Method isExported,
1508
                                          Method addExports,
1509
                                          Method getUnnamedModule) {
×
1510
                    this.getModule = getModule;
×
1511
                    this.isExported = isExported;
×
1512
                    this.addExports = addExports;
×
1513
                    this.getUnnamedModule = getUnnamedModule;
×
1514
                }
×
1515

1516
                /**
1517
                 * {@inheritDoc}
1518
                 */
1519
                @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should always be wrapped for clarity.")
1520
                public void accept(@MaybeNull ClassLoader classLoader, Class<?> target) {
1521
                    Package location = target.getPackage();
×
1522
                    if (location != null) {
×
1523
                        try {
1524
                            Object module = getModule.invoke(target);
×
1525
                            if (!(Boolean) isExported.invoke(module, location.getName())) {
×
1526
                                addExports.invoke(module, location.getName(), getUnnamedModule.invoke(classLoader));
×
1527
                            }
1528
                        } catch (Exception exception) {
×
1529
                            throw new IllegalStateException("Failed to adjust module graph for dispatcher", exception);
×
1530
                        }
×
1531
                    }
1532
                }
×
1533
            }
1534
        }
1535
    }
1536
}
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