Skip to content

Commit

Permalink
scripting fix --import refers to files, not code (#5141)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpollmeier authored Nov 28, 2024
1 parent c52986f commit 07b9785
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 5 deletions.
10 changes: 6 additions & 4 deletions console/src/main/scala/io/joern/console/BridgeBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ case class Config(
scriptFile: Option[Path] = None,
command: Option[String] = None,
params: Map[String, String] = Map.empty,
additionalImports: Seq[Path] = Nil,
predefFiles: Seq[Path] = Nil,
additionalClasspathEntries: Seq[String] = Seq.empty,
addPlugin: Option[String] = None,
rmPlugin: Option[String] = None,
Expand Down Expand Up @@ -70,8 +70,8 @@ trait BridgeBase extends InteractiveShell with ScriptExecution with PluginHandli
.valueName("script1.sc")
.unbounded()
.optional()
.action((x, c) => c.copy(additionalImports = c.additionalImports :+ x))
.text("import (and run) additional script(s) on startup - may be passed multiple times")
.action((x, c) => c.copy(predefFiles = c.predefFiles :+ x))
.text("given source files will be compiled and added to classpath - this may be passed multiple times")

opt[String]("classpathEntry")
.valueName("path/to/classpath")
Expand Down Expand Up @@ -216,7 +216,6 @@ trait BridgeBase extends InteractiveShell with ScriptExecution with PluginHandli

protected def buildRunBeforeCode(config: Config): Seq[String] = {
val builder = Seq.newBuilder[String]
builder ++= config.additionalImports.map(_.toString)
builder ++= runBeforeCode
config.cpgToLoad.foreach { cpgFile =>
builder += s"""importCpg("$cpgFile")"""
Expand All @@ -238,6 +237,7 @@ trait InteractiveShell { this: BridgeBase =>
protected def startInteractiveShell(config: Config) = {
replpp.InteractiveShell.run(
replpp.Config(
predefFiles = config.predefFiles,
runBefore = buildRunBeforeCode(config),
nocolors = config.nocolors,
verbose = config.verbose,
Expand Down Expand Up @@ -267,6 +267,7 @@ trait ScriptExecution { this: BridgeBase =>
} else {
val scriptReturn = ScriptRunner.exec(
replpp.Config(
predefFiles = config.predefFiles,
runBefore = buildRunBeforeCode(config),
scriptFile = Option(scriptFile),
command = config.command,
Expand Down Expand Up @@ -391,6 +392,7 @@ trait ServerHandling { this: BridgeBase =>

protected def startHttpServer(config: Config): Unit = {
val baseConfig = replpp.Config(
predefFiles = config.predefFiles,
runBefore = buildRunBeforeCode(config),
verbose = true, // always print what's happening - helps debugging
classpathConfig = replpp.Config
Expand Down
3 changes: 2 additions & 1 deletion joern-cli/src/main/resources/scripts/trigger-error.sc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
assert(true == false, "trigger an error for testing purposes")
import scala.util.control.NoStackTrace
throw new Exception("triggering an error for testing purposes") with NoStackTrace
2 changes: 2 additions & 0 deletions joern-cli/src/test/resources/additional-import.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def sayHello(to: String) =
s"hello, $to"
118 changes: 118 additions & 0 deletions joern-cli/src/test/scala/io/joern/joerncli/RunScriptTests.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.joern.joerncli

import better.files._
import java.nio.file.Paths
import io.joern.console.Config
import io.joern.joerncli.console.ReplBridge
import io.shiftleft.utils.ProjectRoot
Expand Down Expand Up @@ -27,6 +29,114 @@ class RunScriptTests extends AnyWordSpec with Matchers {
}
}

"execute a simple script" in new Fixture {
def test(scriptFile: File, outputFile: File) = {
val escScriptPath = outputFile.pathAsString.replace("\\", "\\\\")
scriptFile.write(s"""
|val fw = new java.io.FileWriter("$escScriptPath", true)
|fw.write("michael was here")
|fw.close()
""".stripMargin)

ReplBridge.main(Array("--script", scriptFile.pathAsString))

withClue(s"$outputFile content: ") {
outputFile.lines.head shouldBe "michael was here"
}
}
}

"pass parameters to script" in new Fixture {
def test(scriptFile: File, outputFile: File) = {
scriptFile.write(s"""
|@main def foo(outFile: String, magicNumber: Int) = {
| val fw = new java.io.FileWriter(outFile, true)
| fw.write(magicNumber.toString)
| fw.close()
|}
""".stripMargin)

ReplBridge.main(
Array(
"--script",
scriptFile.pathAsString,
"--param",
s"outFile=${outputFile.pathAsString}",
"--param",
"magicNumber=42"
)
)

withClue(s"$outputFile content: ") {
outputFile.lines.head shouldBe "42"
}
}
}

"script with multiple @main methods" in new Fixture {
def test(scriptFile: File, outputFile: File) = {
val escScriptPath = outputFile.pathAsString.replace("\\", "\\\\")

scriptFile.write(s"""
|@main def foo() = {
| val fw = new java.io.FileWriter("$escScriptPath", true)
| fw.write("foo was called")
| fw.close()
|}
|@main def bar() = {
| val fw = new java.io.FileWriter("$escScriptPath", true)
| fw.write("bar was called")
| fw.close()
|}
""".stripMargin)

ReplBridge.main(Array("--script", scriptFile.pathAsString, "--command", "bar"))

withClue(s"$outputFile content: ") {
outputFile.lines.head shouldBe "bar was called"
}
}
}

"use additional import script: //> using file directive" in new Fixture {
def test(scriptFile: File, outputFile: File) = {
val escScriptPath = outputFile.pathAsString.replace("\\", "\\\\")
val additionalImportFile = Paths.get("joern-cli/src/test/resources/additional-import.sc").toAbsolutePath

scriptFile.write(s"""
|//> using file $additionalImportFile
|val fw = new java.io.FileWriter("$escScriptPath", true)
|fw.write(sayHello("michael")) //function defined in additionalImportFile
|fw.close()
""".stripMargin)

ReplBridge.main(Array("--script", scriptFile.pathAsString))

withClue(s"$outputFile content: ") {
outputFile.lines.head shouldBe "hello, michael"
}
}
}

"use additional import script: --import parameter" in new Fixture {
def test(scriptFile: File, outputFile: File) = {
val escScriptPath = outputFile.pathAsString.replace("\\", "\\\\")
val additionalImportFile = Paths.get("joern-cli/src/test/resources/additional-import.sc").toAbsolutePath

scriptFile.write(s"""
|val fw = new java.io.FileWriter("$escScriptPath", true)
|fw.write(sayHello("michael")) //function defined in additionalImportFile
|fw.close()
""".stripMargin)

ReplBridge.main(Array("--script", scriptFile.pathAsString, "--import", additionalImportFile.toString))

withClue(s"$outputFile content: ") {
outputFile.lines.head shouldBe "hello, michael"
}
}
}

"should return Failure if" when {
"script doesn't exist" in {
val result = ReplBridge.runScript(Config(scriptFile = Some(scriptsRoot.resolve("does-not-exist.sc"))))
Expand All @@ -53,4 +163,12 @@ object RunScriptTests {
)
.get
}

trait Fixture {
def test(scriptFile: File, outputFile: File): Unit
for {
scriptFile <- File.temporaryFile()
outputFile <- File.temporaryFile()
} test(scriptFile, outputFile)
}
}

0 comments on commit 07b9785

Please sign in to comment.