diff --git a/gradle.properties b/gradle.properties index 97b00b0..d315d0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,5 @@ seqraOrg=seqra seqraBuildVersion=2025.10.01.6b66511 seqraIrVersion=2026.02.01.c54b6cc -seqraConfigVersion=2026.02.01.02e636c +seqraConfigVersion=2026.03.01.a18a707 seqraUtilVersion=2026.02.01.44bb600 diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/MethodEdgesInitialToFinalTreeApSet.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/MethodEdgesInitialToFinalTreeApSet.kt index e0e5cff..702c12b 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/MethodEdgesInitialToFinalTreeApSet.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/ap/ifds/access/tree/MethodEdgesInitialToFinalTreeApSet.kt @@ -88,7 +88,11 @@ class MethodEdgesInitialToFinalTreeApSet( val currentAccess = edges[edgeSetIdx]!! val mergedAccess = currentAccess.mergeAdd(accessWithExclusion.access) - if (mergedAccess === currentAccess) return null + if (mergedAccess === currentAccess) { + if (mergedExclusion === currentExclusion) return null + + return AccessWithExclusion(mergedAccess, mergedExclusion) + } edges[edgeSetIdx] = mergedAccess return AccessWithExclusion(mergedAccess, mergedExclusion) diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphTopSort.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphTopSort.kt index 4a1e07f..94e83e9 100644 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphTopSort.kt +++ b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/graph/CompactGraphTopSort.kt @@ -30,14 +30,15 @@ class CompactGraphTopSort(val graph: CompactGraph) { fun graphTopOrderNodeComparator(startNode: Int): IntComparator { val topOrderPriority = graphTopOrderNodePriority(startNode) return IntComparator { k1: Int, k2: Int -> - val k1Priority = topOrderPriority.get(k1) - check(k1Priority != NO_NODE) { - "Node: $k1 missed in top order" + var k1Priority = topOrderPriority.get(k1) + if (k1Priority == NO_NODE) { + // todo: consider better approach, maybe better analyze exceptional flows + k1Priority = graph.size + k1 } - val k2Priority = topOrderPriority.get(k2) - check(k2Priority != NO_NODE) { - "Node: $k2 missed in top order" + var k2Priority = topOrderPriority.get(k2) + if (k2Priority == NO_NODE) { + k2Priority = graph.size + k2 } k1Priority.compareTo(k2Priority) diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/sarif/SourceFileResolver.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/sarif/SourceFileResolver.kt deleted file mode 100644 index a7f9fee..0000000 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/sarif/SourceFileResolver.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.seqra.dataflow.sarif - -import org.seqra.ir.api.common.cfg.CommonInst -import java.nio.file.Path - -interface SourceFileResolver { - fun resolveByName(inst: Statement, pkg: String, name: String): Path? - - fun resolveByInst(inst: Statement): Path? - - fun relativeToRoot(path: Path): String -} diff --git a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/util/SarifTraits.kt b/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/util/SarifTraits.kt deleted file mode 100644 index a8f8837..0000000 --- a/seqra-dataflow/src/main/kotlin/org/seqra/dataflow/util/SarifTraits.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.seqra.dataflow.util - -import org.seqra.ir.api.common.CommonMethod -import org.seqra.ir.api.common.cfg.CommonInst -import org.seqra.ir.api.common.cfg.CommonCallExpr -import org.seqra.ir.api.common.cfg.CommonAssignInst -import org.seqra.ir.api.common.cfg.CommonExpr - -interface SarifTraits - where Method : CommonMethod, - Statement : CommonInst { - - fun getCallee(callExpr: CommonCallExpr): Method - fun getCalleeClassName(callExpr: CommonCallExpr): String - fun getMethodClassName(md: @UnsafeVariance Method): String - fun getCallExpr(statement: @UnsafeVariance Statement): CommonCallExpr? - fun getAssign(statement: @UnsafeVariance Statement): CommonAssignInst? - fun getReadableValue(statement: @UnsafeVariance Statement, expr: CommonExpr): String? - fun getReadableAssignee(statement: @UnsafeVariance Statement): String? - - data class LocalInfo(val idx: Int, val name: String) - fun getLocals(expr: CommonExpr): List - fun isRegister(name: String): Boolean - fun getLocalName(md: @UnsafeVariance Method, index: Int): String? - fun printArgumentNth(index: Int, methodName: String? = null): String - fun printArgument(method: @UnsafeVariance Method, index: Int): String - fun printThis(statement: @UnsafeVariance Statement): String - - fun lineNumber(statement: @UnsafeVariance Statement): Int - fun locationFQN(statement: @UnsafeVariance Statement): String - fun locationMachineName(statement: @UnsafeVariance Statement): String -} diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallPositionResolver.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallPositionResolver.kt index 1407dbd..b8d860c 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallPositionResolver.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRCallPositionResolver.kt @@ -1,5 +1,15 @@ package org.seqra.dataflow.jvm.ap.ifds +import org.seqra.dataflow.configuration.jvm.Argument +import org.seqra.dataflow.configuration.jvm.ClassStatic +import org.seqra.dataflow.configuration.jvm.Position +import org.seqra.dataflow.configuration.jvm.PositionResolver +import org.seqra.dataflow.configuration.jvm.PositionWithAccess +import org.seqra.dataflow.configuration.jvm.Result +import org.seqra.dataflow.configuration.jvm.This +import org.seqra.dataflow.jvm.util.isVararg +import org.seqra.dataflow.jvm.util.thisInstance +import org.seqra.dataflow.jvm.util.varargParamIdx import org.seqra.ir.api.jvm.JIRClasspath import org.seqra.ir.api.jvm.JIRMethod import org.seqra.ir.api.jvm.JIRParameter @@ -10,43 +20,47 @@ import org.seqra.ir.api.jvm.cfg.JIRImmediate import org.seqra.ir.api.jvm.cfg.JIRInstanceCallExpr import org.seqra.ir.api.jvm.cfg.JIRValue import org.seqra.ir.api.jvm.ext.toType -import org.seqra.dataflow.configuration.jvm.Argument -import org.seqra.dataflow.configuration.jvm.ClassStatic -import org.seqra.dataflow.configuration.jvm.Position -import org.seqra.dataflow.configuration.jvm.PositionResolver -import org.seqra.dataflow.configuration.jvm.PositionWithAccess -import org.seqra.dataflow.configuration.jvm.Result -import org.seqra.dataflow.configuration.jvm.This -import org.seqra.dataflow.jvm.util.thisInstance -import org.seqra.util.Maybe -import org.seqra.util.fmap -import org.seqra.util.toMaybe + +sealed interface CallPositionValue { + data object None : CallPositionValue + data class Value(val value: JIRValue) : CallPositionValue + data class VarArgValue(val value: JIRValue) : CallPositionValue +} class CallPositionToJIRValueResolver( private val callExpr: JIRCallExpr, private val returnValue: JIRImmediate? -) : PositionResolver> { - override fun resolve(position: Position): Maybe = when (position) { - is Argument -> callExpr.args.getOrNull(position.index).toMaybe() - This -> (callExpr as? JIRInstanceCallExpr)?.instance.toMaybe() - Result -> returnValue.toMaybe() - is PositionWithAccess -> Maybe.none() // todo? - is ClassStatic -> Maybe.none() +) : PositionResolver { + override fun resolve(position: Position): CallPositionValue = when (position) { + is Argument -> callExpr.args.getOrNull(position.index) + .toCallArgValue(callExpr.method.method, position.index) + + is This -> (callExpr as? JIRInstanceCallExpr)?.instance.toCallValue() + is Result -> returnValue.toCallValue() + + is PositionWithAccess, // todo? + is ClassStatic -> CallPositionValue.None } } class CalleePositionToJIRValueResolver( private val method: JIRMethod -) : PositionResolver> { +) : PositionResolver { private val cp = method.enclosingClass.classpath - override fun resolve(position: Position): Maybe = when (position) { - is Argument -> cp.getArgument(method.parameters[position.index]).toMaybe() - This -> method.thisInstance.toMaybe() - is PositionWithAccess -> resolve(position.base).fmap { TODO() } + override fun resolve(position: Position): CallPositionValue = when (position) { + is Argument -> method.parameters.getOrNull(position.index) + ?.let { cp.getArgument(it) } + .toCallArgValue(method, position.index) + + is This -> method.thisInstance.toCallValue() + + // todo + is PositionWithAccess -> CallPositionValue.None + // Inapplicable callee positions - Result -> Maybe.none() - is ClassStatic -> Maybe.none() + is Result, + is ClassStatic -> CallPositionValue.None } private fun JIRClasspath.getArgument(param: JIRParameter): JIRArgument? { @@ -66,3 +80,14 @@ class JIRMethodPositionBaseTypeResolver(private val method: JIRMethod) : Positio is ClassStatic -> null } } + +private fun JIRValue?.toCallArgValue(method: JIRMethod, argumentIdx: Int): CallPositionValue { + val value = this ?: return CallPositionValue.None + if (method.isVararg() && argumentIdx == method.varargParamIdx()) { + return CallPositionValue.VarArgValue(value) + } + return CallPositionValue.Value(value) +} + +private fun JIRValue?.toCallValue(): CallPositionValue = + this?.let { CallPositionValue.Value(it) } ?: CallPositionValue.None diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt index af0cd96..947d0f5 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRLocalAliasAnalysis.kt @@ -32,7 +32,7 @@ class JIRLocalAliasAnalysis( instIdx: Int, base: AccessPathBase.LocalVar ): List? = alias[instIdx].getOrDefault(base.idx, null)?.filter { - it.accessors.isNotEmpty() || it.base != base + it !is AliasApInfo || it.accessors.isNotEmpty() || it.base != base } fun findAlias(base: AccessPathBase.LocalVar, statement: CommonInst): List? { @@ -40,6 +40,11 @@ class JIRLocalAliasAnalysis( return getLocalVarAliases(aliasInfo.aliasBeforeStatement, idx, base) } + fun getAllAliasAtStatement(statement: CommonInst): Int2ObjectOpenHashMap> { + val idx = languageManager.getInstIndex(statement) + return aliasInfo.aliasBeforeStatement[idx] + } + fun findAliasAfterStatement(base: AccessPathBase.LocalVar, statement: CommonInst): List? { val idx = languageManager.getInstIndex(statement) return getLocalVarAliases(aliasInfo.aliasAfterStatement, idx, base) @@ -50,14 +55,11 @@ class JIRLocalAliasAnalysis( } sealed interface AliasAccessor { - data class Field( - val className: String, - val fieldName: String, - val fieldType: String - ) : AliasAccessor - + data class Field(val className: String, val fieldName: String, val fieldType: String) : AliasAccessor data object Array : AliasAccessor } - data class AliasInfo(val base: AccessPathBase, val accessors: List = emptyList()) + sealed interface AliasInfo + data class AliasApInfo(val base: AccessPathBase, val accessors: List): AliasInfo + data class AliasAllocInfo(val allocInst: Int): AliasInfo } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRMarkAwareConditionRewriter.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRMarkAwareConditionRewriter.kt index 2d18675..bff4de3 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRMarkAwareConditionRewriter.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/JIRMarkAwareConditionRewriter.kt @@ -11,11 +11,9 @@ import org.seqra.dataflow.jvm.ap.ifds.analysis.JIRMethodAnalysisContext import org.seqra.dataflow.jvm.ap.ifds.taint.ContainsMarkOnAnyField import org.seqra.dataflow.jvm.ap.ifds.taint.JIRBasicAtomEvaluator import org.seqra.ir.api.common.cfg.CommonInst -import org.seqra.ir.api.jvm.cfg.JIRValue -import org.seqra.util.Maybe class JIRMarkAwareConditionRewriter( - positionResolver: PositionResolver>, + positionResolver: PositionResolver, analysisContext: JIRMethodAnalysisContext, statement: CommonInst, ) { diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt index 782f51c..d0ddce0 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/DSUAliasAnalysis.kt @@ -2,6 +2,7 @@ package org.seqra.dataflow.jvm.ap.ifds.alias import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor import org.seqra.dataflow.jvm.ap.ifds.alias.JIRIntraProcAliasAnalysis.JIRInstGraph import org.seqra.dataflow.jvm.ap.ifds.alias.RefValue.Local import org.seqra.ir.api.jvm.JIRField @@ -37,7 +38,7 @@ class DSUAliasAnalysis( ) private object RootInstEvalContext : InstEvalContext { - override fun createThis(): RefValue = RefValue.This + override fun createThis(isOuter: Boolean): RefValue = RefValue.This(isOuter) override fun createArg(idx: Int): RefValue = RefValue.Arg(idx) override fun createLocal(idx: Int): Local = Local(idx, level = 0, ctx = 0) } @@ -121,7 +122,7 @@ class DSUAliasAnalysis( data class FieldAlias( override val instance: AAInfo, - val field: JIRField, + val field: AliasAccessor.Field, override val depth: Int, override val isImmutable: Boolean, ) : HeapAlias @@ -314,10 +315,15 @@ class DSUAliasAnalysis( } private fun createFieldAliasWrtLimit(instance: AAInfo, field: JIRField): FieldAlias? { + val f = AliasAccessor.Field(field.enclosingClass.name, field.name, field.type.typeName) + return createFieldAliasWrtLimit(instance, f, field.isFinal) + } + + private fun createFieldAliasWrtLimit(instance: AAInfo, field: AliasAccessor.Field, fieldIsImmutable: Boolean): FieldAlias? { val immutability = when (instance) { is HeapAlias -> instance.isImmutable else -> true - } && field.isFinal + } && fieldIsImmutable return createHeapAliasWrtLimit(instance) { depth -> FieldAlias(instance, field, depth, immutability) } } @@ -411,9 +417,9 @@ class DSUAliasAnalysis( private fun RefValue.isOuter(): Boolean = when (this) { is Local -> false + is RefValue.This -> isOuter is RefValue.Arg, - is RefValue.Static, - is RefValue.This -> true + is RefValue.Static -> true } private fun GraphAnalysisState.mapCallFinalStates( @@ -498,7 +504,7 @@ class DSUAliasAnalysis( when (this) { is ArrayAlias -> createArrayAliasWrtLimit(instanceAlternative) - is FieldAlias -> createFieldAliasWrtLimit(instanceAlternative, field) + is FieldAlias -> createFieldAliasWrtLimit(instanceAlternative, field, isImmutable) } } } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt index 2784a78..1e628f7 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/InterProcCallNode.kt @@ -72,7 +72,7 @@ private class NestedCallInstEvalCtx(val call: Stmt.Call, val level: Int) : InstE override fun createArg(idx: Int): Value = call.args.getOrNull(idx) ?: error("Incorrect argument idx: $idx") - override fun createThis(): Value = call.instance + override fun createThis(isOuter: Boolean): Value = call.instance ?: error("Non instance call") override fun createLocal(idx: Int): Local = Local(idx, level = level, ctx = call.originalIdx) diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt index e5712c6..5e3dd18 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRDSUAABuilder.kt @@ -32,7 +32,7 @@ sealed interface Value: ExprOrValue sealed interface RefValue: Value { data class Local(val idx: Int, val level: Int, val ctx: Int) : RefValue data class Arg(val idx: Int) : RefValue - data object This : RefValue + data class This(val isOuter: Boolean) : RefValue data class Static(val type: String) : RefValue } @@ -65,7 +65,7 @@ sealed interface Stmt { } interface InstEvalContext { - fun createThis(): Value + fun createThis(isOuter: Boolean): Value fun createArg(idx: Int): Value fun createLocal(idx: Int): RefValue.Local } @@ -89,12 +89,12 @@ fun InstEvalContext.evalInst(inst: JIRInst): Stmt? { } is JIRReturnInst -> { - val value = inst.returnValue?.let { evalSimpleValue(it as JIRImmediate) } as? RefValue + val value = inst.returnValue?.let { evalSimpleValue(it as JIRImmediate, inst) } as? RefValue Stmt.Return(value, inst.location.index) } is JIRThrowInst -> { - val value = getLocalRefValue(inst.throwable) as? RefValue ?: return null + val value = getLocalRefValue(inst.throwable, inst) as? RefValue ?: return null Stmt.Throw(value, inst.location.index) } @@ -128,12 +128,12 @@ private fun InstEvalContext.evalAssign(lhv: JIRValue, rhv: JIRExpr, inst: JIRIns when (lhv) { is JIRFieldRef -> { val instance = lhv.instance ?: return Stmt.WriteStatic(lhv.field.field, expr, loc.index) - val iv = getLocalRefValue(instance) as? RefValue ?: return null + val iv = getLocalRefValue(instance, inst) as? RefValue ?: return null return Stmt.FieldStore(iv, lhv.field.field, expr, loc.index) } is JIRArrayAccess -> { - val iv = getLocalRefValue(lhv.array) as? RefValue ?: return null + val iv = getLocalRefValue(lhv.array, inst) as? RefValue ?: return null return Stmt.ArrayStore(iv, SimpleValue.Primitive, expr, loc.index) } @@ -149,10 +149,16 @@ private fun InstEvalContext.evalCall( expr: JIRCallExpr, loc: JIRInst, lValue: JIRValue?, -): Stmt { - val args = expr.args.map { evalSimpleValue(it as JIRImmediate) } +): Stmt? { val lhs = (lValue as? JIRLocalVar)?.let { createLocal(it.index) } - val instance = (expr as? JIRInstanceCallExpr)?.instance?.let { evalSimpleValue(it as JIRImmediate) } + + if (expr.method.method.isPrimitiveBoxAllocMethod()) { + if (lhs == null) return null + return Stmt.Assign(lhs, Expr.Alloc(loc), loc.location.index) + } + + val args = expr.args.map { evalSimpleValue(it as JIRImmediate, loc) } + val instance = (expr as? JIRInstanceCallExpr)?.instance?.let { evalSimpleValue(it as JIRImmediate, loc) } val stmt = Stmt.Call(expr.method.method, lhs, instance, args, loc.location.index) return stmt } @@ -166,18 +172,18 @@ private fun InstEvalContext.evalExpr(expr: JIRExpr, inst: JIRInst): ExprOrValue } private fun InstEvalContext.evalValue(value: JIRValue, loc: JIRInst): ExprOrValue = when (value) { - is JIRImmediate -> evalSimpleValue(value) + is JIRImmediate -> evalSimpleValue(value, loc) is JIRRef -> evalRefValue(value, loc) else -> Expr.Unknown } -private fun InstEvalContext.evalSimpleValue(value: JIRImmediate): Value { +private fun InstEvalContext.evalSimpleValue(value: JIRImmediate, loc: JIRInst): Value { if (value.type is JIRPrimitiveType) return SimpleValue.Primitive return when (value) { is JIRConstant -> SimpleValue.RefConst(value) is JIRThis, is JIRArgument, - is JIRLocalVar -> getLocalRefValue(value) + is JIRLocalVar -> getLocalRefValue(value, loc) else -> error("Unexpected value: $value") } @@ -192,14 +198,14 @@ private fun InstEvalContext.evalRefValue(value: JIRRef, loc: JIRInst): ExprOrVal } else { val instance = value.instance ?: return Expr.Alloc(loc) - val iv = getLocalRefValue(instance) as? RefValue ?: return Expr.Unknown + val iv = getLocalRefValue(instance, loc) as? RefValue ?: return Expr.Unknown Expr.FieldLoad(iv, value.field.field) } } is JIRArrayAccess -> { val instance = value.array - val iv = getLocalRefValue(instance) as? RefValue ?: return Expr.Unknown + val iv = getLocalRefValue(instance, loc) as? RefValue ?: return Expr.Unknown Expr.ArrayLoad(iv, SimpleValue.Primitive) } @@ -207,9 +213,25 @@ private fun InstEvalContext.evalRefValue(value: JIRRef, loc: JIRInst): ExprOrVal } } -private fun InstEvalContext.getLocalRefValue(local: JIRValue): Value = when (local) { - is JIRThis -> createThis() +private fun InstEvalContext.getLocalRefValue(local: JIRValue, loc: JIRInst): Value = when (local) { + is JIRThis -> createThis(isOuter = !loc.location.method.isConstructor) is JIRArgument -> createArg(local.index) is JIRLocalVar -> createLocal(local.index) else -> error("Unexpected local: $local") } + +private const val stdPrimitiveBoxMethodName = "valueOf" + +private val stdPrimitiveBoxes = setOf( + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.Char", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", +) + +private fun JIRMethod.isPrimitiveBoxAllocMethod(): Boolean = + name == stdPrimitiveBoxMethodName && enclosingClass.name in stdPrimitiveBoxes diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt index 56cc6f8..96efb33 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/alias/JIRIntraProcAliasAnalysis.kt @@ -6,6 +6,9 @@ import org.seqra.dataflow.graph.CompactGraph import org.seqra.dataflow.graph.MethodInstGraph import org.seqra.dataflow.jvm.ap.ifds.JIRLanguageManager import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAllocInfo +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.AAInfo import org.seqra.dataflow.jvm.ap.ifds.alias.DSUAliasAnalysis.ArrayAlias @@ -93,25 +96,28 @@ class JIRIntraProcAliasAnalysis( } private fun AAInfo.convertToAliasInfo(): AliasInfo? { - var cur = this - val accessors = mutableListOf() + val (nonHeapInfo, accessors) = convertHeapAccessors(this) + return convertBaseAccessor(nonHeapInfo, accessors) + } + + private fun convertHeapAccessors(initial: AAInfo): Pair> { + var cur = initial + val accessors = mutableListOf() while (cur is HeapAlias) { when (cur) { - is FieldAlias -> { - val field = cur.field - accessors.add( - JIRLocalAliasAnalysis.AliasAccessor.Field( - field.enclosingClass.name, - field.name, - field.type.typeName - ) - ) - } - - is ArrayAlias -> accessors.add(JIRLocalAliasAnalysis.AliasAccessor.Array) + is FieldAlias -> accessors.add(cur.field) + is ArrayAlias -> accessors.add(AliasAccessor.Array) } cur = cur.instance } + + accessors.reverse() + val optimizedAccessors = accessors.ifEmpty { emptyList() } + + return cur to optimizedAccessors + } + + private fun convertBaseAccessor(cur: AAInfo, accessors: List): AliasInfo? { val base = when (cur) { is LocalAlias.SimpleLoc -> when (val loc = cur.loc) { is Local -> AccessPathBase.LocalVar(loc.idx) @@ -119,16 +125,26 @@ class JIRIntraProcAliasAnalysis( is RefValue.This -> AccessPathBase.This is RefValue.Static -> AccessPathBase.ClassStatic(loc.type) } + is LocalAlias.Alloc -> { - val assign = cur.stmt as? Stmt.Assign - val const = assign?.expr as? SimpleValue.RefConst + val assign = cur.stmt as? Stmt.Assign ?: return null + + val const = assign.expr as? SimpleValue.RefConst val stringConst = const?.expr as? JIRStringConstant - stringConst?.let { AccessPathBase.Constant("java.lang.String", it.value) } + if (stringConst == null) { + if (accessors.isNotEmpty()) return null + return AliasAllocInfo(assign.originalIdx) + } + + AccessPathBase.Constant("java.lang.String", stringConst.value) } + is CallReturn, - is Unknown -> null + is Unknown -> return null + is HeapAlias -> error("unreachable") } - return base?.let { AliasInfo(it, if (accessors.isEmpty()) emptyList() else accessors) } + + return AliasApInfo(base, accessors) } } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt index 4ac1a18..df9a6d5 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRAliasUtil.kt @@ -8,7 +8,7 @@ import org.seqra.dataflow.ap.ifds.access.FinalFactAp import org.seqra.dataflow.ap.ifds.access.InitialFactAp import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAccessor -import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo import org.seqra.dataflow.jvm.ap.ifds.MethodFlowFunctionUtils import org.seqra.ir.api.jvm.cfg.JIRInst import org.seqra.ir.api.jvm.cfg.locals @@ -16,14 +16,16 @@ import org.seqra.ir.api.jvm.cfg.locals fun JIRLocalAliasAnalysis.forEachAliasAtStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { val base = fact.base as? AccessPathBase.LocalVar ?: return val aliases = findAlias(base, statement) ?: return - aliases.filterNot { alias -> alias.base is AccessPathBase.Constant } + aliases.filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } .forEach { alias -> applyAlias(fact, alias, body) } } fun JIRLocalAliasAnalysis.forEachAliasAfterStatement(statement: JIRInst, fact: FinalFactAp, body: (FinalFactAp) -> Unit) { val base = fact.base as? AccessPathBase.LocalVar ?: return val aliases = findAliasAfterStatement(base, statement) ?: return - aliases.filterNot { alias -> alias.base is AccessPathBase.Constant } + aliases.filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } .forEach { alias -> applyAlias(fact, alias, body) } } @@ -33,11 +35,13 @@ fun JIRLocalAliasAnalysis.forEachAliasAfterCallStatement(statement: JIRInst, fac val aliasesAfter = findAliasAfterStatement(base, statement)?.toSet() ?: return val aliasesPersistedThroughCall = aliasesBefore.filter { it in aliasesAfter } - aliasesPersistedThroughCall.filterNot { alias -> alias.base is AccessPathBase.Constant } + aliasesPersistedThroughCall + .filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } .forEach { alias -> applyAlias(fact, alias, body) } } -private fun applyAlias(fact: FinalFactAp, alias: AliasInfo, body: (FinalFactAp) -> Unit) { +private fun applyAlias(fact: FinalFactAp, alias: AliasApInfo, body: (FinalFactAp) -> Unit) { val result = alias.accessors.foldRight(fact.rebase(alias.base)) { accessor, f -> val apAccessor = accessor.apAccessor() f.prependAccessor(apAccessor) @@ -66,12 +70,18 @@ fun JIRLocalAliasAnalysis.forEachAliasAtStatementAmongBases( ) { bases.forEach { base -> val aliases = findAlias(base, statement) ?: return@forEach - aliases.filterNot { alias -> alias.base is AccessPathBase.Constant } + aliases.filterIsInstance() + .filterNot { alias -> alias.base is AccessPathBase.Constant } .forEach { alias -> applyAlias(fact, base, alias, body) } } } -private fun applyAlias(fact: InitialFactAp, newBase: AccessPathBase.LocalVar, alias: AliasInfo, body: (InitialFactAp) -> Unit) { +private fun applyAlias( + fact: InitialFactAp, + newBase: AccessPathBase.LocalVar, + alias: AliasApInfo, + body: (InitialFactAp) -> Unit +) { if (alias.base != fact.base) { return } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt index e258f1f..eae4b37 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodCallFlowFunction.kt @@ -384,8 +384,6 @@ class JIRMethodCallFlowFunction( TaintSourceActionEvaluator( apManager, exclusion = ExclusionSet.Universe, - analysisContext.factTypeChecker, - returnValueType = callExpr.method.returnType, ) } @@ -485,7 +483,7 @@ class JIRMethodCallFlowFunction( val conditionFactReaders = factReader?.toConditionFactReaders().orEmpty() val sourceEvaluator = TaintSourceActionEvaluator( - apManager, exclusion, analysisContext.factTypeChecker, returnValueType = callExpr.method.returnType, + apManager, exclusion, ) sourceRules.applyRuleWithAssumptions( diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt index 5e1b8aa..8c6ebe2 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodSequentFlowFunction.kt @@ -516,7 +516,6 @@ class JIRMethodSequentFlowFunction( val sourceEvaluator = TaintSourceActionEvaluator( apManager, ExclusionSet.Universe, - analysisContext.factTypeChecker, returnValueType = null, ) val allEvaluatedFacts = hashSetOf() @@ -609,7 +608,7 @@ class JIRMethodSequentFlowFunction( val exclusion = fact?.exclusions ?: ExclusionSet.Universe val sourceEvaluator = TaintSourceActionEvaluator( - apManager, exclusion, analysisContext.factTypeChecker, returnValueType = null, + apManager, exclusion, ) val result = mutableListOf>() @@ -665,7 +664,7 @@ class JIRMethodSequentFlowFunction( val lhv = accessPathBase(currentInst.lhv) ?: return val sourceEvaluator = TaintSourceActionEvaluator( - apManager, ExclusionSet.Universe, factTypeChecker, returnValueType = null + apManager, ExclusionSet.Universe ) for (sourceRule in sourceRules) { diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodStartFlowFunction.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodStartFlowFunction.kt index 8e5c758..5f09676 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodStartFlowFunction.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/analysis/JIRMethodStartFlowFunction.kt @@ -40,9 +40,7 @@ class JIRMethodStartFlowFunction( val sourceEvaluator = TaintSourceActionEvaluator( apManager, - exclusion = ExclusionSet.Universe, - context.factTypeChecker, - returnValueType = null + exclusion = ExclusionSet.Universe ) applyEntryPointConfig( @@ -100,9 +98,7 @@ class JIRMethodStartFlowFunction( val sourceEvaluator = TaintSourceActionEvaluator( apManager, - exclusion = ExclusionSet.Universe, - context.factTypeChecker, - returnValueType = null + exclusion = ExclusionSet.Universe ) val factsAfterSink = mutableListOf() diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/ConditionRewriter.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/ConditionRewriter.kt index b49f842..88aa09c 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/ConditionRewriter.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/ConditionRewriter.kt @@ -11,6 +11,7 @@ import org.seqra.dataflow.configuration.jvm.ConstantTrue import org.seqra.dataflow.configuration.jvm.ContainsMark import org.seqra.dataflow.configuration.jvm.IsConstant import org.seqra.dataflow.configuration.jvm.IsNull +import org.seqra.dataflow.configuration.jvm.IsStaticField import org.seqra.dataflow.configuration.jvm.Not import org.seqra.dataflow.configuration.jvm.Or import org.seqra.dataflow.configuration.jvm.TypeMatches @@ -31,4 +32,5 @@ interface ConditionRewriter : ConditionVisitor { override fun visit(condition: ContainsMark): Condition = condition override fun visit(condition: TypeMatches): Condition = condition override fun visit(condition: TypeMatchesPattern): Condition = condition + override fun visit(condition: IsStaticField): Condition = condition } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt index 023c04c..89c9f3b 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/JIRBasicAtomEvaluator.kt @@ -16,29 +16,38 @@ import org.seqra.dataflow.configuration.jvm.ConstantValue import org.seqra.dataflow.configuration.jvm.ContainsMark import org.seqra.dataflow.configuration.jvm.IsConstant import org.seqra.dataflow.configuration.jvm.IsNull +import org.seqra.dataflow.configuration.jvm.IsStaticField import org.seqra.dataflow.configuration.jvm.Not import org.seqra.dataflow.configuration.jvm.Or +import org.seqra.dataflow.configuration.jvm.Position import org.seqra.dataflow.configuration.jvm.PositionResolver import org.seqra.dataflow.configuration.jvm.TypeMatches import org.seqra.dataflow.configuration.jvm.TypeMatchesPattern +import org.seqra.dataflow.jvm.ap.ifds.CallPositionValue +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasAllocInfo +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasApInfo +import org.seqra.dataflow.jvm.ap.ifds.JIRLocalAliasAnalysis.AliasInfo import org.seqra.dataflow.jvm.ap.ifds.analysis.JIRMethodAnalysisContext import org.seqra.ir.api.common.cfg.CommonInst import org.seqra.ir.api.common.cfg.CommonValue import org.seqra.ir.api.jvm.JIRRefType import org.seqra.ir.api.jvm.cfg.JIRBool +import org.seqra.ir.api.jvm.cfg.JIRCallExpr import org.seqra.ir.api.jvm.cfg.JIRConstant +import org.seqra.ir.api.jvm.cfg.JIRFieldRef +import org.seqra.ir.api.jvm.cfg.JIRInst import org.seqra.ir.api.jvm.cfg.JIRInt import org.seqra.ir.api.jvm.cfg.JIRLocalVar import org.seqra.ir.api.jvm.cfg.JIRNullConstant import org.seqra.ir.api.jvm.cfg.JIRStringConstant import org.seqra.ir.api.jvm.cfg.JIRValue +import org.seqra.ir.api.jvm.ext.cfg.callExpr import org.seqra.ir.api.jvm.ext.isAssignable -import org.seqra.util.Maybe -import org.seqra.util.onSome class JIRBasicAtomEvaluator( private val negated: Boolean, - private val positionResolver: PositionResolver>, + private val positionResolver: PositionResolver, private val analysisContext: JIRMethodAnalysisContext, private val statement: CommonInst, ) : ConditionVisitor { @@ -56,64 +65,67 @@ class JIRBasicAtomEvaluator( return true } - override fun visit(condition: IsConstant): Boolean { - positionResolver.resolve(condition.position).onSome { - return isConstant(it) - } - return false - } - - override fun visit(condition: IsNull): Boolean { - positionResolver.resolve(condition.position).onSome { - return isNull(it) - } - return false - } - - override fun visit(condition: ConstantEq): Boolean { - positionResolver.resolve(condition.position).onSome { value -> - return eqConstant(value, condition.value) - } - return false - } - - override fun visit(condition: ConstantLt): Boolean { - positionResolver.resolve(condition.position).onSome { value -> - return ltConstant(value, condition.value) - } - return false - } - - override fun visit(condition: ConstantGt): Boolean { - positionResolver.resolve(condition.position).onSome { value -> - return gtConstant(value, condition.value) - } - return false - } + override fun visit(condition: IsConstant): Boolean = + condition.position.eval( + value = { isConstant(it) }, + callVarArgValue = { false }, // todo: vararg + ) + + override fun visit(condition: IsNull): Boolean = + condition.position.eval( + value = { isNull(it) }, + callVarArgValue = { false }, // todo: vararg + ) + + override fun visit(condition: ConstantEq): Boolean = + condition.position.eval( + value = { cmpConstant(it, condition.value, matchArrayValue = false, ::eqConstant, ::eqConstant) }, + callVarArgValue = { cmpConstant(it, condition.value, matchArrayValue = true, ::eqConstant, ::eqConstant) }, + ) + + override fun visit(condition: ConstantLt): Boolean = + condition.position.eval( + value = { cmpConstant(it, condition.value, matchArrayValue = false, ::ltConstant, ::ltConstant) }, + callVarArgValue = { cmpConstant(it, condition.value, matchArrayValue = true, ::ltConstant, ::ltConstant) }, + ) + + override fun visit(condition: ConstantGt): Boolean = + condition.position.eval( + value = { cmpConstant(it, condition.value, matchArrayValue = false, ::gtConstant, ::gtConstant) }, + callVarArgValue = { cmpConstant(it, condition.value, matchArrayValue = true, ::gtConstant, ::gtConstant) }, + ) // note: ConstantMatches means StringConstantMatches - override fun visit(condition: ConstantMatches): Boolean { - positionResolver.resolve(condition.position).onSome { value -> - return matches(value, condition.pattern) - } - return false - } - - override fun visit(condition: TypeMatches): Boolean { - positionResolver.resolve(condition.position).onSome { value -> - return typeMatches(value, condition) - } - return false - } + override fun visit(condition: ConstantMatches): Boolean = + condition.position.eval( + value = { matches(it, condition.pattern, matchArrayValue = false) }, + callVarArgValue = { matches(it, condition.pattern, matchArrayValue = true) }, + ) + + override fun visit(condition: TypeMatches): Boolean = + condition.position.eval( + value = { typeMatches(it, condition) }, + callVarArgValue = { typeMatches(it, condition) }, // todo: vararg + ) + + override fun visit(condition: IsStaticField): Boolean = + condition.position.eval( + value = { isStaticField(it, condition, matchArrayValue = false) }, + callVarArgValue = { isStaticField(it, condition, matchArrayValue = true) }, + ) private val typeMatchesCache = hashMapOf() - override fun visit(condition: TypeMatchesPattern): Boolean = typeMatchesCache.computeIfAbsent(condition) { - positionResolver.resolve(condition.position).onSome { value -> - return@computeIfAbsent typeMatchesPattern(value, condition) + override fun visit(condition: TypeMatchesPattern): Boolean = + condition.position.eval( + value = { condition.evalTypeMatches(it) }, + callVarArgValue = { condition.evalTypeMatches(it) }, // todo: vararg + ) + + private fun TypeMatchesPattern.evalTypeMatches(value: JIRValue): Boolean = + typeMatchesCache.computeIfAbsent(this) { + typeMatchesPattern(value, this) } - return@computeIfAbsent false - } private fun isConstant(value: JIRValue): Boolean { return value is JIRConstant @@ -123,7 +135,9 @@ class JIRBasicAtomEvaluator( return value is JIRNullConstant } - private fun eqConstant(value: JIRValue, constant: ConstantValue): Boolean { + private fun eqConstant(value: String, constant: ConstantStringValue): Boolean = value == constant.value + + private fun eqConstant(value: JIRConstant, constant: ConstantValue): Boolean { return when (constant) { is ConstantBooleanValue -> { when (value) { @@ -144,6 +158,9 @@ class JIRBasicAtomEvaluator( } } + @Suppress("unused") + private fun ltConstant(value: String, constant: ConstantStringValue): Boolean = false + private fun ltConstant(value: JIRValue, constant: ConstantValue): Boolean { return when (constant) { is ConstantIntValue -> { @@ -154,6 +171,9 @@ class JIRBasicAtomEvaluator( } } + @Suppress("unused") + private fun gtConstant(value: String, constant: ConstantStringValue): Boolean = false + private fun gtConstant(value: JIRValue, constant: ConstantValue): Boolean { return when (constant) { is ConstantIntValue -> { @@ -164,27 +184,142 @@ class JIRBasicAtomEvaluator( } } - private fun matches(value: JIRValue, pattern: Regex): Boolean { + private fun cmpConstant( + value: JIRValue, + constant: ConstantValue, + matchArrayValue: Boolean, + constCmp: (JIRConstant, ConstantValue) -> Boolean, + strCmp: (String, ConstantStringValue) -> Boolean, + ): Boolean { + if (value is JIRConstant) { + return constCmp(value, constant) + } + + if (value is JIRLocalVar) { + resolveLocalVarValue(value, matchArrayValue) { aliasInfo -> + when (constant) { + is ConstantStringValue -> { + val constants = aliasInfo.stringConstantValues() + if (constants.any { strCmp(it, constant) }) { + return true + } + } + + is ConstantBooleanValue -> { + val boxedBools = aliasInfo.findBoxedConstants("java.lang.Boolean") + if (boxedBools.any { constCmp(it, constant) }) { + return true + } + } + + is ConstantIntValue -> { + val boxedInts = aliasInfo.findBoxedConstants("java.lang.Integer") + if (boxedInts.any { constCmp(it, constant) }) { + return true + } + } + } + } + } + + return false + } + + private fun List.findBoxedConstants(boxType: String): List = this + .findAllocCalls() + .filter { + val m = it.method.method + m.name == "valueOf" && m.enclosingClass.name == boxType + } + .mapNotNull { it.args.getOrNull(0) as? JIRConstant } + + private fun List.findAllocCalls(): List { + val allocs = filterIsInstance() + if (allocs.isEmpty()) return emptyList() + + val instList = (statement as JIRInst).location.method.instList + return allocs + .mapNotNull { instList.getOrNull(it.allocInst) } + .mapNotNull { it.callExpr } + } + + private fun matches(value: JIRValue, pattern: Regex, matchArrayValue: Boolean): Boolean { if (value is JIRStringConstant) { return pattern.matches(value.value) } if (value is JIRLocalVar) { - val base = AccessPathBase.LocalVar(value.index) - val aliasInfo = analysisContext.aliasAnalysis?.findAlias(base, statement) - val constants = aliasInfo - ?.mapNotNull { ai -> ai.base.takeIf { ai.accessors.isEmpty() } } - ?.filterIsInstance() - .orEmpty() - - if (constants.isNotEmpty()) { - return constants.any { pattern.matches(it.value) } + resolveLocalVarValue(value, matchArrayValue) { aliasInfo -> + val constants = aliasInfo.stringConstantValues() + if (constants.any { pattern.matches(it) }) { + return true + } + } + } + + return false + } + + private fun List.stringConstantValues(): List = this + .filterIsInstance() + .mapNotNull { ai -> ai.base.takeIf { ai.accessors.isEmpty() } } + .filterIsInstance() + .map { it.value } + + private fun isStaticField(value: JIRValue, condition: IsStaticField, matchArrayValue: Boolean): Boolean { + if (value is JIRFieldRef) { + val field = value.field.field + return staticFieldMatches(field.enclosingClass.name, field.name, condition) + } + + if (value is JIRLocalVar) { + resolveLocalVarValue(value, matchArrayValue) { aliasInfo -> + val statics = aliasInfo + .filterIsInstance() + .filter { it.base is AccessPathBase.ClassStatic } + .mapNotNull { it.accessors.firstOrNull() } + .filterIsInstance() + + if (statics.any { staticFieldMatches(it.className, it.fieldName, condition) }) { + return true + } } } return false } + private inline fun resolveLocalVarValue(lv: JIRLocalVar, matchArrayValue: Boolean, body: (List) -> Unit) { + val aa = analysisContext.aliasAnalysis ?: return + + val base = AccessPathBase.LocalVar(lv.index) + if (!matchArrayValue) { + // todo: use must alias if negated + val aliasInfo = aa.findAlias(base, statement) + if (aliasInfo != null) { + body(aliasInfo) + } + } else { + val allAliases = aa.getAllAliasAtStatement(statement) + for ((_, aliasSet) in allAliases) { + for (info in aliasSet) { + if (info !is AliasApInfo || info.base != base) continue + val singleAccessor = info.accessors.singleOrNull() ?: continue + if (singleAccessor is JIRLocalAliasAnalysis.AliasAccessor.Array) { + body(aliasSet) + return + } + } + } + } + } + + private fun staticFieldMatches( + className: String, + fieldName: String, + condition: IsStaticField, + ): Boolean = condition.className.match(className) && condition.fieldName.match(fieldName) + private fun typeMatches(value: CommonValue, condition: TypeMatches): Boolean { check(value is JIRValue) return value.type.isAssignable(condition.type) @@ -193,40 +328,43 @@ class JIRBasicAtomEvaluator( private fun typeMatchesPattern(value: JIRValue, condition: TypeMatchesPattern): Boolean { val type = value.type as? JIRRefType ?: return false - when (val pattern = condition.pattern) { - is ConditionNameMatcher.PatternEndsWith -> { - if (type.typeName.endsWith(pattern.suffix)) return true - - // todo: check super classes? - return false - } + val pattern = condition.pattern + if (pattern.match(type.typeName)) return true - is ConditionNameMatcher.PatternStartsWith -> { - if (type.typeName.startsWith(pattern.prefix)) return true - - // todo: check super classes? - return false - } + if (pattern !is ConditionNameMatcher.Concrete) { + // todo: check super classes? + return false + } - is ConditionNameMatcher.Pattern -> { - if (pattern.pattern.containsMatchIn(type.typeName)) return true + if (negated) return false - // todo: check super classes? - return false - } + if (type.typeName == "java.lang.Object") { + // todo: hack to avoid explosion + return false + } - is ConditionNameMatcher.Concrete -> { - if (pattern.name == type.typeName) return true + return typeChecker.typeMayHaveSubtypeOf(type.typeName, pattern.name) + } - if (negated) return false + private fun ConditionNameMatcher.match(name: String): Boolean = when (this) { + is ConditionNameMatcher.PatternEndsWith -> name.endsWith(suffix) + is ConditionNameMatcher.PatternStartsWith -> name.startsWith(prefix) + is ConditionNameMatcher.Simple -> match(name) + } - if (type.typeName == "java.lang.Object") { - // todo: hack to avoid explosion - return false - } + private fun ConditionNameMatcher.Simple.match(name: String): Boolean = when (this) { + is ConditionNameMatcher.Pattern -> pattern.containsMatchIn(name) + is ConditionNameMatcher.Concrete -> this.name == name + is ConditionNameMatcher.AnyName -> true + } - return typeChecker.typeMayHaveSubtypeOf(type.typeName, pattern.name) - } - } + private fun Position.eval( + none: Boolean = false, + value: (value: JIRValue) -> Boolean, + callVarArgValue: (value: JIRValue) -> Boolean, + ): Boolean = when (val res = positionResolver.resolve(this)) { + is CallPositionValue.None -> none + is CallPositionValue.Value -> value(res.value) + is CallPositionValue.VarArgValue -> callVarArgValue(res.value) } } diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/TaintEvaluator.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/TaintEvaluator.kt index 084eb36..fd5da55 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/TaintEvaluator.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/taint/TaintEvaluator.kt @@ -99,13 +99,11 @@ class TaintPassActionEvaluator( val toPositionBaseType = positionTypeResolver.resolve(toPos) - val resultFacts = mutableListOf(mkAccessPath(toPosAccess, factApDelta, fact.exclusions)) - resultFacts.hackResultArray(toPosAccess, factTypeChecker, toPositionBaseType) + val resultFact = mkAccessPath(toPosAccess, factApDelta, fact.exclusions) + val wellTypedFact = resultFact.let { factTypeChecker.filterFactByLocalType(toPositionBaseType, it) } + if (wellTypedFact == null) return Maybe.none() - val wellTypedFacts = resultFacts.mapNotNull { factTypeChecker.filterFactByLocalType(toPositionBaseType, it) } - if (wellTypedFacts.isEmpty()) return Maybe.none() - - return Maybe.some(listOf(factReader.factAp) + wellTypedFacts) + return Maybe.some(listOf(factReader.factAp) + wellTypedFact) } private fun copyFinalFact( @@ -279,15 +277,11 @@ class TaintCleanActionEvaluator( class TaintPassActionPreconditionEvaluator( private val factReader: InitialFactReader, - private val typeChecker: JIRFactTypeChecker, - private val returnValueType: JIRType?, ) : PassActionEvaluator> { override fun evaluate(rule: TaintConfigurationItem, action: CopyAllMarks): Maybe>> { val fromVar = action.from.resolveAp() - val toVariables = mutableListOf(action.to.resolveAp()) - toVariables.hackResultArray(typeChecker, returnValueType) - + val toVariables = listOf(action.to.resolveAp()) return Maybe.from(toVariables).flatMap { toVar -> copyAllFactsPrecondition(fromVar, toVar).fmap { facts -> facts.map { action to it } @@ -297,14 +291,10 @@ class TaintPassActionPreconditionEvaluator( override fun evaluate(rule: TaintConfigurationItem, action: CopyMark): Maybe>> { val fromVar = action.from.resolveAp() + val toVariable = action.to.resolveAp() - val toVariables = mutableListOf(action.to.resolveAp()) - toVariables.hackResultArray(typeChecker, returnValueType) - - return Maybe.from(toVariables).flatMap { toVar -> - copyFinalFactPrecondition(fromVar, toVar, action.mark).fmap { facts -> - facts.map { action to it } - } + return copyFinalFactPrecondition(fromVar, toVariable, action.mark).fmap { facts -> + facts.map { action to it } } } @@ -350,35 +340,26 @@ interface SourceActionEvaluator { class TaintSourceActionEvaluator( private val apManager: ApManager, private val exclusion: ExclusionSet, - private val factTypeChecker: JIRFactTypeChecker, - private val returnValueType: JIRType?, ) : SourceActionEvaluator { override fun evaluate(rule: TaintConfigurationItem, action: AssignMark): Maybe> { val variable = action.position.resolveAp() - val facts = mutableListOf(apManager.mkAccessPath(variable, exclusion, action.mark.name)) - facts.hackResultArray(variable, factTypeChecker, returnValueType) - - return Maybe.from(facts) + val fact = apManager.mkAccessPath(variable, exclusion, action.mark.name) + return Maybe.from(listOf(fact)) } } class TaintSourceActionPreconditionEvaluator( private val factReader: InitialFactReader, - private val typeChecker: JIRFactTypeChecker, - private val returnValueType: JIRType?, ) : SourceActionEvaluator> { override fun evaluate( rule: TaintConfigurationItem, action: AssignMark, ): Maybe>> { - val variables = mutableListOf(action.position.resolveAp()) - variables.hackResultArray(typeChecker, returnValueType) + val variable = action.position.resolveAp() - return Maybe.from(variables).flatMap { variable -> - if (!factReader.containsPositionWithTaintMark(variable, action.mark)) return@flatMap Maybe.none() - Maybe.some(listOf(rule to action)) - } + if (!factReader.containsPositionWithTaintMark(variable, action.mark)) return Maybe.none() + return Maybe.some(listOf(rule to action)) } } @@ -412,35 +393,6 @@ fun Position.resolveAp(baseAp: AccessPathBase): PositionAccess { } } -private fun MutableList.hackResultArray( - access: PositionAccess, - typeChecker: JIRFactTypeChecker, - resultPositionType: JIRType?, -) { - if (resultPositionType == null) return - if (!access.baseIsResult()) return - - if (!with(typeChecker) { resultPositionType.mayBeArray() }) return - - for (fact in this.toList()) { - this += fact.prependAccessor(ElementAccessor) - } -} - -private fun MutableList.hackResultArray( - typeChecker: JIRFactTypeChecker, - resultPositionType: JIRType?, -) { - if (resultPositionType == null) return - - for (access in this.toList()) { - if (!access.baseIsResult()) continue - if (!with(typeChecker) { resultPositionType.mayBeArray() }) continue - - this += access.withPrefix(ElementAccessor) - } -} - private fun PositionAccess.baseIsResult(): Boolean = when (this) { is PositionAccess.Complex -> base.baseIsResult() is PositionAccess.Simple -> base is AccessPathBase.Return diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt index a4b7acb..fbe8f4e 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodCallPrecondition.kt @@ -103,7 +103,7 @@ class JIRMethodCallPrecondition( ) { val entryFactReader = InitialFactReader(fact.rebase(startBase), apManager) val sourcePreconditionEvaluator = TaintSourceActionPreconditionEvaluator( - entryFactReader, analysisContext.factTypeChecker, callExpr.method.returnType + entryFactReader ) val conditionRewriter = JIRMarkAwareConditionRewriter( @@ -138,9 +138,7 @@ class JIRMethodCallPrecondition( if (passRules.isEmpty()) return val entryFactReader = InitialFactReader(fact.rebase(startBase), apManager) - val rulePreconditionEvaluator = TaintPassActionPreconditionEvaluator( - entryFactReader, analysisContext.factTypeChecker, callExpr.method.returnType - ) + val rulePreconditionEvaluator = TaintPassActionPreconditionEvaluator(entryFactReader) val conditionRewriter = JIRMarkAwareConditionRewriter( jIRValueResolver, diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodSequentPrecondition.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodSequentPrecondition.kt index beeaa79..3615f76 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodSequentPrecondition.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodSequentPrecondition.kt @@ -223,7 +223,7 @@ class JIRMethodSequentPrecondition( val entryFactReader = InitialFactReader(fact.rebase(AccessPathBase.Return), apManager) val sourcePreconditionEvaluator = TaintSourceActionPreconditionEvaluator( - entryFactReader, analysisContext.factTypeChecker, returnValueType = null + entryFactReader ) for (sourceRule in sourceRules) { @@ -251,7 +251,7 @@ class JIRMethodSequentPrecondition( val entryFactReader = InitialFactReader(fact, apManager) val sourcePreconditionEvaluator = TaintSourceActionPreconditionEvaluator( - entryFactReader, analysisContext.factTypeChecker, returnValueType = null + entryFactReader ) val valueResolver = CalleePositionToJIRValueResolver(currentInst.location.method) diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodStartPrecondition.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodStartPrecondition.kt index f69d1b1..2b9f80a 100644 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodStartPrecondition.kt +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/ap/ifds/trace/JIRMethodStartPrecondition.kt @@ -32,7 +32,7 @@ class JIRMethodStartPrecondition( val entryFactReader = InitialFactReader(fact, apManager) val sourcePreconditionEvaluator = TaintSourceActionPreconditionEvaluator( - entryFactReader, context.factTypeChecker, returnValueType = null + entryFactReader ) val result = TaintConfigUtils.applyEntryPointConfig( diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JIRSarifTraits.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JIRSarifTraits.kt deleted file mode 100644 index a393dc1..0000000 --- a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JIRSarifTraits.kt +++ /dev/null @@ -1,228 +0,0 @@ -package org.seqra.dataflow.jvm.util - -import org.seqra.dataflow.util.SarifTraits -import org.seqra.ir.api.common.cfg.CommonAssignInst -import org.seqra.ir.api.common.cfg.CommonCallExpr -import org.seqra.ir.api.common.cfg.CommonExpr -import org.seqra.ir.api.jvm.JIRClasspath -import org.seqra.ir.api.jvm.JIRMethod -import org.seqra.ir.api.jvm.cfg.JIRAssignInst -import org.seqra.ir.api.jvm.cfg.JIRCallExpr -import org.seqra.ir.api.jvm.cfg.JIRExpr -import org.seqra.ir.api.jvm.cfg.JIRFieldRef -import org.seqra.ir.api.jvm.cfg.JIRArrayAccess -import org.seqra.ir.api.jvm.cfg.JIRArgument -import org.seqra.ir.api.jvm.cfg.JIRInst -import org.seqra.ir.api.jvm.cfg.JIRLocalVar -import org.seqra.ir.api.jvm.cfg.JIRThis -import org.seqra.ir.api.jvm.cfg.JIRValue -import org.seqra.ir.api.jvm.cfg.JIRInstanceCallExpr -import org.seqra.ir.api.jvm.cfg.values -import org.seqra.ir.api.jvm.ext.cfg.callExpr -import org.seqra.ir.api.jvm.ext.toType -import org.seqra.jvm.util.enclosingMethod - -class JIRSarifTraits( - val cp: JIRClasspath, -) : SarifTraits { - private val methodCache = hashMapOf>() - private val registerStart = '%' - - override fun isRegister(name: String): Boolean { - return name[0] == registerStart - } - - private fun isDefaultArgName(name: String) = - name.isEmpty() || name.startsWith("arg|") - - private fun loadLocalNames(md: JIRMethod) { - if (methodCache.contains(md)) return - val mdLocals = hashMapOf() - methodCache[md] = mdLocals - md.flowGraph().instructions.forEach { insn -> - getAssign(insn)?.let { assign -> - getLocals(assign.lhv).filterNot { isRegister(it.name) }.forEach { localInfo -> - mdLocals[localInfo.idx] = localInfo.name - } - val local = assign.lhv - if (local is JIRLocalVar && assign.rhv is JIRValue) { - if (!mdLocals.containsKey(local.index)) { - val value = tryGetReadableValue(assign as JIRInst, assign.rhv) - if (value is ReadableValue.Value) { - mdLocals[local.index] = value.toString() - } - } - } - } - } - } - - override fun getLocalName(md: JIRMethod, index: Int): String? { - if (!methodCache.contains(md)) { - loadLocalNames(md) - } - return methodCache[md]?.get(index) - } - - private fun getOrdinal(i: Int): String { - val suffix = if (i % 100 in 11..13) "th" else when (i % 10) { - 1 -> "st" - 2 -> "nd" - 3 -> "rd" - else -> "th" - } - return "$i$suffix" - } - - private fun getReadableInstance(statement: JIRInst): String? { - val callExpr = statement.callExpr ?: return null - val instanceCall = callExpr as? JIRInstanceCallExpr ?: return null - return getReadableValue(statement, instanceCall.instance) - } - - override fun printThis(statement: JIRInst): String = - printThis(statement) { getReadableInstance(it) } - - private inline fun printThis(statement: JIRInst, parseStatement: (JIRInst) -> String?): String = - if (statement.callExpr?.callee?.isConstructor == true) { - "the created object" - } else { - parseStatement(statement) ?: "the calling object" - } - - override fun printArgumentNth(index: Int, methodName: String?): String { - val ofMethod = methodName?.let { - if (it.startsWith("lambda$")) - " of lambda" - else - " of \"$it\"" - } ?: "" - return "the ${getOrdinal(index + 1)} argument$ofMethod" - } - - override fun printArgument(method: JIRMethod, index: Int): String { - val methodName = method.name - val paramName = method.parameters.getOrNull(index)?.name ?: return printArgumentNth(index, methodName) - return "\"$paramName\"" - } - - override fun getCallee(callExpr: CommonCallExpr): JIRMethod { - check(callExpr is JIRCallExpr) - return callExpr.callee - } - - override fun getCalleeClassName(callExpr: CommonCallExpr): String { - check(callExpr is JIRCallExpr) - return callExpr.callee.enclosingClass.simpleName - } - - override fun getMethodClassName(md: JIRMethod): String { - return md.enclosingClass.simpleName - } - - override fun getCallExpr(statement: JIRInst): JIRCallExpr? { - return statement.callExpr - } - - sealed interface ReadableValue { - data class Value(val string: String) : ReadableValue { - override fun toString() = - string - } - - data object UnparsedArrayAccess : ReadableValue - - data object UnparsedLocalVar : ReadableValue - } - - private fun tryGetReadableValue(statement: JIRInst, expr: CommonExpr): ReadableValue? { - if (expr !is JIRValue) return null - return when (expr) { - is JIRFieldRef -> { - val instance = expr.instance?.let { - val readableInstance = tryGetReadableValue(statement, it) - if (readableInstance is ReadableValue.Value) - readableInstance.toString() - else - "" - } ?: expr.field.enclosingType.jIRClass.simpleName - ReadableValue.Value("$instance.${expr.field.name}") - } - is JIRArgument -> { - val name = expr.name - if (!isDefaultArgName(name)) - return ReadableValue.Value(name) - else - ReadableValue.Value(printArgument(statement.location.method, expr.index)) - } - is JIRArrayAccess -> { - val arrName = tryGetReadableValue(statement, expr.array) - val elemName = tryGetReadableValue(statement, expr.index) - if (arrName is ReadableValue.Value) - if (elemName is ReadableValue.Value) - ReadableValue.Value("$arrName[$elemName]") - else - ReadableValue.Value("$arrName[]") - else - ReadableValue.UnparsedArrayAccess - } - is JIRLocalVar -> { - if (!isRegister(expr.name)) - ReadableValue.Value(expr.name) - else { - val name = getLocalName(statement.enclosingMethod, expr.index) - if (name == null || isRegister(name)) - ReadableValue.UnparsedLocalVar - else - ReadableValue.Value(name) - } - } - is JIRThis -> { - ReadableValue.Value("this") - } - else -> ReadableValue.Value(expr.toString()) - } - } - - override fun getReadableValue(statement: JIRInst, expr: CommonExpr): String? { - val value = tryGetReadableValue(statement, expr) ?: return null - return when (value) { - is ReadableValue.Value -> "\"${value.string}\"" - is ReadableValue.UnparsedLocalVar -> "a local variable" - is ReadableValue.UnparsedArrayAccess -> "an element of array" - } - } - - override fun getReadableAssignee(statement: JIRInst): String? { - if (statement !is JIRAssignInst) return null - return getReadableValue(statement, statement.lhv) - } - - override fun getLocals(expr: CommonExpr): List { - expr as JIRExpr - return expr.values.filterIsInstance().map { SarifTraits.LocalInfo(it.index, it.name) } - } - - override fun getAssign(statement: JIRInst): CommonAssignInst? { - if (statement !is JIRAssignInst) return null - return statement - } - - override fun lineNumber(statement: JIRInst): Int { - return statement.lineNumber - } - - override fun locationFQN(statement: JIRInst): String { - val method = statement.location.method - return "${method.enclosingClass.name}#${method.name}" - } - - override fun locationMachineName(statement: JIRInst): String = - "${statement.location.method}:${statement.location.index}:($statement)" -} - -val JIRMethod.thisInstance: JIRThis - get() = JIRThis(enclosingClass.toType()) - -val JIRCallExpr.callee: JIRMethod - get() = method.method diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirMethodExt.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirMethodExt.kt new file mode 100644 index 0000000..a330d7c --- /dev/null +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirMethodExt.kt @@ -0,0 +1,13 @@ +package org.seqra.dataflow.jvm.util + +import org.seqra.ir.api.jvm.JIRMethod +import org.seqra.ir.api.jvm.cfg.JIRCallExpr +import org.seqra.ir.api.jvm.cfg.JIRThis +import org.seqra.ir.api.jvm.ext.toType + + +val JIRMethod.thisInstance: JIRThis + get() = JIRThis(enclosingClass.toType()) + +val JIRCallExpr.callee: JIRMethod + get() = method.method diff --git a/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirVararg.kt b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirVararg.kt new file mode 100644 index 0000000..eab9111 --- /dev/null +++ b/seqra-jvm-dataflow/src/main/kotlin/org/seqra/dataflow/jvm/util/JirVararg.kt @@ -0,0 +1,14 @@ +package org.seqra.dataflow.jvm.util + +import org.objectweb.asm.Opcodes +import org.seqra.ir.api.jvm.JIRMethod +import org.seqra.ir.api.jvm.cfg.JIRCallExpr + +fun JIRMethod.isVararg(): Boolean = + access and Opcodes.ACC_VARARGS != 0 + +fun JIRCallExpr.isVararg(): Boolean = + method.method.isVararg() + +fun JIRMethod.varargParamIdx(): Int = + parameters.lastIndex