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.

Home Page:http://spoon.gforge.inria.fr/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: wrong output for try with resources (variable ends up several times in initializer list)

xzel23 opened this issue · comments

Describe the bug

Spoon produces wrong output for try-with-resources without resource declaration. In the example, foo1() and bar1() that both contain a resource declaration produce correct results. The output for the other methods is incorrect, the variable should be present only once in the expression in parentheses.

Note that try-with-resources was changed in Java 9 to explicitly allow using final or effectively final variables in the try-with-resources list.

Actually I have another case, where try (in) { ... } gets transformed to the non-compilable try { ... }but I cannot reproduce with a simplified example, so I report these apparent failures first.

Source code you are trying to analyze/transform

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;

class Issue {

    public static void foo1() throws FileNotFoundException {
        try (PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
            writer.println("foo");
        }
    }

    public static void bar1() throws FileNotFoundException {
        try (PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo2() throws FileNotFoundException {
        PrintWriter writer = new PrintWriter(new File("testWrite.txt"));
        try (writer) {
            writer.println("foo");
        }
    }

    public static void bar2() throws FileNotFoundException {
        PrintWriter writer = new PrintWriter(new File("testWrite.txt"));
        try (writer) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo3(PrintWriter writer) throws FileNotFoundException {
        try (writer) {
            writer.println("foo");
        }
    }

    public static void bar3(PrintWriter writer) throws FileNotFoundException {
        try (writer) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Source code for your Spoon processing

import spoon.Launcher;
import spoon.processing.AbstractProcessor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;

public class Main {
    public static void main(String[] args) {
        // Create and configure a Spoon launcher
        Launcher launcher = new Launcher();
        launcher.addProcessor(new NoOperationProcessor());
        launcher.addInputResource("src/test/resources/Issue.java");
        launcher.run();
        launcher.getModel().getAllTypes().stream()
                .map(CtType::toString)
                .forEach(System.out::println);
    }

    // Create a no-operation processor
    static class NoOperationProcessor extends AbstractProcessor<CtElement> {
        @Override
        public void process(CtElement element) {
            // Do nothing
        }
    }
}

Actual output

class Issue {
    public static void foo1() throws java.io.FileNotFoundException {
        try (java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.File("testWrite.txt"))) {
            writer.println("foo");
        }
    }

    public static void bar1() throws java.io.FileNotFoundException {
        try (java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.File("testWrite.txt"))) {
            writer.println("bar");
        } catch (java.lang.Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo2() throws java.io.FileNotFoundException {
        java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.File("testWrite.txt"));
        try (writer;writer;writer) {
            writer.println("foo");
        }
    }

    public static void bar2() throws java.io.FileNotFoundException {
        java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.File("testWrite.txt"));
        try (writer;writer;writer) {
            writer.println("bar");
        } catch (java.lang.Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo3(java.io.PrintWriter writer) throws java.io.FileNotFoundException {
        try (writer;writer) {
            writer.println("foo");
        }
    }

    public static void bar3(java.io.PrintWriter writer) throws java.io.FileNotFoundException {
        try (writer;writer) {
            writer.println("bar");
        } catch (java.lang.Exception e) {
            e.printStackTrace();
        }
    }
}

Expected output

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;

class Issue {

    public static void foo1() throws FileNotFoundException {
        try (PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
            writer.println("foo");
        }
    }

    public static void bar1() throws FileNotFoundException {
        try (PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo2() throws FileNotFoundException {
        PrintWriter writer = new PrintWriter(new File("testWrite.txt"));
        try (writer) {
            writer.println("foo");
        }
    }

    public static void bar2() throws FileNotFoundException {
        PrintWriter writer = new PrintWriter(new File("testWrite.txt"));
        try (writer) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void foo3(PrintWriter writer) throws FileNotFoundException {
        try (writer) {
            writer.println("foo");
        }
    }

    public static void bar3(PrintWriter writer) throws FileNotFoundException {
        try (writer) {
            writer.println("bar");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Spoon Version

10.4.1

JVM Version

17

What operating system are you using?

macOS Ventura 13.5.2

I can confirm this bug, even with compliance level >= 9 and also without the processor. It is caused by

for (ASTPair pair: this.jdtTreeBuilder.getContextBuilder().getAllContexts()) {
final List<CtLocalVariable> variables = pair.element.getElements(new TypeFilter<>(CtLocalVariable.class));
for (CtLocalVariable v: variables) {
if (v.getSimpleName().equals(variableRef.getSimpleName())) {
// we found the resource
// we clone it in order to comply with the contract of being a tree
final CtLocalVariable clone = v.clone();
clone.setImplicit(true);
tryWithResource.addResource(clone);
break;
}
}
}

which incorrectly searches for declarations in other mehods as well. Generally, there might be a better solution to this.