INRIA / INRIA/spoon

[Bug]: Inconsistent parent for `CtTypeReferenceImpl` in `CtRecordComponentImpl` which points to a `CtMethodImpl`

Open
#5,868 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
2k
Forks
392
Avg merge
11h 24m
Merged PRs (30d)
36

Description

### 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](https://github.com/INRIA/spoon/assets/24826124/9943b274-ffa5-40eb-a753-064a75910c1c)

For some reason the `CtMethodImpl` has the same reference to the type as the `CtRecordComponentImpl`, but `CtFieldImpl` has a distinct one:
![image](https://github.com/INRIA/spoon/assets/24826124/f6008598-7e12-4951-942f-de04cba0b3c7)

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](https://github.com/INRIA/spoon/assets/24826124/22d2818a-0039-4392-b2bd-5d6927b20d31)

### Source code you are trying to analyze/transform

```Java
public record Main(String string) {}
```

### Source code for your Spoon processing

```Java
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 invalidElements;
private final Deque stack;

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

public static List 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 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 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> 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 reduce(CtQuery ctQuery, Class itemClass, T first, BiFunction function) {
AtomicReference 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

```Java
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

```Java
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

Contributor guide

Open the contributing guide

Research direction

Start by running the provided Java 21 reproducer for `public record Main(String string) {}` and inspect `CtRecordComponentImpl`, `CtTypeReferenceImpl`, and the parent traversal in `ParentChecker`. Trace why the component type is also associated with a `CtMethodImpl`; done means the traversal reports no inconsistent parent and the behavior is covered by a regression test.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.