diff --git a/scope4j/build.gradle b/scope4j/build.gradle index 74c3ca8b598d9fb9801d589d931b351e00ef7b25..cf6e3a0f0aec486203016ec437c1894f108d2572 100644 --- a/scope4j/build.gradle +++ b/scope4j/build.gradle @@ -23,6 +23,10 @@ idea { } } +test { + useJUnitPlatform {} +} + sourceSets.main { java { @@ -37,6 +41,10 @@ sourceSets.main { } dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2' + testCompile 'org.junit.platform:junit-platform-runner:1.4.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.4.2' } jastadd { diff --git a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java index 9ac98896cb78232b3c06eaacd43ca5058caf8dbd..333ad33a87634ace49e97261fe6d3db3c156e362 100644 --- a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java +++ b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java @@ -18,6 +18,12 @@ public class ScopeAnalysis extends Frontend { } + public Program getProgram() { + return program; + } + + private Program program; + /** * Entry point for the Java checker. * @@ -28,13 +34,24 @@ public class ScopeAnalysis extends Frontend { List<String> arguments = new ArrayList<>(Arrays.asList(args)); boolean debug = arguments.isEmpty() || arguments.remove("--debug"); + boolean tree = arguments.remove("--tree"); + boolean warnings = arguments.remove("--warnings"); if (arguments.size() > 1) { - System.out.println("usage: ScopeAnalysis [--debug] <directory with java files>"); + System.out.println("usage: ScopeAnalysis [--debug] [--tree] [--warnings] <directory with java files>"); System.exit(-1); } String path = arguments.isEmpty() ? "../testprograms/simpleScope" : arguments.get(arguments.size() - 1); + if (debug) { + new ScopeAnalysis().analyze(path, tree, warnings); + } else { + new ScopeAnalysis().analyzeTimed(path); + } + + } + + public void analyzeTimed(String path) { try { List<String> files = Files.walk(Paths.get(path)) .filter(Files::isRegularFile) @@ -43,37 +60,17 @@ public class ScopeAnalysis extends Frontend { // measure the time (with parsing) from here long startMeasurementTime = System.nanoTime(); - Program program = new ScopeAnalysis().readProgram(files); + program = readProgram(files); // measure the time (without parsing) from here long startGenerationTime = System.nanoTime(); ScopeTree scopeTree = program.scopeTree(); - if (debug) { - scopeTree.printAST(); - - System.out.println("\nExtendJ found the following problems:"); - for (CompilationUnit unit : program.getCompilationUnitList()) { - for (Problem problem : unit.problems()) { - System.out.println(problem); - } - } - System.out.println(); - } - long startAnalysisTime = System.nanoTime(); Set<VariableShadowFinding> findings = scopeTree.variableShadowings(); - if (debug) { - System.out.println("\nScope4J found the following problems:"); - for (VariableShadowFinding finding : findings) { - System.out.println(finding); - } - System.out.println(); - } - // measure the time until here long endTime = System.nanoTime(); @@ -100,6 +97,45 @@ public class ScopeAnalysis extends Frontend { } } + + public Set<VariableShadowFinding> analyze(String path, boolean tree, boolean warnings) { + try { + List<String> files = Files.walk(Paths.get(path)) + .filter(Files::isRegularFile) + .filter(x -> x.getFileName().toString().endsWith(".java")).map(Path::toString).collect(Collectors.toList()); + + program = readProgram(files); + + ScopeTree scopeTree = program.scopeTree(); + + if (tree) { + scopeTree.printAST(); + } + + if (warnings) { + System.out.println("\nExtendJ found the following problems:"); + for (CompilationUnit unit : program.getCompilationUnitList()) { + for (Problem problem : unit.problems()) { + System.out.println(problem); + } + } + System.out.println(); + } + + Set<VariableShadowFinding> findings = scopeTree.variableShadowings(); + + System.out.println("\nScope4J found the following problems:"); + for (VariableShadowFinding finding : findings) { + System.out.println(finding); + } + System.out.println(); + + return findings; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private Program readProgram(Collection<String> files) throws IOException { diff --git a/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java new file mode 100644 index 0000000000000000000000000000000000000000..10bdd1e5861f6e00273dc24e91a42a6724fbb4e6 --- /dev/null +++ b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java @@ -0,0 +1,21 @@ +package org.extendj; + +import org.extendj.ast.Declaration; +import org.extendj.ast.VariableShadowFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public abstract class ScopeAnalysisTest { + static void assertShadow(Set<VariableShadowFinding> findings, String name, int shadowerLine, int shadowedLine) { + for (VariableShadowFinding finding : findings) { + Declaration shadower = finding.getShadower(); + Declaration shadowed = finding.getShadowed(); + if (shadowed.getName().equals(name) && shadowed.lineNumber() == shadowedLine && shadower.lineNumber() == shadowerLine) { + return; + } + } + Assertions.fail("No finding found for name '" + name + "' in lines " + shadowerLine + " > " + shadowedLine); + } +} diff --git a/scope4j/src/test/java/org/extendj/SimpleScopeTest.java b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c11b50280b627dc00575411e672cffd412de697f --- /dev/null +++ b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java @@ -0,0 +1,29 @@ +package org.extendj; + +import org.extendj.ast.VariableShadowFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class SimpleScopeTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<VariableShadowFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", false, false); + + + Assertions.assertEquals(5, findings.size()); + + assertShadow(findings, "localVarA", 19, 11); + assertShadow(findings, "localVarB", 33, 12); + assertShadow(findings, "localVarC", 24, 13); + assertShadow(findings, "fieldA", 29, 3); + assertShadow(findings, "fieldB", 36, 4); + + + } + +} diff --git a/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java b/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3dd2b9e8b86dd95db63201b7c707978cfa7a30fb --- /dev/null +++ b/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java @@ -0,0 +1,26 @@ +package org.extendj; + +import org.extendj.ast.VariableShadowFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class SuperclassFieldsTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<VariableShadowFinding> findings = scopeAnalysis.analyze("src/test/resources/superclassFields", false, false); + + + assertShadow(findings, "fieldC", 19, 3); + assertShadow(findings, "fieldB", 21, 2); + assertShadow(findings, "fieldB", 2, 4); + + Assertions.assertEquals(3, findings.size()); + + } + +} diff --git a/scope4j/src/test/resources/simple/ClassA.java b/scope4j/src/test/resources/simple/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..a066a2aa6bdf14ea306e3c0c077723250ced13fb --- /dev/null +++ b/scope4j/src/test/resources/simple/ClassA.java @@ -0,0 +1,42 @@ +public abstract class ClassA { + + int fieldA; + int fieldB; + + public ClassA(int constructorParameterA) { + int localConstructorVarA = 0; + } + + public void methodNameA(int parameterA) { + int localVarA = 1; + int localVarB = 1; + int localVarC = 1; + + { + int localVarInBlockA = 2; + + // this is shadowing (and forbidden) + int localVarA = 3; + } + + class Local { + { + for (int localVarC = 0; localVarC < 10; localVarC++) System.out.println(localVarC); + } + } + + // this is shadowing (over two levels, not forbidden) + int fieldA; + + try ( + // this is forbidden + java.util.zip.ZipFile localVarB = new java.util.zip.ZipFile("zipFileName"); + + // this is okay + java.io.BufferedWriter fieldB = java.nio.file.Files.newBufferedWriter(null) + ) { /* do stuff */ } catch (java.io.IOException e) {/* do stuff */} + } + + // this does not appear as a scope (and, more importantly, the parameters are not added anywhere else) + public abstract void methodNameB(int parameterForAbstractMethodB); +} diff --git a/scope4j/src/test/resources/superclassFields/ClassA.java b/scope4j/src/test/resources/superclassFields/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..675b9360cbfcb0764893486d7dcc1946b2c10085 --- /dev/null +++ b/scope4j/src/test/resources/superclassFields/ClassA.java @@ -0,0 +1,12 @@ +public abstract class ClassA { + + int fieldA; + int fieldB; + + void m(); + + void n() { + //... + } + +} diff --git a/scope4j/src/test/resources/superclassFields/ClassB.java b/scope4j/src/test/resources/superclassFields/ClassB.java new file mode 100644 index 0000000000000000000000000000000000000000..dbd482e62aeabbb93dc24c05192314c39ee5c9a1 --- /dev/null +++ b/scope4j/src/test/resources/superclassFields/ClassB.java @@ -0,0 +1,26 @@ +public class ClassB extends ClassA { + int fieldB; + int fieldC; + + @Override + void m() { + //Overridden.. + } + + void n() { + //not overriden + } + + void n(int value) { + //polymorphic + } + + class ClassC { + int fieldC; + + public ClassC(int fieldB) { + fieldC = fieldB; + } + } + +}