chidori_static_analysis/language/
mod.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use petgraph::graph::DiGraph;
5use petgraph::graphmap::DiGraphMap;
6use petgraph::visit::EdgeRef;
7use thiserror::Error;
8
9pub mod typechecker;
10pub mod javascript;
11pub mod python;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
14pub struct TextRange {
15    pub start: usize,
16    pub end: usize,
17}
18
19// TODO: implement a function that infers the language from the source code successfully parsing
20
21// TODO: it would be helpful if reports noted if a value is a global, an arg, or a kwarg
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct ReportItem {
24    // pub context_path: Vec<ContextPath>,
25}
26
27#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
28pub struct ReportTriggerableFunctions {
29    pub arguments: Vec<String>,
30    // pub context_path: Vec<ContextPath>,
31    // TODO: these need their own set of depended values
32    // TODO: we need to extract signatures for triggerable functions
33    pub emit_event: Vec<String>,
34    pub trigger_on: Vec<String>,
35}
36
37#[derive(Debug, Default, Clone)]
38pub struct InternalCallGraph {
39    graph: DiGraph<String, ()>
40}
41
42impl Serialize for InternalCallGraph {
43    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44        where
45            S: serde::Serializer,
46    {
47        let mut adjacency_list = HashMap::new();
48        for node in self.graph.node_indices() {
49            let edges = self.graph.edges(node)
50                .map(|edge| edge.target().index())
51                .collect::<Vec<_>>();
52            adjacency_list.insert(self.graph[node].clone(), edges);
53        }
54        adjacency_list.serialize(serializer)
55    }
56}
57
58impl<'de> Deserialize<'de> for InternalCallGraph {
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60        where
61            D: serde::Deserializer<'de>,
62    {
63        let adjacency_list: HashMap<String, Vec<usize>> = HashMap::deserialize(deserializer)?;
64        let mut graph = DiGraph::new();
65        let mut node_indices = HashMap::new();
66
67        // Add nodes
68        for node in adjacency_list.keys() {
69            let index = graph.add_node(node.clone());
70            node_indices.insert(node.clone(), index);
71        }
72
73        // Add edges
74        for (node, edges) in adjacency_list {
75            let source_index = node_indices[&node];
76            for target_index in edges {
77                if let Some(&target_node_index) = node_indices.values().find(|&&idx| idx.index() == target_index) {
78                    graph.add_edge(source_index, target_node_index, ());
79                }
80            }
81        }
82
83        Ok(InternalCallGraph { graph })
84    }
85}
86
87impl PartialEq for InternalCallGraph {
88    fn eq(&self, other: &Self) -> bool {
89        // Compare the graphs for equality
90        self.graph.node_indices().all(|n| {
91            self.graph[n] == other.graph[n]
92        }) && self.graph.edge_indices().all(|e| {
93            self.graph.edge_endpoints(e) == other.graph.edge_endpoints(e)
94        })
95    }
96}
97
98impl Eq for InternalCallGraph {}
99
100impl Hash for InternalCallGraph {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        // Hash the nodes and edges of the graph
103        for node in self.graph.node_indices() {
104            self.graph[node].hash(state);
105        }
106        for edge in self.graph.edge_indices() {
107            self.graph.edge_endpoints(edge).hash(state);
108        }
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct Report {
114    pub internal_call_graph: InternalCallGraph,
115    pub cell_exposed_values: HashMap<String, ReportItem>,
116    pub cell_depended_values: HashMap<String, ReportItem>,
117    pub triggerable_functions: HashMap<String, ReportTriggerableFunctions>,
118}
119
120
121#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
122pub enum ContextPath {
123    Initialized,
124    InFunction(String, TextRange),
125    InAnonFunction,
126    FunctionArguments,
127    FunctionArgument(String),
128    InClass(String),
129    InFunctionDecorator(usize),
130    InCallExpression,
131    ChName,
132    AssignmentToStatement,
133    AssignmentFromStatement,
134    // bool = true (is locally defined)
135    IdentifierReferredTo {
136        name: String,
137        in_scope: bool,
138        exposed: bool
139    },
140    Attribute(String),
141    Constant(String),
142}
143
144
145#[derive(Error, Debug, Serialize, Deserialize)]
146pub enum ChidoriStaticAnalysisError {
147    #[error("Unknown chidori analysis error")]
148    Unknown,
149    #[error("Parse error at offset {offset} in {source_path}: {msg}. Source: {source_code}")]
150    ParseError {
151        msg: String,
152        offset: u32,
153        source_path: String,
154        source_code: String
155    },
156}
157
158
159