Skip to content
New issue

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

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

Already on GitHub? # 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 []

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants