[Bug]: Spoon anonymous function cannot resolve variable type
- Dominant language
- Java
- Stars
- 2k
- Forks
- 392
- Avg merge
- 11h 24m
- Merged PRs (30d)
- 36
Description
### Describe the bug
## Using code parsed by Spoon
```
@RestController
@RequestMapping("/hbdmorder")
public class HbdmOrderController {
@Resource
private HbdmOrderProcessingService hbdmOrderProcessingService;
@PostMapping("/receive")
public R receiveOrder(@RequestBody @Valid HbdmOrderParam hbdmOrderParam) {
return LockUtil.receiveLock(() -> (hbdmOrderProcessingService.receiveOrder(hbdmOrderParam)), hbdmOrderParam.getInvokingSysOrderNo());
}
}
public static R receiveLock(RFunction function, String invokingSysOrderNo) {
return tryLockAndExecute(function, RedisConstants.ORDER_LOCK + invokingSysOrderNo);
}
@FunctionalInterface
public interface RFunction {
R execute();
}
```
By parsing HbdmOrderController # receptiOrder, the following three calls can be obtained
But when parsing the parameter LockUtil.receiveLock, the type obtained is incorrect
**The correct type is: com. hair. hbdm. zhilian. service HbdmOrderProcessingService**
**But what was obtained is: com. hair. hbdm. zhilian. controller. hbdmOrderProcessingService**
## Analyzing Logic
```
public void mavenLoad(String mavenProject, String group, String repo) {
MavenLauncher launcher = new MavenLauncher(mavenProject, MavenLauncher.SOURCE_TYPE.APP_SOURCE);
launcher.getEnvironment().setComplianceLevel(8);
launcher.getEnvironment().setNoClasspath(true);
launcher.buildModel();
CtModel model = launcher.getModel();
scan(model, group, repo);
}
/**
* 扫描整个项目
* 从controller扫描
*
* @param ctModel
*/
public void scan(CtModel ctModel, String group, String repo) {
boolean hasRequestMapping = false;
List> futuresList = new ArrayList<>();
// 获取所有带有Controller、RestController注解的类
for (CtType type : ctModel.getAllTypes()) {
if (!Util.isController(type)) {
continue;
}
log.info("class: " + type.getQualifiedName());
if (type.isInterface()) {
List implementationList = listInterfaceImplElement(ctModel, type);
if (implementationList.isEmpty()) {
continue;
}
for (CtType implementation : implementationList) {
for (CtMethod ctMethod : implementation.getAllMethods()) {
if (!isOverridingInterfaceMethod(type, ctMethod)) {
continue;
}
String nextCallPkg = implementation.getPackage().toString();
String nextCallClazz = implementation.getSimpleName();
String nextCallMethod = ctMethod.getSimpleName();
final String traceId = UuidUtil.getUUID();
Node node = Node.newNode(null, null, null, null, nextCallPkg, nextCallClazz, nextCallMethod, group, repo, traceId);
nodeService.save(node);
CompletableFuture completableFuture = CompletableFuture.runAsync(() -> findNextCalls(ctModel, nextCallPkg, nextCallClazz, nextCallMethod, paramTypesToMd5(ctMethod.getParameters()), group, repo, traceId), threadPoolTaskExecutor);
futuresList.add(completableFuture);
}
continue;
}
} else {
for (CtMethod ctMethod : type.getAllMethods()) {
hasRequestMapping = false;
for (CtAnnotation methodAnnotation : ctMethod.getAnnotations()) {
if (!Util.isRequestMappingAnnotation(methodAnnotation)) {
continue;
}
hasRequestMapping = true;
break;
}
if (!hasRequestMapping) {
continue;
}
String nextCallPkg = type.getPackage().toString();
String nextCallClazz = type.getSimpleName();
String nextCallMethod = ctMethod.getSimpleName();
final String traceId = UuidUtil.getUUID();
Node node = Node.newNode(null, null, null, null, nextCallPkg, nextCallClazz, nextCallMethod, group, repo, traceId);
nodeService.save(node);
CompletableFuture completableFuture = CompletableFuture.runAsync(() -> findNextCalls(ctModel, nextCallPkg, nextCallClazz, nextCallMethod, paramTypesToMd5(ctMethod.getParameters()), group, repo, traceId), threadPoolTaskExecutor);
futuresList.add(completableFuture);
}
}
}
CompletableFuture[] array = futuresList.toArray(new CompletableFuture[0]);
CompletableFuture.allOf(array).join();
}
public void findNextCalls(CtModel ctModel, String pkg, String clazz, String method, String paramTypeMd5, String group, String repo, String traceId) {
if (ctModel == null) {
return;
}
if (StringUtil.isEmpty(pkg) || StringUtil.isEmpty(clazz) || StringUtil.isEmpty(method)) {
return;
}
ctModel.getElements(new TypeFilter>(CtMethod.class) {
@Override
public boolean matches(CtMethod ctMethod) {
if (!(ctMethod.getParent() instanceof CtClass)) {
return false;
}
// log.info("class: " + ctMethod.getDeclaringType().getQualifiedName() + ",method:" + ctMethod.getSimpleName());
CtClass cz = (CtClass) ctMethod.getParent();
String curClazz = cz.getSimpleName();
String curPkg = null != cz.getPackage() ? cz.getPackage().getQualifiedName() : "-";
if (!pkg.equals(curPkg)) {
return false;
}
if (!clazz.equals(curClazz)) {
return false;
}
if (!method.equals(ctMethod.getSimpleName())) {
return false;
}
// 方便测试时不校验参数MD5值
if (paramTypeMd5.equals("-1")) {
return true;
}
String md5 = paramTypesToMd5(ctMethod.getParameters());
if (!md5.equals(paramTypeMd5)) {
return false;
}
return true;
}
}).forEach(ctMethod -> {
List> invocations = ctMethod.getElements(new TypeFilter<>(CtInvocation.class));
String qualifiedName;
for (CtInvocation invocation : invocations) {
if (invocation == null || invocation.getExecutable() == null || invocation.getExecutable().getDeclaringType() == null) {
continue;
}
qualifiedName = invocation.getExecutable().getDeclaringType().getQualifiedName();
String nextCallPkg = null, nextCallClazz = null, nextCallMethod;
if (Util.isPackage(qualifiedName)) {
if (!Util.isHaierPackage(qualifiedName)) {
continue;
}
} else {
if (!(ctMethod.getParent() instanceof CtClass)) {
continue;
}
}
CtExecutableReference executable = invocation.getExecutable();
nextCallMethod = executable.getSimpleName();
CtPackageReference packageReference = executable.getDeclaringType().getPackage();
if (executable.getDeclaringType().isInterface()) {
// 如果是接口,则查找实现类
List implementationList = listInterfaceImplElement(ctModel, executable.getDeclaringType());
if (implementationList.isEmpty()) {
nextCallPkg = packageReference.getSimpleName();
nextCallClazz = executable.getDeclaringType().getSimpleName();
Node node = Node.newNode(pkg, clazz, method, invocation.getPosition().toString(), nextCallPkg, nextCallClazz, nextCallMethod, group, repo, traceId);
nodeService.save(node);
} else {
for (CtType implementation : implementationList) {
nextCallPkg = implementation.getPackage().toString();
nextCallClazz = implementation.getSimpleName();
nodeService.save(node);
// findNextCalls(ctModel, nextCallPkg, nextCallClazz, nextCallMethod, typeReferenceToMd5(executable.getParameters()), group, repo);
String nextTypeMd5 = typeReferenceToMd5(executable.getParameters());
if (pkg.equals(nextCallPkg) && clazz.equals(nextCallClazz) && method.equals(nextCallMethod) && paramTypeMd5.equals(nextTypeMd5)) {
// 递归调用跳过
continue;
}
findNextCalls(ctModel, nextCallPkg, nextCallClazz, nextCallMethod, typeReferenceToMd5(executable.getParameters()), group, repo, traceId);
continue;
}
}
continue;
} else if (executable.getDeclaringType().isEnum()) {
continue;
} else {
packageReference = executable.getDeclaringType().getPackage();
if (packageReference == null || packageReference.getSimpleName().equals("")) {
// 1、针对orderService.gvsLockMarketList(orderGvsLockFormList)获取不到包名及类名
// 回溯到类的字段上
// 2、无法适配lambda中的局部变量,比如下面这种
// 针对() -> (hbdmOrderProcessingService.receiveOrder(hbdmOrderParam)), hbdmOrderParam.getInvokingSysOrderNo()
// 这种情况idea的时序图插件sequence diagram也是无法识别的,具体可以readme.md
List> ctFields = ctMethod.getParent().getElements(new TypeFilter>(CtField.class));
if (ctFields.isEmpty()) {
continue;
}
for (CtField ctField : ctFields) {
if (!ctField.getSimpleName().equals(qualifiedName)) {
continue;
}
nextCallPkg = ctField.getType().getPackage().getSimpleName();
nextCallClazz = ctField.getType().getSimpleName();
break;
}
} else {
nextCallPkg = packageReference.getSimpleName();
nextCallClazz = executable.getDeclaringType().getSimpleName();
}
Node node = Node.newNode(pkg, clazz, method, invocation.getPosition().toString(), nextCallPkg, nextCallClazz, nextCallMethod, group, repo, traceId);
nodeService.save(node);
String nextTypeMd5 = typeReferenceToMd5(executable.getParameters());
if (pkg.equals(nextCallPkg) && clazz.equals(nextCallClazz) && method.equals(nextCallMethod) && paramTypeMd5.equals(nextTypeMd5)) {
// 递归调用跳过
continue;
}
findNextCalls(ctModel, nextCallPkg, nextCallClazz, nextCallMethod, typeReferenceToMd5(executable.getParameters()), group, repo, traceId);
continue;
}
}
});
}
```
### Source code you are trying to analyze/transform
_No response_
### Source code for your Spoon processing
_No response_
### Actual output
_No response_
### Expected output
_No response_
### Spoon Version
11.1.0
### JVM Version
21.0.1
### What operating system are you using?
Windows 10 专业版 21H2
Contributor guide
Research direction
Start with the MavenLauncher/buildModel setup and the findNextCalls entry point, then trace how the CtInvocation executable and declaring type are resolved inside the lambda example. Reproduce the mismatch where hbdmOrderProcessingService resolves to the controller package; done means the referenced service type resolves to its actual package and class.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 38/100