Skip to content

Commit

Permalink
Merge pull request javaparser#4477 from jlerbsc/master
Browse files Browse the repository at this point in the history
Fix: issue 4450 Endless recursion (-> StackOverflow) with cyclic static references
  • Loading branch information
jlerbsc authored Jun 21, 2024
2 parents 24d439f + 557198b commit 19cf5ef
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

package com.github.javaparser.symbolsolver.javaparsermodel.contexts;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
Expand All @@ -36,9 +41,6 @@
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration;
import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration;
import com.github.javaparser.symbolsolver.resolution.SymbolSolver;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
* @author Federico Tomassetti
Expand All @@ -47,6 +49,12 @@ public class CompilationUnitContext extends AbstractJavaParserContext<Compilatio

private static final String DEFAULT_PACKAGE = "java.lang";

// Contains the names of static import declarations with asterisks that have
// already been resolved. The aim is to keep a history of name searches for the
// same resolution attempt in order to avoid a recursive issue leading to a
// stackoverflow exception. See issues 4450 & 2720
private static ThreadLocal<List<String>> resolvedStaticImport = ThreadLocal.withInitial(() -> new ArrayList<String>());

///
/// Static methods
///
Expand Down Expand Up @@ -83,16 +91,20 @@ public SymbolReference<? extends ResolvedValueDeclaration> solveSymbol(String na
if (importDecl.isStatic()) {
if (importDecl.isAsterisk()) {
String qName = importDecl.getNameAsString();
ResolvedTypeDeclaration importedType = typeSolver.solveType(qName);

// avoid infinite recursion
if (!isAncestorOf(importedType)) {
SymbolReference<? extends ResolvedValueDeclaration> ref =
new SymbolSolver(typeSolver).solveSymbolInType(importedType, name);
if (ref.isSolved()) {
return ref;
}
}
// Try to resolve the name in from declarations imported with asterisks only if
// they have not already been analysed, otherwise this can lead to an infinite
// loop via circular dependencies.
if (!isAlreadyResolved(qName)) {
resolvedStaticImport.get().add(qName);
ResolvedTypeDeclaration importedType = typeSolver.solveType(qName);

SymbolReference<? extends ResolvedValueDeclaration> ref = new SymbolSolver(typeSolver)
.solveSymbolInType(importedType, name);
if (ref.isSolved()) {
resolvedStaticImport.remove(); // clear the search history
return ref;
}
}
} else {
String whole = importDecl.getNameAsString();

Expand All @@ -108,9 +120,16 @@ public SymbolReference<? extends ResolvedValueDeclaration> solveSymbol(String na
}
}

// Clear of the search history because we don't want this context to be reused
// in another search.
resolvedStaticImport.remove();
return SymbolReference.unsolved();
}

private boolean isAlreadyResolved(String qName) {
return resolvedStaticImport.get().contains(qName);
}

@Override
public SymbolReference<ResolvedTypeDeclaration> solveType(String name, List<ResolvedType> typeArguments) {

Expand Down Expand Up @@ -306,20 +325,16 @@ public SymbolReference<ResolvedMethodDeclaration> solveMethod(
&& this.wrappedNode.getTypes().stream()
.anyMatch(it -> it.getName().getIdentifier().equals(toSimpleName(importString)))) {
// We are using a static import on a type defined in this file. It means the value was not found
// at
// a lower level so this will fail
// at a lower level so this will fail
return SymbolReference.unsolved();
}

ResolvedTypeDeclaration ref = typeSolver.solveType(importString);
// avoid infinite recursion
if (!isAncestorOf(ref)) {
SymbolReference<ResolvedMethodDeclaration> method =
MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true);
if (method.isSolved()) {
return method;
}
}
ResolvedTypeDeclaration ref = typeSolver.solveType(importString);
SymbolReference<ResolvedMethodDeclaration> method = MethodResolutionLogic.solveMethodInType(ref,
name, argumentsTypes, true);
if (method.isSolved()) {
return method;
}
} else {
String qName = importDecl.getNameAsString();

Expand Down Expand Up @@ -382,7 +397,4 @@ private String getMember(String qName) {
return memberName;
}

private boolean isAncestorOf(ResolvedTypeDeclaration descendant) {
return descendant.toAst().filter(node -> wrappedNode.isAncestorOf(node)).isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ public boolean isAssignableBy(ResolvedType type) {
}

/**
* This method is deprecated because it receives the TypesSolver as a parameter.
* Eventually we would like to remove all usages of TypeSolver as a parameter.
*
* Also, resolution should move out of declarations, so that they are pure declarations and the resolution should
* Resolution should move out of declarations, so that they are pure declarations and the resolution should
* work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three
* implementations.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2013-2024 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/

package com.github.javaparser.symbolsolver;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;

import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;

public class Issue4450Test extends AbstractSymbolResolutionTest {

@Test
public void test() throws IOException {
ParserConfiguration config = new ParserConfiguration();
Path issueResourcesPath = adaptPath("src/test/resources/issue4450");
JavaParserTypeSolver jpts = new JavaParserTypeSolver(issueResourcesPath);
CombinedTypeSolver cts = new CombinedTypeSolver();
cts.add(new ReflectionTypeSolver(false));
cts.add(jpts);
config.setSymbolResolver(new JavaSymbolSolver(cts));
StaticJavaParser.setConfiguration(config);
StaticJavaParser.setConfiguration(config);
CompilationUnit cu = StaticJavaParser.parse(issueResourcesPath.resolve("a/RefCycleClass.java"));

// We shouldn't throw a mismatched symbol
assertDoesNotThrow(() -> cu.findAll(NameExpr.class).stream()
.map(NameExpr::resolve)
.findAny().get());

cu.findAll(NameExpr.class).forEach(expr -> {
expr.resolve().getName();
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package a;

public class RefClass2 {
public static Runnable unknownName;
public Runnable nonStatic;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package a;

import static a.RefCycleEnum.*;
import static a.RefClass2.*;

public class RefCycleClass {

public static int value1Value = 1;

private RefCycleEnum theEnum;

public void runMe() {
unknownName.run();
RefCycleEnum myEnum = Value1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package a;

import static a.RefClass2.*;

public class RefCycleClassFailure {
public void runMe() {
nonStatic.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package a;

import static a.RefCycleClass.*;

public enum RefCycleEnum {
Value1(value1Value);

private RefCycleEnum(int value) {
Underlying = value;
}

public final int Underlying;
}

0 comments on commit 19cf5ef

Please sign in to comment.