INRIA / spoon

Spoon is a metaprogramming library to analyze and transform Java source code. :spoon: is made with :heart:, :beers: and :sparkles:. It parses source files to build a well-designed AST with powerful analysis and transformation API.
http://spoon.gforge.inria.fr/
Other
1.76k stars 352 forks source link

[Bug]: Spoon anonymous function cannot resolve variable type #6021

Open jiankunking opened 1 month ago

jiankunking commented 1 month ago

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<CompletableFuture<Void>> futuresList = new ArrayList<>();
        // 获取所有带有Controller、RestController注解的类
        for (CtType<?> type : ctModel.getAllTypes()) {
            if (!Util.isController(type)) {
                continue;
            }

            log.info("class: " + type.getQualifiedName());
            if (type.isInterface()) {
                List<CtType> 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<Void> 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<Void> 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<?>>(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<CtInvocation<?>> 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<CtType> 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<CtField<?>> ctFields = ctMethod.getParent().getElements(new TypeFilter<CtField<?>>(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