Skip to content

Commit b6d423e

Browse files
committed
feat: add new dataflow analysis, replacing existing analysis for GitHub Actions.
Signed-off-by: Nicholas Allen <[email protected]>
1 parent 27cd721 commit b6d423e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+8645
-1790
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/* Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. */
2+
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */
3+
4+
package main
5+
6+
import (
7+
"flag"
8+
"fmt"
9+
"os"
10+
11+
"github.com/oracle/macaron/golang/internal/bashparser"
12+
"github.com/oracle/macaron/golang/internal/filewriter"
13+
)
14+
15+
// Parse the bash expression and provide parsed objects in JSON format to stdout or a file.
16+
// Params:
17+
//
18+
// -input <EXPR_CONTENT>: the bash expr content in string
19+
// -output <OUTPUT_FILE>: the output file path to store the JSON content
20+
//
21+
// Return code:
22+
//
23+
// 0 - Parse successfully, return the JSON as string to stdout. If -output is set, store the json content to the file.
24+
// If there is any errors storing to file, the result is still printed to stdout, but the errors are put to stderr instead.
25+
// 1 - Error: Missing bash script or output file paths.
26+
// 2 - Error: Could not parse the bash script file. Parse errors will be printed to stderr.
27+
func main() {
28+
input := flag.String("input", "", "The bash expr content to be parsed.")
29+
out_path := flag.String("output", "", "The output file path to store the JSON content.")
30+
flag.Parse()
31+
32+
var json_content string
33+
var parse_err error
34+
if len(*input) <= 0 {
35+
fmt.Fprintln(os.Stderr, "Missing bash expr input.")
36+
flag.PrintDefaults()
37+
os.Exit(1)
38+
} else {
39+
// Read the bash script from command line argument.
40+
json_content, parse_err = bashparser.ParseExpr(*input)
41+
}
42+
43+
if parse_err != nil {
44+
fmt.Fprintln(os.Stderr, parse_err.Error())
45+
os.Exit(2)
46+
}
47+
48+
fmt.Println(json_content)
49+
50+
if len(*out_path) > 0 {
51+
err := filewriter.StoreBytesToFile([]byte(json_content), *out_path)
52+
if err != nil {
53+
fmt.Fprintln(os.Stderr, err.Error())
54+
os.Exit(1)
55+
}
56+
}
57+
58+
os.Exit(0)
59+
}

golang/cmd/bashparser/bashparser.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. */
1+
/* Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. */
22
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */
33

44
package main
@@ -29,13 +29,14 @@ func main() {
2929
file_path := flag.String("file", "", "The path of the bash script file.")
3030
input := flag.String("input", "", "The bash script content to be parsed. Input is prioritized over file option.")
3131
out_path := flag.String("output", "", "The output file path to store the JSON content.")
32+
raw := flag.Bool("raw", false, "Return raw parse-tree")
3233
flag.Parse()
3334

3435
var json_content string
3536
var parse_err error
3637
if len(*input) > 0 {
3738
// Read the bash script from command line argument.
38-
json_content, parse_err = bashparser.ParseCommands(*input)
39+
json_content, parse_err = bashparser.Parse(*input, *raw)
3940
} else if len(*file_path) <= 0 {
4041
fmt.Fprintln(os.Stderr, "Missing bash script input or file path.")
4142
flag.PrintDefaults()
@@ -47,7 +48,7 @@ func main() {
4748
fmt.Fprintln(os.Stderr, read_err.Error())
4849
os.Exit(1)
4950
}
50-
json_content, parse_err = bashparser.ParseCommands(string(data))
51+
json_content, parse_err = bashparser.Parse(string(data), *raw)
5152
}
5253

5354
if parse_err != nil {

golang/internal/bashparser/bashparser.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. */
1+
/* Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. */
22
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */
33

44
// Package bashparser parses the bash scripts and provides parsed objects in JSON.
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212

1313
"mvdan.cc/sh/v3/syntax"
14+
"mvdan.cc/sh/v3/syntax/typedjson"
1415
)
1516

1617
// CMDResult is used to export the bash command results in JSON.
@@ -68,3 +69,63 @@ func ParseCommands(data string) (string, error) {
6869
return string(result_bytes), nil
6970

7071
}
72+
73+
func ParseRaw(data string) (string, error) {
74+
// Replace GitHub Actions's expressions with ``$MACARON_UNKNOWN``` variable because the bash parser
75+
// doesn't recognize such expressions. For example: ``${{ foo }}`` will be replaced by ``$MACARON_UNKNOWN``.
76+
// Note that we don't use greedy matching, so if we have `${{ ${{ foo }} }}`, it will not be replaced by
77+
// `$MACARON_UNKNOWN`.
78+
// See: https://docs.github.com/en/actions/learn-github-actions/expressions.
79+
var re, reg_error = regexp.Compile(`\$\{\{.*?\}\}`)
80+
if reg_error != nil {
81+
return "", reg_error
82+
}
83+
84+
// We replace the GH Actions variables with "$MACARON_UNKNOWN".
85+
data = string(re.ReplaceAll([]byte(data), []byte("$$MACARON_UNKNOWN")))
86+
data_str := strings.NewReader(data)
87+
data_parsed, parse_err := syntax.NewParser().Parse(data_str, "")
88+
if parse_err != nil {
89+
return "", parse_err
90+
}
91+
92+
b := new(strings.Builder)
93+
encode_err := typedjson.Encode(b, data_parsed)
94+
if encode_err != nil {
95+
return "", encode_err
96+
}
97+
98+
return b.String(), nil
99+
}
100+
101+
func Parse(data string, raw bool) (string, error) {
102+
if raw {
103+
return ParseRaw(data)
104+
} else {
105+
return ParseCommands(data)
106+
}
107+
}
108+
109+
func ParseExpr(data string) (string, error) {
110+
data_str := strings.NewReader(data)
111+
result_str := "["
112+
first := true
113+
for word_parsed, parse_err := range syntax.NewParser().WordsSeq(data_str) {
114+
if parse_err != nil {
115+
return "", parse_err
116+
}
117+
b := new(strings.Builder)
118+
encode_err := typedjson.Encode(b, word_parsed)
119+
if encode_err != nil {
120+
return "", encode_err
121+
}
122+
if first {
123+
result_str = result_str + b.String()
124+
first = false
125+
} else {
126+
result_str = result_str + ", " + b.String()
127+
}
128+
}
129+
result_str = result_str + "]"
130+
return result_str, nil
131+
}

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ dependencies = [
4040
"semgrep == 1.113.0",
4141
"email-validator >=2.2.0,<3.0.0",
4242
"rich >=13.5.3,<15.0.0",
43+
"lark >= 1.3.0,<2.0.0",
44+
"frozendict >= 2.4.6, <3.0.0",
4345
]
4446
keywords = []
4547
# https://pypi.org/classifiers/

src/macaron/code_analyzer/call_graph.py

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

0 commit comments

Comments
 (0)