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 for #22461 Empty ClassPath attribute in one or more classpath jars causes crash #22462

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

philwalk
Copy link
Contributor

@philwalk philwalk commented Jan 26, 2025

Change to how an empty or null jar manifest ClassPath: property is handled:

in dotty.tools.dotc.classpath.ClassPathFactory:

  • classesInExpandedPath(...) returns and empty IndexedSeq rather than crash
  • isJarOrZip returns false on a null reference rather than crash

in dotty.tools.dotc.classpath.FileUtils:

  • createSourcePath fails with an error message on a null file parameter.

In the context of #22461, this causes an empty ClassPath: property to be treated the same as a mispelled or missing classpath entry, which are silently ignored, matching the behaviour of legacy scripts.

@som-snytt
Copy link
Contributor

I was interested in trying it out because this is more-or-less shared code with Scala 2. I did not yet reproduce.

I would not expect to handle a possible null in more than one place. Dotty is usually complaining to me that such-and-such might be null, I have to sprinkle | Null if I think that is desired.

@philwalk
Copy link
Contributor Author

philwalk commented Jan 28, 2025

I would not expect to handle a possible null in more than one place.

The critical change is here consisting of a null check inserted after line 71.
EDIT:
As shown in the stacktrace of #22461, an empty String is considered to be a file by if Files.exists(path)

The other changes don't address this bug, and are optional, intended to provide a more to-the-point error message.

Wow, I just noticed I uninitentionally commited a file related to #22443. I will revert that and reduce other changes to the absolute minimum.

@philwalk philwalk force-pushed the empty-classpath-crash branch from 4f49bc2 to 5e2bcf1 Compare January 28, 2025 20:59
@philwalk
Copy link
Contributor Author

philwalk commented Jan 28, 2025

The proposed changes are now reduced to the bare minimum necessary.
A hypothesis as to why the fix is necessary:

          if Files.exists(path)               // `path` is the empty String
          entry = AbstractFile.getFile(path)  // `entry` IS null (Windows only)

I will try to manually verify this

EDIT: the following script verifies the problem in Windows:

#!/usr/bin/env -S scalaQ
object AbstractFileTest {
  def main(args: Array[String]): Unit = {
    import dotty.tools.io.AbstractFile
    import java.nio.file.{Path, Paths, Files}
    val path = Paths.get("")
    printf("path [%s] exists : %s\n", path,  Files.exists(path))
    val entry = AbstractFile.getFile(path)
    printf("entry [%s]\n", entry)
  }
}
# ./abstractFileTest.sc
[warning] MainGenericRunner class is deprecated since Scala 3.5.0, and Scala CLI features will not work.
[warning] Please be sure to update to the Scala CLI launcher to use the new features.
[warning] Check the Scala 3.5.0 release notes to troubleshoot your installation.
path [] exists : true
entry [null]

EDIT:
So an alternate fix would be to replace ClassPathFactory line 71L

  if Files.exists(path)

with this:

  if path.toFile.exists

EDIT:
Although it would fix the problem, performance can be very bad when path doesn't exist, as I recall. If so, the best performance might be this:

  if path.trim.nonEmpty && Files.exists(path)

@philwalk
Copy link
Contributor Author

philwalk commented Jan 28, 2025

The JVM does the same thing in WSL as in Windows, so the reason this bug is Windows-only must be due to what AbstractFile does with an empty String Path.

# WSL
$ wsl /opt/scala/bin/scala -e 'printf("%s\n", java.nio.file.Files.exists(java.nio.file.Paths.get("")))'
true
# Windows
$ /opt/scala/bin/scala -e 'printf("%s\n", java.nio.file.Files.exists(java.nio.file.Paths.get("")))'
true

@philwalk
Copy link
Contributor Author

To my surprise, the problem exists also in WSL, at least implied by the result of the abstractFileTest.sc script.
Here's how I did the test in WSL:

cd ~/workspace/scala3
sbt dist/Universal/packageBin
export SCALA_HOME=`pwd -P`/dist/linux-x86_64/target/universal/stage
export PATH="$SCALA_HOME/bin:$PATH"

BTW, the universal distribution includes scala_legacy in the bin directory.
Here's the test script:

#!/usr/bin/env -S scala_legacy

object AbstractFileTest {
  def main(args: Array[String]): Unit = {
    import dotty.tools.io.AbstractFile
    import java.nio.file.{Path, Paths, Files}
    val path = Paths.get("")
    printf("path [%s] exists : %s\n", path,  Files.exists(path))
    val entry = AbstractFile.getFile(path)
    printf("entry [%s]\n", entry)
  }
}

and here's the output:

(base) philwalk@d5:~/workspace/scala3$ ./abstractFileTest.sc
[warning] MainGenericRunner class is deprecated since Scala 3.5.0, and Scala CLI features will not work.
[warning] Please be sure to update to the Scala CLI launcher to use the new features.
[warning] Check the Scala 3.5.0 release notes to troubleshoot your installation.
path [] exists : true
entry [null]

@som-snytt
Copy link
Contributor

FYI the equivalent code on Scala 2 was changed to test !isDirectory instead of isFile. I even touched ScriptRunner! by coincidence.

scala/scala#6365

I see there was one ancient attempt to improve the code in dotty, but I understand the assumption was that it would be replaced altogether at some point. I assume no one would object to any change you deem useful.

I don't know whether this is useful information.

➜  cat af-test.scala

object AbstractFileTest {
  def main(args: Array[String]): Unit = {
    //import dotty.tools.io.AbstractFile
    import scala.reflect.io.{AbstractFile, Path}
    import java.nio.file.{Paths, Files}
    val path = Paths.get("")
    printf("path [%s] exists : %s\n", path,  Files.exists(path))
    val x = Path(path.toFile)
    val entry = AbstractFile.getFile(x)
    printf("entry [%s]\n", entry)
  }
}
➜  scala af-test.scala
path [] exists : true
entry []

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

Successfully merging this pull request may close these issues.

2 participants