Skip to content

Commit 18789ec

Browse files
authored
Merge pull request #762 from DataDog/julien/K9VULN-8052
[K9VULN-8052] Add new helper functions
2 parents 77c3f3a + ca7da75 commit 18789ec

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

crates/static-analysis-kernel/src/analysis/ddsa_lib/js/ddsa.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2024 Datadog, Inc.
44

5-
import { _findTaintFlows, transpose, vertexId } from "ext:ddsa_lib/flow/graph";
6-
import { MethodFlow } from "ext:ddsa_lib/flow/java";
7-
import { SEALED_EMPTY_ARRAY } from "ext:ddsa_lib/utility";
8-
import { TreeSitterFieldChildNode } from "ext:ddsa_lib/ts_node";
5+
import {_findTaintFlows, transpose, vertexId} from "ext:ddsa_lib/flow/graph";
6+
import {MethodFlow} from "ext:ddsa_lib/flow/java";
7+
import {SEALED_EMPTY_ARRAY} from "ext:ddsa_lib/utility";
8+
import {TreeSitterFieldChildNode} from "ext:ddsa_lib/ts_node";
99

1010
const { op_digraph_adjacency_list_to_dot, op_ts_node_named_children, op_ts_node_parent } = Deno.core.ops;
1111

@@ -42,6 +42,50 @@ export class DDSA {
4242
return children;
4343
}
4444

45+
/**
46+
* Returns an ancestor node that satisfies a given condition, or undefined if none could be found.
47+
* @param {TreeSitterNode | TreeSitterFieldChildNode} node
48+
* @param {function(TreeSitterNode | TreeSitterFieldChildNode): boolean} predicate
49+
* @returns {TreeSitterNode | TreeSitterFieldChildNode | undefined}
50+
*/
51+
findAncestor(node, predicate) {
52+
if (node === undefined) {
53+
return undefined;
54+
}
55+
let n = ddsa.getParent(node);
56+
while (n !== undefined) {
57+
if (predicate(n)) {
58+
return n;
59+
}
60+
n = ddsa.getParent(n);
61+
}
62+
return undefined;
63+
}
64+
65+
/**
66+
* Returns a descendant node that that satisfies a given condition, or undefined if none could be found.
67+
* Descendants are searched depth-first: the first child and all its descendants, then the second child and all its descendants, and so on.
68+
* @param {TreeSitterNode | TreeSitterFieldChildNode} node
69+
* @param {function(TreeSitterNode | TreeSitterFieldChildNode): boolean} predicate
70+
* @returns {TreeSitterNode | TreeSitterFieldChildNode | undefined}
71+
*/
72+
findDescendant(node, predicate) {
73+
if (predicate === undefined) {
74+
return undefined;
75+
}
76+
77+
for (const c of ddsa.getChildren(node)) {
78+
if (predicate(c)) {
79+
return c;
80+
}
81+
const r = ddsa.findDescendant(c, predicate)
82+
if (r !== undefined) {
83+
return r;
84+
}
85+
}
86+
return undefined;
87+
}
88+
4589
/**
4690
* Fetches and returns the provided node's parent in the tree-sitter tree.
4791
* If the node is the root node of the tree, `undefined` will be returned.

crates/static-analysis-kernel/src/analysis/ddsa_lib/js/ddsa.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License, Version 2.0.
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3-
// Copyright 2024 Datadog, Inc.
3+
// Copyright 2024-2025 Datadog, Inc.
44

55
// (NB: There is no need for any Rust business logic here, as `ddsa.js` is purely the user-facing JavaScript API)
66

@@ -12,14 +12,16 @@ mod tests {
1212
use crate::analysis::ddsa_lib::test_utils::{
1313
cfg_test_v8, js_class_eq, js_instance_eq, shorthand_execute_rule,
1414
};
15-
use crate::analysis::ddsa_lib::JsRuntime;
1615
use crate::analysis::tree_sitter::get_tree_sitter_language;
16+
use crate::model::common::Language::Python;
1717

1818
#[test]
1919
fn js_properties_canary() {
2020
let expected = &[
2121
// Methods
2222
"getChildren",
23+
"findAncestor",
24+
"findDescendant",
2325
"getParent",
2426
"getTaintSinks",
2527
"getTaintSources",
@@ -35,6 +37,48 @@ mod tests {
3537
compat_helper_op_ts_node_named_children("ddsa.getChildren(node)")
3638
}
3739

40+
#[test]
41+
fn test_find_descendant() {
42+
let mut rt = cfg_test_v8().new_runtime();
43+
let text = "print(foo)";
44+
let ts_query = "(module)@module";
45+
let rule_code = r#"
46+
function isIdentifier(n) { return n.cstType === "identifier"; }
47+
48+
function visit(query, filename, code) {{
49+
const n = query.captures.module;
50+
const c = ddsa.findDescendant(n, isIdentifier);
51+
console.log(c.text);
52+
}}
53+
"#;
54+
55+
// Then execute the rule that fetches the children of the node.
56+
let res =
57+
shorthand_execute_rule(&mut rt, Python, ts_query, &rule_code, text, None).unwrap();
58+
assert_eq!(res.console_lines[0], "print");
59+
}
60+
61+
#[test]
62+
fn test_find_ancestor() {
63+
let mut rt = cfg_test_v8().new_runtime();
64+
let text = "print(\"foo\")";
65+
let ts_query = "(string_content)@sc";
66+
let rule_code = r#"
67+
function isModule(n) { return n.cstType === "module"; }
68+
69+
function visit(query, filename, code) {
70+
const n = query.captures.sc;
71+
const c = ddsa.findAncestor(n, isModule);
72+
console.log(c.text);
73+
}
74+
"#;
75+
76+
// Then execute the rule that fetches the children of the node.
77+
let res =
78+
shorthand_execute_rule(&mut rt, Python, ts_query, &rule_code, text, None).unwrap();
79+
assert_eq!(res.console_lines[0], "print(\"foo\")");
80+
}
81+
3882
/// Stella syntax can get named children
3983
#[test]
4084
fn op_ts_node_named_children_stella_compat() {

0 commit comments

Comments
 (0)