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]: Inconsistent parent for CtTypeReferenceImpl in CtRecordComponentImpl which points to a CtMethodImpl #5868

Open
Luro02 opened this issue Jun 25, 2024 · 0 comments
Labels

Comments

@Luro02
Copy link
Contributor

Luro02 commented Jun 25, 2024

Describe the bug

When visiting all elements (keeping track of the parents) in the model, one encounters an element (I guess it is the Type of the Record Field) where its parent doesn't match the one that was kept track of.

It scans the elements like this:

1.    CtUnnamedModule (?)
2.    CtRootPackage (?)
3.    CtRecordImpl (D:/projects/playground/Main:1)
4.    CtRecordComponentImpl (?)
5.    CtTypeReferenceImpl (D:/projects/playground/Main:1)

and for some reason the CtTypeReferenceImpl#getParent does not point to CtRecordComponentImpl, but to a CtMethodImpl.

image

For some reason the CtMethodImpl has the same reference to the type as the CtRecordComponentImpl, but CtFieldImpl has a distinct one:
image

This is likely the result of a missing clone somewhere in the code?
My guess is that it is this line in the CtRecordComponentImpl:
image

Source code you are trying to analyze/transform

public record Main(String string) {}

Source code for your Spoon processing

package org.example;

import spoon.Launcher;
import spoon.compiler.Environment;
import spoon.processing.Processor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.DefaultImportComparator;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.ForceImportProcessor;
import spoon.reflect.visitor.ImportCleaner;
import spoon.reflect.visitor.ImportConflictDetector;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.support.compiler.VirtualFile;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;

public final class Main {

    private Main() {
    }
    private static final class ParentChecker extends CtScanner {
        private final List<InvalidElement> invalidElements;
        private final Deque<CtElement> stack;

        private ParentChecker() {
            this.invalidElements = new ArrayList<>();
            this.stack = new ArrayDeque<>();
        }

        public static List<InvalidElement> checkConsistency(CtElement ctElement) {
            ParentChecker parentChecker = new ParentChecker();
            parentChecker.scan(ctElement);
            return parentChecker.invalidElements;
        }

        @Override
        public void enter(CtElement element) {
            if (!this.stack.isEmpty() && (!element.isParentInitialized() || element.getParent() != this.stack.peek())) {
                this.invalidElements.add(new InvalidElement(element, this.stack));
            }


            this.stack.push(element);
        }

        @Override
        protected void exit(CtElement e) {
            this.stack.pop();
        }

        public record InvalidElement(CtElement element, Deque<CtElement> stack) {
            public InvalidElement {
                stack = new ArrayDeque<>(stack);
            }

            public String reason() {
                String name = this.element instanceof CtNamedElement ctNamedElement ? "-" + ctNamedElement.getSimpleName() : "";
                return (this.element.isParentInitialized() ? "inconsistent (%s)".formatted(this.element.getParent().getClass().getSimpleName()) : "null")
                    + " parent for " + this.element.getClass() + name
                    + " - " + this.element.getPosition()
                    + " - " + this.stack.peek();
            }

            public String dumpStack() {
                List<String> output = new ArrayList<>();

                for (CtElement ctElement : this.stack) {
                    output.add("    " + ctElement.getClass().getSimpleName()
                        + " " + (ctElement.getPosition().isValidPosition() ? String.valueOf(ctElement.getPosition()) : "(?)")
                    );
                }

                return String.join(System.lineSeparator(), output);
            }

            @Override
            public String toString() {
                return "%s%n%s".formatted(this.reason(), this.dumpStack());
            }

            @Override
            public boolean equals(Object object) {
                if (this == object) {
                    return true;
                }
                if (!(object instanceof InvalidElement that)) {
                    return false;
                }

                return this.element == that.element();
            }

            @Override
            public int hashCode() {
                return System.identityHashCode(this.element);
            }
        }
    }
    private static CtModel buildModel() {
        Launcher launcher = new Launcher();

        Environment environment = launcher.getEnvironment();
        environment.setPrettyPrinterCreator(() -> new DefaultJavaPrettyPrinter(environment) {
            {
                // copy-pasted from StandardEnvironment#createPrettyPrinterAutoImport
                List<Processor<CtElement>> preprocessors = List.of(
                    // try to import as many types as possible
                    new ForceImportProcessor(),
                    // remove unused imports first. Do not add new imports at a time when conflicts are not resolved
                    new ImportCleaner().setCanAddImports(false),
                    // solve conflicts, the current imports are relevant too
                    new ImportConflictDetector(),
                    // compute final imports
                    new ImportCleaner().setImportComparator(new DefaultImportComparator())
                );
                this.setIgnoreImplicit(false);
                this.setPreprocessors(preprocessors);
                this.setMinimizeRoundBrackets(true);
            }
        });

        environment.setComplianceLevel(21);

        launcher.addInputResource(new VirtualFile(
            "public record Main(String string) {}",
            "Main"
        ));

        return launcher.buildModel();
    }

    public static void main(String[] args) {
        CtModel ctModel = buildModel();

        for (var elem : ParentChecker.checkConsistency(ctModel.getUnnamedModule())) {
            System.out.println(elem);
        }
    }


    public static <T, R extends CtElement> T reduce(CtQuery ctQuery, Class<? super R> itemClass, T first, BiFunction<T, R, T> function) {
        AtomicReference<T> result = new AtomicReference<>(first);

        ctQuery.forEach((R out) -> {
            if (out != null && itemClass.isAssignableFrom(out.getClass())) {
                result.getAndUpdate(value -> function.apply(value, out));
            }
        });

        return result.get();
    }
}

Actual output

inconsistent (CtMethodImpl) parent for class spoon.support.reflect.reference.CtTypeReferenceImpl - (D:/projects/playground/Main:1) - String string
    CtRecordComponentImpl (?)
    CtRecordImpl (D:/projects/playground/Main:1)
    CtRootPackage (?)
    CtUnnamedModule (?)

Process finished with exit code 0

Expected output

Process finished with exit code 0

Spoon Version

11.0.0

JVM Version

openjdk version "21.0.2" 2024-01-16 LTS OpenJDK Runtime Environment Zulu21.32+17-CA (build 21.0.2+13-LTS) OpenJDK 64-Bit Server VM Zulu21.32+17-CA (build 21.0.2+13-LTS, mixed mode, sharing)

What operating system are you using?

Windows 11

@Luro02 Luro02 added the bug label Jun 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant