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

overrides check with a protected java methods throws IllegalStateException #34

Closed
yigit opened this issue Jun 30, 2020 · 9 comments
Closed
Assignees

Comments

@yigit
Copy link
Collaborator

yigit commented Jun 30, 2020

override-bug.zip

See attached sample for repro (simply run its tests)

If I have a java class like this:

public class TestClass {
  protected void test() {}
}

And try to check if test overrides Any.equals, it throws IllegalStateException.

// processor
        override fun process(resolver: Resolver) {
            val testClass = resolver.getClassDeclarationByName(
                resolver.getKSNameFromString("TestClass")
            )!!
            val any = resolver.getClassDeclarationByName(
                resolver.getKSNameFromString("kotlin.Any")
            )!!
            val equalsMethod = any.getAllFunctions().first {
                it.simpleName.asString() == "equals"
            }
            val testMethod = testClass.getDeclaredFunctions().first {
                it.simpleName.asString() == "test"
            }
            check(testMethod.overrides(equalsMethod) == false) { // << throws
                "should not override"
            }
        }
e: java.lang.IllegalStateException: unhandled visibility: protected/*protected and package*/
	at org.jetbrains.kotlin.ksp.symbol.impl.UtilsKt.toKSModifiers(utils.kt:122)
	at org.jetbrains.kotlin.ksp.symbol.impl.binary.KSFunctionDeclarationDescriptorImpl$modifiers$2.invoke(KSFunctionDeclarationDescriptorImpl.kt:101)
	at org.jetbrains.kotlin.ksp.symbol.impl.binary.KSFunctionDeclarationDescriptorImpl$modifiers$2.invoke(KSFunctionDeclarationDescriptorImpl.kt:22)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at org.jetbrains.kotlin.ksp.symbol.impl.binary.KSFunctionDeclarationDescriptorImpl.getModifiers(KSFunctionDeclarationDescriptorImpl.kt)
	at org.jetbrains.kotlin.ksp.symbol.impl.binary.KSFunctionDeclarationDescriptorImpl.overrides(KSFunctionDeclarationDescriptorImpl.kt:47)
@yigit
Copy link
Collaborator Author

yigit commented Jun 30, 2020

Seems like it has nothing to do w/ Any or equals.
If the overriding method has protected, it fails.

e.g. with the following code:

        val source1 = SourceFile.java("TestClass.java", """
            public class TestClass extends Base {
                protected void test() {}
            }
        """.trimIndent())
        val base = SourceFile.java("Base.java", """
            public class BaseClass {
                protected void test() {}
            }
        """.trimIndent())

Checking TestClass.test overrides BaseClass.test also throws.
If I change the protected in TestClass to public, it works fine.

@neetopia neetopia self-assigned this Jun 30, 2020
@yigit
Copy link
Collaborator Author

yigit commented Jun 30, 2020

some more information.

Seems like it is an issue specific to KSFunctionDeclarationDescriptorImpl while it works fine with KSFunctionDeclarationJavaImpl.

More specifically, if I get a class via resolver.getClassDeclarationByName, it returns KSClassDeclarationDescriptorImpl whereas if i get it via a kotlin subclass (symbol.superTypes.first().resolve()?.declaration), then it returns KSClassDeclarationJavaImpl and it works properly.

So seems like an issue in resolver.
I think the fix is straightforward to extend the check to handle protected but i'm not sure if that'll just hide another bug.
Why does the resolver return two different implementations based on the call site?

@yigit
Copy link
Collaborator Author

yigit commented Jun 30, 2020

I tried extending the javaModifiers test and the value returned from java are a bit different:

C: PUBLIC ABSTRACT
staticStr: PRIVATE
s1: FINAL JAVA_TRANSIENT
i1: PROTECTED JAVA_STATIC JAVA_VOLATILE
intFun: JAVA_SYNCHRONIZED JAVA_DEFAULT
foo: ABSTRACT JAVA_STRICT
abstractVoidMethod: PROTECTED

If I visit the declaration by directly getting it via:

resolver.getClassDeclarationByName(
      resolver.getKSNameFromString("C")
)

it prints:

C: ABSTRACT PUBLIC
abstractVoidMethod: OPEN PROTECTED
hashCode: OPEN PUBLIC OVERRIDE
equals: OPEN PUBLIC OPERATOR OVERRIDE
toString: OPEN PUBLIC OVERRIDE
intFun: OPEN
foo: ABSTRACT
staticStr: FINAL PRIVATE
s1: FINAL
i1: FINAL PROTECTED JAVA_STATIC

In some ways, getting it via resolver seems to be returning a more complete information (e.g. OPEN modifier is added).

@neetopia
Copy link
Contributor

some more information.

Seems like it is an issue specific to KSFunctionDeclarationDescriptorImpl while it works fine with KSFunctionDeclarationJavaImpl.

More specifically, if I get a class via resolver.getClassDeclarationByName, it returns KSClassDeclarationDescriptorImpl whereas if i get it via a kotlin subclass (symbol.superTypes.first().resolve()?.declaration), then it returns KSClassDeclarationJavaImpl and it works properly.

So seems like an issue in resolver.
I think the fix is straightforward to extend the check to handle protected but i'm not sure if that'll just hide another bug.
Why does the resolver return two different implementations based on the call site?

They should always be same implementation, I noticed this issue last week but since the fix was rather small and inside the implementation detail therefore I didn't make it an issue, was planning to address it myself, sorry for the confusion.

@neetopia
Copy link
Contributor

I tried extending the javaModifiers test and the value returned from java are a bit different:

C: PUBLIC ABSTRACT
staticStr: PRIVATE
s1: FINAL JAVA_TRANSIENT
i1: PROTECTED JAVA_STATIC JAVA_VOLATILE
intFun: JAVA_SYNCHRONIZED JAVA_DEFAULT
foo: ABSTRACT JAVA_STRICT
abstractVoidMethod: PROTECTED

If I visit the declaration by directly getting it via:

resolver.getClassDeclarationByName(
      resolver.getKSNameFromString("C")
)

it prints:

C: ABSTRACT PUBLIC
abstractVoidMethod: OPEN PROTECTED
hashCode: OPEN PUBLIC OVERRIDE
equals: OPEN PUBLIC OPERATOR OVERRIDE
toString: OPEN PUBLIC OVERRIDE
intFun: OPEN
foo: ABSTRACT
staticStr: FINAL PRIVATE
s1: FINAL
i1: FINAL PROTECTED JAVA_STATIC

In some ways, getting it via resolver seems to be returning a more complete information (e.g. OPEN modifier is added).

It's true that descriptors contains most detailed information as they are fully resolved, but they are not representing the original code structure, like the implicit OPEN for Java symbols, and hence it will be nice if users won't rely on this buggy behavior of getClassDeclarationByName always returns descriptor based implementation.

@yigit
Copy link
Collaborator Author

yigit commented Jul 1, 2020

i think both are probably fine from developer's perspective as long as it is consistent. but when they are inconsistent, it will be really hard to workaround as most of the code in the symbol processor does not necessarily know how the KSClassDeclaration was acquired.

@neetopia
Copy link
Contributor

neetopia commented Jul 1, 2020

i think both are probably fine from developer's perspective as long as it is consistent. but when they are inconsistent, it will be really hard to workaround as most of the code in the symbol processor does not necessarily know how the KSClassDeclaration was acquired.

If consistent mean .isOpen() will return always return true for an open class regardless of JavaImpl or DescriptorImpl, they are consistent by our design. We are making modifiers only include explicit modifier just to make sure there is a way to still inspect code. Developers should look at utility functions like isOpen rather than relying on modifiers which can differ by platform.

@yigit
Copy link
Collaborator Author

yigit commented Jul 1, 2020

I agree with that.
actually i was leaning for the same thing as i'm prototyping the abstraction for room.
I found some places where we unnecessarily access elements APIs. Trying to guard them beyond an abstraction to avoid modelling too much of the elements API.

@neetopia
Copy link
Contributor

neetopia commented Jul 1, 2020

android/kotlin#51 includes a fix to this reproduce project.

copybara-service bot referenced this issue in androidx/androidx Jul 7, 2020
Modifiers are platform dependent hence we should not access them
directly, instead, use helper methods which we can make platform
specific once we add support for KSP.

see: https://github.com/android/kotlin/issues/48#issuecomment-652126757

Bug: 160323720
Test: existing test suite
Change-Id: I79678beff3b3a087bd961db39edaa2fca1fd5ec1
@davidjwiner davidjwiner transferred this issue from android/kotlin Sep 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants