Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: CtRecordComponent::toField adds new fields to the model with broken parents #5265

Closed
Luro02 opened this issue Jun 11, 2023 · 2 comments · Fixed by #5287
Closed

[Bug]: CtRecordComponent::toField adds new fields to the model with broken parents #5265

Luro02 opened this issue Jun 11, 2023 · 2 comments · Fixed by #5287
Assignees
Labels

Comments

@Luro02
Copy link
Contributor

Luro02 commented Jun 11, 2023

Describe the bug

My code queries the CtModel for certain references and then tries to find the first common parent between all references. For this to work, it requires that all CtReferences have at least the same root (should be the unnamed module). I assumed this is always the case, but the code below has two references for which this is not the case.

The call to CtRecordComponent::toField seems to be the main problem, without this call or by setting the parent explicitly (ctField.setParent(ctRecord)) the code works as expected. It is surprising to me that the toField call has side-effects on the model.

Source code you are trying to analyze/transform

public class Main {
    protected static final String PLACEHOLDER = "";

    record Name(String name) {}
}

Source code for your Spoon processing

public final class Main {
    private Main() {
    }

    // returns all parents of a CtElement
    private static Iterable<CtElement> parents(CtElement ctElement) {
        return () -> new Iterator<>() {
            private CtElement current = ctElement;

            @Override
            public boolean hasNext() {
                return this.current.isParentInitialized();
            }

            @Override
            public CtElement next() throws NoSuchElementException {
                if (!this.hasNext()) {
                    throw new NoSuchElementException("No more parents");
                }

                CtElement result = this.current.getParent();
                this.current = result;
                return result;
            }
        };
    }

    private static void run() {
        Launcher launcher = new Launcher();

        launcher.getEnvironment().setComplianceLevel(17);
        launcher.addInputResource(new VirtualFile("""
            public class Main {
                protected static final String PLACEHOLDER = "";

                record Name(String name) {}
            }
            """));

        CtModel ctModel = launcher.buildModel();

        CtRecord ctRecord = ctModel.getElements(new TypeFilter<>(CtRecord.class)).get(0);
        for (CtRecordComponent component : ctRecord.getRecordComponents()) {
            CtField<?> ctField = component.toField();
            System.out.println(ctField);
        }

        CtClass<?> exampleClass = ctModel.getElements(new TypeFilter<>(CtClass.class)).get(0);

        List<CtReference> references = ctModel.getElements(new DirectReferenceFilter<>(exampleClass.getField("PLACEHOLDER").getType()));

        for (CtReference ctReference : references) {
            List<CtElement> path = new ArrayList<>();
            parents(ctReference).forEach(path::add);
            if (path.size() == 1) {
                // this prints the references with broken parents
                System.out.println(
                    "parents('%s'): [%s]".formatted(
                        ctReference,
                        path.stream()
                            .map(CtElement::prettyprint)
                            .collect(Collectors.joining(", "))
                    )
                );
            }
        }
    }

    public static void main(String[] args) {
        run();
    }
}

Actual output

java.lang.String name;
parents('java.lang.String'): [String name;]
parents('java.lang.String'): [String name;]

Expected output

java.lang.String name;

Spoon Version

10.3.0

JVM Version

openjdk version "17.0.1" 2021-10-19 OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12) OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

What operating system are you using?

Windows 10

@Luro02 Luro02 added the bug label Jun 11, 2023
@MartinWitt MartinWitt self-assigned this Jun 11, 2023
@MartinWitt
Copy link
Collaborator

Hey,

The toField is just for convince and returns a view. The field already exists in CtRecord and you shouldn't really need to call this method. Also, I don't see where toField changes the model. If we set a parent in this method, it would change the model. So, intentionally, we don't set the parent in this method but at the call sites of this method. I will update the doc for this behavior.

@MartinWitt
Copy link
Collaborator

After a discord chat, the problem is fixed on his side. So, only updating the doc to mention this is a parentless view is left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants