rapx/analysis/opt/checking/encoding_checking/
string_push.rs1use std::collections::HashSet;
2
3use once_cell::sync::OnceCell;
4
5use rustc_middle::{mir::Local, ty::TyCtxt};
6use rustc_span::Span;
7
8use super::value_is_from_const;
9use crate::{
10 analysis::{
11 core::dataflow::{graph::*, *},
12 utils::def_path::DefPath,
13 },
14 utils::log::{relative_pos_range, span_to_filename, span_to_line_number, span_to_source_code},
15};
16use annotate_snippets::{Level, Renderer, Snippet};
17
18static DEFPATHS: OnceCell<DefPaths> = OnceCell::new();
19
20struct DefPaths {
21 string_new: DefPath,
22 string_push: DefPath,
23}
24
25impl DefPaths {
26 pub fn new(tcx: &TyCtxt<'_>) -> Self {
27 Self {
28 string_new: DefPath::new("std::string::String::new", tcx),
29 string_push: DefPath::new("std::string::String::push", tcx),
30 }
31 }
32}
33
34use crate::analysis::opt::OptCheck;
35
36pub struct StringPushCheck {
37 record: Vec<Span>,
38}
39
40fn extract_value_if_is_string_push(graph: &Graph, node: &GraphNode) -> Option<Local> {
41 let def_paths = DEFPATHS.get().unwrap();
42 for op in node.ops.iter() {
43 if let NodeOp::Call(def_id) = op {
44 if *def_id == def_paths.string_push.last_def_id() {
45 let push_value_idx = graph.edges[node.in_edges[1]].src; return Some(push_value_idx);
47 }
48 }
49 }
50 None
51}
52
53fn find_upside_string_new(graph: &Graph, node_idx: Local) -> Option<Local> {
54 let mut string_new_node_idx = None;
55 let def_paths = DEFPATHS.get().unwrap();
56 let mut node_operator = |graph: &Graph, idx: Local| -> DFSStatus {
57 let node = &graph.nodes[idx];
58 for op in node.ops.iter() {
59 if let NodeOp::Call(def_id) = op {
60 if *def_id == def_paths.string_new.last_def_id() {
61 string_new_node_idx = Some(idx);
62 return DFSStatus::Stop;
63 }
64 }
65 }
66 DFSStatus::Continue
67 };
68 let mut seen = HashSet::new();
69 graph.dfs(
70 node_idx,
71 Direction::Upside,
72 &mut node_operator,
73 &mut Graph::always_true_edge_validator,
74 false,
75 &mut seen,
76 );
77 string_new_node_idx
78}
79
80impl OptCheck for StringPushCheck {
81 fn new() -> Self {
82 Self { record: Vec::new() }
83 }
84
85 fn check(&mut self, graph: &Graph, tcx: &TyCtxt) {
86 let _ = &DEFPATHS.get_or_init(|| DefPaths::new(tcx));
87 for (node_idx, node) in graph.nodes.iter_enumerated() {
88 if let Some(pushed_value_idx) = extract_value_if_is_string_push(graph, node) {
89 if find_upside_string_new(graph, node_idx).is_some() {
90 if !value_is_from_const(graph, pushed_value_idx) {
91 self.record.clear(); return;
93 }
94 self.record.push(node.span);
95 }
96 }
97 }
98 }
99
100 fn report(&self, graph: &Graph) {
101 if !self.record.is_empty() {
102 report_string_push_bug(graph, &self.record);
103 }
104 }
105
106 fn cnt(&self) -> usize {
107 self.record.len()
108 }
109}
110
111fn report_string_push_bug(graph: &Graph, spans: &Vec<Span>) {
112 let code_source = span_to_source_code(graph.span);
113 let filename = span_to_filename(graph.span);
114 let mut snippet = Snippet::source(&code_source)
115 .line_start(span_to_line_number(graph.span))
116 .origin(&filename)
117 .fold(true);
118 for span in spans.iter() {
119 snippet = snippet.annotation(
120 Level::Error
121 .span(relative_pos_range(graph.span, *span))
122 .label("Checked here."),
123 )
124 }
125 let message = Level::Warning
126 .title("Unnecessary encoding checkings detected")
127 .snippet(snippet)
128 .footer(Level::Help.title("Use unsafe APIs instead."));
129 let renderer = Renderer::styled();
130 println!("{}", renderer.render(message));
131}