From 44e3b216b55d21fef14411f9f0c663f4d83ca98a Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 10 Aug 2024 21:03:14 +0200 Subject: [PATCH] fix: fix modeling bug (#5912) --- .../compiler/jdt/JDTTreeBuilderHelper.java | 2 +- .../compiler/jdt/JDTTreeBuilderQuery.java | 7 ++- .../prettyprinter/QualifiedThisRefTest.java | 47 +++++++++++++++++++ .../test/targeted/TargetedExpressionTest.java | 6 ++- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java index 1652c496fc7..a3406f5efdf 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderHelper.java @@ -723,7 +723,7 @@ CtExpression createTargetFieldAccess(QualifiedNameReference qualifiedNameRefe } else if (ref.isStatic()) { target = createTypeAccess(qualifiedNameReference, ref); } else if (!ref.isStatic() && !ref.getDeclaringType().isAnonymous()) { - if (!JDTTreeBuilderQuery.isResolvedField(qualifiedNameReference)) { + if (!JDTTreeBuilderQuery.isFieldReference(qualifiedNameReference)) { target = createTypeAccessNoClasspath(qualifiedNameReference); } else { target = jdtTreeBuilder.getFactory().Code().createThisAccess(jdtTreeBuilder.getReferencesBuilder().getTypeReference(qualifiedNameReference.actualReceiverType), true); diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java index 97c3de7a902..83e4683a7f3 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilderQuery.java @@ -217,15 +217,14 @@ static boolean isValidProblemBindingField(QualifiedNameReference qualifiedNameRe } /** - * Check if the name reference is resolved in the JDT tree, i.e. that the declaration is available. + * Check if the name reference is resolved as a field ref in the JDT tree. * * @param qualifiedNameReference * Reference which should contain a field binding. * @return true if the field has been resolved by the jdt builder. */ - static boolean isResolvedField(QualifiedNameReference qualifiedNameReference) { - return qualifiedNameReference.binding instanceof FieldBinding - && ((FieldBinding) qualifiedNameReference.binding).original().sourceField() != null; + static boolean isFieldReference(QualifiedNameReference qualifiedNameReference) { + return qualifiedNameReference.binding instanceof FieldBinding; } diff --git a/src/test/java/spoon/test/prettyprinter/QualifiedThisRefTest.java b/src/test/java/spoon/test/prettyprinter/QualifiedThisRefTest.java index 6ce0f6c5a7d..f1c0b96392f 100644 --- a/src/test/java/spoon/test/prettyprinter/QualifiedThisRefTest.java +++ b/src/test/java/spoon/test/prettyprinter/QualifiedThisRefTest.java @@ -25,7 +25,9 @@ import org.junit.jupiter.api.Test; import spoon.Launcher; import spoon.compiler.SpoonResourceHelper; +import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtFieldRead; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; @@ -39,7 +41,9 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.support.compiler.VirtualFile; import spoon.support.reflect.code.CtFieldAccessImpl; +import spoon.support.reflect.code.CtTypeAccessImpl; import spoon.test.delete.testclasses.Adobada; import spoon.test.prettyprinter.testclasses.QualifiedThisRef; @@ -144,4 +148,47 @@ public void testPrintCtFieldAccessWorkEvenWhenParentNotInitialized() { assertFalse(printer.getResult().isEmpty()); } + + @Test + public void testGuavaBug() { + // contract: does not model buf as a typeaccess, does not write ByteArrayOutputStream.buf + // this bug was found by spooning Guava in Jenkins + final String code = "class ExposedByteArrayOutputStream extends java.io.ByteArrayOutputStream {\n" + + " ExposedByteArrayOutputStream(int expectedInputSize) {\n" + + " super(expectedInputSize);\n" + + " }\n" + + "\n" + + " void write(java.nio.ByteBuffer input) {\n" + + " int remaining = input.remaining();\n" + + " if ((count + remaining) > buf.length) {\n" + + " buf = java.util.Arrays.copyOf(buf, count + remaining);\n" + + " }\n" + + " input.get(buf, count, remaining);\n" + + " count += remaining;\n" + + " }\n" + + "\n" + + " byte[] byteArray() {\n" + + " return buf;\n" + + " }\n" + + "\n" + + " int length() {\n" + + " return count;\n" + + " }\n" + + " }"; + + Launcher launcher = new Launcher(); + launcher.addInputResource(new VirtualFile(code)); + launcher.getEnvironment().setNoClasspath(false); + launcher.getEnvironment().setAutoImports(true); + + CtClass c = (CtClass) launcher.buildModel().getAllTypes().iterator().next(); + assertEquals(c.getSimpleName().toString(), "ExposedByteArrayOutputStream"); + + final List list = c.filterChildren(new TypeFilter<>(CtBinaryOperator.class)).list(); + CtBinaryOperator binaryOperator = (CtBinaryOperator) list.get(0); + assertTrue(CtFieldRead.class.isAssignableFrom(binaryOperator.getRightHandOperand().getClass())); + assertEquals("(count + remaining) > buf.length", binaryOperator.toString()); + assertFalse(c.toString().contains("ByteArrayOutputStream.buf"), "that will not compile for sure"); + + } } diff --git a/src/test/java/spoon/test/targeted/TargetedExpressionTest.java b/src/test/java/spoon/test/targeted/TargetedExpressionTest.java index 2e44e599c2f..d1eb9349eff 100644 --- a/src/test/java/spoon/test/targeted/TargetedExpressionTest.java +++ b/src/test/java/spoon/test/targeted/TargetedExpressionTest.java @@ -296,12 +296,16 @@ public void testOnlyStaticTargetFieldReadNoClasspath() { final Launcher launcher = new Launcher(); launcher.getEnvironment().setNoClasspath(true); launcher.addInputResource("./src/test/resources/spoon/test/noclasspath/targeted/StaticFieldReadOnly.java"); + CtModel model = launcher.buildModel(); List> invocations = model.getElements(e -> e.getExecutable().getSimpleName().equals("error")); CtInvocation inv = invocations.get(0); CtFieldRead fieldRead = (CtFieldRead) inv.getTarget(); - CtExpression target = fieldRead.getTarget(); + // we do have the right type access in noclasspath mode + // the slight behavior change is that PR 5812 adds one level of indirection in the model, hence the filterChildren call + // however correct behavior is full classpath mode is higher priority, see https://github.com/INRIA/spoon/pull/5912 + CtTypeAccess target = (CtTypeAccess) fieldRead.filterChildren(new TypeFilter<>(CtTypeAccess.class)).list().get(0); assertTrue(target instanceof CtTypeAccess); assertEquals("Launcher", ((CtTypeAccess) target).getAccessedType().getSimpleName());