luaur_bytecode_cli/functions/
analyze_file.rs1use alloc::string::String;
2use alloc::vec::Vec;
3use std::ffi::CString;
4use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
5
6use luaur_ast::records::allocator::Allocator;
7use luaur_ast::records::ast_name_table::AstNameTable;
8use luaur_ast::records::parse_error::ParseError;
9use luaur_ast::records::parse_options::ParseOptions;
10use luaur_ast::records::parser::Parser;
11use luaur_bytecode::records::bytecode_builder::BytecodeBuilder;
12use luaur_cli_lib::functions::read_file::read_file;
13use luaur_code_gen::functions::summarize_bytecode::summarize_bytecode;
14use luaur_code_gen::records::function_bytecode_summary::FunctionBytecodeSummary;
15use luaur_compiler::functions::compile_or_throw_compiler::compile_or_throw_bytecode_builder_parse_result_ast_name_table_compile_options;
16use luaur_compiler::records::compile_error::CompileError;
17use luaur_vm::functions::lua_close::lua_close;
18use luaur_vm::functions::lua_l_newstate::lua_l_newstate;
19use luaur_vm::functions::luau_load::luau_load;
20use luaur_vm::type_aliases::lua_state::lua_State;
21
22use crate::functions::copts::copts;
23
24struct LuaStateGuard(*mut lua_State);
25
26impl Drop for LuaStateGuard {
27 fn drop(&mut self) {
28 if !self.0.is_null() {
29 unsafe {
30 lua_close(self.0);
31 }
32 }
33 }
34}
35
36fn nul_terminated(value: &str) -> Option<String> {
37 CString::new(value)
38 .ok()
39 .and_then(|value| String::from_utf8(value.as_bytes_with_nul().to_vec()).ok())
40}
41
42fn report_parse_error(name: &str, error: &ParseError) {
43 let location = error.get_location();
44 eprintln!(
45 "{}({},{}): SyntaxError: {}",
46 name,
47 location.begin.line + 1,
48 location.begin.column + 1,
49 error.what()
50 );
51}
52
53fn report_compile_error(name: &str, error: &CompileError) {
54 let location = error.get_location();
55 eprintln!(
56 "{}({},{}): CompileError: {}",
57 name,
58 location.begin.line + 1,
59 location.begin.column + 1,
60 error
61 );
62}
63
64pub fn analyze_file(
65 name: &str,
66 nesting_limit: u32,
67 summaries: &mut Vec<FunctionBytecodeSummary>,
68) -> bool {
69 let Some(name_with_nul) = nul_terminated(name) else {
70 eprintln!("Error opening {}", name);
71 return false;
72 };
73
74 let Some(source) = read_file(&name_with_nul) else {
75 eprintln!("Error opening {}", name);
76 return false;
77 };
78
79 let mut allocator = Allocator::allocator();
80 let mut names = AstNameTable::new(&mut allocator);
81 let parse_options = ParseOptions::default();
82 let parse_result = Parser::parse(
83 source.as_str(),
84 source.len(),
85 &mut names,
86 &mut allocator,
87 parse_options,
88 );
89
90 if !parse_result.errors.is_empty() {
91 for error in &parse_result.errors {
92 report_parse_error(name, error);
93 }
94
95 return false;
96 }
97
98 let mut bcb = BytecodeBuilder::new(None);
99 let options = copts();
100 let compile_result = catch_unwind(AssertUnwindSafe(|| {
101 compile_or_throw_bytecode_builder_parse_result_ast_name_table_compile_options(
102 &mut bcb,
103 &parse_result,
104 &mut names,
105 &options,
106 );
107 }));
108
109 if let Err(payload) = compile_result {
110 if let Some(error) = payload.downcast_ref::<CompileError>() {
111 report_compile_error(name, error);
112 return false;
113 }
114
115 resume_unwind(payload);
116 }
117
118 let bytecode = bcb.get_bytecode();
119 let global_state = LuaStateGuard(lua_l_newstate());
120 let l = global_state.0;
121
122 if unsafe {
123 luau_load(
124 l,
125 name_with_nul.as_ptr() as *const core::ffi::c_char,
126 bytecode.as_ptr() as *const core::ffi::c_char,
127 bytecode.len(),
128 0,
129 )
130 } == 0
131 {
132 *summaries = summarize_bytecode(l, -1, nesting_limit);
133 true
134 } else {
135 eprintln!("Error loading bytecode {}", name);
136 false
137 }
138}