artichoke_backend/
eval.rs1use std::ffi::OsStr;
2use std::path::Path;
3
4use scolapasta_path::os_str_to_bytes;
5use spinoso_exception::{ArgumentError, Fatal, LoadError};
6
7use crate::Artichoke;
8use crate::core::{Eval, LoadSources, Parser};
9use crate::error::Error;
10use crate::ffi::InterpreterExtractError;
11use crate::state::parser::Context;
12use crate::sys;
13use crate::sys::protect;
14use crate::value::Value;
15use crate::{RubyException, exception_handler};
16
17impl Eval for Artichoke {
18 type Value = Value;
19
20 type Error = Error;
21
22 fn eval(&mut self, code: &[u8]) -> Result<Self::Value, Self::Error> {
23 let result = unsafe {
27 let state = self.state.as_deref_mut().ok_or_else(InterpreterExtractError::new)?;
28 let parser = state.parser.as_mut().ok_or_else(InterpreterExtractError::new)?;
29 let context: *mut sys::mrbc_context = parser.context_mut();
30 self.with_ffi_boundary(|mrb| protect::eval(mrb, context, code))?
31 };
32
33 let result = result.map(Value::from).map_err(Value::from);
34
35 match result {
36 Ok(value) if value.is_unreachable() => {
37 emit_fatal_warning!("eval returned an unreachable Ruby value");
43 Err(Fatal::from("eval returned an unreachable Ruby value").into())
44 }
45 Ok(value) => Ok(self.protect(value)),
46 Err(exception) => {
47 let exception = self.protect(exception);
48 Err(exception_handler::last_error(self, exception)?)
49 }
50 }
51 }
52
53 fn eval_os_str(&mut self, code: &OsStr) -> Result<Self::Value, Self::Error> {
54 let code = os_str_to_bytes(code)?;
55 self.eval(code)
56 }
57
58 fn eval_file(&mut self, file: &Path) -> Result<Self::Value, Self::Error> {
59 let context = Context::new(os_str_to_bytes(file.as_os_str())?.to_vec())
60 .ok_or_else(|| ArgumentError::with_message("path name contains null byte"))?;
61 self.push_context(context)?;
62 let code = self
63 .read_source_file_contents(file)
64 .map_err(|err| {
65 let mut message = b"ruby: ".to_vec();
66 message.extend_from_slice(err.message().as_ref());
67 if let Ok(bytes) = os_str_to_bytes(file.as_os_str()) {
68 message.extend_from_slice(b" -- ");
69 message.extend_from_slice(bytes);
70 }
71 LoadError::from(message)
72 })?
73 .into_owned();
74 let result = self.eval(code.as_slice());
75 self.pop_context()?;
76 result
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 #[cfg(unix)]
83 use std::ffi::OsStr;
84 #[cfg(unix)]
85 use std::os::unix::ffi::OsStrExt;
86 use std::path::Path;
87
88 use bstr::ByteSlice;
89
90 use crate::test::prelude::*;
91
92 #[test]
93 fn root_eval_context() {
94 let mut interp = interpreter();
95 let result = interp.eval(b"__FILE__").unwrap();
96 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
97 assert_eq!(result, "(eval)");
98 }
99
100 #[test]
101 fn context_is_restored_after_eval() {
102 let mut interp = interpreter();
103 let context = Context::new(&b"context.rb"[..]).unwrap();
104 interp.push_context(context).unwrap();
105 interp.eval(b"15").unwrap();
106 let context = interp.peek_context().unwrap();
107 let filename = context.unwrap().filename();
108 assert_eq!(filename.as_bstr(), b"context.rb".as_bstr());
109 }
110
111 #[test]
112 fn root_context_is_not_pushed_after_eval() {
113 let mut interp = interpreter();
114 interp.eval(b"15").unwrap();
115 let context = interp.peek_context().unwrap();
116 assert!(context.is_none());
117 }
118
119 mod nested {
120 use crate::test::prelude::*;
121
122 #[derive(Debug)]
123 struct NestedEval;
124
125 unsafe extern "C-unwind" fn nested_eval_file(
126 mrb: *mut sys::mrb_state,
127 _slf: sys::mrb_value,
128 ) -> sys::mrb_value {
129 unwrap_interpreter!(mrb, to => guard);
130 let Ok(value) = guard.eval(b"__FILE__") else {
131 return Value::nil().inner();
132 };
133 value.inner()
134 }
135
136 impl File for NestedEval {
137 type Artichoke = Artichoke;
138
139 type Error = Error;
140
141 fn require(interp: &mut Artichoke) -> Result<(), Self::Error> {
142 let spec = module::Spec::new(interp, "NestedEval", c"NestedEval", None)?;
143 module::Builder::for_spec(interp, &spec)
144 .add_self_method("file", nested_eval_file, sys::mrb_args_none())?
145 .define()?;
146 interp.def_module::<Self>(spec)?;
147 Ok(())
148 }
149 }
150
151 #[test]
152 #[should_panic]
153 #[expect(clippy::should_panic_without_expect, reason = "this test is known broken")]
154 fn eval_context_is_a_stack() {
155 let mut interp = interpreter();
156 interp.def_file_for_type::<_, NestedEval>("nested_eval.rb").unwrap();
157 let code = b"require 'nested_eval'; NestedEval.file";
158 let result = interp.eval(code).unwrap();
159 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
160 assert_eq!(result, "/src/lib/nested_eval.rb");
161 }
162 }
163
164 #[test]
165 fn eval_with_context() {
166 let mut interp = interpreter();
167
168 let context = Context::new(b"source.rb".as_ref()).unwrap();
169 interp.push_context(context).unwrap();
170 let result = interp.eval(b"__FILE__").unwrap();
171 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
172 assert_eq!(result, "source.rb");
173 interp.pop_context().unwrap();
174
175 let context = Context::new(b"source.rb".as_ref()).unwrap();
176 interp.push_context(context).unwrap();
177 let result = interp.eval(b"__FILE__").unwrap();
178 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
179 assert_eq!(result, "source.rb");
180 interp.pop_context().unwrap();
181
182 let context = Context::new(b"main.rb".as_ref()).unwrap();
183 interp.push_context(context).unwrap();
184 let result = interp.eval(b"__FILE__").unwrap();
185 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
186 assert_eq!(result, "main.rb");
187 interp.pop_context().unwrap();
188 }
189
190 #[test]
191 fn unparseable_code_returns_err_syntax_error() {
192 let mut interp = interpreter();
193 let err = interp.eval(b"'a").unwrap_err();
194 assert_eq!("SyntaxError", err.name().as_ref());
195 }
196
197 #[test]
198 fn interpreter_is_usable_after_syntax_error() {
199 let mut interp = interpreter();
200 let err = interp.eval(b"'a").unwrap_err();
201 assert_eq!("SyntaxError", err.name().as_ref());
202 let result = interp.eval(b"'a' * 10 ").unwrap();
204 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
205 assert_eq!(result, "a".repeat(10));
206 }
207
208 #[test]
209 fn file_magic_constant() {
210 let file = if cfg!(windows) {
211 "c:/artichoke/virtual_root/src/lib/source.rb"
212 } else {
213 "/artichoke/virtual_root/src/lib/source.rb"
214 };
215 let mut interp = interpreter();
216 interp
217 .def_rb_source_file("source.rb", &b"def file; __FILE__; end"[..])
218 .unwrap();
219 let result = interp.eval(b"require 'source'; file").unwrap();
220 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
221 assert_eq!(result, file);
222 }
223
224 #[test]
225 fn file_not_persistent() {
226 let mut interp = interpreter();
227 interp
228 .def_rb_source_file("source.rb", &b"def file; __FILE__; end"[..])
229 .unwrap();
230 let result = interp.eval(b"require 'source'; __FILE__").unwrap();
231 let result = result.try_convert_into_mut::<&str>(&mut interp).unwrap();
232 assert_eq!(result, "(eval)");
233 }
234
235 #[test]
236 fn return_syntax_error() {
237 let mut interp = interpreter();
238 interp
239 .def_rb_source_file("fail.rb", &b"def bad; 'as'.scan(; end"[..])
240 .unwrap();
241 let err = interp.eval(b"require 'fail'").unwrap_err();
242 assert_eq!("SyntaxError", err.name().as_ref());
243 }
244
245 #[test]
246 fn eval_file_error_file_not_found() {
247 let mut interp = interpreter();
248 let err = interp.eval_file(Path::new("no/such/file.rb")).unwrap_err();
249 assert_eq!("LoadError", err.name().as_ref());
250 assert_eq!(
251 b"ruby: file not found in virtual file system -- no/such/file.rb",
252 err.message().as_ref()
253 );
254 }
255
256 #[test]
257 #[cfg(unix)]
258 fn eval_file_error_invalid_path() {
259 let mut interp = interpreter();
260 let err = interp
261 .eval_file(Path::new(OsStr::from_bytes(b"not/valid/utf8/\xff.rb")))
262 .unwrap_err();
263 assert_eq!("LoadError", err.name().as_ref());
264 assert_eq!(
265 b"ruby: file not found in virtual file system -- not/valid/utf8/\xFF.rb",
266 err.message().as_ref()
267 );
268 }
269}