diff --git a/gradle.properties b/gradle.properties index d8cb228c..f3105c6f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=1.0.8 +version=1.0.9 diff --git a/src/main/java/com/ibm/cldk/SymbolTable.java b/src/main/java/com/ibm/cldk/SymbolTable.java index 7abe086d..32e68694 100644 --- a/src/main/java/com/ibm/cldk/SymbolTable.java +++ b/src/main/java/com/ibm/cldk/SymbolTable.java @@ -10,7 +10,7 @@ import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.nodeTypes.NodeWithName; -import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.*; import com.github.javaparser.ast.type.ReferenceType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; @@ -273,7 +273,9 @@ private static Pair processCallableDeclaration(CallableDeclara // add the complete declaration string, including modifiers, throws, and // parameter names - callableNode.setDeclaration(callableDecl.getDeclarationAsString(true, true, true).strip()); + callableNode.setDeclaration(callableDecl + .getDeclarationAsString(true, true, true) + .strip().replaceAll("//.*\n", "")); // add information about callable parameters: for each parameter, type, name, // annotations, @@ -301,11 +303,32 @@ private static Pair processCallableDeclaration(CallableDeclara callableNode.setAccessedFields(getAccessedFields(body, classFields, typeName)); callableNode.setCallSites(getCallSites(body)); callableNode.setVariableDeclarations(getVariableDeclarations(body)); + callableNode.setCyclomaticComplexity(getCyclomaticComplexity(callableDecl)); String callableSignature = (callableDecl instanceof MethodDeclaration) ? callableDecl.getSignature().asString() : callableDecl.getSignature().asString().replace(callableDecl.getSignature().getName(), ""); return Pair.of(callableSignature, callableNode); } + /** + * Computes cyclomatic complexity for the given callable. + * + * @param callableDeclaration Callable to compute cyclomatic complexity for + * @return cyclomatic complexity + */ + private static int getCyclomaticComplexity(CallableDeclaration callableDeclaration) { + int ifStmtCount = callableDeclaration.findAll(IfStmt.class).size(); + int loopStmtCount = callableDeclaration.findAll(DoStmt.class).size() + + callableDeclaration.findAll(ForStmt.class).size() + + callableDeclaration.findAll(ForEachStmt.class).size() + + callableDeclaration.findAll(WhileStmt.class).size(); + int switchCaseCount = callableDeclaration.findAll(SwitchStmt.class).stream() + .map(stmt -> stmt.getEntries().size()) + .reduce(0, Integer::sum); + int conditionalExprCount = callableDeclaration.findAll(ConditionalExpr.class).size(); + int catchClauseCount = callableDeclaration.findAll(CatchClause.class).size(); + return ifStmtCount + loopStmtCount + switchCaseCount + conditionalExprCount + catchClauseCount + 1; + } + /** * Processes the given field declaration to extract information about the * declared field and diff --git a/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java b/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java index 89bc0f80..a9e3e8f3 100644 --- a/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java +++ b/src/main/java/com/ibm/cldk/utils/AnalysisUtils.java @@ -23,6 +23,7 @@ import com.ibm.wala.ipa.callgraph.impl.DefaultEntrypoint; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.ssa.IR; +import com.ibm.wala.ssa.ISSABasicBlock; import com.ibm.wala.ssa.SSAConditionalBranchInstruction; import com.ibm.wala.ssa.SSASwitchInstruction; import com.ibm.wala.types.ClassLoaderReference; @@ -96,7 +97,11 @@ public static int getCyclomaticComplexity(IR ir) { int switchBranchCount = Arrays.stream(ir.getInstructions()) .filter(inst -> inst instanceof SSASwitchInstruction) .map(inst -> ((SSASwitchInstruction) inst).getCasesAndLabels().length).reduce(0, Integer::sum); - return conditionalBranchCount + switchBranchCount + 1; + Iterable iterableBasicBlocks = ir::getBlocks; + int catchBlockCount = (int) StreamSupport.stream(iterableBasicBlocks.spliterator(), false) + .filter(ISSABasicBlock::isCatchBlock) + .count(); + return conditionalBranchCount + switchBranchCount + catchBlockCount + 1; } public static Pair getCallableFromSymbolTable(IMethod method) { diff --git a/src/main/java/com/ibm/cldk/utils/BuildProject.java b/src/main/java/com/ibm/cldk/utils/BuildProject.java index bd808b3e..917d2cf0 100644 --- a/src/main/java/com/ibm/cldk/utils/BuildProject.java +++ b/src/main/java/com/ibm/cldk/utils/BuildProject.java @@ -9,6 +9,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -54,7 +55,7 @@ private static String getGradleCmd() { String gradleWrapperExists = new File(projectRootPom, gradleWrapper).exists() ? "true" : "false"; if (new File(projectRootPom, gradleWrapper).exists()) { - GRADLE_CMD = gradleWrapper; + GRADLE_CMD = String.valueOf(new File(projectRootPom, gradleWrapper)); } else { GRADLE_CMD = gradle; } @@ -88,19 +89,44 @@ private static String getGradleCmd() { " }\n" + "}"; - private static boolean commandExists(String command) { + private static AbstractMap.SimpleEntry commandExists(String command) { + StringBuilder output = new StringBuilder(); try { - Process process = new ProcessBuilder(command, "--version").start(); + Process process = new ProcessBuilder().directory(new File(projectRootPom)).command(command, "--version").start(); + // Read the output stream + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()) + ); + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + + // Read the error stream + BufferedReader errorReader = new BufferedReader( + new InputStreamReader(process.getErrorStream()) + ); + while ((line = errorReader.readLine()) != null) { + output.append(line).append("\n"); + } + + int exitCode = process.waitFor(); - return exitCode == 0; + return new AbstractMap.SimpleEntry<>( + exitCode == 0, + output.toString().trim() + ); } catch (IOException | InterruptedException exceptions) { - return false; + return new AbstractMap.SimpleEntry<>( + false, + exceptions.getMessage() + ); } } private static boolean buildWithTool(String[] buildCommand) { Log.info("Building the project using " + buildCommand[0] + "."); - ProcessBuilder processBuilder = new ProcessBuilder(buildCommand); + ProcessBuilder processBuilder = new ProcessBuilder().directory(new File(projectRootPom)).command(buildCommand); try { Process process = processBuilder.start(); @@ -125,7 +151,7 @@ private static boolean buildWithTool(String[] buildCommand) { * @return true if Maven is installed, false otherwise. */ private static boolean isMavenInstalled() { - ProcessBuilder processBuilder = new ProcessBuilder(MAVEN_CMD, "--version"); + ProcessBuilder processBuilder = new ProcessBuilder().directory(new File(projectRootPom)).command(MAVEN_CMD, "--version"); try { Process process = processBuilder.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); @@ -227,8 +253,9 @@ public static boolean downloadLibraryDependencies(String projectPath, String pro File pomFile = new File(projectRoot, "pom.xml"); if (pomFile.exists()) { Log.info("Found pom.xml in the project directory. Using Maven to download dependencies."); - if (!commandExists(MAVEN_CMD)) - throw new IllegalStateException("Could not find a valid maven command. I did not find " + MAVEN_CMD + " in the project directory or in the system PATH."); + AbstractMap.SimpleEntry mavenCheck = commandExists(MAVEN_CMD); + if (!mavenCheck.getKey()) + throw new IllegalStateException("Unable to execute Maven command. Attempt failed with message\n" + mavenCheck.getValue()); String[] mavenCommand = { MAVEN_CMD, "--no-transfer-progress", "-f", @@ -239,8 +266,9 @@ public static boolean downloadLibraryDependencies(String projectPath, String pro return buildWithTool(mavenCommand); } else if (new File(projectRoot, "build.gradle").exists() || new File(projectRoot, "build.gradle.kts").exists()) { Log.info("Found build.gradle or build.gradle.kts in the project directory. Using gradle to download dependencies."); - if (!commandExists(GRADLE_CMD)) - throw new IllegalStateException("Could not find a valid Gradle command. I did not find " + GRADLE_CMD + " in the project directory or in the system PATH."); + AbstractMap.SimpleEntry gradleCheck = commandExists(GRADLE_CMD); + if (!gradleCheck.getKey()) + throw new IllegalStateException("Could not execute Gradle command. Attempt failed with message\n" + gradleCheck.getValue()); Log.info("Found build.gradle[.kts] in the project directory. Using Gradle to download dependencies."); tempInitScript = Files.writeString(tempInitScript, GRADLE_DEPENDENCIES_TASK);