Skip to content

Commit 4a769ad

Browse files
authored
Add taint analysis configuration parser (#1770)
1 parent 41e76c8 commit 4a769ad

23 files changed

+1752
-0
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ kryoSerializersVersion=0.45
6060
asmVersion=9.2
6161
testNgVersion=7.6.0
6262
mockitoInlineVersion=4.0.0
63+
kamlVersion = 0.51.0
6364
jacksonVersion = 2.12.3
6465
javasmtSolverZ3Version=4.8.9-sosy1
6566
slf4jVersion=1.7.36

utbot-framework-test/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
exclude group:'com.google.guava', module:'guava'
3838
}
3939

40+
implementation group: 'com.charleskorn.kaml', name: 'kaml', version: kamlVersion
4041
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jacksonVersion
4142
implementation group: 'org.sosy-lab', name: 'javasmt-solver-z3', version: javasmtSolverZ3Version
4243
implementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: rgxgenVersion
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package org.utbot.taint.parser
2+
3+
import com.charleskorn.kaml.YamlException
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.assertThrows
7+
import org.utbot.taint.parser.constants.*
8+
import org.utbot.taint.parser.model.*
9+
import org.utbot.taint.parser.yaml.ConfigurationParseError
10+
11+
class TaintAnalysisConfigurationParserTest {
12+
13+
@Test
14+
fun `parse should parse correct yaml`() {
15+
val actualConfiguration = TaintAnalysisConfigurationParser.parse(yamlInput)
16+
assertEquals(expectedConfiguration, actualConfiguration)
17+
}
18+
19+
@Test
20+
fun `parse should throw exception on malformed yaml`() {
21+
val malformedYamlInput = yamlInput.replace("{", "")
22+
assertThrows<YamlException> {
23+
TaintAnalysisConfigurationParser.parse(malformedYamlInput)
24+
}
25+
}
26+
27+
@Test
28+
fun `parse should throw exception on incorrect yaml`() {
29+
val incorrectYamlInput = yamlInput.replace(k_not, "net")
30+
assertThrows<ConfigurationParseError> {
31+
TaintAnalysisConfigurationParser.parse(incorrectYamlInput)
32+
}
33+
}
34+
35+
// test data
36+
37+
private val yamlInput = """
38+
$k_sources:
39+
- java.lang.System.getenv:
40+
$k_signature: [ ${k_lt}java.lang.String${k_gt} ]
41+
$k_addTo: $k_return
42+
$k_marks: environment
43+
44+
$k_passes:
45+
- java.lang.String:
46+
- concat:
47+
$k_conditions:
48+
$k_this: { $k_not: "" }
49+
$k_getFrom: $k_this
50+
$k_addTo: $k_return
51+
$k_marks: sensitive-data
52+
- concat:
53+
$k_conditions:
54+
${k_arg}1: { $k_not: "" }
55+
$k_getFrom: ${k_arg}1
56+
$k_addTo: $k_return
57+
$k_marks: sensitive-data
58+
59+
$k_cleaners:
60+
- java.lang.String.isEmpty:
61+
$k_conditions:
62+
$k_return: true
63+
$k_removeFrom: $k_this
64+
$k_marks: [ sql-injection, xss ]
65+
66+
$k_sinks:
67+
- org.example.util.unsafe:
68+
$k_signature: [ $k__, ${k_lt}java.lang.Integer${k_gt} ]
69+
$k_conditions:
70+
${k_arg}2: 0
71+
$k_check: ${k_arg}2
72+
$k_marks: environment
73+
""".trimIndent()
74+
75+
private val expectedConfiguration = Configuration(
76+
sources = listOf(
77+
Source(
78+
methodFqn = MethodFqn(listOf("java", "lang"), "System", "getenv"),
79+
addTo = TaintEntitiesSet(setOf(ReturnValue)),
80+
marks = TaintMarksSet(setOf(TaintMark("environment"))),
81+
signature = SignatureList(listOf(ArgumentTypeString("java.lang.String"))),
82+
conditions = NoConditions
83+
)
84+
),
85+
passes = listOf(
86+
Pass(
87+
methodFqn = MethodFqn(listOf("java", "lang"), "String", "concat"),
88+
getFrom = TaintEntitiesSet(setOf(ThisObject)),
89+
addTo = TaintEntitiesSet(setOf(ReturnValue)),
90+
marks = TaintMarksSet(setOf(TaintMark("sensitive-data"))),
91+
signature = AnySignature,
92+
conditions = ConditionsMap(mapOf(ThisObject to NotCondition(ValueCondition(ArgumentValueString("")))))
93+
),
94+
Pass(
95+
methodFqn = MethodFqn(listOf("java", "lang"), "String", "concat"),
96+
getFrom = TaintEntitiesSet(setOf(MethodArgument(1u))),
97+
addTo = TaintEntitiesSet(setOf(ReturnValue)),
98+
marks = TaintMarksSet(setOf(TaintMark("sensitive-data"))),
99+
signature = AnySignature,
100+
conditions = ConditionsMap(mapOf(MethodArgument(1u) to NotCondition(ValueCondition(ArgumentValueString("")))))
101+
)
102+
),
103+
cleaners = listOf(
104+
Cleaner(
105+
methodFqn = MethodFqn(listOf("java", "lang"), "String", "isEmpty"),
106+
removeFrom = TaintEntitiesSet(setOf(ThisObject)),
107+
marks = TaintMarksSet(setOf(TaintMark("sql-injection"), TaintMark("xss"))),
108+
signature = AnySignature,
109+
conditions = ConditionsMap(mapOf(ReturnValue to ValueCondition(ArgumentValueBoolean(true))))
110+
)
111+
),
112+
sinks = listOf(
113+
Sink(
114+
methodFqn = MethodFqn(listOf("org", "example"), "util", "unsafe"),
115+
check = TaintEntitiesSet(setOf(MethodArgument(2u))),
116+
marks = TaintMarksSet(setOf(TaintMark("environment"))),
117+
signature = SignatureList(argumentTypes = listOf(ArgumentTypeAny, ArgumentTypeString("java.lang.Integer"))),
118+
conditions = ConditionsMap(mapOf(MethodArgument(2u) to ValueCondition(ArgumentValueLong(0L))))
119+
)
120+
)
121+
)
122+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.utbot.taint.parser.constants
2+
3+
import org.utbot.taint.parser.yaml.Constants
4+
5+
internal const val k_sources = Constants.KEY_SOURCES
6+
internal const val k_passes = Constants.KEY_PASSES
7+
internal const val k_cleaners = Constants.KEY_CLEANERS
8+
internal const val k_sinks = Constants.KEY_SINKS
9+
10+
internal const val k_addTo = Constants.KEY_ADD_TO
11+
internal const val k_getFrom = Constants.KEY_GET_FROM
12+
internal const val k_removeFrom = Constants.KEY_REMOVE_FROM
13+
internal const val k_check = Constants.KEY_CHECK
14+
internal const val k_marks = Constants.KEY_MARKS
15+
16+
internal const val k_signature = Constants.KEY_SIGNATURE
17+
internal const val k_conditions = Constants.KEY_CONDITIONS
18+
19+
internal const val k_not = Constants.KEY_CONDITION_NOT
20+
21+
internal const val k_this = Constants.KEY_THIS
22+
internal const val k_return = Constants.KEY_RETURN
23+
internal const val k_arg = Constants.KEY_ARG
24+
25+
internal const val k_lt = Constants.ARGUMENT_TYPE_PREFIX
26+
internal const val k_gt = Constants.ARGUMENT_TYPE_SUFFIX
27+
internal const val k__ = Constants.ARGUMENT_TYPE_UNDERSCORE
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package org.utbot.taint.parser.yaml
2+
3+
import com.charleskorn.kaml.*
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.DisplayName
6+
import org.junit.jupiter.api.Nested
7+
import org.junit.jupiter.api.Test
8+
import org.junit.jupiter.api.assertThrows
9+
import org.utbot.taint.parser.constants.*
10+
import org.utbot.taint.parser.model.*
11+
12+
class ConditionParserTest {
13+
14+
@Nested
15+
@DisplayName("parseCondition")
16+
inner class ParseConditionTest {
17+
@Test
18+
fun `should parse yaml null as ValueCondition`() {
19+
val yamlNull = Yaml.default.parseToYamlNode("null")
20+
val expectedCondition = ValueCondition(ArgumentValueNull)
21+
22+
val actualCondition = ConditionParser.parseCondition(yamlNull)
23+
assertEquals(expectedCondition, actualCondition)
24+
}
25+
26+
@Test
27+
fun `should parse yaml scalar with argument value as ValueCondition`() {
28+
val yamlScalar = Yaml.default.parseToYamlNode("some-string")
29+
val expectedCondition = ValueCondition(ArgumentValueString("some-string"))
30+
31+
val actualCondition = ConditionParser.parseCondition(yamlScalar)
32+
assertEquals(expectedCondition, actualCondition)
33+
}
34+
35+
@Test
36+
fun `should parse yaml scalar with argument type as TypeCondition`() {
37+
val yamlScalar = Yaml.default.parseToYamlNode("${k_lt}java.lang.Integer${k_gt}")
38+
val expectedCondition = TypeCondition(ArgumentTypeString("java.lang.Integer"))
39+
40+
val actualCondition = ConditionParser.parseCondition(yamlScalar)
41+
assertEquals(expectedCondition, actualCondition)
42+
}
43+
44+
@Test
45+
fun `should parse yaml list as OrCondition`() {
46+
val yamlList = Yaml.default.parseToYamlNode("[ 1, true, ${k_lt}java.lang.Integer${k_gt} ]")
47+
val expectedCondition = OrCondition(listOf(
48+
ValueCondition(ArgumentValueLong(1L)),
49+
ValueCondition(ArgumentValueBoolean(true)),
50+
TypeCondition(ArgumentTypeString("java.lang.Integer")),
51+
))
52+
53+
val actualCondition = ConditionParser.parseCondition(yamlList)
54+
assertEquals(expectedCondition, actualCondition)
55+
}
56+
57+
@Test
58+
fun `should parse yaml map with a key 'not' as NotCondition`() {
59+
val yamlMap = Yaml.default.parseToYamlNode("$k_not: ${k_lt}int${k_gt}")
60+
val expectedCondition = NotCondition(TypeCondition(ArgumentTypeString("int")))
61+
62+
val actualCondition = ConditionParser.parseCondition(yamlMap)
63+
assertEquals(expectedCondition, actualCondition)
64+
}
65+
66+
@Test
67+
fun `should fail on yaml map without a key 'not'`() {
68+
val yamlMap = Yaml.default.parseToYamlNode("net: ${k_lt}int${k_gt}")
69+
70+
assertThrows<ConfigurationParseError> {
71+
ConditionParser.parseCondition(yamlMap)
72+
}
73+
}
74+
75+
@Test
76+
fun `should fail on yaml map with unknown keys`() {
77+
val yamlMap = Yaml.default.parseToYamlNode("{ $k_not: ${k_lt}int${k_gt}, unknown-key: 0 }")
78+
79+
assertThrows<ConfigurationParseError> {
80+
ConditionParser.parseCondition(yamlMap)
81+
}
82+
}
83+
84+
@Test
85+
fun `should parse complicated yaml node`() {
86+
val yamlMap = Yaml.default.parseToYamlNode("$k_not: [ { $k_not: 0 }, ${k_lt}int${k_gt}, { $k_not: null } ]")
87+
val expectedCondition = NotCondition(OrCondition(listOf(
88+
NotCondition(ValueCondition(ArgumentValueLong(0L))),
89+
TypeCondition(ArgumentTypeString("int")),
90+
NotCondition(ValueCondition(ArgumentValueNull))
91+
)))
92+
93+
val actualCondition = ConditionParser.parseCondition(yamlMap)
94+
assertEquals(expectedCondition, actualCondition)
95+
}
96+
97+
@Test
98+
fun `should fail on another yaml type`() {
99+
val yamlTaggedNode = YamlTaggedNode("some-tag", YamlNull(YamlPath.root))
100+
101+
assertThrows<ConfigurationParseError> {
102+
ConditionParser.parseCondition(yamlTaggedNode)
103+
}
104+
}
105+
}
106+
107+
@Nested
108+
@DisplayName("parseConditions")
109+
inner class ParseConditionsTest {
110+
@Test
111+
fun `should parse correct yaml map as Conditions`() {
112+
val yamlMap = Yaml.default.parseToYamlNode("{ $k_this: \"\", ${k_arg}2: { $k_not: ${k_lt}int${k_gt} }, $k_return: [ 0, 1 ] }")
113+
val expectedConditions = ConditionsMap(mapOf(
114+
ThisObject to ValueCondition(ArgumentValueString("")),
115+
MethodArgument(2u) to NotCondition(TypeCondition(ArgumentTypeString("int"))),
116+
ReturnValue to OrCondition(listOf(ValueCondition(ArgumentValueLong(0L)), ValueCondition(ArgumentValueLong(1L))))
117+
))
118+
119+
val actualConditions = ConditionParser.parseConditions(yamlMap)
120+
assertEquals(expectedConditions, actualConditions)
121+
}
122+
123+
@Test
124+
fun `should parse empty yaml map as NoConditions`() {
125+
val yamlMap = Yaml.default.parseToYamlNode("{}")
126+
val expectedConditions = NoConditions
127+
128+
val actualConditions = ConditionParser.parseConditions(yamlMap)
129+
assertEquals(expectedConditions, actualConditions)
130+
}
131+
132+
@Test
133+
fun `should fail on another yaml type`() {
134+
val yamlList = Yaml.default.parseToYamlNode("[]")
135+
136+
assertThrows<ConfigurationParseError> {
137+
ConditionParser.parseConditions(yamlList)
138+
}
139+
}
140+
}
141+
142+
@Nested
143+
@DisplayName("parseConditionsKey")
144+
inner class ParseConditionsKeyTest {
145+
@Test
146+
fun `should parse yaml map with a key 'conditions'`() {
147+
val yamlMap = Yaml.default.parseToYamlNode("$k_conditions: { $k_return: null }").yamlMap
148+
val expectedConditions = ConditionsMap(mapOf(ReturnValue to ValueCondition(ArgumentValueNull)))
149+
150+
val actualConditions = ConditionParser.parseConditionsKey(yamlMap)
151+
assertEquals(expectedConditions, actualConditions)
152+
}
153+
154+
@Test
155+
fun `should parse yaml map without a key 'conditions' as NoConditions`() {
156+
val yamlMap = Yaml.default.parseToYamlNode("$k_marks: []").yamlMap
157+
val expectedConditions = NoConditions
158+
159+
val actualConditions = ConditionParser.parseConditionsKey(yamlMap)
160+
assertEquals(expectedConditions, actualConditions)
161+
}
162+
}
163+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.utbot.taint.parser.yaml
2+
3+
import com.charleskorn.kaml.Yaml
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.DisplayName
6+
import org.junit.jupiter.api.Nested
7+
import org.junit.jupiter.api.Test
8+
import org.utbot.taint.parser.constants.*
9+
import org.utbot.taint.parser.model.*
10+
import org.junit.jupiter.api.assertThrows
11+
12+
class ConfigurationParserTest {
13+
14+
@Nested
15+
@DisplayName("parseConfiguration")
16+
inner class ParseConfigurationTest {
17+
@Test
18+
fun `should parse yaml map as Configuration`() {
19+
val yamlMap = Yaml.default.parseToYamlNode("{ $k_sources: [], $k_passes: [], $k_cleaners: [], $k_sinks: [] }")
20+
val expectedConfiguration = Configuration(listOf(), listOf(), listOf(), listOf())
21+
22+
val actualConfiguration = ConfigurationParser.parseConfiguration(yamlMap)
23+
assertEquals(expectedConfiguration, actualConfiguration)
24+
}
25+
26+
@Test
27+
fun `should not fail if yaml map does not contain some keys`() {
28+
val yamlMap = Yaml.default.parseToYamlNode("{ $k_sources: [], $k_sinks: [] }")
29+
val expectedConfiguration = Configuration(listOf(), listOf(), listOf(), listOf())
30+
31+
val actualConfiguration = ConfigurationParser.parseConfiguration(yamlMap)
32+
assertEquals(expectedConfiguration, actualConfiguration)
33+
}
34+
35+
@Test
36+
fun `should fail on other yaml types`() {
37+
val yamlList = Yaml.default.parseToYamlNode("[]")
38+
39+
assertThrows<ConfigurationParseError> {
40+
ConfigurationParser.parseConfiguration(yamlList)
41+
}
42+
}
43+
44+
@Test
45+
fun `should fail on yaml map with unknown keys`() {
46+
val yamlMap = Yaml.default.parseToYamlNode(
47+
"{ $k_sources: [], $k_passes: [], $k_cleaners: [], $k_sinks: [], unknown-key: [] }"
48+
)
49+
50+
assertThrows<ConfigurationParseError> {
51+
ConfigurationParser.parseConfiguration(yamlMap)
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)