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

Code and location from children #1411

Merged
merged 7 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -143,13 +143,33 @@ abstract class LanguageFrontend<AstNode, TypeNode>(
* @return the String of the newline
*/
fun getNewLineType(node: Node): String {
var region = node.location?.region
return getNewLineType(node.code ?: "", region)
}

/**
* To prevent issues with different newline types and formatting.
*
* @param multilineCode
* - The newline type is extracted from the code assuming it contains newlines
*
* @return the String of the newline or \n as default
*/
fun getNewLineType(multilineCode: String, region: Region? = null): String {
var code = multilineCode
region?.let {
if (it.startLine != it.endLine) {
code = code.substring(0, code.length - it.endColumn + 1)
}
}

val nls = listOf("\n\r", "\r\n", "\n")
for (nl in nls) {
if (node.toString().endsWith(nl)) {
if (code.endsWith(nl)) {
return nl
}
}
log.debug("Could not determine newline type. Assuming \\n. {}", node)
log.debug("Could not determine newline type. Assuming \\n.")
return "\n"
}

Expand All @@ -169,7 +189,11 @@ abstract class LanguageFrontend<AstNode, TypeNode>(
*/
fun getCodeOfSubregion(node: Node, nodeRegion: Region, subRegion: Region): String {
val code = node.code ?: return ""
val nlType = getNewLineType(node)
return getCodeOfSubregion(code, nodeRegion, subRegion)
}

fun getCodeOfSubregion(code: String, nodeRegion: Region, subRegion: Region): String {
val nlType = getNewLineType(code, nodeRegion)
val start =
if (subRegion.startLine == nodeRegion.startLine) {
subRegion.startColumn - nodeRegion.startColumn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.types.*
import de.fraunhofer.aisec.cpg.passes.inference.IsImplicitProvider
import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider
import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation
import de.fraunhofer.aisec.cpg.sarif.Region
import org.slf4j.LoggerFactory

object NodeBuilder {
Expand Down Expand Up @@ -270,3 +271,81 @@ fun <T : Node, S> T.codeAndLocationFrom(frontend: LanguageFrontend<S, *>, rawNod

return this
}

/**
* This function allows the setting of a node's code and location region as the code and location of
* its children. Sometimes, when we translate a parent node in the language-specific AST with its
* children into the CPG AST, we have to set a specific intermediate Node between, that has no
* language-specific AST that can give it a proper code and location.
*
* While the location of the node is determined by the start and end of the child locations, the
* code is extracted from the parent node to catch separators and auxiliary syntactic elements that
* are between the child nodes.
*
* @param frontend Used to invoke language specific code and location generation
* @param parentNode Used to extract the code for this node
*/
fun <T : Node, S> T.codeAndLocationFromChildren(
konradweiss marked this conversation as resolved.
Show resolved Hide resolved
frontend: LanguageFrontend<S, *>,
konradweiss marked this conversation as resolved.
Show resolved Hide resolved
parentNode: S
): T {
var first: Node? = null
var last: Node? = null

// Search through all children to find the first and last node based on region startLine and
// startColumn
val worklist: MutableList<Node> = this.astChildren.toMutableList()
while (worklist.isNotEmpty()) {
val current = worklist.removeFirst()
if (current.location?.region == null || current.location?.region == Region()) {
// If the node has no location we use the same search on his children again
worklist.addAll(current.astChildren)
konradweiss marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Compare nodes by line and column in lexicographic order, i.e. column is compared if
// lines are equal
if (first == null) {
first = current
last = current
}
first =
minOf(
first,
current,
compareBy(
{ it.location?.region?.startLine },
{ it.location?.region?.startColumn }
)
)
last =
maxOf(
last,
current,
compareBy(
{ it?.location?.region?.endLine },
{ it?.location?.region?.endColumn }
)
)
}
}

if (first != null && last != null) {
// Starts and ends are combined to one region
val newRegion =
Region(
startLine = first.location?.region?.startLine ?: -1,
startColumn = first.location?.region?.startColumn ?: -1,
endLine = last.location?.region?.endLine ?: -1,
endColumn = last.location?.region?.endColumn ?: -1,
)
this.location?.region = newRegion

val parentCode = frontend.codeOf(parentNode)
val parentRegion = frontend.locationOf(parentNode)?.region
if (parentCode != null && parentRegion != null) {
// If the parent has code and region the new region is used to extract the code
this.code = frontend.getCodeOfSubregion(parentCode, parentRegion, newRegion)
}
}

return this
}
Loading