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

[TypeRecovery] Re-work for Fixed Iteration Strategy & Using POSSIBLE_TYPES #3733

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
70e3abe
Added toString and copy to Symbol Table
DavidBakerEffendi Oct 6, 2023
b37129d
JavaScript passing, Python has a few tests failing, Java and Ruby nee…
DavidBakerEffendi Oct 6, 2023
07ccb59
Compiling again, and now each task gets a method with a fresh diff gr…
DavidBakerEffendi Oct 9, 2023
532dc58
Everything passes and is a little bit faster
DavidBakerEffendi Oct 9, 2023
facfa41
Migrated to use `POSSIBLE_TYPES`
DavidBakerEffendi Oct 10, 2023
22f57d7
Merge remote-tracking branch 'origin/master' into dave/type-recovery/…
DavidBakerEffendi Oct 10, 2023
06c179c
Added missing `this` receivers to dynamic calls in ruby
DavidBakerEffendi Oct 10, 2023
0f3007b
Added missing `this` receivers to dynamic calls in ruby
DavidBakerEffendi Oct 10, 2023
cc5c1a3
Tests are working
DavidBakerEffendi Oct 11, 2023
ed99b0f
Formatting
DavidBakerEffendi Oct 11, 2023
7c7afd2
Formatting
DavidBakerEffendi Oct 11, 2023
f193414
Finished rebase
DavidBakerEffendi Oct 11, 2023
97b40d6
Make task creation lazier to avoid filling memory up with symbol tabl…
DavidBakerEffendi Oct 11, 2023
587f3fb
See if flaky test is fixed after an additional iteration
DavidBakerEffendi Oct 11, 2023
3fc6687
See if flaky test is fixed after an additional iteration for JS
DavidBakerEffendi Oct 11, 2023
3cf1671
Use fixed parallelism for better predictability of resource usage.
DavidBakerEffendi Oct 12, 2023
fe1217f
More tweaks to ensure determinism
DavidBakerEffendi Oct 12, 2023
e59d535
WIP: Check if method parallelism is causing flakiness
DavidBakerEffendi Oct 12, 2023
99d90a3
WIP
DavidBakerEffendi Oct 13, 2023
f0b776e
Bringing across changes from PR #3750
DavidBakerEffendi Oct 23, 2023
8f1c98c
Merge remote-tracking branch 'origin/master' into dave/type-recovery/…
DavidBakerEffendi Oct 23, 2023
a0cae53
Python tests passing, restructured one, removed an unnecessary one
DavidBakerEffendi Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.joern.pysrc2cpg.*
import io.joern.x2cpg.X2Cpg
import io.joern.x2cpg.passes.base.AstLinkerPass
import io.joern.x2cpg.passes.callgraph.NaiveCallLinker
import io.joern.x2cpg.passes.frontend.XTypeRecoveryConfig
import io.joern.x2cpg.passes.frontend.TypeRecoveryConfig
import io.shiftleft.codepropertygraph.Cpg

import java.nio.file.Path
Expand All @@ -32,8 +32,8 @@ case class PythonSrcCpgGenerator(config: FrontendConfig, rootPath: Path) extends
new DynamicTypeHintFullNamePass(cpg).createAndApply()
new PythonInheritanceNamePass(cpg).createAndApply()
val typeRecoveryConfig = pyConfig match
case Some(config) => XTypeRecoveryConfig(config.typePropagationIterations, !config.disableDummyTypes)
case None => XTypeRecoveryConfig()
case Some(config) => TypeRecoveryConfig(config.typePropagationIterations, !config.disableDummyTypes)
case None => TypeRecoveryConfig()
new PythonTypeRecoveryPass(cpg, typeRecoveryConfig).createAndApply()
new PythonTypeHintCallLinker(cpg).createAndApply()
new NaiveCallLinker(cpg).createAndApply()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.joern.javasrc2cpg.passes.{
TypeInferencePass
}
import io.joern.x2cpg.X2Cpg.withNewEmptyCpg
import io.joern.x2cpg.passes.frontend.{MetaDataPass, TypeNodePass, XTypeRecoveryConfig}
import io.joern.x2cpg.passes.frontend.{MetaDataPass, TypeNodePass, TypeRecoveryConfig}
import io.joern.x2cpg.X2CpgFrontend
import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.codepropertygraph.generated.Languages
Expand Down Expand Up @@ -57,7 +57,7 @@ object JavaSrc2Cpg {

def typeRecoveryPasses(cpg: Cpg, config: Option[Config] = None): List[CpgPassBase] = {
List(
new JavaTypeRecoveryPass(cpg, XTypeRecoveryConfig(enabledDummyTypes = !config.exists(_.disableDummyTypes))),
new JavaTypeRecoveryPass(cpg, TypeRecoveryConfig(enabledDummyTypes = !config.exists(_.disableDummyTypes))),
new JavaTypeHintCallLinker(cpg)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import io.joern.javasrc2cpg.util.{Delombok, SourceParser}
import io.joern.javasrc2cpg.{Config, JavaSrc2Cpg}
import io.joern.x2cpg.SourceFiles
import io.joern.x2cpg.datastructures.Global
import io.joern.x2cpg.passes.frontend.XTypeRecoveryConfig
import io.joern.x2cpg.passes.frontend.TypeRecoveryConfig
import io.joern.x2cpg.utils.dependency.DependencyResolver
import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.passes.ConcurrentWriterCpgPass
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,86 @@
package io.joern.javasrc2cpg.passes

import io.joern.x2cpg.Defines
import io.joern.x2cpg.passes.frontend._
import io.joern.x2cpg.passes.frontend.*
import io.joern.x2cpg.passes.frontend.ImportsPass.ResolvedImport
import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.codepropertygraph.generated.PropertyNames
import io.shiftleft.codepropertygraph.generated.nodes._
import io.shiftleft.semanticcpg.language._
import io.shiftleft.codepropertygraph.generated.nodes.*
import overflowdb.BatchedUpdate.DiffGraphBuilder
import overflowdb.traversal.ImplicitsTmp.toTraversalSugarExt

class JavaTypeRecoveryPass(cpg: Cpg, config: XTypeRecoveryConfig = XTypeRecoveryConfig())
extends XTypeRecoveryPass[Method](cpg, config) {
override protected def generateRecoveryPass(state: XTypeRecoveryState): XTypeRecovery[Method] =
new JavaTypeRecovery(cpg, state)
}

private class JavaTypeRecovery(cpg: Cpg, state: XTypeRecoveryState) extends XTypeRecovery[Method](cpg, state) {

override def compilationUnit: Iterator[Method] = cpg.method.isExternal(false).iterator
import java.util.concurrent.ExecutorService

override def generateRecoveryForCompilationUnitTask(
unit: Method,
builder: DiffGraphBuilder
): RecoverForXCompilationUnit[Method] = {
val newConfig = state.config.copy(enabledDummyTypes = state.isFinalIteration && state.config.enabledDummyTypes)
new RecoverForJavaFile(cpg, unit, builder, state.copy(config = newConfig))
}
class JavaTypeRecoveryPass(cpg: Cpg, config: TypeRecoveryConfig = TypeRecoveryConfig())
extends XTypeRecoveryPass(cpg, config) {
override protected def generateRecoveryPass(state: TypeRecoveryState, executor: ExecutorService): XTypeRecovery =
new JavaTypeRecovery(cpg, state, executor)
}

private class RecoverForJavaFile(cpg: Cpg, cu: Method, builder: DiffGraphBuilder, state: XTypeRecoveryState)
extends RecoverForXCompilationUnit[Method](cpg, cu, builder, state) {
private class JavaTypeRecovery(cpg: Cpg, state: TypeRecoveryState, executor: ExecutorService)
extends XTypeRecovery(cpg, state, executor) {

override protected val initialSymbolTable = new SymbolTable[LocalKey](javaNodeToLocalKey)

private def javaNodeToLocalKey(n: AstNode): Option[LocalKey] = n match {
case i: Identifier if i.name == "this" && i.code == "super" => Option(LocalVar("super"))
case _ => SBKey.fromNodeToLocalKey(n)
}

override protected val symbolTable = new SymbolTable[LocalKey](javaNodeToLocalKey)
override protected def recoverTypesForProcedure(
cpg: Cpg,
procedure: Method,
initialSymbolTable: SymbolTable[LocalKey],
builder: DiffGraphBuilder,
state: TypeRecoveryState
): RecoverTypesForProcedure =
RecoverForJavaFile(cpg, procedure, initialSymbolTable, builder, state)

override protected def isConstructor(c: Call): Boolean = isConstructor(c.name)
override protected def importNodes(cu: File): List[ResolvedImport] =
cu.namespaceBlock.flatMap(_.astOut).collectAll[Import].flatMap(visitImport).l

override protected def isConstructor(name: String): Boolean = !name.isBlank && name.charAt(0).isUpper
// Java has a much simpler import structure that doesn't need resolution
override protected def visitImport(i: Import): Iterator[ImportsPass.ResolvedImport] = {
for {
alias <- i.importedAs
fullName <- i.importedEntity
} {
if (alias != "*") {
initialSymbolTable.append(CallAlias(alias, Option("this")), fullName)
initialSymbolTable.append(LocalVar(alias), fullName)
}
}
Iterator.empty
}

override protected def postVisitImports(): Unit = {
symbolTable.view.foreach { case (k, ts) =>
initialSymbolTable.view.foreach { case (k, ts) =>
val tss = ts.filterNot(_.startsWith(Defines.UnresolvedNamespace))
if (tss.isEmpty)
symbolTable.remove(k)
initialSymbolTable.remove(k)
else
symbolTable.put(k, tss)
initialSymbolTable.put(k, tss)
}
}

}

private class RecoverForJavaFile(
cpg: Cpg,
procedure: Method,
symbolTable: SymbolTable[LocalKey],
builder: DiffGraphBuilder,
state: TypeRecoveryState
) extends RecoverTypesForProcedure(cpg, procedure, symbolTable, builder, state) {

override protected def isConstructor(c: Call): Boolean = isConstructor(c.name)

override protected def isConstructor(name: String): Boolean = !name.isBlank && name.charAt(0).isUpper

// There seems to be issues with inferring these, often due to situations where super and this are confused on name
// and code properties.
override protected def storeIdentifierTypeInfo(i: Identifier, types: Seq[String]): Unit = if (i.name != "this") {
super.storeIdentifierTypeInfo(i, types)
super.storeIdentifierTypeInfo(i, types.filterNot(_ == "null"))
}

override protected def storeCallTypeInfo(c: Call, types: Seq[String]): Unit =
Expand All @@ -64,7 +90,9 @@ private class RecoverForJavaFile(cpg: Cpg, cu: Method, builder: DiffGraphBuilder
case t if t.endsWith(c.signature) => t
case t => s"$t:${c.signature}"
}
builder.setNodeProperty(c, PropertyNames.DYNAMIC_TYPE_HINT_FULL_NAME, signedTypes)
if (c.possibleTypes != signedTypes) {
builder.setNodeProperty(c, PropertyNames.POSSIBLE_TYPES, signedTypes)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,16 @@ class JavaTypeRecoveryPassTests extends JavaSrcCode2CpgFixture(enableTypeRecover
Seq("net", "javaguides", "hibernate", "NamedQueryExample.java").mkString(File.separator)
)

"should be resolved using dummy return values" in {
"receive a full namespace from Java inference" in {
val Some(getResultList) = cpg.call("createNamedQuery").headOption: @unchecked
getResultList.methodFullName shouldBe "org.hibernate.Session.createNamedQuery:<unresolvedSignature>(2)"
}

"resolve the second call using dummy return values" in {
val Some(getResultList) = cpg.call("getResultList").headOption: @unchecked
// Changes the below from <unresolvedNamespace>.getResultList:<unresolvedSignature>(0) to:
getResultList.methodFullName shouldBe "org.hibernate.Session.createNamedQuery:<unresolvedSignature>(2).<returnValue>.getResultList:<unresolvedSignature>(0)"
getResultList.dynamicTypeHintFullName shouldBe Seq()
getResultList.possibleTypes shouldBe Seq()
}

"hint that `transaction` may be of the null type" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.joern.jssrc2cpg.utils.AstGenRunner
import io.joern.x2cpg.X2Cpg.withNewEmptyCpg
import io.joern.x2cpg.X2CpgFrontend
import io.joern.x2cpg.passes.callgraph.NaiveCallLinker
import io.joern.x2cpg.passes.frontend.XTypeRecoveryConfig
import io.joern.x2cpg.passes.frontend.TypeRecoveryConfig
import io.joern.x2cpg.utils.{HashUtil, Report}
import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.passes.CpgPassBase
Expand Down Expand Up @@ -58,8 +58,8 @@ object JsSrc2Cpg {

def postProcessingPasses(cpg: Cpg, config: Option[Config] = None): List[CpgPassBase] = {
val typeRecoveryConfig = config
.map(c => XTypeRecoveryConfig(c.typePropagationIterations, !c.disableDummyTypes))
.getOrElse(XTypeRecoveryConfig())
.map(c => TypeRecoveryConfig(c.typePropagationIterations, !c.disableDummyTypes))
.getOrElse(TypeRecoveryConfig())
List(
new JavaScriptInheritanceNamePass(cpg),
new ConstClosurePass(cpg),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ class ImportResolverPass(cpg: Cpg) extends XImportResolverPass(cpg) {
if (methodMatches.nonEmpty) methodMatches.fullName.toSet
else constructorMatches.fullName.toSet
if (methodPaths.nonEmpty) {
methodPaths.flatMap(x => Set(ResolvedMethod(x, alias, Option("this")), ResolvedTypeDecl(x)))
methodPaths.flatMap(x => Set(ResolvedMethod(x, alias, Option("this")), ResolvedTypeDecl(x, alias)))
} else if (moduleExportsThisVariable) {
Set(ResolvedMember(targetModule.fullName.head, b.name))
Set(ResolvedMember(targetModule.fullName.head, b.name, alias))
} else {
Set.empty
}
Expand All @@ -108,7 +108,7 @@ class ImportResolverPass(cpg: Cpg) extends XImportResolverPass(cpg) {
b.referencedMethod.astParent.iterator
.collectAll[Method]
.fullName
.map(x => ResolvedTypeDecl(x))
.map(x => ResolvedTypeDecl(x, alias))
.toSet ++ Set(ResolvedMethod(b.methodFullName, callName, receiver))
case ::(_, ::(y: Call, _)) =>
// Exported closure with a method ref within the AST of the RHS
Expand All @@ -118,7 +118,7 @@ class ImportResolverPass(cpg: Cpg) extends XImportResolverPass(cpg) {
}
}.toSet
} else {
Set(UnknownMethod(entity, alias, Option("this")), UnknownTypeDecl(entity))
Set(UnknownMethod(entity, alias, Option("this")), UnknownTypeDecl(entity, alias))
}).foreach(x => resolvedImportToTag(x, importCall, diffGraph))
}

Expand Down
Loading
Loading