Skip to content

Commit

Permalink
enhance checking for unresolved variables
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxMilshin committed Jun 9, 2024
1 parent db7f95f commit 8a737a8
Show file tree
Hide file tree
Showing 10 changed files with 2,128 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import org.jacodb.panda.dynamic.api.*

private val logger = mu.KotlinLogging.logger {}

class ArgumentParameterMatchingAnalyser(val project: PandaProject) {
class ArgumentParameterMatchingChecker(val project: PandaProject) {
val graph = PandaApplicationGraphImpl(project)

fun analyseOneCase(startMethods: List<String>): List<Pair<PandaInst, PandaMethod>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ enum class ImplicitCastAnalysisMode {
POSSIBILITY_CHECK
}

class ImplicitCastingAnalyser(val project: PandaProject) {
class ImplicitCastingChecker(val project: PandaProject) {
val graph = PandaApplicationGraphImpl(project)

fun analyseOneCase(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import org.jacodb.panda.dynamic.api.*

private val logger = mu.KotlinLogging.logger {}

class MissingMembersAnalyser(val project: PandaProject) {
class MissingMembersChecker(val project: PandaProject) {
val graph = PandaApplicationGraphImpl(project)

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ import org.jacodb.panda.dynamic.api.*

private val logger = mu.KotlinLogging.logger {}

class UndeclaredVariablesAnalyser(val project: PandaProject) {
enum class VarAccess {
READ, WRITE
}

class UndeclaredVariablesCheckerError(
val varName: String,
val inst: PandaInst,
val varAccess: VarAccess,
val isConstant: Lazy<Boolean>
)

class UndeclaredVariablesChecker(val project: PandaProject) {
val graph = PandaApplicationGraphImpl(project)

private val orderedInstructions = mutableListOf<PandaInst>()
Expand Down Expand Up @@ -55,13 +66,14 @@ class UndeclaredVariablesAnalyser(val project: PandaProject) {
orderedInstructions.reverse()
}

// TODO(): expand for writing (trysttoglobalbyname) and constants (stconsttoglobalbyname)
fun analyse(): List<Pair<String, PandaInst>> {
fun analyse(): List<UndeclaredVariablesCheckerError> {
topologicalSort(graph)

val instToGlobalVars = mutableMapOf<PandaInst, MutableSet<String>>()

val unresolvedVariables = mutableListOf<Pair<String, PandaInst>>()
val isVarConstantMap = mutableMapOf<String, Boolean>()

val unresolvedVariables = mutableListOf<UndeclaredVariablesCheckerError>()

for (inst in orderedInstructions) {
var predecessorInstructions = mutableListOf<PandaInst>()
Expand All @@ -78,24 +90,79 @@ class UndeclaredVariablesAnalyser(val project: PandaProject) {
instToGlobalVars[inst]!!.intersect(instToGlobalVars[predecessorInst]!!)
}
if (inst is PandaAssignInst && inst.varName != null) {
instToGlobalVars[inst]!!.add(inst.varName!!)
val varName = inst.varName!!
val name = when {
varName.startsWith("constant.") -> {
val slicedName = varName.substring(9)
isVarConstantMap[slicedName] = true
slicedName
}
else -> varName
}
instToGlobalVars[inst]!!.add(name)
}
// adhoc check for tryldglobalname, TODO(): check trysttoglobalname (for both will be cooler after better global variable processing)

// TODO("refactor after better global variable processing in IR")
// check for tryldglobalname
val probablyUndefinedVarNames = inst.recursiveOperands.mapNotNull { op ->
if (op is PandaLoadedValue && op.instance is PandaStringConstant) {
((op.instance) as PandaStringConstant).value
Pair(
((op.instance) as PandaStringConstant).value,
VarAccess.READ
)
} else null
}.toMutableList()

// check for trystglobalbyname
if (inst is PandaAssignInst && inst.lhv is PandaLoadedValue) {
val lhvInstance = (inst.lhv as PandaLoadedValue).instance
if (lhvInstance is PandaStringConstant) {
probablyUndefinedVarNames.add(
Pair(
lhvInstance.value,
VarAccess.WRITE
)
)
}
}

val stdVarNames = listOf("console") // TODO(): need more smart filter
probablyUndefinedVarNames.forEach { varName ->
probablyUndefinedVarNames.forEach { varInfo ->
val varName = varInfo.first
val varAccess = varInfo.second
if (varName !in stdVarNames && varName !in instToGlobalVars[inst]!!) {
unresolvedVariables.add(Pair(varName, inst))
logger.info { "unresolved variable $varName in $inst with location: (method:${inst.location.method}, index: ${inst.location.index})" }
unresolvedVariables.add(
UndeclaredVariablesCheckerError(
varName = varName,
inst = inst,
varAccess = varAccess,
isConstant = lazy {
isVarConstantMap.getOrDefault(varName, false)
}
)
)
}
}
}

val varAccessToStr: (VarAccess) -> String = { varAccess ->
when(varAccess) {
VarAccess.WRITE -> "Write"
VarAccess.READ -> "Read"
}
}

val isVarConstantToString: (Boolean) -> String = { isVarConstant ->
when(isVarConstant) {
true -> "constant"
false -> "regular"
}
}

unresolvedVariables.forEach { err ->
logger.info { "${varAccessToStr(err.varAccess)} access to undeclared ${isVarConstantToString(err.isConstant.value)} variable ${err.varName} in ${err.inst} (location: ${err.inst.location})" }
}

return unresolvedVariables
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,14 @@ class IRParser(
}
val localVar = inputs[0]
method.nameToLocalVarId[variableName] = localVar
// tmp
if (inputs[0] is PandaConstant) {
val lv = PandaLocalVar(method.currentLocalVarId++, PandaAnyType)
val assignment = PandaAssignInst(locationFromOp(this), lv, inputs[0], varName = "constant.${stringData!!}")
method.pushInst(assignment)
program!!.setLocalAssignment(method.signature, lv, assignment)
env.setLocalVar(stringData!!, lv)
}
}

"Intrinsic.callthis0" -> {
Expand Down Expand Up @@ -1125,9 +1133,12 @@ class IRParser(
}

"Intrinsic.trystglobalbyname" -> {
val lv = env.getLocalVar(stringData!!)
?: error("Can't load local var from environment for literal \"$stringData\"")
method.pushInst(PandaAssignInst(locationFromOp(this), lv, inputs[0]))
val name = stringData!!
// val lv = env.getLocalVar(name)
// ?: error("Can't load local var from environment for literal \"$stringData\"")
val assignee = env.getLocalVar(name)
?: PandaLoadedValue(PandaStringConstant(name))
method.pushInst(PandaAssignInst(locationFromOp(this), assignee, inputs[0]))
}

else -> checkIgnoredInstructions(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@

package analysis

import org.jacodb.panda.dynamic.api.PandaProject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import panda.primary.*
import parser.loadIr

class PrimaryStaticAnalysisTest {
private fun getProjectByProgramName(programName: String): PandaProject {
val parser = loadIr("/samples/${programName}.json")
return parser.getProject()
}

@Nested
inner class ArgumentParameterCorrespondenceTest {
private val programName = "codeqlSamples/parametersArgumentsMismatch"
private val parser = loadIr("/samples/${programName}.json")
private val project = parser.getProject()
private val analyser = ArgumentParameterMatchingAnalyser(project)
private val project = getProjectByProgramName(programName)
private val analyser = ArgumentParameterMatchingChecker(project)

@Test
fun `test for mismatch detection in regular function call`() {
Expand Down Expand Up @@ -74,24 +79,39 @@ class PrimaryStaticAnalysisTest {

@Nested
inner class UnresolvedVariableTest {
private val programName = "codeqlSamples/unresolvedVariable"
private val parser = loadIr("/samples/${programName}.json")
private val project = parser.getProject()
private val analyser = UndeclaredVariablesAnalyser(project)
private fun getAnalyserByProgramName(programName: String): UndeclaredVariablesChecker {
val project = getProjectByProgramName(programName)
val analyser = UndeclaredVariablesChecker(project)
return analyser
}

@Test
fun `counterexample - program that read some unresolved variables`() {
val analyser = getAnalyserByProgramName("codeqlSamples/unresolvedVariable")
val unresolvedVariables = analyser.analyse()
assert(unresolvedVariables.size == 4)
}

@Test
fun `counterexample - program that also write into unresolved variables`() {
val analyser = getAnalyserByProgramName("codeqlSamples/unresolvedVariable2")
val unresolvedVariables = analyser.analyse()
assert(unresolvedVariables.size == 3)
}

@Test
fun `counterexample - program that read undeclared const variables`() {
val analyser = getAnalyserByProgramName("codeqlSamples/unresolvedVariable3")
val unresolvedVariables = analyser.analyse()
assert(unresolvedVariables.size == 3)
}
}

@Nested
inner class ImplicitCastingTest {
private val programName = "codeqlSamples/implicitCasting"
private val parser = loadIr("/samples/${programName}.json")
private val project = parser.getProject()
private val analyser = ImplicitCastingAnalyser(project)
private val project = getProjectByProgramName(programName)
private val analyser = ImplicitCastingChecker(project)

@Test
fun `test implicit casting observation in binary expressions with primitive literals`() {
Expand Down Expand Up @@ -132,10 +152,9 @@ class PrimaryStaticAnalysisTest {

@Nested
inner class MissingMembersTest {
private fun getAnalyserByProgramName(programName: String): MissingMembersAnalyser {
val parser = loadIr("/samples/${programName}.json")
val project = parser.getProject()
val analyser = MissingMembersAnalyser(project)
private fun getAnalyserByProgramName(programName: String): MissingMembersChecker {
val project = getProjectByProgramName(programName)
val analyser = MissingMembersChecker(project)
return analyser
}

Expand Down
Loading

0 comments on commit 8a737a8

Please sign in to comment.