forked from joernio/joern
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[jimple2cpg] - Enable Decompilation with CFR (joernio#4214)
- Loading branch information
1 parent
66852fb
commit 389c1f0
Showing
7 changed files
with
237 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
joern-cli/frontends/jimple2cpg/src/main/scala/io/joern/jimple2cpg/util/Decompiler.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package io.joern.jimple2cpg.util | ||
|
||
import better.files.File | ||
import org.benf.cfr.reader.api.OutputSinkFactory.{Sink, SinkClass, SinkType} | ||
import org.benf.cfr.reader.api.SinkReturns.Decompiled | ||
import org.benf.cfr.reader.api.{CfrDriver, OutputSinkFactory} | ||
import org.slf4j.LoggerFactory | ||
|
||
import java.util | ||
import java.util.{Collection, Collections} | ||
import scala.collection.mutable | ||
import scala.jdk.CollectionConverters.* | ||
|
||
class Decompiler(classFile: List[File]) { | ||
|
||
private val logger = LoggerFactory.getLogger(getClass) | ||
private val classToDecompiledSource: mutable.HashMap[String, String] = mutable.HashMap.empty; | ||
|
||
/** Decompiles the class files and returns a map of the method name to its source code contents. | ||
*/ | ||
def decompile(): mutable.HashMap[String, String] = { | ||
val driver = new CfrDriver.Builder().withOutputSink(outputSink).build() | ||
driver.analyse(SeqHasAsJava(classFile.map(_.pathAsString)).asJava) | ||
classToDecompiledSource | ||
} | ||
|
||
private val outputSink: OutputSinkFactory = new OutputSinkFactory() { | ||
|
||
override def getSupportedSinks(sinkType: SinkType, collection: util.Collection[SinkClass]): util.List[SinkClass] = | ||
if (sinkType == SinkType.JAVA && collection.contains(SinkClass.DECOMPILED)) { | ||
util.Arrays.asList(SinkClass.DECOMPILED) | ||
} else { | ||
Collections.singletonList(SinkClass.STRING) | ||
} | ||
|
||
override def getSink[T](sinkType: SinkType, sinkClass: SinkClass): OutputSinkFactory.Sink[T] = new Sink[T]() { | ||
override def write(s: T): Unit = { | ||
sinkType match | ||
case OutputSinkFactory.SinkType.JAVA => | ||
s match | ||
case x: Decompiled => | ||
val className = x.getClassName | ||
val packageName = x.getPackageName | ||
val classFullName = Seq(packageName, className).filterNot(_.isBlank).mkString(".") | ||
logger.debug(s"Decompiled '$classFullName', parsing...") | ||
|
||
classToDecompiledSource.put(classFullName, x.getJava) | ||
case _ => | ||
logger.error(s"Unhandled decompilation type ${s.getClass}") | ||
case OutputSinkFactory.SinkType.PROGRESS => | ||
val className = s.toString.split(" ").last | ||
logger.debug(s"Decompiling class '$className'") | ||
case OutputSinkFactory.SinkType.EXCEPTION => | ||
logger.error(s.toString) | ||
case _ => // ignore | ||
} | ||
} | ||
} | ||
|
||
} |
117 changes: 117 additions & 0 deletions
117
...li/frontends/jimple2cpg/src/test/scala/io/joern/jimple2cpg/querying/CodeDumperTests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package io.joern.jimple2cpg.querying | ||
|
||
import io.joern.jimple2cpg.Config | ||
import io.joern.jimple2cpg.testfixtures.JimpleCode2CpgFixture | ||
import io.shiftleft.codepropertygraph.Cpg | ||
import io.shiftleft.semanticcpg.language._ | ||
|
||
class CodeDumperTests extends JimpleCode2CpgFixture { | ||
private val config = Config().withDisableFileContent(false) | ||
|
||
"a Java source code CPG" should { | ||
implicit val finder: NodeExtensionFinder = DefaultNodeExtensionFinder | ||
val cpg: Cpg = code( | ||
""" | ||
|public class Foo { | ||
| | ||
| public void test() { | ||
| var a = 1; | ||
| var b = 2; | ||
| var c = a + b; | ||
| } | ||
| | ||
|} | ||
|""".stripMargin, | ||
"Foo.java" | ||
) | ||
.withConfig(config) | ||
.cpg | ||
|
||
"allow one to get decompiled java code in one file" in { | ||
inside(cpg.file.name(".*Foo.class").l) { | ||
case decompiledJava :: Nil => | ||
decompiledJava.content.linesIterator.map(_.strip).l shouldBe List( | ||
"/*", | ||
"* Decompiled with CFR 0.152.", | ||
"*/", | ||
"public class Foo {", | ||
"public void test() {", | ||
"int a = 1;", | ||
"int b = 2;", | ||
"int c = a + b;", | ||
"}", | ||
"}" | ||
) | ||
case content => fail(s"Expected exactly 1 file") | ||
} | ||
} | ||
} | ||
|
||
"Java Source CPG across multiple files" should { | ||
implicit val finder: NodeExtensionFinder = DefaultNodeExtensionFinder | ||
val cpg: Cpg = code( | ||
""" | ||
|package bar; | ||
|public class Foo { | ||
| public void test() { | ||
| var a = 1; | ||
| var b = 2; | ||
| var c = a + b; | ||
| } | ||
|} | ||
|""".stripMargin, | ||
"Foo.java" | ||
).moreCode( | ||
""" | ||
|package bar; | ||
|public class Baz { | ||
| public void bazTest() { | ||
| var fooObj = new Foo(); | ||
| var b = 2; | ||
| } | ||
|} | ||
|""".stripMargin, | ||
"Baz.java" | ||
).withConfig(config) | ||
.cpg | ||
|
||
"allow one to get java decompiled code for all classes" in { | ||
inside(cpg.file.name(".*Foo.class").l) { | ||
case decompiledJavaFoo :: Nil => | ||
decompiledJavaFoo.content.linesIterator.map(_.strip).filter(_.nonEmpty).l shouldBe List( | ||
"/*", | ||
"* Decompiled with CFR 0.152.", | ||
"*/", | ||
"package bar;", | ||
"public class Foo {", | ||
"public void test() {", | ||
"int a = 1;", | ||
"int b = 2;", | ||
"int c = a + b;", | ||
"}", | ||
"}" | ||
) | ||
|
||
case _ => fail("Expected exactly 1 file") | ||
} | ||
|
||
inside(cpg.file.name(".*Baz.class").l) { | ||
case decompiledJavaBaz :: Nil => | ||
decompiledJavaBaz.content.linesIterator.map(_.strip).filter(_.nonEmpty).l shouldBe List( | ||
"/*", | ||
"* Decompiled with CFR 0.152.", | ||
"*/", | ||
"package bar;", | ||
"import bar.Foo;", | ||
"public class Baz {", | ||
"public void bazTest() {", | ||
"Foo fooObj = new Foo();", | ||
"int b = 2;", | ||
"}", | ||
"}" | ||
) | ||
case _ => fail("Expected exactly 1 file") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters