diff --git a/scope/src/main/jastadd/Shadowing.jadd b/scope/src/main/jastadd/Shadowing.jadd index 7f05f3e84d8d21841274cf9ada6d406c365d5ac1..08b0f586d5fb57c70c2fcf9124fed83d6f961fc9 100644 --- a/scope/src/main/jastadd/Shadowing.jadd +++ b/scope/src/main/jastadd/Shadowing.jadd @@ -1,5 +1,7 @@ aspect Shadowing { - public class VariableShadowFinding { + public abstract class AbstractFinding {} + + public class VariableShadowFinding extends AbstractFinding { private Declaration shadower, shadowed; @@ -24,4 +26,20 @@ aspect Shadowing { } } } +public class MultipleDeclarationFinding extends AbstractFinding { + + private Declaration declaration; + + public MultipleDeclarationFinding(Declaration declaration) { + this.declaration = declaration; + } + + public Declaration getDeclaration() { + return declaration; + } + + public String toString() { + return declaration.sourceFile() + ": declaration '" + declaration + "' (l." + declaration.lineNumber() + ") is not unique in its scope!"; + } + } } diff --git a/scope/src/main/jastadd/Shadowing.jrag b/scope/src/main/jastadd/Shadowing.jrag index 4d2015810632aa05cc2637a7cd8ff5370c3214a2..3ca64528596533602746ca50763fdab9af416a3d 100644 --- a/scope/src/main/jastadd/Shadowing.jrag +++ b/scope/src/main/jastadd/Shadowing.jrag @@ -1,9 +1,11 @@ aspect Shadowing { - coll Set<VariableShadowFinding> ScopeTree.variableShadowings() [new HashSet<>()] with add root ScopeTree; + coll Set<AbstractFinding> ScopeTree.variableShadowings() [new HashSet<>()] with add root ScopeTree; Declaration contributes new VariableShadowFinding(shadowed(), this) when isShadowing() to ScopeTree.variableShadowings(); + Declaration contributes new MultipleDeclarationFinding(this) when isShadowingInSameScope() to ScopeTree.variableShadowings(); - syn Declaration Declaration.shadowed() = shadowed(this); + inh Declaration Declaration.shadowed(); + eq Scope.getElement(int i).shadowed() = shadowed(getElement(i).asDeclaration()); inh Declaration Element.shadowed(Declaration shadower); eq ScopeTree.getElement().shadowed(Declaration shadower) { @@ -23,7 +25,19 @@ aspect Shadowing { return shadowed(shadower); } + inh Declaration Declaration.shadowedInSameScope(); + eq Scope.getElement(int i).shadowedInSameScope() { + Declaration declaration = getElement(i).asDeclaration(); + for (Element otherElement : getElementList()) { + if (otherElement != declaration && otherElement.isDeclaration() && otherElement.asDeclaration().getName().equals(declaration.getName())) { + return otherElement.asDeclaration(); + } + } + return null; + } + syn boolean Declaration.isShadowing() = shadowed() != null; + syn boolean Declaration.isShadowingInSameScope() = shadowedInSameScope() != null; diff --git a/scope4j/src/main/jastadd/ProgramToScopeTree.jrag b/scope4j/src/main/jastadd/ProgramToScopeTree.jrag index 6a155b9845b353fa9faf03d6824af5aff96c685c..c0aa6077129c779861bd24eb60b25765079a69b7 100644 --- a/scope4j/src/main/jastadd/ProgramToScopeTree.jrag +++ b/scope4j/src/main/jastadd/ProgramToScopeTree.jrag @@ -23,6 +23,26 @@ aspect ProgramToScopeTree { Declarator contributes asDeclaration() to ASTNode.scope() for containingScope(); ParameterDeclaration contributes asDeclaration() to ASTNode.scope() for containingScope(); + Collection<Declaration> ClassDecl.createSuperClassFieldDeclarators() { + ArrayList<Declaration> result = new ArrayList<>(); + TypeDecl supertype = superclass(); + + System.out.println("supertype " + supertype.getID()); + while (supertype.isClassDecl() && supertype != unknownType()) { + for (BodyDecl bodyDecl : supertype.getBodyDeclList()) { + if (bodyDecl instanceof FieldDecl) { + for (FieldDeclarator declarator : ((FieldDecl)bodyDecl).getDeclaratorList()) { + JavaDeclaration declaration = new JavaDeclaration(declarator.getID()); + declaration.setDeclarator(declarator); + result.add(declaration); + } + } + } + supertype = ((ClassDecl)supertype).superclass(); + } + return result; + } + /** fallback attribute to ensure every AST element could pontentially be a scope */ syn Scope ASTNode.asScope() { throw new RuntimeException("unable to create a scope for type " + getClass().getSimpleName()); @@ -31,6 +51,11 @@ aspect ProgramToScopeTree { syn lazy TypeDeclScope TypeDecl.asScope() { TypeDeclScope scope = new TypeDeclScope(); scope.setTypeDecl(this); + if (isClassDecl()) { + for (Declaration declaration : ((ClassDecl)this).createSuperClassFieldDeclarators()) { + scope.addElement(declaration); + } + } return scope; } diff --git a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java index 333ad33a87634ace49e97261fe6d3db3c156e362..4409ba34b48dd9fc47d1c27aaafd82619ebe8d15 100644 --- a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java +++ b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java @@ -69,7 +69,7 @@ public class ScopeAnalysis extends Frontend { long startAnalysisTime = System.nanoTime(); - Set<VariableShadowFinding> findings = scopeTree.variableShadowings(); + Set<AbstractFinding> findings = scopeTree.variableShadowings(); // measure the time until here long endTime = System.nanoTime(); @@ -98,7 +98,7 @@ public class ScopeAnalysis extends Frontend { } - public Set<VariableShadowFinding> analyze(String path, boolean tree, boolean warnings) { + public Set<AbstractFinding> analyze(String path, boolean tree, boolean warnings) { try { List<String> files = Files.walk(Paths.get(path)) .filter(Files::isRegularFile) @@ -122,10 +122,10 @@ public class ScopeAnalysis extends Frontend { System.out.println(); } - Set<VariableShadowFinding> findings = scopeTree.variableShadowings(); + Set<AbstractFinding> findings = scopeTree.variableShadowings(); System.out.println("\nScope4J found the following problems:"); - for (VariableShadowFinding finding : findings) { + for (AbstractFinding finding : findings) { System.out.println(finding); } System.out.println(); diff --git a/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java index 10bdd1e5861f6e00273dc24e91a42a6724fbb4e6..95d479ce7b90b3d7deb7684ea36c07c3dba1777a 100644 --- a/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java +++ b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java @@ -1,21 +1,36 @@ package org.extendj; +import org.extendj.ast.AbstractFinding; import org.extendj.ast.Declaration; +import org.extendj.ast.MultipleDeclarationFinding; 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; + static void assertShadow(Set<AbstractFinding> findings, String name, int shadowerLine, int shadowedLine) { + for (AbstractFinding finding : findings) { + if (finding instanceof VariableShadowFinding) { + Declaration shadower = ((VariableShadowFinding)finding).getShadower(); + Declaration shadowed = ((VariableShadowFinding)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); + Assertions.fail("No shadow finding found for name '" + name + "' in lines " + shadowerLine + " > " + shadowedLine); + } + + static void assertRedefinition(Set<AbstractFinding> findings, String name, int declLine) { + for (AbstractFinding finding : findings) { + if (finding instanceof MultipleDeclarationFinding) { + Declaration declaration = ((MultipleDeclarationFinding)finding).getDeclaration(); + if (declaration.getName().equals(name) && declaration.lineNumber() == declLine) { + return; + } + } + } + Assertions.fail("No multi-decl finding found for name '" + name + "' in line " + declLine); } } diff --git a/scope4j/src/test/java/org/extendj/SimpleScopeTest.java b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java index c11b50280b627dc00575411e672cffd412de697f..2bb7111bfc3b5b754ce4d9c1012226713f5a0e9d 100644 --- a/scope4j/src/test/java/org/extendj/SimpleScopeTest.java +++ b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java @@ -1,6 +1,6 @@ package org.extendj; -import org.extendj.ast.VariableShadowFinding; +import org.extendj.ast.AbstractFinding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ public class SimpleScopeTest extends ScopeAnalysisTest { void test() { ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); - Set<VariableShadowFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", false, false); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", false, false); Assertions.assertEquals(5, findings.size()); diff --git a/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java b/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java index 3dd2b9e8b86dd95db63201b7c707978cfa7a30fb..e32199d181284dd4a6528ac93ae15d76e04180b2 100644 --- a/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java +++ b/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java @@ -1,5 +1,6 @@ package org.extendj; +import org.extendj.ast.AbstractFinding; import org.extendj.ast.VariableShadowFinding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -12,14 +13,15 @@ public class SuperclassFieldsTest extends ScopeAnalysisTest { void test() { ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); - Set<VariableShadowFinding> findings = scopeAnalysis.analyze("src/test/resources/superclassFields", false, false); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/superclassFields", true, false); assertShadow(findings, "fieldC", 19, 3); - assertShadow(findings, "fieldB", 21, 2); - assertShadow(findings, "fieldB", 2, 4); + assertShadow(findings, "fieldB", 21, 4); + assertRedefinition(findings, "fieldB", 2); + assertRedefinition(findings, "fieldB", 4); - Assertions.assertEquals(3, findings.size()); + Assertions.assertEquals(4, findings.size()); }