Skip to content

Commit 90b4a67

Browse files
data-douserCopilot
andauthored
feature(actions): support Models-as-Data for actions PrintAST & PrintCFG queries (#360)
* feature(actions): support MaD for actions PrintAST & PrintCFG queries * Refine PrintAST configuration comment Updated the configuration comment for PrintAST to clarify source file selection. Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> * Fix Actions graph query suffix matching at path boundaries Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> * Isolate Actions PrintCFG test fixtures from PrintAST source selection Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> * docs(actions): require source files for PrintCFG Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> --------- Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent a565232 commit 90b4a67

15 files changed

Lines changed: 191 additions & 22 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Shared extensible predicate declarations for MCP server tools queries.
3+
* Values are provided via dataExtensions YAML files during testing,
4+
* or via a temporary data extension pack at runtime from the MCP server.
5+
*/
6+
7+
/** Holds for each selected source file path for AST/CFG printing. */
8+
extensible predicate selectedSourceFiles(string path);

‎server/ql/actions/tools/src/PrintAST/PrintAST.ql‎

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,38 @@
99
private import codeql.actions.ideContextual.IDEContextual
1010
import codeql.actions.ideContextual.printAst
1111
private import codeql.actions.Ast
12+
import ExternalPredicates
1213

1314
/**
14-
* Configuration for PrintAST that uses test directory structure (maintains original behavior for unit tests).
15+
* Gets a single source file from the comma-separated list.
16+
*/
17+
string getSelectedSourceFile() {
18+
exists(string s | selectedSourceFiles(s) | result = s.splitAt(",").trim())
19+
}
20+
21+
/**
22+
* Configuration for PrintAST that uses external predicates to specify source files.
23+
*
24+
* Matching is done on the location's own strings because the actions library does not expose a
25+
* `File` type the way the other languages' libraries do.
1526
*/
1627
class Cfg extends PrintAstConfiguration {
1728
override predicate shouldPrintNode(PrintAstNode n) {
1829
super.shouldPrintNode(n) and
19-
// Only include source files with a `test` directory structure
20-
(
21-
// For a file located under some `test/*/.github/workflows` directory structure
22-
n.getLocation()
23-
.getFile()
24-
.getParentContainer()
25-
.getParentContainer()
26-
.getParentContainer()
27-
.getParentContainer()
28-
.getBaseName() = "test"
30+
exists(string selectedFile | selectedFile = getSelectedSourceFile() |
31+
// Match by exact relative path from source root
32+
n.getLocation().getFile().getRelativePath() = selectedFile
33+
or
34+
// Match by file name if no path separators
35+
not selectedFile.matches("%/%") and n.getLocation().getFile().getBaseName() = selectedFile
2936
or
30-
// For a file located under some `test/*/*/action.yml` directory structure
31-
n.getLocation()
32-
.getFile()
33-
.getParentContainer()
34-
.getParentContainer()
35-
.getParentContainer()
36-
.getBaseName() = "test" and
37-
n.getLocation().getFile().getBaseName() = "action.yml"
37+
// Match by ending path component
38+
exists(string absolute | absolute = n.getLocation().getFile().getAbsolutePath() |
39+
absolute = selectedFile
40+
or
41+
absolute.length() > selectedFile.length() and
42+
absolute.suffix(absolute.length() - selectedFile.length() - 1) = "/" + selectedFile
43+
)
3844
)
3945
}
4046
}

‎server/ql/actions/tools/src/PrintCFG/PrintCFG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Produces a representation of a file's Control Flow Graph (CFG) for GitHub Action
66

77
The Control Flow Graph represents the order in which steps and jobs are executed in a GitHub Actions workflow. Each node in the graph represents a control-flow element (job, step, or action component), and edges represent possible execution paths between them.
88

9-
This query outputs all CFG nodes and their successor relationships for GitHub Actions YAML files, which is useful for understanding workflow execution flow and analyzing action dependencies.
9+
This query outputs CFG nodes and their successor relationships for the GitHub Actions YAML files selected by `sourceFiles`, which is useful for understanding workflow execution flow and analyzing action dependencies.
10+
11+
When invoking this query with `codeql_query_run` for the `actions` language, `sourceFiles` is required. Provide a comma-separated list of source file paths, such as `".github/workflows/ci.yml,action.yml"`.
1012

1113
## Use Cases
1214

‎server/ql/actions/tools/src/PrintCFG/PrintCFG.ql‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,52 @@
99
import codeql.actions.Ast
1010
private import codeql.actions.Cfg
1111
import codeql.actions.controlflow.BasicBlocks
12+
import ExternalPredicates
1213

1314
/**
14-
* Configuration for PrintCFG that outputs all CFG nodes and edges.
15+
* Gets a single source file from the comma-separated list.
16+
*/
17+
string getSelectedSourceFile() {
18+
exists(string s | selectedSourceFiles(s) | result = s.splitAt(",").trim())
19+
}
20+
21+
/**
22+
* Holds if this CFG node should be included in output.
23+
*
24+
* Unfiltered, this query emits every node in the database, which for a real repository is
25+
* unbounded rather than merely large. Matching is done on the location's own strings because
26+
* the actions library does not expose a `File` type the way the other languages' libraries do.
27+
*/
28+
predicate shouldPrintNode(Node node) {
29+
exists(string selectedFile | selectedFile = getSelectedSourceFile() |
30+
// Match by exact relative path from source root
31+
node.getLocation().getFile().getRelativePath() = selectedFile
32+
or
33+
// Match by file name if no path separators
34+
not selectedFile.matches("%/%") and
35+
node.getLocation().getFile().getBaseName() = selectedFile
36+
or
37+
// Match by ending path component
38+
exists(string absolute | absolute = node.getLocation().getFile().getAbsolutePath() |
39+
absolute = selectedFile
40+
or
41+
absolute.length() > selectedFile.length() and
42+
absolute.suffix(absolute.length() - selectedFile.length() - 1) = "/" + selectedFile
43+
)
44+
)
45+
}
46+
47+
/**
48+
* Configuration for PrintCFG that outputs filtered CFG nodes and edges.
1549
*/
1650
query predicate nodes(Node node, string property, string value) {
51+
shouldPrintNode(node) and
1752
property = "semmle.label" and
1853
value = node.toString()
1954
}
2055

21-
query predicate edges(Node pred, Node succ) { pred.getASuccessor() = succ }
56+
query predicate edges(Node pred, Node succ) {
57+
shouldPrintNode(pred) and
58+
shouldPrintNode(succ) and
59+
pred.getASuccessor() = succ
60+
}

‎server/ql/actions/tools/test/PrintAST/PrintAST.expected‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,35 @@ action-example1/action.yml:
6363

6464
# 18| [ExpressionImpl] inputs.who-to-greet
6565

66+
nested/action-example1/action.yml:
67+
# 1| [CompositeActionImpl] name: S ... action
68+
#-----| 1 -> [ScalarValueImpl] Selected action
69+
#-----| 2 -> [ScalarValueImpl] A complete directory suffix still matches
70+
#-----| 3 -> [RunsImpl] using: composite
71+
#-----| 4 -> [ScalarValueImpl] composite
72+
#-----| 5 -> [ScalarValueImpl] echo "Selected"
73+
#-----| 6 -> [ScalarValueImpl] bash
74+
75+
# 1| [ScalarValueImpl] Selected action
76+
77+
# 2| [ScalarValueImpl] A complete directory suffix still matches
78+
79+
# 4| [RunsImpl] using: composite
80+
#-----| 1 -> [ScalarValueImpl] composite
81+
#-----| 2 -> [StepImpl] Run Step
82+
#-----| 3 -> [ScalarValueImpl] echo "Selected"
83+
#-----| 4 -> [ScalarValueImpl] bash
84+
85+
# 4| [ScalarValueImpl] composite
86+
87+
# 6| [StepImpl] Run Step
88+
#-----| 1 -> [ScalarValueImpl] echo "Selected"
89+
#-----| 2 -> [ScalarValueImpl] bash
90+
91+
# 6| [ScalarValueImpl] echo "Selected"
92+
93+
# 7| [ScalarValueImpl] bash
94+
6695
.github/workflows/reusable_workflow.yml:
6796
# 1| [WorkflowImpl] name: R ... example
6897
#-----| 1 -> [ScalarValueImpl] Reusable workflow example
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: Unselected action
2+
description: A directory component must match in full
3+
runs:
4+
using: composite
5+
steps:
6+
- run: echo "Not selected"
7+
shell: bash
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: Unselected workflow
2+
on: [push]
3+
jobs:
4+
test:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- run: echo "Not selected"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: Selected action
2+
description: A complete directory suffix still matches
3+
runs:
4+
using: composite
5+
steps:
6+
- run: echo "Selected"
7+
shell: bash

‎server/ql/actions/tools/test/PrintCFG/PrintCFG.expected‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ workflow.yml:
22
# 1| enter name: Example1
33
#-----| -> name: Example1
44

5+
nested/cfg-example1/action.yml:
6+
# 1| enter name: S ... action
7+
#-----| -> name: S ... action
8+
9+
workflow.yml:
510
# 1| name: Example1
611
#-----| -> Job: test
712

@@ -17,7 +22,29 @@ workflow.yml:
1722
# 5| Job: test
1823
#-----| -> Uses Step
1924

25+
nested/cfg-example1/action.yml:
26+
# 1| name: S ... action
27+
#-----| -> using: composite
28+
29+
# 4| using: composite
30+
#-----| -> Run Step
31+
32+
# 6| Run Step
33+
#-----| -> echo "Selected"
34+
35+
# 6| echo "Selected"
36+
#-----| -> exit name: S ... action (normal)
37+
38+
workflow.yml:
2039
# 1| exit name: Example1 (normal)
2140
#-----| -> exit name: Example1
2241

42+
nested/cfg-example1/action.yml:
43+
# 1| exit name: S ... action (normal)
44+
#-----| -> exit name: S ... action
45+
46+
workflow.yml:
2347
# 1| exit name: Example1
48+
49+
nested/cfg-example1/action.yml:
50+
# 1| exit name: S ... action
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: Unselected action
2+
description: A directory component must match in full
3+
runs:
4+
using: composite
5+
steps:
6+
- run: echo "Not selected"
7+
shell: bash

0 commit comments

Comments
 (0)