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 parse nesting switch-case expression #5978

Closed ZenLiuCN closed 2 months ago

ZenLiuCN commented 2 months ago

Describe the bug

Use spoon to compile the source cause java.lang.RuntimeException: Inconsistent Stack static Object lookup(TypeMirror type). It may something wrong with spoon to parse nesting enumration expression switch statement.

debugging located in spoon.support.compiler.jdt.ContextBuilder

void exit(ASTNode node) {
        ASTPair pair = stack.pop();
        if (pair.node != node) {
            throw new RuntimeException("Inconsistent Stack " + node + "\n" + pair.node); //! here fails
        }
        CtElement current = pair.element;
        if (!stack.isEmpty()) {
            this.jdtTreeBuilder.getExiter().setChild(current);
            this.jdtTreeBuilder.getExiter().setChild(pair.node);
            ASTPair parentPair = stack.peek();
            this.jdtTreeBuilder.getExiter().exitParent(parentPair);
        }
    }

It seems that maybe the parse stage not resolve the right node. (same code is compiled with just jdk).

static Object lookup(TypeMirror type) {
  case BOOLEAN ->
 N;
  case BYTE ->
 N;
  case SHORT ->
 N;
  case INT ->
 N;
  case LONG ->
 N;
  case CHAR ->
 N;
  case FLOAT ->
 N;
  case DOUBLE ->
 N;
  case ARRAY ->
  {
    var ct = ((ArrayType) type).getComponentType();
    yield = $missing$;
    switch (ct.getKind()) {
    case BOOLEAN ->
 N;
    case BYTE ->
 N;
    case SHORT ->
 N;
    case INT ->
 N;
    case LONG ->
 N;
    case CHAR ->
 N;
    case FLOAT ->
 N;
    case DOUBLE ->
 N;
    case ARRAY,DECLARED ->
 N;
    default ->
        throw new IllegalStateException("");
    }
    ;
  }
  case DECLARED ->
  var dt = (DeclaredType) type;
}

Source code you are trying to analyze/transform

package spoon.bug;

import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeKind.*;

public interface Sample {

    Object N = null;

    static Object lookup(TypeMirror type) {
        return switch (type.getKind()) {
            case BOOLEAN -> N;
            case BYTE -> N;
            case SHORT -> N;
            case INT -> N;
            case LONG -> N;
            case CHAR -> N;
            case FLOAT -> N;
            case DOUBLE -> N;
            case ARRAY -> {
                var ct = ((ArrayType) type).getComponentType();
                yield switch (ct.getKind()) {
                    case BOOLEAN -> N;
                    case BYTE -> N;
                    case SHORT -> N;
                    case INT -> N;
                    case LONG -> N;
                    case CHAR -> N;
                    case FLOAT -> N;
                    case DOUBLE -> N;
                    case ARRAY, DECLARED -> N;
                    default -> throw new IllegalStateException("");
                };
            }
            case DECLARED -> {
                var dt = (DeclaredType) type;
                yield switch (dt.getTypeArguments().size()) {
                    //simple
                    case 0 -> switch (dt.asElement().getSimpleName().toString()) {
                        case "Boolean" -> N;
                        case "Integer" -> N;
                        case "String" -> N;
                        case "Buffer" -> N;
                        case "Number" -> N;
                        case "BigDecimal" -> N;
                        case "Long" -> N;
                        case "Float" -> N;
                        case "Double" -> N;
                        case "Instant" -> N;
                        case "Duration" -> N;
                        case "JsonArray" -> N;
                        case "JsonObject" -> N;
                        default -> {
                            throw new IllegalStateException("");
                        }
                    };
                    case 1 -> {

                        throw new IllegalStateException("");
                    }
                    case 2 -> {

                        throw new IllegalStateException("");
                    }
                    default -> {

                        throw new IllegalStateException("");
                    }
                };
            }
            default -> throw new IllegalStateException("");
        };

    }

}

Source code for your Spoon processing

package spoon.bug;

import org.junit.jupiter.api.Test;
import spoon.Launcher;

import static org.junit.jupiter.api.Assertions.*;

class SampleTest {
    @Test
    public void testName() throws Exception {
        var l = new Launcher();
        l.getEnvironment().setNoClasspath(true);
        l.getEnvironment().setShouldCompile(false);
        l.addInputResource("src/main/java");
        l.addProcessor(new Entrance());
        l.run();
    }
}

Actual output

13:59:20.240 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable BOOLEAN at (/src/main/java/spoon/bug/Sample.java:15).
13:59:20.244 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable BYTE at (/src/main/java/spoon/bug/Sample.java:16).
13:59:20.245 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable SHORT at (/src/main/java/spoon/bug/Sample.java:17).
13:59:20.245 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable INT at (/src/main/java/spoon/bug/Sample.java:18).
13:59:20.246 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable LONG at (/src/main/java/spoon/bug/Sample.java:19).
13:59:20.246 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable CHAR at (/src/main/java/spoon/bug/Sample.java:20).
13:59:20.247 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable FLOAT at (/src/main/java/spoon/bug/Sample.java:21).
13:59:20.247 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable DOUBLE at (/src/main/java/spoon/bug/Sample.java:22).
13:59:20.247 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable ARRAY at (/src/main/java/spoon/bug/Sample.java:23).
13:59:20.251 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable DECLARED at (/src/main/java/spoon/bug/Sample.java:34).
13:59:20.253 [main] DEBUG spoon.support.compiler.jdt.JDTTreeBuilder -- Could not find declaration for variable DECLARED at (/src/main/java/spoon/bug/Sample.java:38).

java.lang.RuntimeException: Inconsistent Stack static Object lookup(TypeMirror type) {
  case BOOLEAN ->
 N;
  case BYTE ->
 N;
  case SHORT ->
 N;
  case INT ->
 N;
  case LONG ->
 N;
  case CHAR ->
 N;
  case FLOAT ->
 N;
  case DOUBLE ->
 N;
  case ARRAY ->
  {
    var ct = ((ArrayType) type).getComponentType();
    yield = $missing$;
    switch (ct.getKind()) {
    case BOOLEAN ->
 N;
    case BYTE ->
 N;
    case SHORT ->
 N;
    case INT ->
 N;
    case LONG ->
 N;
    case CHAR ->
 N;
    case FLOAT ->
 N;
    case DOUBLE ->
 N;
    case ARRAY,DECLARED ->
 N;
    default ->
        throw new IllegalStateException("");
    }
    ;
  }
  case DECLARED ->
  var dt = (DeclaredType) type;
}
case DECLARED ->

    at spoon.support.compiler.jdt.ContextBuilder.exit(ContextBuilder.java:122)
    at spoon.support.compiler.jdt.JDTTreeBuilder.endVisit(JDTTreeBuilder.java:540)
    at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.traverse(MethodDeclaration.java:440)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.traverse(TypeDeclaration.java:1702)
    at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.traverse(CompilationUnitDeclaration.java:827)
    at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.traverse(CompilationUnitDeclaration.java:788)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.traverseUnitDeclaration(JDTBasedSpoonCompiler.java:481)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.lambda$buildModel$0(JDTBasedSpoonCompiler.java:438)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.forEachCompilationUnit(JDTBasedSpoonCompiler.java:465)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.buildModel(JDTBasedSpoonCompiler.java:436)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.buildUnitsAndModel(JDTBasedSpoonCompiler.java:373)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.buildSources(JDTBasedSpoonCompiler.java:336)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.build(JDTBasedSpoonCompiler.java:117)
    at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.build(JDTBasedSpoonCompiler.java:100)
    at spoon.Launcher.buildModel(Launcher.java:781)
    at spoon.Launcher.run(Launcher.java:732)
    at spoon.bug.SampleTest.testName(SampleTest.java:16)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)

Expected output

No response

Spoon Version

10.4.1

JVM Version

17.0.10

What operating system are you using?

windows 10

ZenLiuCN commented 2 months ago

It looks like yield not supported yet?

I-Al-Istannen commented 2 months ago

Can you try setting the compliance level on the environment to a Version supporting that? Maybe 17 or 20 or whatever your code needs.

ZenLiuCN commented 2 months ago

code without yield is fine.

package spoon.bug;

import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeKind.*;

public interface Sample {

    Object N = null;

    static Object lookup(TypeMirror type) {
        final Object v;
         switch (type.getKind()) {
            case BOOLEAN ->v= N;
            case BYTE ->v= N;
            case SHORT ->v= N;
            case INT ->v= N;
            case LONG ->v= N;
            case CHAR ->v= N;
            case FLOAT ->v= N;
            case DOUBLE ->v= N;
            case ARRAY -> {
                var ct = ((ArrayType) type).getComponentType();
                switch (ct.getKind()) {
                    case BOOLEAN ->v=N;
                    case BYTE ->v= N;
                    case SHORT ->v= N;
                    case INT ->v= N;
                    case LONG ->v= N;
                    case CHAR ->v= N;
                    case FLOAT ->v= N;
                    case DOUBLE ->v= N;
                    case ARRAY, DECLARED ->v= N;
                    default -> throw new IllegalStateException("");
                };
            }
            case DECLARED -> {
                var dt = (DeclaredType) type;
               v= switch (dt.getTypeArguments().size()) {
                    //simple
                    case 0 -> switch (dt.asElement().getSimpleName().toString()) {
                        case "Boolean" -> N;
                        case "Integer" -> N;
                        case "String" -> N;
                        case "Buffer" -> N;
                        case "Number" -> N;
                        case "BigDecimal" -> N;
                        case "Long" -> N;
                        case "Float" -> N;
                        case "Double" -> N;
                        case "Instant" -> N;
                        case "Duration" -> N;
                        case "JsonArray" -> N;
                        case "JsonObject" -> N;
                        default -> {
                            throw new IllegalStateException("");
                        }
                    };
                    case 1 -> {

                        throw new IllegalStateException("");
                    }
                    case 2 -> {

                        throw new IllegalStateException("");
                    }
                    default -> {

                        throw new IllegalStateException("");
                    }
                };
            }
            default -> throw new IllegalStateException("");
        };
        return v;
    }

}
I-Al-Istannen commented 2 months ago

The default compliance level is 8 (I think), so yield will not be accepted. If you change the level to something supporting it and it still breaks, then spoon has a bug. Otherwise this is just spoon trying to use Java 8 rules to parse Java 13+ code, which can break. If that is your problem, the error message isn't quite the most helpful though.

ZenLiuCN commented 2 months ago

Can you try setting the compliance level on the environment to a Version supporting that? Maybe 17 or 20 or whatever your code needs.

added l.getEnvironment().setComplianceLevel(17);, it's ok. so it not bug. My bad to assume that language levels are defualt to the JDK levels. Thanks very much.

I-Al-Istannen commented 2 months ago

To default has not been useful for years now, but changing that is a bit hard to do now in a backwards compatible way. The best default is probably not to have one and require that...

ZenLiuCN commented 2 months ago

To default has not been useful for years now, but changing that is a bit hard to do now in a backwards compatible way. The best default is probably not to have one and require that...

I like this idea. Hidden nothing may reduce many mistakes.