1//! Raw R evaluation in a `#[miniextendr]` body.
2//!
3//! - MXL305: Warns on `Rf_eval` / `R_forceAndCall` / `Rf_applyClosure` /
4//! `R_CheckUserInterrupt` (or their `_unchecked` forms) inside a `#[miniextendr]`
5//! fn, a method of a `#[miniextendr]` impl block, or a default method of a
6//! `#[miniextendr]` trait.
7//! An R error or a pending interrupt there longjmps over the body's Rust frames:
8//! their destructors don't run, and a receiver the generated code borrowed stays
9//! borrowed for the rest of the session (#2093).
1011use crate::crate_index::CrateIndex;
12use crate::diagnostic::Diagnostic;
13use crate::lint_code::LintCode;
1415pub fn check(index: &CrateIndex, diagnostics: &mut Vec<Diagnostic>) {
16for (path, data) in &index.file_data {
17for (fn_name, line) in &data.raw_r_eval_calls {
18let interrupt = fn_name.starts_with("R_CheckUserInterrupt");
19let what = if interrupt {
20"a pending interrupt"
21} else {
22"an R error in the code it runs"
23};
24let help = if interrupt {
25"Use `#[miniextendr(check_interrupt)]` to check once, before the arguments \
26 are converted and the receiver is borrowed. A check inside a long loop has \
27 no safe helper yet (A2-ai/miniextendr#2101). Suppress an intentional site \
28 with `// mxl::allow(MXL305)`."
29} else {
30"Run R code through the `miniextendr_api::expression` evaluators, which stop \
31 an R error before it crosses a Rust frame: `RCall::eval` / `eval_as` and \
32 `r_eval_str` return it as `Err` (the caller's condition handlers don't see \
33 the evaluation); `eval_with_handlers` keeps the caller's handlers and \
34 carries an R error through the Rust frames as an unwind; \
35 `try_eval_with_handlers` keeps the handlers and returns the error as `Err`. \
36 `Quoted::eval()` evaluates an unevaluated argument. For `R_forceAndCall` / \
37 `Rf_applyClosure`, build the call with `RCall::from_sexp(f).arg(..)` and \
38 evaluate it the same way. Suppress an intentional site with \
39 `// mxl::allow(MXL305)`."
40};
41 diagnostics.push(
42 Diagnostic::new(
43 LintCode::MXL305,
44 path,
45*line,
46::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}()` in a `#[miniextendr]` body: {1} longjmps over the body\'s Rust frames, so their destructors don\'t run and a receiver the generated code borrowed stays borrowed (`miniextendr_borrow_conflict`) for the rest of the R session.",
fn_name, what))
})format!(
47"`{fn_name}()` in a `#[miniextendr]` body: {what} longjmps over the \
48 body's Rust frames, so their destructors don't run and a receiver \
49 the generated code borrowed stays borrowed \
50 (`miniextendr_borrow_conflict`) for the rest of the R session.",
51 ),
52 )
53 .with_help(help),
54 );
55 }
56 }
57}