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

Type inference after erasure uses package private type over public one leading to java.lang.IllegalAccessError at runtime #16474

Closed
chungonn opened this issue Dec 7, 2022 · 6 comments · Fixed by #16477
Assignees
Labels
area:infer area:typer compat:java itype:bug regression This worked in a previous version but doesn't anymore
Milestone

Comments

@chungonn
Copy link

chungonn commented Dec 7, 2022

Below is a minimal code fragment to reproduce the problem. It works from Scala 3.0 to Scala 3.2.0 and it breaks with Scala 3.2.1 and Scala.3.2.2-RC1

With Scala 3.2.1, when the method jweEncrypt is invoked, it throws a java.lang.IllegalAccessError. With all versions prior to Scala 3.2.1, the method is able to be executed.

Java Version

Java 17

Compiler version

Scala 3.2.1, Scala 3.2.2-RC1

Minimized code

Change the scala3Version to 3.2.0 then run the test suite to see the expected output.

build.sbt

val scala3Version = "3.2.1"

lazy val root = project
  .in(file("."))
  .settings(
    name := "Test",
    version := "0.1.0-SNAPSHOT",

    scalaVersion := scala3Version,

    libraryDependencies ++= Seq(
      "com.nimbusds" % "nimbus-jose-jwt" % "9.25.6",
      "org.scalameta" %% "munit" % "0.7.29" % Test
    )
  )

MySuite.scala

class MySuite extends munit.FunSuite {

  def jweEncrypter(publicJwk: JWK): Try[JWEEncrypter] = Try {
    publicJwk match
      case rsa: RSAKey => new RSAEncrypter(rsa)
      case ec: ECKey => new ECDHEncrypter(ec)
      case _ => throw new RuntimeException(s"Unsupported jwk ${publicJwk}")
  }

  test("JWS signing and verify using private cert pem file") {
    val jwkTry = jweEncrypter(null)
    println(s"jwkTry = ${jwkTry}")
  }
}

Output

Testing started at 3:22 pm ...

Process finished with exit code 255

java.lang.IllegalAccessError: failed to access class com.nimbusds.jose.crypto.impl.BaseJWEProvider from class MySuite (com.nimbusds.jose.crypto.impl.BaseJWEProvider and MySuite are in unnamed module of loader 'app')

	at MySuite.jweEncrypter(MySuite.scala:14)
	at MySuite.$init$$$anonfun$1(MySuite.scala:17)

Expectation

Testing started at 3:26 pm ...
jwkTry = Failure(java.lang.RuntimeException: Unsupported jwk null)

Process finished with exit code 0
@chungonn chungonn added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 7, 2022
@chungonn
Copy link
Author

chungonn commented Dec 7, 2022

I have a simple test project for the above code, I can upload it if required.

@WojciechMazur WojciechMazur added the regression This worked in a previous version but doesn't anymore label Dec 7, 2022
@WojciechMazur
Copy link
Contributor

Thank you for this report, I've managed to reproduce it locally. It seems to be a regression introduced since 3.2.0. I'll try to provide dependency free minimization soon

@WojciechMazur
Copy link
Contributor

WojciechMazur commented Dec 7, 2022

Reproducer

// Encrypter.java
package repro;
public interface Encrypter{}
// BaseProvider.java
package repro.impl;

import repro.*;
abstract class BaseProvider{}
// Case1Provider.java
package repro.impl;

public abstract class Case1Provider extends BaseProvider {}
// Case1.java
package repro;

import repro.impl.*;

public class Case1 extends Case1Provider implements Encrypter {
  public Case1(){}
}
// Case2Provider.java
package repro.impl;

public abstract class Case2Provider extends BaseProvider {}
// Case2.java
package repro;

import repro.impl.*;

public class Case2 extends Case2Provider implements Encrypter {
  public Case2(){}
}
// test.scala
import repro.*
import scala.util.Try

def get(arg: Any): Try[Encrypter] = Try {
  val x: Any = 1
  arg match
    case 1 => new Case1()
    case 2 => new Case2()
    case _ => throw new RuntimeException(s"Unsupported got $arg")
}

@main def Test = 
    val result = get(null)

@WojciechMazur WojciechMazur added compat:java and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Dec 7, 2022
@WojciechMazur
Copy link
Contributor

One notable difference between 3.2.0 and 3.2.1 is the type infereded for anonymous function of method get after erasure phase.
In bytecode of 3.2.1 we have signature:

  private final repro.impl.BaseProvider get$$anonfun$1(java.lang.Object);

in 3.2.0 it was :

  private final repro.Encrypter get$$anonfun$1(java.lang.Object);

In case if both classes Case1 and Case2 would not inherit from a common public ancestor then signature of get$$anonfun$1 would be inferred to java package private final repro.impl.BaseProvider and though would always lead to IllegalAccessException in runtime.

At the time of typer phase anonfun in inferred to be (repro.impl.BaseProvider & repro.Encrypter

@WojciechMazur WojciechMazur changed the title java.lang.IllegalAccessError: failed to access class com.nimbusds.jose.crypto.impl.BaseJWEProvider from class MySuite (com.nimbusds.jose.crypto.impl.BaseJWEProvider and MySuite are in unnamed module of loader 'app') Type inference after erasure uses package private type over public one leading to java.lang.IllegalAccessError Dec 7, 2022
@WojciechMazur WojciechMazur changed the title Type inference after erasure uses package private type over public one leading to java.lang.IllegalAccessError Type inference after erasure uses package private type over public one leading to java.lang.IllegalAccessError at runtime Dec 7, 2022
@WojciechMazur
Copy link
Contributor

Bisect points to 824496b

odersky added a commit to dotty-staging/dotty that referenced this issue Dec 7, 2022
By joining union types we could previously uncover inaccessible base classes that could
lead to access errors at runtime. We now filter out such classes when computing the orDominator.

Fixes scala#16474
@odersky
Copy link
Contributor

odersky commented Dec 7, 2022

I don't think 824496b introduced that problem but maybe it was hidden by some obscure type inference difference before.

smarter added a commit that referenced this issue Dec 8, 2022
By joining union types we could previously uncover inaccessible base
classes that could lead to access errors at runtime. We now filter out
such classes when computing the orDominator.

Fixes #16474
Kordyjan pushed a commit to dotty-staging/dotty that referenced this issue Dec 22, 2022
By joining union types we could previously uncover inaccessible base classes that could
lead to access errors at runtime. We now filter out such classes when computing the orDominator.

Fixes scala#16474
@Kordyjan Kordyjan added this to the 3.3.0 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:infer area:typer compat:java itype:bug regression This worked in a previous version but doesn't anymore
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants