diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index ed951a5..d09ae85 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -593,7 +593,7 @@ private static String getTypeErasureSignature(CallableDeclaration callableDecl) Parameter parameter = (Parameter) param; ResolvedType resolvedType = parameter.getType().resolve(); if (parameter.isVarArgs()) { - erasureParameterTypes.add(resolvedType.describe() + "[]"); + erasureParameterTypes.add(resolvedType.erasure().describe() + "[]"); } else { erasureParameterTypes.add(resolvedType.erasure().describe()); } @@ -1147,21 +1147,44 @@ private static boolean excludeSourceRoot(Path sourceRoot) { return false; } + /** + * Sets up lexical preserving printer for the given compilation unit in a safe manner by checking + * whether any node in the unit is missing ranges, which can result in exception. + * + * @param compilationUnit Compilation unit to be set with lexical preserving printer + * @return compilation unit set up with lexical preserving printer or the original compilation + * unit if the unit contains range-missing nodes + */ + private static CompilationUnit safeLexicalPreservingPrinterSetup(CompilationUnit compilationUnit) { + // setup lexical-preserving printer only if CU has no missing-range nodes + boolean hasNodeWithMissingRange = compilationUnit.findAll(Node.class).stream() + .anyMatch(n -> !n.getRange().isPresent()); + if (!hasNodeWithMissingRange) { + return LexicalPreservingPrinter.setup(compilationUnit); + } + return compilationUnit; + } + public static Pair, Map>> extractAll(Path projectRootPath) throws IOException { - SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(); + ParserConfiguration config = new ParserConfiguration() + .setStoreTokens(true) + .setAttributeComments(true) + .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); + SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(config); ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(projectRootPath); javaSymbolSolver = (JavaSymbolSolver) symbolSolverCollectionStrategy.getParserConfiguration() - .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21).getSymbolResolver().get(); + .getSymbolResolver().get(); Map symbolTable = new LinkedHashMap<>(); Map> parseProblems = new HashMap<>(); for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { if (excludeSourceRoot(sourceRoot.getRoot())) { continue; } + sourceRoot.setParserConfiguration(config); for (ParseResult parseResult : sourceRoot.tryToParse()) { if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); + CompilationUnit compilationUnit = safeLexicalPreservingPrinterSetup(parseResult.getResult().get()); symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); } else { @@ -1181,13 +1204,15 @@ public static Pair, Map>> combinedTypeSolver.add(new ReflectionTypeSolver()); ParserConfiguration parserConfiguration = new ParserConfiguration() + .setStoreTokens(true) + .setAttributeComments(true) .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); parserConfiguration.setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver)); JavaParser javaParser = new JavaParser(parserConfiguration); ParseResult parseResult = javaParser.parse(code); if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); + CompilationUnit compilationUnit = safeLexicalPreservingPrinterSetup(parseResult.getResult().get()); Log.debug("Successfully parsed code. Now processing compilation unit"); symbolTable.put("", processCompilationUnit(compilationUnit)); } else { @@ -1216,6 +1241,8 @@ public static Pair, Map>> .getSymbolResolver().get(); Log.info("Setting parser language level to JAVA_21"); ParserConfiguration parserConfiguration = new ParserConfiguration() + .setStoreTokens(true) + .setAttributeComments(true) .setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); parserConfiguration.setSymbolResolver(javaSymbolSolver); @@ -1229,7 +1256,7 @@ public static Pair, Map>> for (Path javaFilePath : javaFilePaths) { ParseResult parseResult = javaParser.parse(javaFilePath); if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = LexicalPreservingPrinter.setup(parseResult.getResult().get()); + CompilationUnit compilationUnit = safeLexicalPreservingPrinterSetup(parseResult.getResult().get()); System.out.println("Successfully parsed file: " + javaFilePath.toString()); symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), processCompilationUnit(compilationUnit)); diff --git a/src/test/java/com/ibm/cldk/SymbolTableTest.java b/src/test/java/com/ibm/cldk/SymbolTableTest.java index 5cd3510..826443a 100644 --- a/src/test/java/com/ibm/cldk/SymbolTableTest.java +++ b/src/test/java/com/ibm/cldk/SymbolTableTest.java @@ -26,7 +26,7 @@ private String getJavaCodeForTestResource(String resourcePath) { } @Test - public void testExtractSingleGenricsDuplicateSignature() throws IOException { + public void testExtractSingleGenricsDuplicateSignature_Validate() throws IOException { String javaCode = getJavaCodeForTestResource("test-applications/generics-varargs-duplicate-signature-test/Validate.java"); Map symbolTable = SymbolTable.extractSingle(javaCode).getLeft(); Assertions.assertEquals(1, symbolTable.size()); @@ -36,6 +36,26 @@ public void testExtractSingleGenricsDuplicateSignature() throws IOException { Assertions.assertEquals(17, callables.size()); } + @Test + public void testExtractSingleGenricsDuplicateSignature_FunctorUtils() throws IOException { + String javaCode = getJavaCodeForTestResource("test-applications/generics-varargs-duplicate-signature-test/FunctorUtils.java"); + Map symbolTable = SymbolTable.extractSingle(javaCode).getLeft(); + Assertions.assertEquals(1, symbolTable.size()); + Map typeDeclaration = symbolTable.values().iterator().next().getTypeDeclarations(); + Assertions.assertEquals(1, typeDeclaration.size()); + Map callables = typeDeclaration.values().iterator().next().getCallableDeclarations(); + Assertions.assertEquals(10, callables.size()); + } + + @Test + public void testExtractSingleMissingNodeRange() throws IOException { + String javaCode = getJavaCodeForTestResource("test-applications/missing-node-range-test/WeakHashtableTestCase.java"); + Map symbolTable = SymbolTable.extractSingle(javaCode).getLeft(); + Assertions.assertEquals(1, symbolTable.size()); + Map typeDeclaration = symbolTable.values().iterator().next().getTypeDeclarations(); + Assertions.assertEquals(2, typeDeclaration.size()); + } + @Test public void testCallSiteArgumentExpression() throws IOException { String javaCode = getJavaCodeForTestResource("test-applications/generics-varargs-duplicate-signature-test/Validate.java"); diff --git a/src/test/resources/test-applications/generics-varargs-duplicate-signature-test/FunctorUtils.java b/src/test/resources/test-applications/generics-varargs-duplicate-signature-test/FunctorUtils.java new file mode 100644 index 0000000..dc52e08 --- /dev/null +++ b/src/test/resources/test-applications/generics-varargs-duplicate-signature-test/FunctorUtils.java @@ -0,0 +1,76 @@ +import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.commons.collections4.Predicate; + +final class FunctorUtils { + + private static T[] clone(final T... array) { + return array != null ? array.clone() : null; + } + + static , P extends java.util.function.Predicate, T> R coerce(final P predicate) { + return (R) predicate; + } + + static , P extends Function, I, O> R coerce(final P transformer) { + return (R) transformer; + } + + static > T[] copy(final T... consumers) { + return clone(consumers); + } + + static > T[] copy(final T... predicates) { + return clone(predicates); + } + + static > T[] copy(final T... transformers) { + return clone(transformers); + } + + static Predicate[] validate(final Collection> predicates) { + Objects.requireNonNull(predicates, "predicates"); + // convert to array like this to guarantee iterator() ordering + @SuppressWarnings("unchecked") // OK + final Predicate[] preds = new Predicate[predicates.size()]; + int i = 0; + for (final java.util.function.Predicate predicate : predicates) { + preds[i] = (Predicate) predicate; + if (preds[i] == null) { + throw new NullPointerException("predicates[" + i + "]"); + } + i++; + } + return preds; + } + + static void validate(final Consumer... consumers) { + Objects.requireNonNull(consumers, "consumers"); + for (int i = 0; i < consumers.length; i++) { + if (consumers[i] == null) { + throw new NullPointerException("closures[" + i + "]"); + } + } + } + + static void validate(final Function... functions) { + Objects.requireNonNull(functions, "functions"); + for (int i = 0; i < functions.length; i++) { + if (functions[i] == null) { + throw new NullPointerException("functions[" + i + "]"); + } + } + } + + static void validate(final java.util.function.Predicate... predicates) { + Objects.requireNonNull(predicates, "predicates"); + for (int i = 0; i < predicates.length; i++) { + if (predicates[i] == null) { + throw new NullPointerException("predicates[" + i + "]"); + } + } + } + +} diff --git a/src/test/resources/test-applications/missing-node-range-test/WeakHashtableTestCase.java b/src/test/resources/test-applications/missing-node-range-test/WeakHashtableTestCase.java new file mode 100644 index 0000000..4781ea5 --- /dev/null +++ b/src/test/resources/test-applications/missing-node-range-test/WeakHashtableTestCase.java @@ -0,0 +1,36 @@ +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import junit.framework.TestCase; + +public class WeakHashtableTestCase extends TestCase { + + public static class TestThread extends Thread { + + public TestThread(final String name) { + super(name); + } + + @Override + public void run() { + for (int i = 0; i < RUN_LOOPS; i++) { + hashtable.put("key:" + i % 10, Boolean.TRUE); + if (i % 50 == 0) { + yield(); + } + } + } + } + private static final int RUN_LOOPS = 3000; + private static WeakHashtable hashtable; + + public WeakHashtableTestCase(final String testName) { + super(testName); + } + +}