INRIA / INRIA/spoon

[Bug]: Spoon anonymous function cannot resolve variable type

Open
#6,021 0 comments 0 reactions 0 assignees View on GitHub
bug
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

type不正确

**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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.