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: NullPointerException for shadow anonymous classes #3563

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5ff8503
Fetching compilation unit module from the JDT compiler
Strum355 Aug 25, 2020
dbc79be
Fallback to resolving compilation unit from filename as previously
Strum355 Aug 25, 2020
8956c25
Fixed JSLCorrectness fix when simplenamePart == ""
Strum355 Aug 25, 2020
d7b2ae0
Fixed PositionBuilder failing if modifier is followed by generic with…
Strum355 Aug 27, 2020
6dd5a94
Fallback to resolving compilation unit from filename as previously
Strum355 Aug 25, 2020
41cb654
Added test case for PositionBuilder when generic type param has no sp…
Strum355 Aug 27, 2020
0739121
Merge branch 'compunit-jdt-module' into nsc/merges
Strum355 Aug 27, 2020
f15a71d
Merge branch 'generic-modifier-pos' into nsc/merges
Strum355 Aug 27, 2020
de427f5
Merge branch 'jlscorrect-fix' into nsc/merges
Strum355 Aug 27, 2020
097c68f
Added test case for PositionBuilder when generic type param has no sp…
Strum355 Aug 27, 2020
5bc2ce2
Merge branch 'generic-modifier-pos' into nsc/merges
Strum355 Aug 27, 2020
cd33472
Added null check to handle anonymous classes to TypeFactory
Strum355 Aug 27, 2020
b9f8481
Merge branch 'typefactory-anon-class' into nsc/merges
Strum355 Aug 27, 2020
e9aab13
Added test case for PositionBuilder when generic type param has no sp…
Strum355 Aug 27, 2020
8bd0810
Merge branch 'generic-modifier-pos' into nsc/merges
Strum355 Aug 27, 2020
9b50b1c
Improved source and test source directory resolving by climbing the p…
Strum355 Sep 2, 2020
a58547f
Merge branch 'better-pom' into nsc/merges
Strum355 Sep 2, 2020
4ebe590
Added GSON test case
Strum355 Sep 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ maven-eclipse.xml
!.idea/codeStyleSettings.xml
*.iml

# VSCode
.vscode

# Spoon
spooned-classes/
spooned/
Expand All @@ -31,3 +34,6 @@ doc/_jekyll/_site/

# Nodejs
node_modules/

# Gradle
.gradle
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

<artifactId>spoon-core</artifactId>
<packaging>jar</packaging>
<version>8.3.0-SNAPSHOT</version>
<version>8.4.0-SNAPSHOT</version>
<name>Spoon Core</name>
<description>Spoon is a tool for meta-programming, analysis and transformation of Java programs.</description>
<url>http://spoon.gforge.inria.fr/</url>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/spoon/reflect/factory/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,9 @@ public <T> CtType<T> get(Class<?> cl) {
} catch (Throwable e) {
throw new SpoonClassNotFoundException("cannot create shadow class: " + cl.getName(), e);
}
/* if (newShadowClass == null) {
return null;
} */
newShadowClass.setFactory(factory);
newShadowClass.accept(new CtScanner() {
@Override
Expand Down
87 changes: 78 additions & 9 deletions src/main/java/spoon/support/compiler/SpoonPom.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -82,7 +83,7 @@ public SpoonPom(String path, SpoonPom parent, MavenLauncher.SOURCE_TYPE sourceTy
if (!path.endsWith(".xml") && !path.endsWith(".pom")) {
path = Paths.get(path, "pom.xml").toString();
}
this.pomFile = new File(path);
this.pomFile = new File(path).getAbsoluteFile();
if (!pomFile.exists()) {
throw new IOException("Pom does not exists.");
}
Expand Down Expand Up @@ -131,9 +132,20 @@ public List<File> getSourceDirectories() {
sourcePath = build.getSourceDirectory();
}
if (sourcePath == null) {
sourcePath = Paths.get("src/main/java").toString();
sourcePath = getSourceDirectoryFromParent(getParentPom());
if (sourcePath == null) {
sourcePath = Paths.get("src/main/java").toString();
}
}
sourcePath = extractVariable(sourcePath);
Path path = Paths.get(sourcePath);

String absoluteSourcePath;
if (path.isAbsolute()) {
absoluteSourcePath = path.toString();
} else {
absoluteSourcePath = Paths.get(directory.getAbsolutePath(), sourcePath).toString();
}
String absoluteSourcePath = Paths.get(directory.getAbsolutePath(), sourcePath).toString();
File source = new File(absoluteSourcePath);
if (source.exists()) {
output.add(source);
Expand All @@ -148,6 +160,28 @@ public List<File> getSourceDirectories() {
return output;
}

/**
* Climbs the pom.xml hierarchy until a model is found in which
* a source directory is declared.
* @return the uninterpolated source directory declared in the nearest ancestor
*/
private String getSourceDirectoryFromParent(SpoonPom parent) {
if (parent == null) {
return null;
}
String sourcePath = null;
Build build = parent.model.getBuild();
if (build != null) {
sourcePath = build.getSourceDirectory();
if (sourcePath == null && parent.getParentPom() != null) {
return getSourceDirectoryFromParent(parent.getParentPom());
}
} else if (parent.getParentPom() != null) {
return getSourceDirectoryFromParent(parent.getParentPom());
}
return sourcePath;
}

/**
* Get the list of test directories of the project
* @return the list of test directories
Expand All @@ -161,9 +195,20 @@ public List<File> getTestDirectories() {
sourcePath = build.getTestSourceDirectory();
}
if (sourcePath == null) {
sourcePath = Paths.get("src/test/java").toString();
sourcePath = getTestSourceDirectoryFromParent(getParentPom());
if (sourcePath == null) {
sourcePath = Paths.get("src/test/java").toString();
}
}
sourcePath = extractVariable(sourcePath);
Path path = Paths.get(sourcePath);

String absoluteSourcePath;
if (path.isAbsolute()) {
absoluteSourcePath = path.toString();
} else {
absoluteSourcePath = Paths.get(directory.getAbsolutePath(), sourcePath).toString();
}
String absoluteSourcePath = Paths.get(directory.getAbsolutePath(), sourcePath).toString();
File source = new File(absoluteSourcePath);
if (source.exists()) {
output.add(source);
Expand All @@ -178,6 +223,28 @@ public List<File> getTestDirectories() {
return output;
}

/**
* Climbs the pom.xml hierarchy until a model is found in which
* a test source directory is declared.
* @return the uninterpolated test source directory declared in the nearest ancestor
*/
private String getTestSourceDirectoryFromParent(SpoonPom parent) {
if (parent == null) {
return null;
}
String sourcePath = null;
Build build = parent.model.getBuild();
if (build != null) {
sourcePath = build.getTestSourceDirectory();
if (sourcePath == null && parent.getParentPom() != null) {
return getTestSourceDirectoryFromParent(parent.getParentPom());
}
} else if (parent.getParentPom() != null) {
return getTestSourceDirectoryFromParent(parent.getParentPom());
}
return sourcePath;
}

/**
* Get the list of classpath files generated by maven
* @return the list of classpath files
Expand Down Expand Up @@ -213,29 +280,31 @@ private String extractVariable(String value) {
}

/**
* Get the value of a property
* Get the value of a property. Reference: https://maven.apache.org/ref/3.6.3/maven-model-builder/#Model_Interpolation
* @param key the key of the property
* @return the property value if key exists or null
*/
private String getProperty(String key) {
if ("project.version".equals(key) || "pom.version".equals(key)) {
if ("project.version".equals(key) || "pom.version".equals(key) || "version".equals(key)) {
if (model.getVersion() != null) {
return model.getVersion();
} else if (model.getParent() != null) {
return model.getParent().getVersion();
}
} else if ("project.groupId".equals(key) || "pom.groupId".equals(key)) {
} else if ("project.groupId".equals(key) || "pom.groupId".equals(key) || "groupId".equals(key)) {
if (model.getGroupId() != null) {
return model.getGroupId();
} else if (model.getParent() != null) {
return model.getParent().getGroupId();
}
} else if ("project.artifactId".equals(key) || "pom.artifactId".equals(key)) {
} else if ("project.artifactId".equals(key) || "pom.artifactId".equals(key) || "artifactId".equals(key)) {
if (model.getArtifactId() != null) {
return model.getArtifactId();
} else if (model.getParent() != null) {
return model.getParent().getArtifactId();
}
} else if ("project.basedir".equals(key) || "pom.basedir".equals(key) || "basedir".equals(key)) {
return pomFile.getParent();
}
String value = extractVariable(model.getProperties().getProperty(key));
if (value == null) {
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/spoon/support/compiler/jdt/JDTBatchCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,14 @@ public CompilationUnit[] getCompilationUnits() {
} else {
lastSlash += 1;
}
//TODO the module name parsed by JDK compiler is in `this.modNames`
compilationUnit.module = CharOperation.subarray(modulePath, lastSlash, modulePath.length);

if (this.module == null) {
compilationUnit.module = CharOperation.subarray(modulePath, lastSlash, modulePath.length);
} else {
//TODO the module name parsed by JDK compiler is in `this.modNames`, consider using that instead?
compilationUnit.module = this.module.name();
}

pathToModName.put(String.valueOf(modulePath), compilationUnit.module);
}
} else {
Expand Down
22 changes: 20 additions & 2 deletions src/main/java/spoon/support/compiler/jdt/PositionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package spoon.support.compiler.jdt;

import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
Expand Down Expand Up @@ -46,6 +47,7 @@
import spoon.support.compiler.jdt.ContextBuilder.CastInfo;
import spoon.support.reflect.CtExtendedModifier;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -366,7 +368,12 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) {

TypeParameter[] typeParameters = methodDeclaration.typeParameters();
if (typeParameters != null && typeParameters.length > 0) {
modifiersSourceEnd = typeParameters[0].declarationSourceStart - 3;
// if there is no space between the modifier and the type parameter vs if there is a space
if (contents[typeParameters[0].declarationSourceStart - 2] != ' ') {
modifiersSourceEnd = typeParameters[0].declarationSourceStart - 2;
} else {
modifiersSourceEnd = typeParameters[0].declarationSourceStart - 3;
}
}

if (getModifiers(methodDeclaration.modifiers, false, true).isEmpty()) {
Expand Down Expand Up @@ -577,10 +584,21 @@ private void setModifiersPosition(CtModifiable e, int start, int end) {
if (o2 == -1) {
o2 = end;
}

// this is the index into the modifier char array snippet, so must be +o1 if >-1
int chevronIndex = ArrayUtils.indexOf(Arrays.copyOfRange(contents, o1, o2), '<');
if (chevronIndex != -1) {
o2 = o1 + chevronIndex;
}

String modifierName = String.valueOf(contents, o1, o2 - o1);
CtExtendedModifier modifier = explicitModifiersByName.remove(modifierName);
if (modifier != null) {
modifier.setPosition(cf.createSourcePosition(cu, o1, o2 - 1, jdtTreeBuilder.getContextBuilder().getCompilationUnitLineSeparatorPositions()));
/* if (chevronIndex > -1) {
modifier.setPosition(cf.createSourcePosition(cu, o1, o2, jdtTreeBuilder.getContextBuilder().getCompilationUnitLineSeparatorPositions()));
} else { */
modifier.setPosition(cf.createSourcePosition(cu, o1, o2 - 1, jdtTreeBuilder.getContextBuilder().getCompilationUnitLineSeparatorPositions()));
//}
}
start = o2;
}
Expand Down
42 changes: 24 additions & 18 deletions src/main/java/spoon/support/reflect/reference/CtReferenceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,22 @@ public boolean equals(Object o) {
return false;
}
private void checkIdentiferForJLSCorrectness(String simplename) {
/*
* At the level of the Java Virtual Machine, every constructor written in the Java programming language (JLS §8.8)
* appears as an instance initialization method that has the special name <init>.
* This name is supplied by a compiler. Because the name is not a valid identifier,
* it cannot be used directly in a program written in the Java programming language.
*/
//JDTTreeBuilderHelper.computeAnonymousName returns "$numbers$Name" so we have to skip them if they start with numbers
//allow empty identifier because they are sometimes used.
/*
* At the level of the Java Virtual Machine, every constructor written in the Java programming language (JLS §8.8)
* appears as an instance initialization method that has the special name <init>.
* This name is supplied by a compiler. Because the name is not a valid identifier,
* it cannot be used directly in a program written in the Java programming language.
*/
//JDTTreeBuilderHelper.computeAnonymousName returns "$numbers$Name" so we have to skip them if they start with numbers
//allow empty identifier because they are sometimes used.
if (!simplename.matches("<.*>|\\d.*|^.{0}$")) {
//split at "<" and ">" because "Iterator<Cache.Entry<K,Store.ValueHolder<V>>>" submits setSimplename ("Cache.Entry<K")
String[] splittedSimplename = simplename.split("\\.|<|>");
if (checkAllParts(splittedSimplename)) {
throw new SpoonException("Not allowed javaletter or keyword in identifier found. See JLS for correct identifier. Identifier: " + simplename);
}
}
}
}
private boolean isKeyword(String simplename) {
return keywords.contains(simplename);
}
Expand All @@ -118,19 +118,25 @@ private boolean checkAllParts(String[] simplenameParts) {
simpleName = simpleName.replaceAll("\\[\\]|@", "");
if (isKeyword(simpleName) || checkIdentifierChars(simpleName)) {
return true;
}
}
}
return false;
return false;
}
private boolean checkIdentifierChars(String simplename) {
return (!Character.isJavaIdentifierStart(simplename.charAt(0))) || simplename.chars().anyMatch(letter -> !Character.isJavaIdentifierPart(letter));
if (simplename.length() == 0) {
return false;
}
return (!Character.isJavaIdentifierStart(simplename.charAt(0)))
|| simplename.chars().anyMatch(letter -> !Character.isJavaIdentifierPart(letter)
);
}

private static Collection<String> fillWithKeywords() {
//removed types because needed as ref: "int","short", "char", "void", "byte","float", "true","false","boolean","double","long","class", "null"
return Stream.of("abstract", "continue", "for", "new", "switch", "assert", "default", "if", "package", "synchronized", "do", "goto", "private",
"this", "break", "implements", "protected", "throw", "else", "import", "public", "throws", "case", "enum", "instanceof", "return",
"transient", "catch", "extends", "try", "final", "interface", "static", "finally", "strictfp", "volatile",
"const", "native", "super", "while", "_")
.collect(Collectors.toCollection(HashSet::new));
//removed types because needed as ref: "int","short", "char", "void", "byte","float", "true","false","boolean","double","long","class", "null"
return Stream.of("abstract", "continue", "for", "new", "switch", "assert", "default", "if", "package", "synchronized", "do", "goto", "private",
"this", "break", "implements", "protected", "throw", "else", "import", "public", "throws", "case", "enum", "instanceof", "return",
"transient", "catch", "extends", "try", "final", "interface", "static", "finally", "strictfp", "volatile",
"const", "native", "super", "while", "_")
.collect(Collectors.toCollection(HashSet::new));
}
}
Loading