• 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

88.89
/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/InvocationHandlerAdapter.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.implementation;
17

18
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
19
import net.bytebuddy.description.field.FieldDescription;
20
import net.bytebuddy.description.method.MethodDescription;
21
import net.bytebuddy.description.type.TypeDescription;
22
import net.bytebuddy.description.type.TypeList;
23
import net.bytebuddy.dynamic.scaffold.FieldLocator;
24
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
25
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
26
import net.bytebuddy.implementation.bytecode.Removal;
27
import net.bytebuddy.implementation.bytecode.StackManipulation;
28
import net.bytebuddy.implementation.bytecode.assign.Assigner;
29
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
30
import net.bytebuddy.implementation.bytecode.constant.MethodConstant;
31
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
32
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
33
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
34
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
35
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
36
import net.bytebuddy.utility.RandomString;
37
import org.objectweb.asm.MethodVisitor;
38
import org.objectweb.asm.Opcodes;
39

40
import java.lang.reflect.InvocationHandler;
41
import java.util.ArrayList;
42
import java.util.List;
43

44
import static net.bytebuddy.matcher.ElementMatchers.fieldType;
45
import static net.bytebuddy.matcher.ElementMatchers.genericFieldType;
46
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
47
import static net.bytebuddy.matcher.ElementMatchers.named;
48

49
/**
50
 * An adapter for adapting an {@link java.lang.reflect.InvocationHandler}. The adapter allows the invocation handler
51
 * to also intercept method calls to non-interface methods.
52
 */
53
@HashCodeAndEqualsPlugin.Enhance
54
public abstract class InvocationHandlerAdapter implements Implementation.Composable {
55

56
    /**
57
     * A type description of the {@link InvocationHandler}.
58
     */
59
    private static final TypeDescription.Generic INVOCATION_HANDLER_TYPE = TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(InvocationHandler.class);
1✔
60

61
    /**
62
     * Indicates that a value should not be cached.
63
     */
64
    private static final boolean UNCACHED = false;
65

66
    /**
67
     * Indicates that a {@link java.lang.reflect.Method} should be cached.
68
     */
69
    private static final boolean CACHED = true;
70

71
    /**
72
     * Indicates that a lookup of a method constant should not be looked up using an {@code java.security.AccessController}.
73
     */
74
    private static final boolean UNPRIVILEGED = false;
75

76
    /**
77
     * Indicates that a lookup of a method constant should be looked up using an {@code java.security.AccessController}.
78
     */
79
    private static final boolean PRIVILEGED = true;
80

81
    /**
82
     * Indicates that a handler is returning its return value.
83
     */
84
    private static final boolean RETURNING = true;
85

86
    /**
87
     * Indicates that a handler is dropping its return value.
88
     */
89
    private static final boolean DROPPING = false;
90

91
    /**
92
     * The name of the field for storing an invocation handler.
93
     */
94
    protected final String fieldName;
95

96
    /**
97
     * Determines if the {@link java.lang.reflect.Method} instances that are handed to the intercepted methods are
98
     * cached in {@code static} fields.
99
     */
100
    protected final boolean cached;
101

102
    /**
103
     * Determines if the {@link java.lang.reflect.Method} instances are retrieved by using an {@code java.security.AccessController}.
104
     */
105
    protected final boolean privileged;
106

107
    /**
108
     * Determines if this implementation is returning the result value or is dropping it.
109
     */
110
    protected final boolean returning;
111

112
    /**
113
     * The assigner that is used for assigning the return invocation handler's return value to the
114
     * intercepted method's return value.
115
     */
116
    protected final Assigner assigner;
117

118
    /**
119
     * Creates a new invocation handler for a given field.
120
     *
121
     * @param fieldName  The name of the field.
122
     * @param cached     Determines if the {@link java.lang.reflect.Method} instances that are handed to the
123
     *                   intercepted methods are cached in {@code static} fields.
124
     * @param privileged Determines if the {@link java.lang.reflect.Method} instances are retrieved by using an {@code java.security.AccessController}.
125
     * @param returning  Determines if this implementation is returning the result value or is dropping it.
126
     * @param assigner   The assigner to apply when defining this implementation.
127
     */
128
    protected InvocationHandlerAdapter(String fieldName, boolean cached, boolean privileged, boolean returning, Assigner assigner) {
1✔
129
        this.fieldName = fieldName;
1✔
130
        this.cached = cached;
1✔
131
        this.privileged = privileged;
1✔
132
        this.returning = returning;
1✔
133
        this.assigner = assigner;
1✔
134
    }
1✔
135

136
    /**
137
     * Creates an implementation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
138
     * all method interceptions to the given instance which will be stored in a {@code static} field.
139
     *
140
     * @param invocationHandler The invocation handler to which all method calls are delegated.
141
     * @return An implementation that delegates all method interceptions to the given invocation handler.
142
     */
143
    public static InvocationHandlerAdapter of(InvocationHandler invocationHandler) {
144
        return of(invocationHandler, ForInstance.PREFIX + "$" + RandomString.hashOf(invocationHandler));
1✔
145
    }
146

147
    /**
148
     * Creates an implementation for any instance of an {@link java.lang.reflect.InvocationHandler} that delegates
149
     * all method interceptions to the given instance which will be stored in a {@code static} field.
150
     *
151
     * @param invocationHandler The invocation handler to which all method calls are delegated.
152
     * @param fieldName         The name of the field.
153
     * @return An implementation that delegates all method interceptions to the given invocation handler.
154
     */
155
    public static InvocationHandlerAdapter of(InvocationHandler invocationHandler, String fieldName) {
156
        return new ForInstance(fieldName, CACHED, UNPRIVILEGED, RETURNING, Assigner.DEFAULT, invocationHandler);
1✔
157
    }
158

159
    /**
160
     * Creates an implementation for any {@link java.lang.reflect.InvocationHandler} that delegates
161
     * all method interceptions to a field with the given name. This field has to be of a subtype of invocation
162
     * handler and needs to be set before any invocations are intercepted. Otherwise, a {@link java.lang.NullPointerException}
163
     * will be thrown.
164
     *
165
     * @param name The name of the field.
166
     * @return An implementation that delegates all method interceptions to an instance field of the given name.
167
     */
168
    public static InvocationHandlerAdapter toField(String name) {
169
        return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
1✔
170
    }
171

172
    /**
173
     * Creates an implementation for any {@link java.lang.reflect.InvocationHandler} that delegates
174
     * all method interceptions to a field with the given name. This field has to be of a subtype of invocation
175
     * handler and needs to be set before any invocations are intercepted. Otherwise, a {@link java.lang.NullPointerException}
176
     * will be thrown.
177
     *
178
     * @param name                The name of the field.
179
     * @param fieldLocatorFactory The field locator factory
180
     * @return An implementation that delegates all method interceptions to an instance field of the given name.
181
     */
182
    public static InvocationHandlerAdapter toField(String name, FieldLocator.Factory fieldLocatorFactory) {
183
        return new ForField(name, CACHED, UNPRIVILEGED, RETURNING, Assigner.DEFAULT, fieldLocatorFactory);
1✔
184
    }
185

186
    /**
187
     * Returns a list of stack manipulations that loads all arguments of an instrumented method.
188
     *
189
     * @param instrumentedMethod The method that is instrumented.
190
     * @return A list of stack manipulation that loads all arguments of an instrumented method.
191
     */
192
    private List<StackManipulation> argumentValuesOf(MethodDescription instrumentedMethod) {
193
        TypeList.Generic parameterTypes = instrumentedMethod.getParameters().asTypeList();
1✔
194
        List<StackManipulation> instruction = new ArrayList<StackManipulation>(parameterTypes.size());
1✔
195
        int currentIndex = 1;
1✔
196
        for (TypeDescription.Generic parameterType : parameterTypes) {
1✔
197
            instruction.add(new StackManipulation.Compound(
1✔
198
                    MethodVariableAccess.of(parameterType).loadFrom(currentIndex),
1✔
199
                    assigner.assign(parameterType, TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class), Assigner.Typing.STATIC)));
1✔
200
            currentIndex += parameterType.getStackSize().getSize();
1✔
201
        }
1✔
202
        return instruction;
1✔
203
    }
204

205
    /**
206
     * By default, any {@link java.lang.reflect.Method} instance that is handed over to an
207
     * {@link java.lang.reflect.InvocationHandler} is cached in a static field. By invoking this method,
208
     * this feature can be disabled.
209
     *
210
     * @return A similar invocation handler adapter that applies caching.
211
     */
212
    public abstract WithoutPrivilegeConfiguration withoutMethodCache();
213

214
    /**
215
     * Configures an assigner to use with this invocation handler adapter.
216
     *
217
     * @param assigner The assigner to apply when defining this implementation.
218
     * @return This instrumentation with the given {@code assigner} configured.
219
     */
220
    public abstract Implementation withAssigner(Assigner assigner);
221

222
    /**
223
     * Configures that the method constants supplied to the invocation handler adapter are resolved using an {@code java.security.AccessController}.
224
     *
225
     * @return This instrumentation with a privileged lookup configured.
226
     */
227
    public abstract AssignerConfigurable withPrivilegedLookup();
228

229
    /**
230
     * Applies an implementation that delegates to a invocation handler.
231
     *
232
     * @param methodVisitor         The method visitor for writing the byte code to.
233
     * @param implementationContext The implementation context for the current implementation.
234
     * @param instrumentedMethod    The method that is instrumented.
235
     * @param preparingManipulation A stack manipulation that applies any preparation to the operand stack.
236
     * @param fieldDescription      The field that contains the value for the invocation handler.
237
     * @return The size of the applied assignment.
238
     */
239
    protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
240
                                          Context implementationContext,
241
                                          MethodDescription instrumentedMethod,
242
                                          StackManipulation preparingManipulation,
243
                                          FieldDescription fieldDescription) {
244
        if (instrumentedMethod.isStatic() || instrumentedMethod.isConstructor()) {
1✔
245
            throw new IllegalStateException("It is not possible to apply an invocation handler onto the static method or constructor " + instrumentedMethod);
1✔
246
        }
247
        MethodConstant.CanCache methodConstant = privileged
1✔
248
                ? MethodConstant.ofPrivileged(instrumentedMethod.asDefined())
1✔
249
                : MethodConstant.of(instrumentedMethod.asDefined());
1✔
250
        StackManipulation.Size stackSize = new StackManipulation.Compound(
1✔
251
                preparingManipulation,
252
                FieldAccess.forField(fieldDescription).read(),
1✔
253
                MethodVariableAccess.loadThis(),
1✔
254
                cached ? methodConstant.cached() : methodConstant,
1✔
255
                instrumentedMethod.getParameters().isEmpty()
1✔
256
                        ? NullConstant.INSTANCE
257
                        : ArrayFactory.forType(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class)).withValues(argumentValuesOf(instrumentedMethod)),
1✔
258
                MethodInvocation.invoke(INVOCATION_HANDLER_TYPE.getDeclaredMethods().filter(isAbstract()).getOnly()),
1✔
259
                returning ? new StackManipulation.Compound(assigner.assign(TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class),
1✔
260
                        instrumentedMethod.getReturnType(),
1✔
261
                        Assigner.Typing.DYNAMIC), MethodReturn.of(instrumentedMethod.getReturnType())) : Removal.SINGLE
1✔
262
        ).apply(methodVisitor, implementationContext);
1✔
263
        return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
1✔
264
    }
265

266
    /**
267
     * Allows for the configuration of an {@link net.bytebuddy.implementation.bytecode.assign.Assigner}
268
     * of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter}.
269
     */
270
    public interface AssignerConfigurable extends Implementation.Composable {
271

272
        /**
273
         * Configures an assigner to use with this invocation handler adapter.
274
         *
275
         * @param assigner The assigner to apply when defining this implementation.
276
         * @return This instrumentation with the given {@code assigner} configured.
277
         */
278
        Implementation.Composable withAssigner(Assigner assigner);
279
    }
280

281
    /**
282
     * Allows the configuration of privileged lookup for the resolution of {@link java.lang.reflect.Method}
283
     * constants that are provided to the invocation handler.
284
     */
285
    public interface WithoutPrivilegeConfiguration extends AssignerConfigurable {
286

287
        /**
288
         * Configures that the method constants supplied to the invocation handler adapter are resolved
289
         * using an {@code java.security.AccessController}.
290
         *
291
         * @return This instrumentation with a privileged lookup configured.
292
         */
293
        AssignerConfigurable withPrivilegedLookup();
294
    }
295

296
    /**
297
     * An implementation of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter} that delegates method
298
     * invocations to an adapter that is stored in a static field.
299
     */
300
    @HashCodeAndEqualsPlugin.Enhance
301
    protected static class ForInstance extends InvocationHandlerAdapter implements WithoutPrivilegeConfiguration {
302

303
        /**
304
         * The prefix for field that are created for storing the instrumented value.
305
         */
306
        private static final String PREFIX = "invocationHandler";
307

308
        /**
309
         * The invocation handler to which method interceptions are to be delegated.
310
         */
311
        protected final InvocationHandler invocationHandler;
312

313
        /**
314
         * Creates a new invocation handler adapter for delegating invocations to an invocation handler that is stored
315
         * in a static field.
316
         *
317
         * @param fieldName         The name of the field.
318
         * @param cached            Determines if the {@link java.lang.reflect.Method} instances that are handed to the
319
         *                          intercepted methods are cached in {@code static} fields.
320
         * @param privileged        Determines if the {@link java.lang.reflect.Method} instances are retrieved by
321
         *                          using an {@code java.security.AccessController}.
322
         * @param returning         Determines if this implementation is returning the result value or is dropping it.
323
         * @param assigner          The assigner to apply when defining this implementation.
324
         * @param invocationHandler The invocation handler to which all method calls are delegated.
325
         */
326
        protected ForInstance(String fieldName, boolean cached, boolean privileged, boolean returning, Assigner assigner, InvocationHandler invocationHandler) {
327
            super(fieldName, cached, privileged, returning, assigner);
1✔
328
            this.invocationHandler = invocationHandler;
1✔
329
        }
1✔
330

331
        @Override
332
        public WithoutPrivilegeConfiguration withoutMethodCache() {
333
            return new ForInstance(fieldName, UNCACHED, privileged, returning, assigner, invocationHandler);
1✔
334
        }
335

336
        @Override
337
        public Implementation.Composable withAssigner(Assigner assigner) {
338
            return new ForInstance(fieldName, cached, privileged, returning, assigner, invocationHandler);
×
339
        }
340

341
        @Override
342
        public AssignerConfigurable withPrivilegedLookup() {
343
            return new ForInstance(fieldName, cached, PRIVILEGED, returning, assigner, invocationHandler);
1✔
344
        }
345

346
        /**
347
         * {@inheritDoc}
348
         */
349
        public Implementation andThen(Implementation implementation) {
350
            return new Compound(new ForInstance(fieldName, cached, privileged, DROPPING, assigner, invocationHandler), implementation);
×
351
        }
352

353
        /**
354
         * {@inheritDoc}
355
         */
356
        public Composable andThen(Composable implementation) {
357
            return new Compound.Composable(new ForInstance(fieldName, cached, privileged, DROPPING, assigner, invocationHandler), implementation);
×
358
        }
359

360
        /**
361
         * {@inheritDoc}
362
         */
363
        public InstrumentedType prepare(InstrumentedType instrumentedType) {
364
            if (!instrumentedType.getDeclaredFields().filter(named(fieldName).and(fieldType(INVOCATION_HANDLER_TYPE.asErasure()))).isEmpty()) {
1✔
365
                throw new IllegalStateException("Field with name " + fieldName
×
366
                        + " and type " + INVOCATION_HANDLER_TYPE.asErasure()
×
367
                        + " already declared by " + instrumentedType);
368
            }
369
            return instrumentedType
1✔
370
                    .withField(new FieldDescription.Token(fieldName,
1✔
371
                            Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC,
372
                            INVOCATION_HANDLER_TYPE))
1✔
373
                    .withInitializer(new LoadedTypeInitializer.ForStaticField(fieldName, invocationHandler));
1✔
374
        }
375

376
        /**
377
         * {@inheritDoc}
378
         */
379
        public ByteCodeAppender appender(Target implementationTarget) {
380
            return new Appender(implementationTarget.getInstrumentedType());
1✔
381
        }
382

383
        /**
384
         * An appender for implementing the {@link ForInstance}.
385
         */
386
        @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
387
        protected class Appender implements ByteCodeAppender {
388

389
            /**
390
             * The instrumented type for which the methods are being intercepted.
391
             */
392
            private final TypeDescription instrumentedType;
393

394
            /**
395
             * Creates a new appender.
396
             *
397
             * @param instrumentedType The type that is instrumented.
398
             */
399
            protected Appender(TypeDescription instrumentedType) {
1✔
400
                this.instrumentedType = instrumentedType;
1✔
401
            }
1✔
402

403
            /**
404
             * {@inheritDoc}
405
             */
406
            public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
407
                return ForInstance.this.apply(methodVisitor,
1✔
408
                        implementationContext,
409
                        instrumentedMethod,
410
                        StackManipulation.Trivial.INSTANCE,
411
                        instrumentedType.getDeclaredFields().filter(named(fieldName).and(genericFieldType(INVOCATION_HANDLER_TYPE))).getOnly());
1✔
412
            }
413
        }
414
    }
415

416
    /**
417
     * An implementation of an {@link net.bytebuddy.implementation.InvocationHandlerAdapter} that delegates method
418
     * invocations to an adapter that is stored in an instance field.
419
     */
420
    @HashCodeAndEqualsPlugin.Enhance
421
    protected static class ForField extends InvocationHandlerAdapter implements WithoutPrivilegeConfiguration {
422

423
        /**
424
         * The field locator factory to use.
425
         */
426
        private final FieldLocator.Factory fieldLocatorFactory;
427

428
        /**
429
         * Creates a new invocation handler adapter that loads its value from a field.
430
         *
431
         * @param fieldName           The name of the field.
432
         * @param cached              Determines if the {@link java.lang.reflect.Method} instances that are handed to the
433
         *                            intercepted methods are cached in {@code static} fields.
434
         * @param privileged          Determines if the {@link java.lang.reflect.Method} instances are retrieved by using
435
         *                            an {@code java.security.AccessController}.
436
         * @param returning           Determines if this implementation is returning the result value or is dropping it.
437
         * @param assigner            The assigner to apply when defining this implementation.
438
         * @param fieldLocatorFactory The field locator factory to use.
439
         */
440
        protected ForField(String fieldName, boolean cached, boolean privileged, boolean returning, Assigner assigner, FieldLocator.Factory fieldLocatorFactory) {
441
            super(fieldName, cached, privileged, returning, assigner);
1✔
442
            this.fieldLocatorFactory = fieldLocatorFactory;
1✔
443
        }
1✔
444

445
        @Override
446
        public WithoutPrivilegeConfiguration withoutMethodCache() {
447
            return new ForField(fieldName, UNCACHED, privileged, returning, assigner, fieldLocatorFactory);
1✔
448
        }
449

450
        @Override
451
        public Implementation.Composable withAssigner(Assigner assigner) {
452
            return new ForField(fieldName, cached, privileged, returning, assigner, fieldLocatorFactory);
×
453
        }
454

455
        @Override
456
        public AssignerConfigurable withPrivilegedLookup() {
457
            return new ForField(fieldName, cached, PRIVILEGED, returning, assigner, fieldLocatorFactory);
×
458
        }
459

460
        /**
461
         * {@inheritDoc}
462
         */
463
        public Implementation andThen(Implementation implementation) {
464
            return new Compound(new ForField(fieldName, cached, privileged, DROPPING, assigner, fieldLocatorFactory), implementation);
×
465
        }
466

467
        /**
468
         * {@inheritDoc}
469
         */
470
        public Composable andThen(Composable implementation) {
471
            return new Compound.Composable(new ForField(fieldName, cached, privileged, DROPPING, assigner, fieldLocatorFactory), implementation);
×
472
        }
473

474
        /**
475
         * {@inheritDoc}
476
         */
477
        public InstrumentedType prepare(InstrumentedType instrumentedType) {
478
            return instrumentedType;
1✔
479
        }
480

481
        /**
482
         * {@inheritDoc}
483
         */
484
        public ByteCodeAppender appender(Target implementationTarget) {
485
            FieldLocator.Resolution resolution = fieldLocatorFactory.make(implementationTarget.getInstrumentedType()).locate(fieldName);
1✔
486
            if (!resolution.isResolved()) {
1✔
487
                throw new IllegalStateException("Could not find a field named '" + fieldName + "' for " + implementationTarget.getInstrumentedType());
1✔
488
            } else if (!resolution.getField().getType().asErasure().isAssignableTo(InvocationHandler.class)) {
1✔
489
                throw new IllegalStateException("Field " + resolution.getField() + " does not declare a type that is assignable to invocation handler");
1✔
490
            }
491
            return new Appender(resolution.getField());
1✔
492
        }
493

494
        /**
495
         * An appender for implementing the {@link ForField}.
496
         */
497
        @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
498
        protected class Appender implements ByteCodeAppender {
499

500
            /**
501
             * The field that contains the invocation handler.
502
             */
503
            private final FieldDescription fieldDescription;
504

505
            /**
506
             * Creates a new appender.
507
             *
508
             * @param fieldDescription The field that contains the invocation handler.
509
             */
510
            protected Appender(FieldDescription fieldDescription) {
1✔
511
                this.fieldDescription = fieldDescription;
1✔
512
            }
1✔
513

514
            /**
515
             * {@inheritDoc}
516
             */
517
            public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
518
                return ForField.this.apply(methodVisitor,
1✔
519
                        implementationContext,
520
                        instrumentedMethod,
521
                        fieldDescription.isStatic()
1✔
522
                                ? StackManipulation.Trivial.INSTANCE
523
                                : MethodVariableAccess.loadThis(),
1✔
524
                        fieldDescription);
525
            }
526
        }
527
    }
528
}
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