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

Add Comprehensions of Lists, Sets and Maps and generator expressions #1786

Merged
merged 42 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7f6d456
Add comprehension expression
KuechA Oct 10, 2024
907dc27
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 10, 2024
92114cc
Initial python translation for listcomp
KuechA Oct 10, 2024
43e779f
SetComp and DictComp in python frontend
KuechA Oct 10, 2024
a5f6dab
First simple tests
KuechA Oct 10, 2024
7d3048a
test in main
KuechA Oct 11, 2024
c9aadd2
Try to add DFG edges
KuechA Oct 16, 2024
7441d33
Fix not implemented error
KuechA Oct 17, 2024
530bc52
Fix more bugs
KuechA Oct 17, 2024
54193eb
Also handle GeneratorExp, add some documentation.
KuechA Oct 17, 2024
4afc7f1
Extract nested class to own file
KuechA Oct 17, 2024
ef08691
Fix bug, aggregate predicates
KuechA Oct 17, 2024
49a7be9
Remove unnecessary changes
KuechA Oct 17, 2024
aa289ed
Specify idea for EOG
KuechA Oct 17, 2024
1361c53
Fake higher test coverage
KuechA Oct 17, 2024
487b38e
More testing
KuechA Oct 17, 2024
4a3607e
More tests
KuechA Oct 17, 2024
3c919ed
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 17, 2024
9fc31e8
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 18, 2024
a30738d
Fix error from renaming
KuechA Oct 18, 2024
b4ff771
Handle the comprehension expression in the control flow sensitive DFG
KuechA Oct 21, 2024
b36e104
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 21, 2024
d334a17
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 22, 2024
313dd45
Adding alternatives to EOG for collection comprehension and fixing sy…
konradweiss Oct 22, 2024
4d38317
Merge branch 'ak/slice-all-collections' of github.com:Fraunhofer-AISE…
konradweiss Oct 22, 2024
9c26db3
Adding alternative that properly depicts generator behavior
konradweiss Oct 22, 2024
ee0e9dd
Small fix
konradweiss Oct 22, 2024
49e45e7
Alternative for ComprehensionExpression
konradweiss Oct 22, 2024
737415c
Fix
konradweiss Oct 22, 2024
e8d7719
Adding EOG handling for ComprehensionExpression and CollectionCompreh…
konradweiss Oct 22, 2024
389eb01
Add test and fix EOG pass implementation
konradweiss Oct 24, 2024
467c6f5
Allow th addition to something that holds arguments and something tha…
konradweiss Oct 24, 2024
6d6af25
Remove useless stuff from ControlflowSensitiveDFGPass
KuechA Oct 24, 2024
761c244
Make non-optional things non-optional
KuechA Oct 24, 2024
b70c63b
Fix test
KuechA Oct 24, 2024
20e863a
Remove condition to reduce code which needs coverage
KuechA Oct 24, 2024
f41d765
More tests
KuechA Oct 24, 2024
91f4ea1
Update stuff
KuechA Oct 29, 2024
b5450ff
review
KuechA Oct 29, 2024
acc7b79
review
KuechA Oct 29, 2024
e6c639f
Merge branch 'main' into ak/slice-all-collections
KuechA Oct 29, 2024
fb30be7
generator type
KuechA Oct 30, 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 @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log
import de.fraunhofer.aisec.cpg.graph.edges.flows.ContextSensitiveDataflow
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.AssignExpression
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CollectionComprehension
import de.fraunhofer.aisec.cpg.graph.types.ProblemType
import de.fraunhofer.aisec.cpg.graph.types.Type

Expand Down Expand Up @@ -525,6 +526,24 @@ fun MetadataProvider.newInitializerListExpression(
return node
}

@JvmOverloads
fun MetadataProvider.newComprehensionExpression(rawNode: Any? = null): ComprehensionExpression {
val node = ComprehensionExpression()
node.applyMetadata(this, EMPTY_NAME, rawNode, true)

log(node)
return node
}

@JvmOverloads
fun MetadataProvider.newCollectionComprehension(rawNode: Any? = null): CollectionComprehension {
val node = CollectionComprehension()
node.applyMetadata(this, EMPTY_NAME, rawNode, true)

log(node)
return node
}

/**
* Creates a new [TypeExpression]. The [MetadataProvider] receiver will be used to fill different
* meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.*
import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope
import de.fraunhofer.aisec.cpg.graph.statements.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.*
import de.fraunhofer.aisec.cpg.graph.statements.expressions.CollectionComprehension
import de.fraunhofer.aisec.cpg.graph.types.FunctionType
import de.fraunhofer.aisec.cpg.graph.types.Type
import de.fraunhofer.aisec.cpg.graph.types.UnknownType
Expand Down Expand Up @@ -325,6 +326,47 @@ fun LanguageFrontend<*, *>.subscriptExpr(
return node
}

context(Holder<out Statement>)
fun LanguageFrontend<*, *>.listComp(
init: (CollectionComprehension.() -> Unit)? = null
): CollectionComprehension {
val node = newCollectionComprehension()

if (init != null) {
init(node)
}

// Only add this to an argument holder if the nearest holder is an argument holder
val holder = this@Holder
if (holder is StatementHolder) {
holder += node
}
if (holder is ArgumentHolder) {
holder += node
KuechA marked this conversation as resolved.
Show resolved Hide resolved
}

return node
}

context(Holder<out Statement>)
fun LanguageFrontend<*, *>.compExpr(
init: (ComprehensionExpression.() -> Unit)? = null
): ComprehensionExpression {
val node = newComprehensionExpression()

if (init != null) {
init(node)
}

// Only add this to an argument holder if the nearest holder is an argument holder
val holder = this@Holder
if (holder is ArgumentHolder) {
KuechA marked this conversation as resolved.
Show resolved Hide resolved
holder += node
}

return node
}

/**
* Creates a new [DeclarationStatement] in the Fluent Node DSL and adds it to the
* [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fun <NodeType : Node> Node.astEdgesOf(
}

/**
* Creates an single optional [AstEdge] starting from this node (wrapped in a [EdgeSingletonList]
* Creates a single optional [AstEdge] starting from this node (wrapped in a [EdgeSingletonList]
* container).
*/
fun <NodeType : Node> Node.astOptionalEdgeOf(
Expand All @@ -64,7 +64,7 @@ fun <NodeType : Node> Node.astOptionalEdgeOf(
}

/**
* Creates an single [AstEdge] starting from this node (wrapped in a [EdgeSingletonList] container).
* Creates a single [AstEdge] starting from this node (wrapped in a [EdgeSingletonList] container).
*/
fun <NodeType : Node> Node.astEdgeOf(
of: NodeType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ class UnwrappedEdgeList<NodeType : Node, EdgeType : Edge<NodeType>>(
}

override fun subList(fromIndex: Int, toIndex: Int): MutableList<NodeType> {
TODO("Not yet implemented")
return if (list.outgoing) {
list.subList(fromIndex, toIndex).map { it.end }.toMutableList()
} else {
@Suppress("UNCHECKED_CAST")
KuechA marked this conversation as resolved.
Show resolved Hide resolved
list.subList(fromIndex, toIndex).map { it.start as NodeType }.toMutableList()
}
}

override fun get(index: Int): NodeType {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2024, 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.cpg.graph.statements.expressions

import de.fraunhofer.aisec.cpg.graph.*
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
import de.fraunhofer.aisec.cpg.graph.statements.Statement
import java.util.Objects
import org.apache.commons.lang3.builder.ToStringBuilder
import org.neo4j.ogm.annotation.Relationship

/**
* Represent a list/set/map comprehension or similar expression. It contains four major components:
* The statement, the variable, the iterable and a predicate which are combined to something like
* `[statement(variable) : variable in iterable if predicate(variable)]`.
*
* Some languages provide a way to have multiple variables, iterables and predicates. For this
* reason, we represent the `variable, iterable and predicate in its own class
* [CollectionComprehension.ComprehensionExpression].
*/
class CollectionComprehension : Expression(), ArgumentHolder {

@Relationship("COMPREHENSION_EXPRESSIONS")
var comprehensionExpressionEdges = astEdgesOf<ComprehensionExpression>()
/**
* This field contains one or multiple [ComprehensionExpression]s.
*
* Note: Instead of having a list here, we could also enforce that the frontend nests the
* expressions in a meaningful way (in particular this would help us to satisfy dependencies
* between the comprehensions' variables).
*/
var comprehensionExpressions by
unwrapping(CollectionComprehension::comprehensionExpressionEdges)

@Relationship("STATEMENT") var statementEdge = astOptionalEdgeOf<Statement>()
/**
* This field contains the statement which is applied to each element of the input for which the
* predicate returned `true`.
*/
var statement by unwrapping(CollectionComprehension::statementEdge)

override fun toString() =
ToStringBuilder(this, TO_STRING_STYLE)
.appendSuper(super.toString())
.append("statement", statement)
.append("comprehensions", comprehensionExpressions)
.toString()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CollectionComprehension) return false
return super.equals(other) &&
statement == other.statement &&
comprehensionExpressions == other.comprehensionExpressions
}

override fun hashCode() = Objects.hash(super.hashCode(), statement, comprehensionExpressions)

override fun addArgument(expression: Expression) {
if (this.statement == null) {
this.statement = expression
} else if (expression is ComprehensionExpression) {
this.comprehensionExpressions += expression
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
if (this.statement == old) {
this.statement = new
return true
}
if (new !is ComprehensionExpression) return false
var changedSomething = false
val newCompExp =
this.comprehensionExpressions.map {
if (it == old) {
changedSomething = true
new
} else it
}
this.comprehensionExpressions.clear()
this.comprehensionExpressions.addAll(newCompExp)
return changedSomething
}

override fun hasArgument(expression: Expression): Boolean {
return this.statement == expression || expression in this.comprehensionExpressions
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright (c) 2024, 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.cpg.graph.statements.expressions

import de.fraunhofer.aisec.cpg.graph.AccessValues
import de.fraunhofer.aisec.cpg.graph.ArgumentHolder
import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf
import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
import de.fraunhofer.aisec.cpg.graph.statements.Statement
import java.util.Objects
import kotlin.collections.plusAssign
KuechA marked this conversation as resolved.
Show resolved Hide resolved
import org.apache.commons.lang3.builder.ToStringBuilder
import org.neo4j.ogm.annotation.Relationship

/** This class holds the variable, iterable and predicate of the [CollectionComprehension]. */
class ComprehensionExpression : Expression(), ArgumentHolder {
@Relationship("VARIABLE")
var variableEdge =
astEdgeOf<Statement>(
of = ProblemExpression("Missing variableEdge in ${this::class}"),
onChanged = { _, new ->
val end = new?.end
if (end is Reference) {
end.access = AccessValues.WRITE
}
}
)

/**
* This field contains the iteration variable of the comprehension. It can be either a new
* variable declaration or a reference (probably to a new variable).
*/
var variable by unwrapping(ComprehensionExpression::variableEdge)

@Relationship("ITERABLE")
var iterableEdge =
astEdgeOf<Expression>(ProblemExpression("Missing iterable in ${this::class}"))

/** This field contains the iteration subject of the loop. */
var iterable by unwrapping(ComprehensionExpression::iterableEdge)

@Relationship("PREDICATE") var predicateEdge = astOptionalEdgeOf<Statement>()

/**
* This field contains the predicate which has to hold to evaluate `statement(variable)` and
* include it in the result.
*/
var predicate by unwrapping(ComprehensionExpression::predicateEdge)

override fun toString() =
ToStringBuilder(this, TO_STRING_STYLE)
.appendSuper(super.toString())
.append("variable", variable)
.append("iterable", iterable)
.append("predicate", predicate)
.toString()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ComprehensionExpression) return false
return super.equals(other) &&
variable == other.variable &&
iterable == other.iterable &&
predicate == other.predicate
}

override fun hashCode() = Objects.hash(super.hashCode(), variable, iterable, predicate)

override fun addArgument(expression: Expression) {
KuechA marked this conversation as resolved.
Show resolved Hide resolved
if (this.variable == null) {
this.variable = expression
} else if (this.iterable == null) {
this.iterable = expression
} else {
this.predicate = expression
}
}

override fun replaceArgument(old: Expression, new: Expression): Boolean {
KuechA marked this conversation as resolved.
Show resolved Hide resolved
if (this.variable == old) {
this.variable = new
return true
}

if (this.iterable == old) {
this.iterable = new
return true
}

if (this.predicate == old) {
this.predicate = new
return true
}
return false
}

override fun hasArgument(expression: Expression): Boolean {
KuechA marked this conversation as resolved.
Show resolved Hide resolved
return this.variable == expression ||
this.iterable == expression ||
expression == this.predicate
}
}
Loading
Loading