Skip to content

Commit 335059b

Browse files
committed
fix: custom listener shell generate failed
1 parent 6711a17 commit 335059b

File tree

6 files changed

+125
-14
lines changed

6 files changed

+125
-14
lines changed

generator/src/main/java/com/reajason/javaweb/memshell/config/ShellToolConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import lombok.Data;
55
import lombok.NoArgsConstructor;
66
import lombok.experimental.SuperBuilder;
7+
import net.bytebuddy.description.type.TypeDescription;
78

89
/**
910
* @author ReaJason
@@ -18,6 +19,7 @@ public class ShellToolConfig {
1819
* 模板类 shellClass
1920
*/
2021
private Class<?> shellClass;
22+
private TypeDescription shellTypeDescription;
2123

2224
/**
2325
* shellClass 的类名

generator/src/main/java/com/reajason/javaweb/memshell/generator/ByteBuddyShellGenerator.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.reajason.javaweb.memshell.generator;
22

33
import com.reajason.javaweb.ClassBytesShrink;
4+
import com.reajason.javaweb.GenerationException;
45
import com.reajason.javaweb.ShellGenerator;
56
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
67
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
@@ -10,6 +11,7 @@
1011
import com.reajason.javaweb.memshell.config.ShellConfig;
1112
import com.reajason.javaweb.memshell.config.ShellToolConfig;
1213
import com.reajason.javaweb.memshell.server.AbstractServer;
14+
import net.bytebuddy.description.type.TypeDescription;
1315
import net.bytebuddy.dynamic.DynamicType;
1416

1517
/**
@@ -29,15 +31,21 @@ protected ByteBuddyShellGenerator(ShellConfig shellConfig, T shellToolConfig) {
2931

3032
@Override
3133
public byte[] getBytes() {
32-
Class<?> shellClass = shellToolConfig.getShellClass();
3334
String shellClassName = shellToolConfig.getShellClassName();
3435
DynamicType.Builder<?> builder = getBuilder();
36+
Class<?> shellClass = shellToolConfig.getShellClass();
37+
if (shellClass != null) {
38+
shellToolConfig.setShellTypeDescription(TypeDescription.ForLoadedType.of(shellClass));
39+
}
40+
if (shellToolConfig.getShellTypeDescription() == null) {
41+
throw new GenerationException("shellClass or shellTypeDescription could not be null.");
42+
}
3543

3644
String shellType = shellConfig.getShellType();
3745
AbstractServer server = ServerFactory.getServer(shellConfig.getServer());
3846

3947
if (ShellType.LISTENER.equals(shellType) || ShellType.JAKARTA_LISTENER.equals(shellType)) {
40-
builder = ListenerGenerator.build(builder, server.getListenerInterceptor(), shellClass, shellClassName);
48+
builder = ListenerGenerator.build(builder, server.getListenerInterceptor(), shellToolConfig.getShellTypeDescription(), shellClassName);
4149
}
4250

4351
if (ShellType.VALVE.equals(shellType) || ShellType.JAKARTA_VALVE.equals(shellType)) {

generator/src/main/java/com/reajason/javaweb/memshell/generator/CustomShellGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected DynamicType.Builder<?> getBuilder() {
3232
new TypePool.CacheProvider.Simple(), classFileLocator,
3333
TypePool.Default.ReaderMode.FAST, TypePool.Default.ofSystemLoader()
3434
).describe(className).resolve();
35+
shellToolConfig.setShellTypeDescription(typeDescription);
3536
return new ByteBuddy()
3637
.redefine(typeDescription, classFileLocator);
3738
}

generator/src/main/java/com/reajason/javaweb/memshell/generator/ListenerGenerator.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.reajason.javaweb.memshell.generator;
22

3+
import com.reajason.javaweb.GenerationException;
34
import com.reajason.javaweb.buddy.MethodCallReplaceVisitorWrapper;
45
import com.reajason.javaweb.utils.ShellCommonUtil;
56
import net.bytebuddy.asm.Advice;
7+
import net.bytebuddy.description.method.MethodDescription;
8+
import net.bytebuddy.description.method.MethodList;
69
import net.bytebuddy.description.modifier.Ownership;
710
import net.bytebuddy.description.modifier.Visibility;
811
import net.bytebuddy.description.type.TypeDescription;
912
import net.bytebuddy.dynamic.DynamicType;
1013
import net.bytebuddy.implementation.FixedValue;
14+
import net.bytebuddy.matcher.ElementMatchers;
1115

1216
import static net.bytebuddy.matcher.ElementMatchers.named;
1317
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
@@ -18,19 +22,26 @@
1822
*/
1923
public class ListenerGenerator {
2024

21-
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, Class<?> implInterceptor, Class<?> targetClass, String newClassName) {
22-
builder = builder
23-
.visit(MethodCallReplaceVisitorWrapper.newInstance(
24-
"getResponseFromRequest", newClassName, ShellCommonUtil.class.getName()))
25-
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
25+
public static DynamicType.Builder<?> build(DynamicType.Builder<?> builder, Class<?> implInterceptor,
26+
TypeDescription typeDefinition, String newClassName) {
27+
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
2628

27-
boolean methodNotFound = targetClass != null && TypeDescription.ForLoadedType.of(targetClass)
28-
.getDeclaredMethods()
29-
.filter(named("getFieldValue")
30-
.and(takesArguments(Object.class, String.class)))
31-
.isEmpty();
29+
if (methods.filter(ElementMatchers.named("getResponseFromRequest")
30+
.and(ElementMatchers.takesArguments(Object.class))
31+
.and(ElementMatchers.returns(Object.class)))
32+
.isEmpty()) {
33+
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
34+
" make sure arg and return type is Object.class");
35+
} else {
36+
builder = builder
37+
.visit(MethodCallReplaceVisitorWrapper.newInstance(
38+
"getResponseFromRequest", newClassName, ShellCommonUtil.class.getName()))
39+
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
40+
}
3241

33-
if (methodNotFound) {
42+
if (methods.filter(named("getFieldValue")
43+
.and(takesArguments(Object.class, String.class)))
44+
.isEmpty()) {
3445
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
3546
.withParameters(Object.class, String.class)
3647
.throwing(Exception.class)

generator/src/test/java/com/reajason/javaweb/memshell/generator/CustomShellGeneratorTest.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import com.reajason.javaweb.memshell.ShellType;
66
import com.reajason.javaweb.memshell.config.CustomConfig;
77
import com.reajason.javaweb.memshell.config.ShellConfig;
8+
import com.reajason.javaweb.memshell.shelltool.command.CommandListener;
89
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve;
910
import com.reajason.javaweb.utils.CommonUtil;
1011
import lombok.SneakyThrows;
1112
import net.bytebuddy.ByteBuddy;
13+
import net.bytebuddy.description.type.TypeDescription;
1214
import org.junit.jupiter.api.Test;
1315
import org.objectweb.asm.ClassReader;
1416

@@ -23,10 +25,31 @@
2325
* @since 2025/3/19
2426
*/
2527
class CustomShellGeneratorTest {
28+
@Test
29+
@SneakyThrows
30+
void testListener() {
31+
byte[] bytes = new ByteBuddy()
32+
.redefine(CommandListener.class)
33+
.name(CommonUtil.generateShellClassName()).make().getBytes();
34+
String className = CommonUtil.generateShellClassName();
35+
ShellConfig shellConfig = ShellConfig.builder()
36+
.server(Server.Tomcat)
37+
.shellType(ShellType.LISTENER)
38+
.build();
39+
CustomConfig customConfig = CustomConfig.builder()
40+
.shellClassName(className)
41+
.shellClassBase64(Base64.getEncoder().encodeToString(bytes))
42+
.shellTypeDescription(TypeDescription.ForLoadedType.of(CommandListener.class))
43+
.build();
44+
byte[] bytes1 = new CustomShellGenerator(shellConfig, customConfig).getBytes();
45+
46+
ClassReader classReader = new ClassReader(bytes1);
47+
assertEquals(className, classReader.getClassName().replace("/", "."));
48+
}
2649

2750
@Test
2851
@SneakyThrows
29-
void test() {
52+
void testFilter() {
3053
byte[] bytes = new ByteBuddy()
3154
.subclass(Object.class)
3255
.name(CommonUtil.generateShellClassName()).make().getBytes();
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.reajason.javaweb.memshell.generator;
2+
3+
import com.reajason.javaweb.GenerationException;
4+
import com.reajason.javaweb.memshell.server.Tomcat;
5+
import lombok.SneakyThrows;
6+
import net.bytebuddy.ByteBuddy;
7+
import net.bytebuddy.description.type.TypeDescription;
8+
import net.bytebuddy.dynamic.DynamicType;
9+
import org.junit.jupiter.api.Assertions;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.platform.commons.util.ReflectionUtils;
12+
13+
import javax.servlet.http.HttpServletRequest;
14+
import javax.servlet.http.HttpServletResponse;
15+
16+
import java.lang.reflect.Method;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
20+
/**
21+
* @author ReaJason
22+
* @since 2025/9/16
23+
*/
24+
class ListenerGeneratorTest {
25+
26+
public static class L {
27+
public Object getResponseFromRequest(Object request) {
28+
return null;
29+
}
30+
}
31+
32+
public static class J {
33+
public HttpServletResponse getResponseFromRequest(HttpServletRequest request) {
34+
return null;
35+
}
36+
}
37+
38+
public static class FakeRequest {
39+
public Object response = "i'm a good boy";
40+
}
41+
42+
@Test
43+
void testNoGetResponseFromRequest() {
44+
DynamicType.Builder<?> builder = new ByteBuddy().redefine(Object.class);
45+
Assertions.assertThrows(GenerationException.class, () -> ListenerGenerator.build(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(Object.class), "hello.world"));
46+
}
47+
48+
@Test
49+
void testGetResponseFromRequestSignatureError() {
50+
DynamicType.Builder<?> builder = new ByteBuddy().redefine(J.class);
51+
Assertions.assertThrows(GenerationException.class, () -> ListenerGenerator.build(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(J.class), "hello.world"));
52+
}
53+
54+
@Test
55+
@SneakyThrows
56+
void test() {
57+
String className = "hello.world";
58+
DynamicType.Builder<?> build = ListenerGenerator.build(new ByteBuddy().redefine(L.class).name(className), Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(L.class), className);
59+
Class<?> clazz = build.make().load(getClass().getClassLoader()).getLoaded();
60+
Object obj = clazz.newInstance();
61+
Method getResponseFromRequest = clazz.getDeclaredMethod("getResponseFromRequest", Object.class);
62+
getResponseFromRequest.setAccessible(true);
63+
Object response = getResponseFromRequest.invoke(obj, new FakeRequest());
64+
assertEquals("i'm a good boy", response);
65+
}
66+
}

0 commit comments

Comments
 (0)