Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix CtTypeReferenceImpl#canAccess of protected and interface members #1189

Merged
merged 1 commit into from
Feb 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,22 @@ public boolean isSubtypeOf(CtTypeReference<?> type) {
}
}

/**
* Detects if this type is an code responsible for implementing of that type.<br>
* In means it detects whether this type can access protected members of that type
* @return true if this type or any declaring type recursively is subtype of type or directly is the type.
*/
private boolean isImplementationOf(CtTypeReference<?> type) {
CtTypeReference<?> impl = this;
while (impl != null) {
if (impl.isSubtypeOf(type)) {
return true;
}
impl = impl.getDeclaringType();
}
return false;
}

@Override
public <C extends CtActualTypeContainer> C setActualTypeArguments(List<? extends CtTypeReference<?>> actualTypeArguments) {
if (actualTypeArguments == null || actualTypeArguments.isEmpty()) {
Expand Down Expand Up @@ -621,28 +637,47 @@ public boolean canAccess(CtTypeReference<?> type) {
return true;
}
if (modifiers.contains(ModifierKind.PROTECTED)) {
if (isSubtypeOf(type)) {
//is visible in subtypes
//the accessed type is protected in scope of declaring type.
CtTypeReference<?> declaringType = type.getDeclaringType();
if (declaringType == null) {
//top level type cannot be protected. So this is a model inconsistency.
throw new SpoonException("The protected class " + type.getQualifiedName() + " has no declaring class.");
}
if (isImplementationOf(declaringType)) {
//type is visible in code which implements declaringType
return true;
} //else it is visible in same package, like package protected
return isInSamePackage(type);
}
if (modifiers.contains(ModifierKind.PRIVATE)) {
//it is visible in scope of the same class only
return type.getTopLevelType().getQualifiedName().equals(this.getTopLevelType().getQualifiedName());
}
//package protected
if (type.getTopLevelType().getPackage().getSimpleName().equals(this.getTopLevelType().getPackage().getSimpleName())) {
//visible only in scope of the same package
/*
* no modifier, we have to check if it is nested type and if yes, if parent is interface or class.
* In case of no parent then implicit access is package protected
* In case of parent is interface, then implicit access is PUBLIC
* In case of parent is class, then implicit access is package protected
*/
CtTypeReference<?> declaringTypeRef = type.getDeclaringType();
if (declaringTypeRef != null && declaringTypeRef.isInterface()) {
//the declaring type is interface, then implicit access is PUBLIC
return true;
}
return false;
//package protected
//visible only in scope of the same package
return isInSamePackage(type);
} catch (SpoonClassNotFoundException e) {
handleParentNotFound(e);
//if the modifiers cannot be resolved then we expect that it is visible
return true;
}
}

private boolean isInSamePackage(CtTypeReference<?> type) {
return type.getTopLevelType().getPackage().getSimpleName().equals(this.getTopLevelType().getPackage().getSimpleName());
}

@Override
public CtTypeReference<?> getTopLevelType() {
CtTypeReference<?> type = this;
Expand All @@ -659,7 +694,7 @@ public CtTypeReference<?> getTopLevelType() {
public CtTypeReference<?> getAccessType() {
CtTypeReference<?> declType = this.getDeclaringType();
if (declType == null) {
throw new SpoonException("The nestedType is expected, but it is: " + getQualifiedName());
throw new SpoonException("The declaring type is expected, but " + getQualifiedName() + " is top level type");
}
CtType<?> contextType = getParent(CtType.class);
if (contextType == null) {
Expand Down
91 changes: 89 additions & 2 deletions src/test/java/spoon/test/imports/ImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.junit.Test;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.SpoonModelBuilder;
import spoon.compiler.SpoonResource;
import spoon.compiler.SpoonResourceHelper;
Expand Down Expand Up @@ -46,8 +47,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static spoon.testing.utils.ModelUtils.canBeBuilt;

Expand Down Expand Up @@ -204,7 +205,8 @@ public void testSpoonWithImports() throws Exception {
final Collection<CtTypeReference<?>> imports = importScanner.computeImports(aClass);
assertEquals(2, imports.size());
final Collection<CtTypeReference<?>> imports1 = importScanner.computeImports(anotherClass);
assertEquals(1, imports1.size());
//ClientClass needs 2 imports: ChildClass, PublicInterface2
assertEquals(2, imports1.size());
//check that printer did not used the package protected class like "SuperClass.InnerClassProtected"
assertTrue(anotherClass.toString().indexOf("InnerClass extends ChildClass.InnerClassProtected")>0);
final Collection<CtTypeReference<?>> imports2 = importScanner.computeImports(classWithInvocation);
Expand Down Expand Up @@ -386,6 +388,91 @@ public void testAccessType() throws Exception {
assertEquals("spoon.test.imports.testclasses.internal.ChildClass.InnerClassProtected", innerClassProtectedByGetSuperClass.toString());
assertEquals("spoon.test.imports.testclasses.internal.SuperClass.InnerClassProtected", innerClassProtectedByQualifiedName.toString());
}

@Test
public void testCanAccess() throws Exception {

class Checker {
final Launcher launcher;
final CtTypeReference<?> aClientClass;
final CtTypeReference<?> anotherClass;
Checker() {
launcher = new Launcher();
launcher.setArgs(new String[] {
"-i", "./src/test/java/spoon/test/imports/testclasses", "--with-imports"
});
launcher.buildModel();
aClientClass = launcher.getFactory().Class().get(ClientClass.class).getReference();
anotherClass = launcher.getFactory().Class().get(Tacos.class).getReference();
}
void checkCanAccess(String aClassName, boolean isInterface, boolean canAccessClientClass, boolean canAccessAnotherClass, String clientAccessType, String anotherAccessType) {
CtTypeReference<?> target;
if(isInterface) {
target = launcher.getFactory().Interface().create(aClassName).getReference();
} else {
target = launcher.getFactory().Class().get(aClassName).getReference();
}
boolean isNested = target.getDeclaringType()!=null;
CtTypeReference<?> accessType;

target.setParent(aClientClass.getTypeDeclaration());
if(canAccessClientClass) {
assertTrue("ClientClass should have access to "+aClassName+" but it has not", aClientClass.canAccess(target));
} else {
assertFalse("ClientClass should have NO access to "+aClassName+" but it has", aClientClass.canAccess(target));
}
if(isNested) {
accessType = target.getAccessType();
if(clientAccessType!=null) {
assertEquals(clientAccessType, accessType.getQualifiedName());
} else if(accessType!=null){
fail("ClientClass should have NO accessType to "+aClassName+" but it has "+accessType.getQualifiedName());
}
}

target.setParent(anotherClass.getTypeDeclaration());
if(canAccessAnotherClass) {
assertTrue("Tacos class should have access to "+aClassName+" but it has not", anotherClass.canAccess(target));
} else {
assertFalse("Tacos class should have NO access to "+aClassName+" but it has", anotherClass.canAccess(target));
}
if(isNested) {
if(anotherAccessType!=null) {
accessType = target.getAccessType();
assertEquals(anotherAccessType, accessType.getQualifiedName());
} else {
try {
accessType = target.getAccessType();
} catch (SpoonException e) {
if(e.getMessage().indexOf("Cannot compute access path to type: ")==-1) {
throw e;
}//else OK, it should throw exception
accessType = null;
}
if(accessType!=null){
fail("Tacos class should have NO accessType to "+aClassName+" but it has "+accessType.getQualifiedName());
}
}
}
}
}
Checker c = new Checker();

c.checkCanAccess("spoon.test.imports.testclasses.ClientClass", false, true, true, null, null);
c.checkCanAccess("spoon.test.imports.testclasses.ClientClass$InnerClass", false, true, false, "spoon.test.imports.testclasses.ClientClass", "spoon.test.imports.testclasses.ClientClass");
c.checkCanAccess("spoon.test.imports.testclasses.internal.ChildClass", false, true, true, null, null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.PublicInterface2", true, true, true, null, null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.PublicInterface2$NestedInterface", true, true, true, "spoon.test.imports.testclasses.internal.PublicInterface2", "spoon.test.imports.testclasses.internal.PublicInterface2");
c.checkCanAccess("spoon.test.imports.testclasses.internal.PublicInterface2$NestedClass", true, true, true, "spoon.test.imports.testclasses.internal.PublicInterface2", "spoon.test.imports.testclasses.internal.PublicInterface2");
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$PublicInterface", true, true, true, "spoon.test.imports.testclasses.internal.ChildClass", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$PackageProtectedInterface", true, false, false, "spoon.test.imports.testclasses.internal.ChildClass", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$ProtectedInterface", true, true, false, "spoon.test.imports.testclasses.internal.ChildClass", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$ProtectedInterface$NestedOfProtectedInterface", true, true, true/*canAccess, but has no access to accessType*/, "spoon.test.imports.testclasses.internal.SuperClass$ProtectedInterface", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$ProtectedInterface$NestedPublicInterface", true, true, true/*canAccess, but has no access to accessType*/, "spoon.test.imports.testclasses.internal.SuperClass$ProtectedInterface", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$PublicInterface", true, true, true/*canAccess, but has no access to accessType*/, "spoon.test.imports.testclasses.internal.ChildClass", null);
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$PublicInterface$NestedOfPublicInterface", true, true, true/*canAccess, has access to first accessType, but not to full accesspath*/, "spoon.test.imports.testclasses.internal.SuperClass$PublicInterface", "spoon.test.imports.testclasses.internal.SuperClass$PublicInterface");
c.checkCanAccess("spoon.test.imports.testclasses.internal.SuperClass$PublicInterface$NestedPublicInterface", true, true, true/*canAccess, has access to first accessType, but not to full accesspath*/, "spoon.test.imports.testclasses.internal.SuperClass$PublicInterface", "spoon.test.imports.testclasses.internal.SuperClass$PublicInterface");
}

@Test
public void testNestedAccessPathWithTypedParameter() throws Exception {
Expand Down
18 changes: 15 additions & 3 deletions src/test/java/spoon/test/imports/testclasses/ClientClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
import spoon.test.imports.testclasses.internal.ChildClass;

public class ClientClass extends ChildClass {
private class InnerClass extends spoon.test.imports.testclasses.internal.ChildClass.InnerClassProtected {
}
}
private class InnerClass extends spoon.test.imports.testclasses.internal.ChildClass.InnerClassProtected {}
private class InnerClass2 implements spoon.test.imports.testclasses.internal.PublicInterface2 {}
private class InnerClass3a implements spoon.test.imports.testclasses.internal.PublicInterface2.NestedInterface {}
private class InnerClass3b extends spoon.test.imports.testclasses.internal.PublicInterface2.NestedClass {}
//SuperClass is package protected so it is not visible.
// private class InnerClassX implements spoon.test.imports.testclasses.internal.SuperClass.PublicInterface {}
//ChildClass.PackageProtectedInterface is package protected so it is not visible.
// private class InnerClassX implements spoon.test.imports.testclasses.internal.ChildClass.PackageProtectedInterface {}
private class InnerClass4 implements spoon.test.imports.testclasses.internal.ChildClass.ProtectedInterface {}
private class InnerClass5 implements spoon.test.imports.testclasses.internal.ChildClass.ProtectedInterface.NestedOfProtectedInterface {}
private class InnerClass6 implements spoon.test.imports.testclasses.internal.ChildClass.ProtectedInterface.NestedPublicInterface {}
private class InnerClass7 implements spoon.test.imports.testclasses.internal.ChildClass.PublicInterface {}
private class InnerClass8 implements spoon.test.imports.testclasses.internal.ChildClass.PublicInterface.NestedOfPublicInterface {}
private class InnerClass9 implements spoon.test.imports.testclasses.internal.ChildClass.PublicInterface.NestedPublicInterface {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package spoon.test.imports.testclasses.internal;

public interface PublicInterface2 {
//this is public too https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.1
interface NestedInterface {
}
class NestedClass {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,22 @@
class SuperClass {
protected class InnerClassProtected {
}
interface PackageProtectedInterface {
interface NestedOfPackageProtectedInterface {
}
public interface NestedPublicInterface {
}
}
protected interface ProtectedInterface {
interface NestedOfProtectedInterface {
}
public interface NestedPublicInterface {
}
}
public interface PublicInterface {
interface NestedOfPublicInterface {
}
public interface NestedPublicInterface {
}
}
}