Skip to content

Commit 7eff2f4

Browse files
authored
jacodb-taint-configuration: better IsType condition specializer (#208)
* jacodb-taint-configuration: better `IsType` condition specializer * Add test
1 parent 207c306 commit 7eff2f4

File tree

3 files changed

+96
-18
lines changed

3 files changed

+96
-18
lines changed

jacodb-taint-configuration/src/main/kotlin/org/jacodb/taint/configuration/TaintConfigurationFeature.kt

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616

1717
package org.jacodb.taint.configuration
1818

19-
import kotlinx.coroutines.runBlocking
2019
import kotlinx.serialization.decodeFromString
2120
import kotlinx.serialization.json.Json
2221
import kotlinx.serialization.modules.SerializersModule
2322
import kotlinx.serialization.modules.polymorphic
2423
import kotlinx.serialization.modules.subclass
2524
import org.jacodb.api.*
2625
import org.jacodb.api.ext.*
27-
import org.jacodb.impl.features.hierarchyExt
2826
import java.nio.file.Path
2927
import kotlin.io.path.readText
3028

@@ -295,31 +293,49 @@ class TaintConfigurationFeature private constructor(
295293
return mkOr(types.flatMap { type -> position.map { TypeMatches(it, type) } })
296294
}
297295

298-
typeMatcher as ClassMatcher
296+
val typeMatchers = (typeMatcher as ClassMatcher).extractAlternatives()
297+
val unresolvedMatchers = mutableListOf<ClassMatcher>()
298+
val disjuncts = mutableListOf<Condition>()
299299

300-
val pkgMatcher = typeMatcher.pkg
301-
val clsMatcher = typeMatcher.classNameMatcher
302300
val cp = method.enclosingClass.classpath
303301

304-
if (pkgMatcher is NameExactMatcher && clsMatcher is NameExactMatcher) {
302+
for (matcher in typeMatchers) {
303+
val pkgMatcher = matcher.pkg
304+
val clsMatcher = matcher.classNameMatcher
305+
306+
if (pkgMatcher !is NameExactMatcher || clsMatcher !is NameExactMatcher) {
307+
unresolvedMatchers += matcher
308+
continue
309+
}
310+
305311
val type = cp.findTypeOrNull("${pkgMatcher.name}$DOT_DELIMITER${clsMatcher.name}")
306-
?: return mkOr(emptyList())
307-
return mkOr(position.map { TypeMatches(it, type) })
308-
}
312+
?: continue
309313

310-
val alternatives = typeMatcher.extractAlternatives()
311-
val disjuncts = mutableListOf<Condition>()
314+
position.mapTo(disjuncts) { TypeMatches(it, type) }
315+
}
312316

313-
alternatives.forEach { classMatcher ->
314-
val allClasses = runBlocking {
315-
cp.hierarchyExt().findSubClasses(cp.objectClass, allHierarchy = true, includeOwn = true)
317+
if (unresolvedMatchers.isNotEmpty()) {
318+
val allClassNames = cp.registeredLocations.flatMapTo(hashSetOf()) {
319+
val names = it.jcLocation?.classNames ?: return@flatMapTo emptyList()
320+
names.map { name ->
321+
val packageName = name.substringBeforeLast(DOT_DELIMITER, missingDelimiterValue = "")
322+
val simpleName = name.substringAfterLast(DOT_DELIMITER)
323+
packageName to simpleName
324+
}
316325
}
317326

318-
val types = allClasses.filter {
319-
matches(classMatcher.pkg, it.packageName) && matches(classMatcher.classNameMatcher, it.simpleName)
320-
}
327+
unresolvedMatchers.forEach { classMatcher ->
328+
val matchedClassNames = allClassNames.filter { (packageName, simpleName) ->
329+
matches(classMatcher.pkg, packageName) && matches(classMatcher.classNameMatcher, simpleName)
330+
}
321331

322-
disjuncts += types.flatMap { type -> position.map { TypeMatches(it, type.toType()) } }.toList()
332+
matchedClassNames.flatMapTo(disjuncts) { (packageName, simpleName) ->
333+
val type = cp.findTypeOrNull("${packageName}$DOT_DELIMITER${simpleName}")
334+
?: return@flatMapTo emptyList()
335+
336+
position.map { TypeMatches(it, type) }
337+
}
338+
}
323339
}
324340

325341
return mkOr(disjuncts)

jacodb-taint-configuration/src/test/kotlin/org/jacodb/taint/configuration/ConfigurationTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,15 @@ class ConfigurationTest : BaseTest() {
120120

121121
assertTrue(rules.singleOrNull() != null)
122122
}
123+
124+
@Test
125+
fun testIsTypeMatcher() {
126+
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
127+
val method = cp.findClass<java.util.List<*>>().methods.single {
128+
it.name == "removeAll" && it.parameters.size == 1
129+
}
130+
val rules = taintFeature.getConfigForMethod(method)
131+
132+
assertTrue(rules.singleOrNull() != null)
133+
}
123134
}

jacodb-taint-configuration/src/test/resources/testJsonConfig.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,5 +479,56 @@
479479
}
480480
]
481481
}
482+
},
483+
{
484+
"_": "MethodSink",
485+
"ruleNote": "Test rule for isType matcher",
486+
"cwe": [
487+
-1
488+
],
489+
"methodInfo": {
490+
"cls": {
491+
"packageMatcher": {
492+
"_": "NameIsEqualTo",
493+
"name": "java.util"
494+
},
495+
"classNameMatcher": {
496+
"_": "NameIsEqualTo",
497+
"name": "List"
498+
}
499+
},
500+
"functionName": {
501+
"_": "NameIsEqualTo",
502+
"name": "removeAll"
503+
},
504+
"parametersMatchers": [
505+
],
506+
"returnTypeMatcher": {
507+
"_": "AnyTypeMatches"
508+
},
509+
"applyToOverrides": false,
510+
"functionLabel": null,
511+
"modifier": -1,
512+
"exclude": [
513+
]
514+
},
515+
"condition": {
516+
"_": "IsType",
517+
"position": {
518+
"_": "Argument",
519+
"number": 0
520+
},
521+
"type": {
522+
"_": "ClassMatcher",
523+
"packageMatcher": {
524+
"_": "NameMatches",
525+
"pattern": "java\\..*"
526+
},
527+
"classNameMatcher": {
528+
"_": "NameMatches",
529+
"pattern": "List.*"
530+
}
531+
}
532+
}
482533
}
483534
]

0 commit comments

Comments
 (0)