diff --git a/scope/src/main/jastadd/ScopeTree.relast b/scope/src/main/jastadd/ScopeTree.relast index 8ed72413754ad3fc998ac9d0e6e0f6a0caa4e435..3da708098f1cdcc38ff9eb5e5470f2242883970c 100644 --- a/scope/src/main/jastadd/ScopeTree.relast +++ b/scope/src/main/jastadd/ScopeTree.relast @@ -2,3 +2,5 @@ ScopeTree : Scope; abstract Element; Declaration:Element ::= <Name:String>; Scope:Element ::= Element*; + +rel Scope.inheritedScope* -> Scope; diff --git a/scope/src/main/jastadd/Shadowing.jrag b/scope/src/main/jastadd/Shadowing.jrag index 3ca64528596533602746ca50763fdab9af416a3d..8bd777b431711d498044c13495e64978e5029b21 100644 --- a/scope/src/main/jastadd/Shadowing.jrag +++ b/scope/src/main/jastadd/Shadowing.jrag @@ -4,25 +4,26 @@ aspect Shadowing { Declaration contributes new VariableShadowFinding(shadowed(), this) when isShadowing() to ScopeTree.variableShadowings(); Declaration contributes new MultipleDeclarationFinding(this) when isShadowingInSameScope() to ScopeTree.variableShadowings(); - inh Declaration Declaration.shadowed(); - eq Scope.getElement(int i).shadowed() = shadowed(getElement(i).asDeclaration()); + syn Declaration Declaration.shadowed()= shadowed(asDeclaration()); inh Declaration Element.shadowed(Declaration shadower); - eq ScopeTree.getElement().shadowed(Declaration shadower) { + eq Scope.getElement().shadowed(Declaration shadower) = shadowedLocally(shadower); + + syn Declaration Scope.shadowedLocally(Declaration shadower) { + // first look in the current scope for (Declaration declaration : declarations()) { if (declaration != shadower && declaration.getName().equals(shadower.getName())) { return declaration; } } - return null; - } - eq Scope.getElement().shadowed(Declaration shadower) { - for (Declaration declaration : declarations()) { - if (declaration != shadower && declaration.getName().equals(shadower.getName())) { - return declaration; + // the look in the inherited scopes + for (Scope inherited : getInheritedScopeList()) { + Declaration shadowed = inherited.shadowedLocally(shadower); + if (shadowed != null) { + return shadowed; } } - return shadowed(shadower); + return (this instanceof ScopeTree) ? null : shadowed(shadower); } inh Declaration Declaration.shadowedInSameScope(); @@ -39,8 +40,6 @@ aspect Shadowing { syn boolean Declaration.isShadowing() = shadowed() != null; syn boolean Declaration.isShadowingInSameScope() = shadowedInSameScope() != null; - - } aspect Statictics { diff --git a/scope4j/src/main/jastadd/ProgramToScopeTree.jrag b/scope4j/src/main/jastadd/ProgramToScopeTree.jrag index c0aa6077129c779861bd24eb60b25765079a69b7..05b0ea10e0ac4f9eecbb2eff28c7c26ad21cbbb4 100644 --- a/scope4j/src/main/jastadd/ProgramToScopeTree.jrag +++ b/scope4j/src/main/jastadd/ProgramToScopeTree.jrag @@ -1,46 +1,72 @@ aspect ProgramToScopeTree { + /** a relational nta collection attribute to compute the scope tree */ syn lazy ScopeTree Program.scopeTree() { - ScopeTree tree = new ScopeTree(); - tree.setProgram(this); + ScopeTree tree = asScopeTree(); + + // add all classes + for (CompilationUnit cu : getCompilationUnitList()) { + for (TypeDecl typeDecl : cu.getTypeDeclList()) { + tree.addElement(typeDecl.isClassDecl() ? ((ClassDecl)typeDecl).protectedScope() : typeDecl.scope()); + } + } - for (CompilationUnit compilationUnit : getCompilationUnitList()) - for (TypeDecl typeDecl : compilationUnit.getTypeDeclList()) - tree.addElement(typeDecl.scope()); + tree.updateInheritance(); // traverse the tree and add all inheritance relations return tree; } - /** a relational nta collection attribute to compute the scope tree */ + /** helper method to add inheritance relations */ + public void Scope.updateInheritance() { + for (Element element : getElementList()) { + if (element.isScope()) { + element.asScope().updateInheritance(); + } + } + } + + public void ProtectedClassDeclScope.updateInheritance() { + if (getTypeDecl().isClassDecl()) { + ClassDecl classDecl = (ClassDecl)getTypeDecl(); + if(classDecl.superclass().isClassDecl() && classDecl.superclass().compilationUnit().fromSource()) { + addInheritedScope(((ClassDecl)classDecl.superclass()).asPackageScope()); + } + } + super.updateInheritance(); + } + + /** a relational nta collection attribute to compute scopes */ coll Scope ASTNode.scope() [asScope()] with addElement root Program; + /** a relational nta collection attribute to compute a special scope containing visible fields and subtypes */ + coll TypeDeclScope ClassDecl.protectedScope() [asProtectedScope()] with addElement root Program; + coll TypeDeclScope ClassDecl.packageScope() [asPackageScope()] with addElement root Program; + // collect all scopes - TypeDecl contributes scope() to ASTNode.scope() for containingScope(); + TypeDecl contributes scope() when !isNestedType() && !isClassDecl() to ASTNode.scope() for containingScope(); + ClassDecl contributes protectedScope() when isNestedType() to ASTNode.scope() for containingScope(); Block contributes scope() to ASTNode.scope() for containingScope(); ForStmt contributes scope() when !(getStmt() instanceof Block) to ASTNode.scope() for containingScope(); EnhancedForStmt contributes scope() when !(getStmt() instanceof Block) to ASTNode.scope() for containingScope(); // collect all elements - Declarator contributes asDeclaration() to ASTNode.scope() for containingScope(); + Declarator contributes asDeclaration() when !isField() || isPrivate() to ASTNode.scope() for containingScope(); + // field which is neither private, protected, nor public -> package-private scope + Declarator contributes asDeclaration() when isField() && !isPrivate() && !(isProtected() || isPublic()) to ClassDecl.packageScope() for containingScope(); + // field which is either protected or public -> protected scope + Declarator contributes asDeclaration() when isField() && !isPrivate() && (isProtected() || isPublic()) to ClassDecl.protectedScope() 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; +/** + * aspect containing helper methods to construct (mostly empty) AST nodes of the scope tree + * There are few parts added manually, but stubs could easily be generated from the mapping grammar + */ +aspect ScopeTreeConstructors { + + syn lazy ScopeTree Program.asScopeTree() { + ScopeTree tree = new ScopeTree(); + tree.setProgram(this); + return tree; } /** fallback attribute to ensure every AST element could pontentially be a scope */ @@ -51,11 +77,26 @@ 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; + } + + syn lazy TypeDeclScope ClassDecl.asScope() { + TypeDeclScope scope = new PrivateClassDeclScope(); + scope.setTypeDecl(this); + return scope; + } + + syn lazy TypeDeclScope ClassDecl.asProtectedScope() { + TypeDeclScope scope = new ProtectedClassDeclScope(); + scope.setTypeDecl(this); + scope.addElement(packageScope()); + return scope; + } + + syn lazy TypeDeclScope ClassDecl.asPackageScope() { + TypeDeclScope scope = new PackageClassDeclScope(); + scope.setTypeDecl(this); + scope.addElement(scope()); return scope; } @@ -77,8 +118,8 @@ aspect ProgramToScopeTree { return scope; } - syn lazy JavaDeclaration Declarator.asDeclaration() { - JavaDeclaration decl = new JavaDeclaration(getID()); + syn lazy SimpleJavaDeclaration Declarator.asDeclaration() { + SimpleJavaDeclaration decl = new SimpleJavaDeclaration(getID()); decl.setDeclarator(this); return decl; } @@ -113,4 +154,8 @@ aspect ScopeGenerationAttributes { eq EnhancedForStmt.getVariableDecl().containingScope() = (getStmt() instanceof Block) ? getStmt() : this; eq ForStmt.getInitStmt().containingScope() = (getStmt() instanceof Block) ? getStmt() : this; eq ForStmt.getUpdateStmt().containingScope() = (getStmt() instanceof Block) ? getStmt() : this; + + // allow host package to be called from all AST nodes + inh String ASTNode.hostPackage(); + eq Program.getCompilationUnit(int i).hostPackage() = getCompilationUnit(i).getPackageDecl(); } diff --git a/scope4j/src/main/jastadd/ProgramToScopeTree.relast b/scope4j/src/main/jastadd/ProgramToScopeTree.relast index a1d84e0153fe227e592e61c3a456e9553462c79e..6d6cbf9ac7e4fd65a6df12cb675bda9a46fc1e73 100644 --- a/scope4j/src/main/jastadd/ProgramToScopeTree.relast +++ b/scope4j/src/main/jastadd/ProgramToScopeTree.relast @@ -1,23 +1,29 @@ // glue relation for the Java-based variable shadowing analysis rel ScopeTree.Program -> Program; -TypeDeclScope : Scope; +abstract JavaScope : Scope; +TypeDeclScope : JavaScope; rel TypeDeclScope.typeDecl -> TypeDecl; -BlockScope : Scope; +ProtectedClassDeclScope : TypeDeclScope; +PackageClassDeclScope : TypeDeclScope; +PrivateClassDeclScope : TypeDeclScope; + +BlockScope : JavaScope; rel BlockScope.block -> Block; -ForStmtScope : Scope; +ForStmtScope : JavaScope; rel ForStmtScope.forStmt -> ForStmt; -EnhancedForStmtScope : Scope; +EnhancedForStmtScope : JavaScope; rel EnhancedForStmtScope.enhancedForStmt -> EnhancedForStmt; -JavaDeclaration : Declaration; -rel JavaDeclaration.declarator -> Declarator; +abstract JavaDeclaration : Declaration ; +SimpleJavaDeclaration : JavaDeclaration; +rel SimpleJavaDeclaration.declarator -> Declarator; -JavaParameterDeclaration : Declaration; +JavaParameterDeclaration : JavaDeclaration; rel JavaParameterDeclaration.parameterDeclaration -> ParameterDeclaration; -JavaInferredLambdaParameterDeclaration : Declaration; +JavaInferredLambdaParameterDeclaration : JavaDeclaration; rel JavaInferredLambdaParameterDeclaration.inferredParameterDeclaration -> InferredParameterDeclaration; diff --git a/scope4j/src/main/jastadd/Shadow.jadd b/scope4j/src/main/jastadd/Shadow.jadd index 644a178f4dba9057f6ab1617f8360e82b6f8f493..0caf2f5f46964f4fa319576dbe5c50e2ca074fe3 100644 --- a/scope4j/src/main/jastadd/Shadow.jadd +++ b/scope4j/src/main/jastadd/Shadow.jadd @@ -1,55 +1,37 @@ aspect Shadow{ + // --- toString --- eq TypeDeclScope.toString() = getTypeDecl().getClass().getSimpleName() + "-Scope"; eq BlockScope.toString() = getBlock().getClass().getSimpleName() + "-Scope"; eq ForStmtScope.toString() = getForStmt().getClass().getSimpleName() + "-Scope"; eq EnhancedForStmtScope.toString() = getEnhancedForStmt().getClass().getSimpleName() + "-Scope"; - eq JavaDeclaration.toString() = getDeclarator().getClass().getSimpleName() + ":" + super.toString(); + eq SimpleJavaDeclaration.toString() = getDeclarator().getClass().getSimpleName() + ":" + super.toString(); eq JavaParameterDeclaration.toString() = getParameterDeclaration().getClass().getSimpleName() + ":" + super.toString(); eq JavaInferredLambdaParameterDeclaration.toString() = getInferredParameterDeclaration().getClass().getSimpleName() + ":" + super.toString(); - public int TypeDeclScope.lineNumber(){ - return getTypeDecl().lineNumber(); - } - public int BlockScope.lineNumber(){ - return getBlock().lineNumber(); - } - public int ForStmtScope.lineNumber(){ - return getForStmt().lineNumber(); - } - public int EnhancedForStmtScope.lineNumber(){ - return getEnhancedForStmt().lineNumber(); - } - public int JavaDeclaration.lineNumber() { - return getDeclarator().lineNumber(); - } - public int JavaParameterDeclaration.lineNumber() { - return getParameterDeclaration().lineNumber(); - } - public int JavaInferredLambdaParameterDeclaration.lineNumber() { - return getInferredParameterDeclaration().lineNumber(); - } + // --- lineNumber --- + syn int JavaScope.lineNumber(); + eq TypeDeclScope.lineNumber() = getTypeDecl().lineNumber(); + eq BlockScope.lineNumber() = getBlock().lineNumber(); + eq ForStmtScope.lineNumber() = getForStmt().lineNumber(); + eq EnhancedForStmtScope.lineNumber() = getEnhancedForStmt().lineNumber(); + syn int JavaDeclaration.lineNumber(); + eq SimpleJavaDeclaration.lineNumber() = getDeclarator().lineNumber(); + eq JavaParameterDeclaration.lineNumber() = getParameterDeclaration().lineNumber(); + eq JavaInferredLambdaParameterDeclaration.lineNumber() = getInferredParameterDeclaration().lineNumber(); - public String TypeDeclScope.sourceFile() { - return getTypeDecl().sourceFile(); - } - public String BlockScope.sourceFile() { - return getBlock().sourceFile(); - } - public String ForStmtScope.sourceFile() { - return getForStmt().sourceFile(); - } - public String EnhancedForStmtScope.sourceFile() { - return getEnhancedForStmt().sourceFile(); - } - public String JavaDeclaration.sourceFile() { - return getDeclarator().sourceFile(); - } - public String JavaParameterDeclaration.sourceFile() { - return getParameterDeclaration().sourceFile(); - } - public String JavaInferredLambdaParameterDeclaration.sourceFile() { - return getInferredParameterDeclaration().sourceFile(); - } + // --- sourceFile --- + syn String JavaScope.sourceFile(); + eq TypeDeclScope.sourceFile() = getTypeDecl().sourceFile(); + eq BlockScope.sourceFile() = getBlock().sourceFile(); + eq ForStmtScope.sourceFile() = getForStmt().sourceFile(); + eq EnhancedForStmtScope.sourceFile() = getEnhancedForStmt().sourceFile(); + syn String JavaDeclaration.sourceFile(); + eq SimpleJavaDeclaration.sourceFile() = getDeclarator().sourceFile(); + eq JavaParameterDeclaration.sourceFile() = getParameterDeclaration().sourceFile(); + eq JavaInferredLambdaParameterDeclaration.sourceFile() = getInferredParameterDeclaration().sourceFile(); + // --- asJavaDeclaration --- + syn JavaDeclaration Declaration.asJavaDeclaration() = null; + eq JavaDeclaration.asJavaDeclaration() = this; } diff --git a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java index 4409ba34b48dd9fc47d1c27aaafd82619ebe8d15..c6802fa0af437d36c7e77341d28f5ed219fa67c6 100644 --- a/scope4j/src/main/java/org/extendj/ScopeAnalysis.java +++ b/scope4j/src/main/java/org/extendj/ScopeAnalysis.java @@ -137,7 +137,7 @@ public class ScopeAnalysis extends Frontend { } private Program readProgram(Collection<String> files) throws IOException { - + System.out.println("Reading " + (files.size() > 10 ? files.size() + " files" : files.toString())); Program program = new Program(); program.resetStatistics(); diff --git a/scope4j/src/test/java/org/extendj/FieldsTest.java b/scope4j/src/test/java/org/extendj/FieldsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f08b58fe3ea8e6eaf3fb972ee1ac81a3cd56e663 --- /dev/null +++ b/scope4j/src/test/java/org/extendj/FieldsTest.java @@ -0,0 +1,19 @@ +package org.extendj; + +import org.extendj.ast.AbstractFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class FieldsTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/fields", true, false); + + } + +} diff --git a/scope4j/src/test/java/org/extendj/InnerInheritanceTest.java b/scope4j/src/test/java/org/extendj/InnerInheritanceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b983b028001dee0f0d863e3bcd09098746042885 --- /dev/null +++ b/scope4j/src/test/java/org/extendj/InnerInheritanceTest.java @@ -0,0 +1,23 @@ +package org.extendj; + +import org.extendj.ast.AbstractFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class InnerInheritanceTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/innerInheritance", true, true); + + assertShadow(findings, "fieldA", "ClassB", 6, "ClassA", 3); + assertShadow(findings, "fieldB", "ClassB", 7, "ClassB", 3); + + Assertions.assertEquals(2, findings.size()); + } + +} diff --git a/scope4j/src/test/java/org/extendj/InnerTest.java b/scope4j/src/test/java/org/extendj/InnerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d0ee15f843447579d5eb695ce68ed11f7150d9d9 --- /dev/null +++ b/scope4j/src/test/java/org/extendj/InnerTest.java @@ -0,0 +1,46 @@ +package org.extendj; + +import org.extendj.ast.AbstractFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class InnerTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/inner", true, true); + + System.out.println(findings); + + // anonymous class + assertShadow(findings, "fieldA", "ClassA", 11, "ClassA", 13); + assertShadow(findings, "fieldA", "ClassA", 13, "ClassA", 3); + + // local inner class + assertShadow(findings, "fieldA", "ClassA", 27, "ClassA", 29); + assertShadow(findings, "fieldA", "ClassA", 29, "ClassA", 3); + assertShadow(findings, "changingVar", "ClassA", 25, "ClassA", 19); + + // static member class + assertShadow(findings, "fieldA", "ClassA", 37, "ClassA", 35); + assertShadow(findings, "fieldA", "ClassA", 35, "ClassA", 3); + + // member class + assertShadow(findings, "fieldA", "ClassA", 44, "ClassA", 42); + assertShadow(findings, "fieldA", "ClassA", 42, "ClassA", 3); + + // anonymous class defined in other class + assertShadow(findings, "fieldB", "ClassB", 5, "ClassB", 10); + + // the anonymous class inherited a field + assertShadow(findings, "fieldB", "ClassB", 10, "ClassA", 4); + + + Assertions.assertEquals(11, findings.size()); + } + +} diff --git a/scope4j/src/test/java/org/extendj/ProtectedTest.java b/scope4j/src/test/java/org/extendj/ProtectedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3d53bb46300f7188caf02a89e8bff8c4a775d975 --- /dev/null +++ b/scope4j/src/test/java/org/extendj/ProtectedTest.java @@ -0,0 +1,113 @@ +package org.extendj; + +import org.extendj.ast.AbstractFinding; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class ProtectedTest extends ScopeAnalysisTest { + + @Test + void test() { + + ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/protected_", true, true); + + System.out.println(findings); + + final String classA = "src/test/resources/protected_/one/ClassA.java"; + final String classB = "src/test/resources/protected_/one/ClassB.java"; + final String classC = "src/test/resources/protected_/one/ClassC.java"; + final String classD = "src/test/resources/protected_/two/ClassD.java"; + final String classE = "src/test/resources/protected_/two/ClassE.java"; + + // within class A + // A.A() + assertShadow(findings, "fPrivate", classA, 19, classA, 9); + assertShadow(findings, "fPackage", classA, 20, classA, 10); + assertShadow(findings, "fProtected", classA, 21, classA, 11); + assertShadow(findings, "fPublic", classA, 22, classA, 12); + // A.foo() + assertShadow(findings, "fPrivate", classA, 26, classA, 9); + assertShadow(findings, "fPackage", classA, 27, classA, 10); + assertShadow(findings, "fProtected", classA, 28, classA, 11); + assertShadow(findings, "fPublic", classA, 29, classA, 12); + // A.bar() + assertShadow(findings, "fPrivate", classA, 32, classA, 9); + assertShadow(findings, "fPackage", classA, 33, classA, 10); + assertShadow(findings, "fProtected", classA, 34, classA, 11); + assertShadow(findings, "fPublic", classA, 35, classA, 12); + + // within class B + assertShadow(findings, "fPackage", classB, 14, classA, 10); + assertShadow(findings, "fProtected", classB, 15, classA, 11); + assertShadow(findings, "fPublic", classB, 16, classA, 12); + // B.B() + assertShadow(findings, "fPrivate", classB, 19, classB, 13); + assertShadow(findings, "fPackage", classB, 20, classB, 14); + assertShadow(findings, "fProtected", classB, 21, classB, 15); + assertShadow(findings, "fPublic", classB, 22, classB, 16); + // B.foo() + assertShadow(findings, "fPrivate", classB, 26, classB, 13); + assertShadow(findings, "fPackage", classB, 27, classB, 14); + assertShadow(findings, "fProtected", classB, 28, classB, 15); + assertShadow(findings, "fPublic", classB, 29, classB, 16); + // B.bar() + assertShadow(findings, "fPrivate", classB, 32, classB, 13); + assertShadow(findings, "fPackage", classB, 33, classB, 14); + assertShadow(findings, "fProtected", classB, 34, classB, 15); + assertShadow(findings, "fPublic", classB, 35, classB, 16); + + // within class C + // C.C() + assertShadow(findings, "fPackage", classC, 20, classA, 10); + assertShadow(findings, "fProtected", classC, 21, classA, 11); + assertShadow(findings, "fPublic", classC, 22, classA, 12); + // C.foo() + assertShadow(findings, "fPackage", classC, 27, classA, 10); + assertShadow(findings, "fProtected", classC, 28, classA, 11); + assertShadow(findings, "fPublic", classC, 29, classA, 12); + // C.bar() + assertShadow(findings, "fPackage", classC, 33, classA, 10); + assertShadow(findings, "fProtected", classC, 34, classA, 11); + assertShadow(findings, "fPublic", classC, 35, classA, 12); + + // within class D + assertNotShadow(findings, "fPackage", classD, 20, classA, 10); + assertShadow(findings, "fProtected", classD, 15, classA, 11); + assertShadow(findings, "fPublic", classD, 16, classA, 12); + // D.D() + assertNotShadow(findings, "fPrivate", classD, 19, classA, 9); // true, but not helpful + assertNotShadow(findings, "fPackage", classD, 20, classA, 10); // true, but not helpful + assertShadow(findings, "fProtected", classD, 21, classD, 15); + assertShadow(findings, "fPublic", classD, 22, classD, 16); + // D.foo() + assertNotShadow(findings, "fPrivate", classD, 26, classA, 9); // true, but not helpful + assertNotShadow(findings, "fPackage", classD, 27, classA, 10); // true, but not helpful + assertShadow(findings, "fProtected", classD, 28, classD, 15); + assertShadow(findings, "fPublic", classD, 29, classD, 16); + // D.bar() + assertNotShadow(findings, "fPrivate", classD, 32, classA, 9); // true, but not helpful + assertNotShadow(findings, "fPackage", classD, 33, classA, 10); // true, but not helpful + assertShadow(findings, "fProtected", classD, 34, classD, 15); + assertShadow(findings, "fPublic", classD, 35, classD, 16); + + // within class E + // E.E() + assertNotShadow(findings, "fPackage", classE, 20, classA, 10); + assertShadow(findings, "fProtected", classE, 21, classA, 11); + assertShadow(findings, "fPublic", classE, 22, classA, 12); + // E.foo() + assertNotShadow(findings, "fPackage", classE, 27, classA, 10); + assertShadow(findings, "fProtected", classE, 28, classA, 11); + assertShadow(findings, "fPublic", classE, 29, classA, 12); + // E.bar() + assertNotShadow(findings, "fPackage", classE, 33, classA, 14); + assertShadow(findings, "fProtected", classE, 34, classA, 11); + assertShadow(findings, "fPublic", classE, 35, classA, 12); + + Assertions.assertEquals(56, findings.size()); + } + +} diff --git a/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java index 95d479ce7b90b3d7deb7684ea36c07c3dba1777a..f54ba0fa2060ac9b76c155824a91153a42e83c09 100644 --- a/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java +++ b/scope4j/src/test/java/org/extendj/ScopeAnalysisTest.java @@ -1,25 +1,37 @@ 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.extendj.ast.*; import org.junit.jupiter.api.Assertions; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Supplier; public abstract class ScopeAnalysisTest { - static void assertShadow(Set<AbstractFinding> findings, String name, int shadowerLine, int shadowedLine) { + private static void assertShadow(Set<AbstractFinding> findings, + BiFunction<JavaDeclaration, JavaDeclaration, Boolean> test, + Supplier<String> failMessage) { 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) { + JavaDeclaration shadower = ((VariableShadowFinding)finding).getShadower().asJavaDeclaration(); + JavaDeclaration shadowed = ((VariableShadowFinding)finding).getShadowed().asJavaDeclaration(); + if (test.apply(shadower, shadowed)) { return; } } } - Assertions.fail("No shadow finding found for name '" + name + "' in lines " + shadowerLine + " > " + shadowedLine); + Assertions.fail(failMessage.get()); + } + + static void assertShadow(Set<AbstractFinding> findings, String name, String shadowerSourceFile, int shadowerLine, String shadowedSourceFile, int shadowedLine) { + assertShadow(findings, + (shadower, shadowed) -> + shadowed.getName().equals(name) && + shadowed.sourceFile().contains(shadowedSourceFile) && + shadowed.lineNumber() == shadowedLine && + shadower.sourceFile().contains(shadowerSourceFile) && + shadower.lineNumber() == shadowerLine, + () -> "No shadow finding found for name '" + name + "' in " +shadowerSourceFile + ":" + shadowerLine + " > " + shadowedSourceFile + ":" + shadowedLine); } static void assertRedefinition(Set<AbstractFinding> findings, String name, int declLine) { @@ -33,4 +45,13 @@ public abstract class ScopeAnalysisTest { } Assertions.fail("No multi-decl finding found for name '" + name + "' in line " + declLine); } + + static void assertNotShadow(Set<AbstractFinding> findings, String name, String shadowerSourceFile, int shadowerLine, String shadowedSourceFile, int shadowedLine) { + try { + assertShadow(findings, name, shadowerSourceFile, shadowerLine, shadowedSourceFile, shadowedLine); + } catch (AssertionError e) { + return; + } + Assertions.fail("There should not be a shadow finding for name '" + name + "' in " + shadowerSourceFile + ":" + shadowerLine + " > " + shadowedSourceFile + ":" + shadowedLine); + } } diff --git a/scope4j/src/test/java/org/extendj/SimpleScopeTest.java b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java index 2bb7111bfc3b5b754ce4d9c1012226713f5a0e9d..ae16c02891af19193758b8400ce236aebdd34bf9 100644 --- a/scope4j/src/test/java/org/extendj/SimpleScopeTest.java +++ b/scope4j/src/test/java/org/extendj/SimpleScopeTest.java @@ -12,18 +12,15 @@ public class SimpleScopeTest extends ScopeAnalysisTest { void test() { ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); - Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", false, false); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/simple", true, false); + assertShadow(findings, "localVarA", "ClassA", 19, "ClassA", 11); + assertShadow(findings, "localVarB", "ClassA", 33, "ClassA", 12); + assertShadow(findings, "localVarC", "ClassA", 24, "ClassA", 13); + assertShadow(findings, "fieldA", "ClassA", 29, "ClassA", 3); + assertShadow(findings, "fieldB", "ClassA", 36, "ClassA", 4); 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 index e32199d181284dd4a6528ac93ae15d76e04180b2..6f01c7e2daf89de2df98953194d1d9e23d0c1b36 100644 --- a/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java +++ b/scope4j/src/test/java/org/extendj/SuperclassFieldsTest.java @@ -13,15 +13,14 @@ public class SuperclassFieldsTest extends ScopeAnalysisTest { void test() { ScopeAnalysis scopeAnalysis = new ScopeAnalysis(); - Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/superclassFields", true, false); + Set<AbstractFinding> findings = scopeAnalysis.analyze("src/test/resources/superclassFields", false, false); - assertShadow(findings, "fieldC", 19, 3); - assertShadow(findings, "fieldB", 21, 4); - assertRedefinition(findings, "fieldB", 2); - assertRedefinition(findings, "fieldB", 4); + assertShadow(findings, "fieldC", "ClassB", 19, "ClassB", 3); + assertShadow(findings, "fieldB", "ClassB", 21, "ClassB", 2); + assertShadow(findings, "fieldB", "ClassB", 2, "ClassA", 4); - Assertions.assertEquals(4, findings.size()); + Assertions.assertEquals(3, findings.size()); } diff --git a/scope4j/src/test/resources/fields/ClassA.java b/scope4j/src/test/resources/fields/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..a94c5b76f0b5cc267a77ba1dc017dcd43c6ebe9f --- /dev/null +++ b/scope4j/src/test/resources/fields/ClassA.java @@ -0,0 +1,15 @@ +public abstract class ClassA { + + public int publicField; + protected int protectedField; + int packageField; + private int privateField; + + void n(int parameter) { + int member; + { + int memberInBlock; + } + } + +} diff --git a/scope4j/src/test/resources/inner/ClassA.java b/scope4j/src/test/resources/inner/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..c9dd1fa19fa804b33a9eb664ed58b6e400280727 --- /dev/null +++ b/scope4j/src/test/resources/inner/ClassA.java @@ -0,0 +1,47 @@ +public abstract class ClassA { + + int fieldA; + int fieldB; + + abstract void toBeDefined(); + + void method1() { + ClassA anonymous = new ClassA() { + void toBeDefined() { + int fieldA = 11; + } + int fieldA = 1; + }; + } + + void method2() { + final int finalVar = 1; + int changingVar = 0; + changingVar = 1; // changingVar is not-final and not-effective-final, thus can not be used in InnerA + class InnerA extends ClassA { + /* This variable shares the name, but actually could never reference the outer scope + We include it anyway, because a) it would obscure analysis for this edge-case, and b) warns for potentially + unwanted effects (as all shadowing-warnings do) */ + int changingVar = 4; + void toBeDefined() { + int fieldA = 21 + changingVar + finalVar; + } + int fieldA = 2; + } + ClassA inner = new InnerA(); + } + + static class StaticMemberClass extends ClassA { + int fieldA = 3; + void toBeDefined() { + int fieldA = 31; + } + } + + class MemberClass extends ClassA { + int fieldA = 4; + void toBeDefined() { + int fieldA = 41; + } + } +} diff --git a/scope4j/src/test/resources/inner/ClassB.java b/scope4j/src/test/resources/inner/ClassB.java new file mode 100644 index 0000000000000000000000000000000000000000..3213a0f7f5f67c18ce9a0385b94bfb08ca897b27 --- /dev/null +++ b/scope4j/src/test/resources/inner/ClassB.java @@ -0,0 +1,13 @@ +public class ClassB { + void anonymousClassFromOtherSourceFile() { + ClassA anonymous = new ClassA() { + void toBeDefined() { + int fieldB = 11; + } + + /* false-negative. there should be two scopes: ClassA and this method of ClassB + * But there is only the method, thus, not shadowing of ClassA.fieldB is detected. */ + int fieldB = 1; + } + } +} diff --git a/scope4j/src/test/resources/innerInheritance/ClassA.java b/scope4j/src/test/resources/innerInheritance/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..bc019b729a24c59a2ab1b747b04173e969e941cc --- /dev/null +++ b/scope4j/src/test/resources/innerInheritance/ClassA.java @@ -0,0 +1,5 @@ +public class ClassA { + + public int fieldA; + +} diff --git a/scope4j/src/test/resources/innerInheritance/ClassB.java b/scope4j/src/test/resources/innerInheritance/ClassB.java new file mode 100644 index 0000000000000000000000000000000000000000..7f6755e216ec6b0780045d210e71bee8655e4a41 --- /dev/null +++ b/scope4j/src/test/resources/innerInheritance/ClassB.java @@ -0,0 +1,9 @@ +public class ClassB { + + int fieldB; + + class ClassC extends ClassA { + int fieldA; + int fieldB; + } +} diff --git a/scope4j/src/test/resources/protected_/one/ClassA.java b/scope4j/src/test/resources/protected_/one/ClassA.java new file mode 100644 index 0000000000000000000000000000000000000000..9929229498cee9696281fb62d9f28de648b53023 --- /dev/null +++ b/scope4j/src/test/resources/protected_/one/ClassA.java @@ -0,0 +1,38 @@ +/* + As of https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html, an underscore shall be added to + avoid conflicts with Java keywords. +*/ +package protected_.one; + +public class ClassA { + + private int fPrivate = 0; + int fPackage = 0; + protected int fProtected = 0; + public int fPublic = 0; + + + + + + protected ClassA() { + int fPrivate = 1; + int fPackage = 1; + int fProtected = 1; + int fPublic = 1; + } + + void foo() { + int fPrivate = 2; + int fPackage = 2; + int fProtected = 2; + int fPublic = 2; + } + + void bar(int fPrivate, + int fPackage, + int fProtected, + int fPublic) { + // empty + } +} diff --git a/scope4j/src/test/resources/protected_/one/ClassB.java b/scope4j/src/test/resources/protected_/one/ClassB.java new file mode 100644 index 0000000000000000000000000000000000000000..bde099846c3ce8b8fb4c17b60ba1b07c0c8ddde8 --- /dev/null +++ b/scope4j/src/test/resources/protected_/one/ClassB.java @@ -0,0 +1,38 @@ +/* + As of https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html, an underscore shall be added to + avoid conflicts with Java keywords. +*/ +package protected_.one; + +class ClassB extends ClassA { + + + + + + private int fPrivate = 0; + int fPackage = 0; + protected int fProtected = 0; + public int fPublic = 0; + + ClassB() { + int fPrivate = 1; + int fPackage = 1; + int fProtected = 1; + int fPublic = 1; + } + + void foo() { + int fPrivate = 2; + int fPackage = 2; + int fProtected = 2; + int fPublic = 2; + } + + void bar(int fPrivate, + int fPackage, + int fProtected, + int fPublic) { + // empty + } +} diff --git a/scope4j/src/test/resources/protected_/one/ClassC.java b/scope4j/src/test/resources/protected_/one/ClassC.java new file mode 100644 index 0000000000000000000000000000000000000000..5fc85fdd3e7390f533f3111ff9149c9e44299d70 --- /dev/null +++ b/scope4j/src/test/resources/protected_/one/ClassC.java @@ -0,0 +1,38 @@ +/* + As of https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html, an underscore shall be added to + avoid conflicts with Java keywords. +*/ +package protected_.one; + +class ClassC extends ClassA { + + + + + + + + + + + ClassC() { + int fPrivate = 1; + int fPackage = 1; + int fProtected = 1; + int fPublic = 1; + } + + void foo() { + int fPrivate = 2; + int fPackage = 2; + int fProtected = 2; + int fPublic = 2; + } + + void bar(int fPrivate, + int fPackage, + int fProtected, + int fPublic) { + // empty + } +} diff --git a/scope4j/src/test/resources/protected_/two/ClassD.java b/scope4j/src/test/resources/protected_/two/ClassD.java new file mode 100644 index 0000000000000000000000000000000000000000..b5fe8e114e989e3b165f6cc77f95f5aa77c52cd7 --- /dev/null +++ b/scope4j/src/test/resources/protected_/two/ClassD.java @@ -0,0 +1,38 @@ +/* + As of https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html, an underscore shall be added to + avoid conflicts with Java keywords. +*/ +package protected_.two; +import protected_.one.ClassA; +class ClassD extends ClassA { + + + + + + int fPrivate = 0; + int fPackage = 0; + int fProtected = 0; + int fPublic = 0; + + ClassD() { + int fPrivate = 1; + int fPackage = 1; + int fProtected = 1; + int fPublic = 1; + } + + void foo() { + int fPrivate = 2; + int fPackage = 2; + int fProtected = 2; + int fPublic = 2; + } + + void bar(int fPrivate, + int fPackage, + int fProtected, + int fPublic) { + // empty + } +} diff --git a/scope4j/src/test/resources/protected_/two/ClassE.java b/scope4j/src/test/resources/protected_/two/ClassE.java new file mode 100644 index 0000000000000000000000000000000000000000..84c0d0aba6f92f0b086edf9c76c491f2f7b7da90 --- /dev/null +++ b/scope4j/src/test/resources/protected_/two/ClassE.java @@ -0,0 +1,38 @@ +/* + As of https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html, an underscore shall be added to + avoid conflicts with Java keywords. +*/ +package protected_.two; + +class ClassE extends protected_.one.ClassA { + + + + + + + + + + + ClassE() { + int fPrivate = 1; + int fPackage = 1; + int fProtected = 1; + int fPublic = 1; + } + + void foo() { + int fPrivate = 2; + int fPackage = 2; + int fProtected = 2; + int fPublic = 2; + } + + void bar(int fPrivate, + int fPackage, + int fProtected, + int fPublic) { + // empty + } +} diff --git a/scope4j/src/test/resources/simple/ClassA.java b/scope4j/src/test/resources/simple/ClassA.java index a066a2aa6bdf14ea306e3c0c077723250ced13fb..4cd7ec11b7fb1f8f21cb133aec2ce25501ea5f4c 100644 --- a/scope4j/src/test/resources/simple/ClassA.java +++ b/scope4j/src/test/resources/simple/ClassA.java @@ -37,6 +37,7 @@ public abstract class ClassA { ) { /* 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) + // these do not appear as a scope (and, more importantly, the parameters are not added anywhere else) public abstract void methodNameB(int parameterForAbstractMethodB); + public abstract void methodNameC(int fieldA); } diff --git a/scope4j/src/test/resources/superclassFields/ClassA.java b/scope4j/src/test/resources/superclassFields/ClassA.java index 675b9360cbfcb0764893486d7dcc1946b2c10085..1bd25cd71c600406dc7af4bf5040a3def7753497 100644 --- a/scope4j/src/test/resources/superclassFields/ClassA.java +++ b/scope4j/src/test/resources/superclassFields/ClassA.java @@ -1,12 +1,12 @@ public abstract class ClassA { - + int fieldA; int fieldB; - void m(); + abstract void m(); void n() { //... } - + }