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]: StackOverflowError occurred in CtExecutableReference.getExecutableDeclaration #5584

Open sretake opened 11 months ago

sretake commented 11 months ago

Describe the bug

StackOverflowError occurred in CtExecutableReference.getExecutableDeclaration

Source code you are trying to analyze/transform

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

class A {
    public void checkDataValidDimension(String versionCode, List<? extends FcBasicDataVO> fcBasicDataList,
        BiFunction<FcBasicDataVO, FcBasicDataFullVo, Boolean> isMatchFunc, Function<FcBasicDataVO, String> msgFunc,
        BiConsumer<FcBasicDataVO, FcBasicDataFullVo> matchedFunc) {

        // dimensionVo List
        List<FcBasicDataFullVo> dimensionVoList = getFcDimensionListByVersionCode(versionCode);
        fcBasicDataList.stream().forEach(checkVo -> {
            for (FcBasicDataFullVo dimensionVo : dimensionVoList) {
                if (isMatchFunc.apply(checkVo, dimensionVo)) {
                }
            }
        });
    }
}

Source code for your Spoon processing

Launcher launcher = new Launcher();
        launcher.getEnvironment().setComplianceLevel(11);
        launcher.getEnvironment().setAutoImports(true);
        launcher.getEnvironment().setIgnoreDuplicateDeclarations(true);
        launcher.addInputResource(filteringFolder);
        launcher.run();
        List<CtExecutableReference> elements = model.getElements(
            new spoon.reflect.visitor.filter.TypeFilter<>(CtExecutableReference.class));
        for (CtExecutableReference element : elements) {
            element.getExecutableDeclaration();
        }

Actual output

java.lang.StackOverflowError
    at spoon.support.util.internal.ElementNameMap.get(ElementNameMap.java:169)
    at spoon.support.reflect.declaration.CtPackageImpl.getPackage(CtPackageImpl.java:130)
    at spoon.reflect.factory.PackageFactory.getPackageFromModule(PackageFactory.java:216)
    at spoon.reflect.factory.PackageFactory.get(PackageFactory.java:176)
    at spoon.reflect.factory.TypeFactory.get(TypeFactory.java:434)
    at spoon.reflect.factory.TypeFactory.get(TypeFactory.java:562)
    at spoon.support.reflect.reference.CtTypeReferenceImpl.getTypeDeclaration(CtTypeReferenceImpl.java:245)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)
    at spoon.support.reflect.declaration.CtTypeImpl.isSameParameter(CtTypeImpl.java:767)
    at spoon.support.reflect.declaration.CtTypeImpl.hasSameParameters(CtTypeImpl.java:759)
    at spoon.support.reflect.declaration.CtTypeImpl.getMethod(CtTypeImpl.java:733)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getCtExecutable(CtExecutableReferenceImpl.java:121)
    at spoon.support.reflect.reference.CtExecutableReferenceImpl.getExecutableDeclaration(CtExecutableReferenceImpl.java:113)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getDeclaration(CtTypeParameterReferenceImpl.java:134)
    at spoon.support.reflect.reference.CtTypeParameterReferenceImpl.getTypeErasure(CtTypeParameterReferenceImpl.java:174)

Expected output

.

Spoon Version

10.4.3-beta-10

JVM Version

11

What operating system are you using?

windows 11

sretake commented 11 months ago

When class C is not in the current project, stack overflow will occur in the following test example

        @Test()
    void foo(){
        Launcher launcher = new Launcher();
        launcher.getEnvironment().setComplianceLevel(11);
        launcher.getEnvironment().setAutoImports(true);
        launcher.addInputResource(new VirtualFile("" +
            "import com.C;\n" +
            "import java.util.List;\n" +
            "import java.util.function.BiFunction;\n" +
            "\n" +
            "public class A {\n" +
            "    public void test( List<? extends C> list, BiFunction<C, B, Boolean> func) {\n" +
            "        list.stream().forEach(c -> {\n" +
            "            func.apply(c, null);\n" +
            "        });\n" +
            "    }\n" +
            "}\n" +
            "class B{ }" +
            ""));
        CtModel ctModel = launcher.buildModel();
        List<CtExecutableReference> elements = ctModel.getElements(new TypeFilter<>(CtExecutableReference.class));
        for (CtExecutableReference element : elements) {
            try{
                element.getExecutableDeclaration();
            }catch(StackOverflowError e){
                e.printStackTrace();
            }
        }
    }
sretake commented 11 months ago

When class C is in the current project, the following test cases are normal

        @Test()
    void foo(){
        Launcher launcher = new Launcher();
        launcher.getEnvironment().setComplianceLevel(11);
        launcher.getEnvironment().setAutoImports(true);
        launcher.addInputResource(new VirtualFile("" +
            "import java.util.List;\n" +
            "import java.util.function.BiFunction;\n" +
            "\n" +
            "public class A {\n" +
            "    public void test( List<? extends C> list, BiFunction<C, B, Boolean> func) {\n" +
            "        list.stream().forEach(c -> {\n" +
            "            func.apply(c, null);\n" +
            "        });\n" +
            "    }\n" +
            "}\n" +
            "class B{ }class C{}" +
            ""));
        CtModel ctModel = launcher.buildModel();
        List<CtExecutableReference> elements = ctModel.getElements(new TypeFilter<>(CtExecutableReference.class));
        for (CtExecutableReference element : elements) {
            try{
                element.getExecutableDeclaration();
            }catch(StackOverflowError e){
                e.printStackTrace();
            }
        }
    }
sretake commented 10 months ago

Can anyone answer me if it's actually a bug @alcides

I-Al-Istannen commented 10 months ago

I mean, it is kind of a bug but not really. It only happens in noclasspath mode, because in there JDT's type inference gives up:

grafik

As JDT is not able to resolve this to the apply(Object, Object) method in the class, Spoon would need to make that translation on a best-effort basis. Sadly, during erasure computation for the parameter spoon enters an infinite loop. I am sure spoon could be more clever here, but it still won't really be able to answer that query correctly with the types provided by JDT.

The question is whether we want to try and make Spoon a bit smarter here and let it return from the call -- possibly returning garbage information. We could also make it return null, I guess, but I am not sure how straightforward the required changes in spoon would be (if at all possible to detect).

sretake commented 10 months ago

thanks, i suggest you can return null or throw SpoonException instead of StackOverflowError, because it will cause my program to crash.