Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Length Type Keyword #876

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
31acd51
introduce the Length Type to check the length of an argument
CodingDepot Jun 17, 2024
0369e26
Merge branch 'main' into rh/length-type
CodingDepot Jun 17, 2024
d81132e
Add Length related tests
CodingDepot Jun 24, 2024
bb82ebd
rewrite cpgGetNodes to return the associated Result with the Node
CodingDepot Jun 24, 2024
46b86ea
move result to own file and rewrite cpgSignature to use results
CodingDepot Jun 26, 2024
1cb9af5
have cpgGetNodes return a Map and adapt the Evaluators
CodingDepot Jul 1, 2024
aee5df3
changes around new cpgGetNodes
CodingDepot Jul 1, 2024
e2371a4
fixes to the signatureTest
CodingDepot Jul 1, 2024
945768e
remove custom and implementation between Boolean and Result because o…
CodingDepot Jul 1, 2024
3001542
add code documentation
CodingDepot Jul 1, 2024
f9f02e9
fix code style issues
CodingDepot Jul 3, 2024
54f2d74
Merge branch 'refs/heads/main' into rh/length-type
CodingDepot Jul 3, 2024
9c03a5b
only add the result when the CallExpression is not invalid
CodingDepot Jul 3, 2024
893f827
Merge branch 'refs/heads/main' into rh/length-type
CodingDepot Jul 3, 2024
5985bf9
disable tests that rely on missing CPG features
CodingDepot Jul 8, 2024
3804986
remove all not-length-type related changes
CodingDepot Jul 10, 2024
1d8267b
cleanup
CodingDepot Jul 10, 2024
42a8c44
add signature test for the Length
CodingDepot Jul 24, 2024
5cb49cb
differentiate between good and bad length parameter
CodingDepot Jul 24, 2024
a60f69b
add test for Long Range instead of only testing Int Range
CodingDepot Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.query.dataFlow
import de.fraunhofer.aisec.cpg.query.executionPath
import de.fraunhofer.aisec.cpg.query.*

//
// all functions/properties defined here must use CokoBackend
Expand Down Expand Up @@ -168,14 +167,49 @@
val regex = Regex(this)
regex.matches((it as? Expression)?.evaluate()?.toString().orEmpty()) || regex.matches(it.code.orEmpty())
}
// Separate cases for IntRange and LongRange result in a huge performance boost for large ranges
is LongRange, is IntRange -> checkRange(that)
is Iterable<*> -> this.any { it?.cpgFlowsTo(that) ?: false }
is Array<*> -> this.any { it?.cpgFlowsTo(that) ?: false }
is Node -> that.any { dataFlow(this, it).value }
is ParameterGroup -> this.parameters.all { it?.cpgFlowsTo(that) ?: false }
is Length -> checkLength(that)
else -> this in that.map { (it as Expression).evaluate() }
}
}

private fun Any.checkRange(that: Collection<Node>): Boolean {
when (this) {
// I would love to combine the following two cases, but any implementation loses the benefit of
// quickly reading the last value of the range, therefore making the whole distinction useless.
is IntRange -> {
return that.all {
val minValue = min(it).value.toInt()
val maxValue = max(it).value.toInt()
minValue > this.first && maxValue < this.last
}
}
is LongRange -> {
return that.all {
val minValue = min(it).value.toInt()
val maxValue = max(it).value.toInt()
minValue > this.first && maxValue < this.last
}
}
else -> throw IllegalArgumentException("Unexpected type")

Check warning on line 199 in codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt

View check run for this annotation

Codecov / codecov/patch

codyze-backends/cpg/src/main/kotlin/de/fraunhofer/aisec/codyze/backends/cpg/coko/dsl/ImplementationDsl.kt#L199

Added line #L199 was not covered by tests
}
}

private fun Length.checkLength(that: Collection<Node>): Boolean {
return that.all {
val size = sizeof(it).value
if (size == -1) {
// TODO: Handle case where size could not be determined -> OPEN Finding
}
size in this.value
}
}

context(CokoBackend)
// TODO: better description
// TODO: in mark there is "..." to symbolize that the last arguments don't matter
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package de.fraunhofer.aisec.codyze.backends.cpg

import de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl.*
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.CokoBackend
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Length
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Type
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.withType
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression
Expand Down Expand Up @@ -144,4 +145,22 @@ class SignatureTest {
for (i in args.indices) verify { with(node) { params[i].cpgFlowsTo(args[i]) } }
}
// TODO hasVarargs

@Test
fun `test signature with good String length`() {
every { node.arguments } returns listOf(stringArgument)
every { stringArgument.type.typeName } returns "kotlin.String"
every { stringArgument.value } returns "test"

assertTrue { with(backend) { with(node) { cpgSignature(Length(4..4)) } } }
}

@Test
fun `test signature with bad String length`() {
every { node.arguments } returns listOf(stringArgument)
every { stringArgument.type.typeName } returns "kotlin.String"
every { stringArgument.value } returns "string"

assertFalse { with(backend) { with(node) { cpgSignature(Length(-1..4)) } } }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2022, Fraunhofer AISEC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.fraunhofer.aisec.codyze.backends.cpg.coko.dsl

import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend
import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.io.path.toPath
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

class ImplementationDslTest {

@Test
fun `test cpgGetAllNodes`() {
val op = op {
"Foo.fun" {
signature(2)
}
}
with(simpleBackend) {
val allNodes = op.cpgGetAllNodes()
assertEquals(
5,
allNodes.size,
"cpgGetAllNodes returned ${allNodes.size} node(s) instead of 5 nodes fo the Op: $op."
)
}
}

@Test
fun `test cpgGetNodes`() {
val op = op {
"Foo.fun" {
signature(1..10)
}
}
with(simpleBackend) {
val nodes = op.cpgGetNodes()
assertEquals(
2,
nodes.size,
"cpgGetNodes returned ${nodes.size} node(s) instead of 2 nodes for the Op: $op."
)
}
}

@Test
fun `test cpgGetNodes with Long Range`() {
val op = op {
"Foo.fun" {
signature(1..10L)
}
}
with(simpleBackend) {
val nodes = op.cpgGetNodes()
assertEquals(
2,
nodes.size,
"cpgGetNodes returned ${nodes.size} node(s) instead of 2 nodes for the Op: $op."
)
}
}

@Test
fun `test cpgGetNodes with unordered parameters`() {
val op = op {
"Foo.bar" {
signature(arrayOf(".*Test.*")) {
- Wildcard
- Wildcard
}
}
}
with(simpleBackend) {
val nodes = op.cpgGetNodes()
assertEquals(
3,
nodes.size,
"cpgGetNodes returned ${nodes.size} node(s) instead of 3 nodes for the Op: $op."
)
}
}

@Disabled("sizeof in CPG currently does not support InitListExpression as initializer")
@Test
fun `test Array Length`() {
val opX: MutableList<Op> = mutableListOf()
for (i in 0..3) {
opX += op {
"Foo.fun" {
signature(
Length(i..i)
)
}
}
}

val results = arrayOf(1, 0, 1, 2)
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

Check warning

Code scanning / detekt

Using `Array<Primitive>` leads to implicit boxing and a performance hit. Warning test

Using Array<Primitive> leads to implicit boxing and a performance hit.
for (i in 0..3) {
with(lengthBackend) {
val nodes = opX[i].cpgGetNodes()
// TODO: filter out false positives
assertEquals(
results[i],
nodes.size,
"cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes " +
"for the Op: ${opX[i]}."
)
}
}
}

@Disabled("sizeof in CPG currently does not support MemberCallExpression as Initializer")
@Test
fun `test List Length`() {
val opX: MutableList<Op> = mutableListOf()
for (i in 0..3) {
opX += op {
"Foo.bar" {
signature(
Length(i..i)
)
}
}
}

val results = arrayOf(1, 0, 1, 2)
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

Check warning

Code scanning / detekt

Using `Array<Primitive>` leads to implicit boxing and a performance hit. Warning test

Using Array<Primitive> leads to implicit boxing and a performance hit.
for (i in 0..3) {
with(lengthBackend) {
val nodes = opX[i].cpgGetNodes()
// TODO: filter out false positives
assertEquals(
results[i],
nodes.size,
"cpgGetNodes returned ${nodes.size} node(s) instead of ${results[i]} nodes " +
"for the Op: ${opX[i]}."
)
}
}
}

companion object {

lateinit var simpleBackend: CokoCpgBackend
lateinit var lengthBackend: CokoCpgBackend

@BeforeAll
@JvmStatic
fun startup() {
val classLoader = ImplementationDslTest::class.java.classLoader

val simpleFileResource = classLoader.getResource("ImplementationDslTest/SimpleJavaFile.java")
val lengthFileResource = classLoader.getResource("ImplementationDslTest/LengthJavaFile.java")

assertNotNull(simpleFileResource)
assertNotNull(lengthFileResource)

val simpleFile = simpleFileResource.toURI().toPath()
val lengthFile = lengthFileResource.toURI().toPath()

simpleBackend = CokoCpgBackend(config = createCpgConfiguration(simpleFile))
lengthBackend = CokoCpgBackend(config = createCpgConfiguration(lengthFile))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.fraunhofer.aisec.codyze.backends.cpg
package de.fraunhofer.aisec.codyze.backends.cpg.coko.evaluators

import de.fraunhofer.aisec.codyze.backends.cpg.coko.CokoCpgBackend
import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding
import de.fraunhofer.aisec.codyze.backends.cpg.createCpgConfiguration
import de.fraunhofer.aisec.codyze.backends.cpg.dummyRule
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.definition
Expand Down
Loading