8000 Add checks for sealed types (#2392) · mockito/mockito@96883a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 96883a1

Browse files
authored
Add checks for sealed types (#2392)
1 parent 28020c0 commit 96883a1

File tree

4 files changed

+80
-2
lines changed

4 files changed

+80
-2
lines changed

src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,9 @@ private <T> void checkSupportedCombination(
344344
if (subclassingRequired
345345
&& !features.mockedType.isArray()
346346
&& !features.mockedType.isPrimitive()
347-
&& Modifier.isFinal(features.mockedType.getModifiers())) {
347+
&& (Modifier.isFinal(features.mockedType.getModifiers())
348+
|| TypeSupport.INSTANCE.isSealed(features.mockedType)
349+
|| features.interfaces.stream().anyMatch(TypeSupport.INSTANCE::isSealed))) {
348350
throw new MockitoException(
349351
"Unsupported settings with this type '" + features.mockedType.getName() + "'");
350352
}

src/main/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMaker.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ public TypeMockability isTypeMockable(final Class<?> type) {
162162
return new TypeMockability() {
163163
@Override
164164
public boolean mockable() {
165-
return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
165+
return !type.isPrimitive()
166+
&& !Modifier.isFinal(type.getModifiers())
167+
&& !TypeSupport.INSTANCE.isSealed(type);
166168
}
167169

168170
@Override
@@ -176,6 +178,9 @@ public String nonMockableReason() {
176178
if (Modifier.isFinal(type.getModifiers())) {
177179
return "final class";
178180
}
181+
if (TypeSupport.INSTANCE.isSealed(type)) {
182+
return "sealed class";
183+
}
179184
return join("not handled type");
180185
}
181186
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2021 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockito.internal.creation.bytebuddy;
6+
7+
import org.mockito.exceptions.base.MockitoException;
8+
9+
import java.lang.reflect.Method;
10+
11+
class TypeSupport {
12+
13+
static final TypeSupport INSTANCE;
14+
15+
static {
16+
Method isSealed;
17+
try {
18+
isSealed = Class.class.getMethod("isSealed");
19+
} catch (NoSuchMethodException ignored) {
20+
isSealed = null;
21+
}
22+
INSTANCE = new TypeSupport(isSealed);
23+
}
24+
25+
private final Method isSealed;
26+
27+
private TypeSupport(Method isSealed) {
28+
this.isSealed = isSealed;
29+
}
30+
31+
boolean isSealed(Class<?> type) {
32+
if (isSealed == null) {
33+
return false;
34+
}
35+
try {
36+
return (boolean) isSealed.invoke(type);
37+
} catch (Throwable t) {
38+
throw new MockitoException(
39+
"Failed to check if type is sealed using handle " + isSealed, t);
40+
}
41+
}
42+
}

src/test/java/org/mockito/internal/creation/bytebuddy/SubclassByteBuddyMockMakerTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import java.util.Observable;
1212
import java.util.Observer;
1313

14+
import net.bytebuddy.ByteBuddy;
15+
import net.bytebuddy.ClassFileVersion;
16+
import net.bytebuddy.description.modifier.TypeManifestation;
17+
import net.bytebuddy.dynamic.DynamicType;
1418
import org.junit.Test;
1519
import org.mockito.internal.creation.MockSettingsImpl;
1620
import org.mockito.plugins.MockMaker;
@@ -29,6 +33,31 @@ public void is_type_mockable_excludes_primitive_wrapper_classes() {
2933
assertThat(mockable.nonMockableReason()).contains("final");
3034
}
3135

36+
@Test
37+
public void is_type_mockable_excludes_sealed_classes() {
38+
// is only supported on Java 17 and later
39+
if (ClassFileVersion.ofThisVm().isAtMost(ClassFileVersion.JAVA_V16)) {
40+
return;
41+
}
42+
DynamicType.Builder<Object> base = new ByteBuddy().subclass(Object.class);
43+
DynamicType.Unloaded<Object> dynamic =
44+
new ByteBuddy()
45+
.subclass(Object.class)
46+
.permittedSubclass(base.toTypeDescription())
47+
.make();
48+
Class<?> type =
49+
new ByteBuddy()
50+
.subclass(base.toTypeDescription())
51+
.merge(TypeManifestation.FINAL)
52+
.make()
53+
.include(dynamic)
54+
.load(null)
55+
.getLoaded();
56+
MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(type);
57+
assertThat(mockable.mockable()).isFalse();
58+
assertThat(mockable.nonMockableReason()).contains("sealed");
59+
}
60+
3261
@Test
3362
public void is_type_mockable_excludes_primitive_classes() {
3463
MockMaker.TypeMockability mockable = mockMaker.isTypeMockable(int.class);

0 commit comments

Comments
 (0)
0