Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BeginPanic handling in const eval #16938

Merged
merged 6 commits into from Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 41 additions & 39 deletions crates/hir-def/src/body/lower.rs
Expand Up @@ -1869,42 +1869,45 @@ impl ExprCollector<'_> {
) -> ExprId {
match count {
Some(FormatCount::Literal(n)) => {
match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) {
Some(count_is) => {
let count_is = self.alloc_expr_desugared(Expr::Path(count_is));
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
*n as u128,
Some(BuiltinUint::Usize),
)));
self.alloc_expr_desugared(Expr::Call {
callee: count_is,
args: Box::new([args]),
is_assignee_expr: false,
})
}
None => self.missing_expr(),
}
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
*n as u128,
Some(BuiltinUint::Usize),
)));
let count_is =
match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) {
Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)),
None => self.missing_expr(),
};
self.alloc_expr_desugared(Expr::Call {
callee: count_is,
args: Box::new([args]),
is_assignee_expr: false,
})
}
Some(FormatCount::Argument(arg)) => {
if let Ok(arg_index) = arg.index {
let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));

match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) {
Some(count_param) => {
let count_param = self.alloc_expr_desugared(Expr::Path(count_param));
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)));
self.alloc_expr_desugared(Expr::Call {
callee: count_param,
args: Box::new([args]),
is_assignee_expr: false,
})
}
let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(
i as u128,
Some(BuiltinUint::Usize),
)));
let count_param = match LangItem::FormatCount.ty_rel_path(
self.db,
self.krate,
name![Param],
) {
Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
None => self.missing_expr(),
}
};
self.alloc_expr_desugared(Expr::Call {
callee: count_param,
args: Box::new([args]),
is_assignee_expr: false,
})
} else {
// FIXME: This drops arg causing it to potentially not be resolved/type checked
// when typing?
self.missing_expr()
}
}
Expand All @@ -1925,7 +1928,8 @@ impl ExprCollector<'_> {
fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId {
use ArgumentType::*;
use FormatTrait::*;
match LangItem::FormatArgument.ty_rel_path(

let new_fn = match LangItem::FormatArgument.ty_rel_path(
self.db,
self.krate,
match ty {
Expand All @@ -1941,16 +1945,14 @@ impl ExprCollector<'_> {
Usize => name![from_usize],
},
) {
Some(new_fn) => {
let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn));
self.alloc_expr_desugared(Expr::Call {
callee: new_fn,
args: Box::new([arg]),
is_assignee_expr: false,
})
}
Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)),
None => self.missing_expr(),
}
};
self.alloc_expr_desugared(Expr::Call {
callee: new_fn,
args: Box::new([arg]),
is_assignee_expr: false,
})
}
// endregion: format
}
Expand Down
26 changes: 14 additions & 12 deletions crates/hir-def/src/body/tests.rs
Expand Up @@ -318,18 +318,20 @@ fn f() {

expect![[r#"
fn f() {
$crate::panicking::panic_fmt(
builtin#lang(Arguments::new_v1_formatted)(
&[
"cc",
],
&[],
&[],
unsafe {
builtin#lang(UnsafeArg::new)()
},
),
);
{
$crate::panicking::panic_fmt(
builtin#lang(Arguments::new_v1_formatted)(
&[
"cc",
],
&[],
&[],
unsafe {
builtin#lang(UnsafeArg::new)()
},
),
);
};
}"#]]
.assert_eq(&body.pretty_print(&db, def))
}
5 changes: 5 additions & 0 deletions crates/hir-ty/src/chalk_ext.rs
Expand Up @@ -27,6 +27,7 @@ pub trait TyExt {
fn is_scalar(&self) -> bool;
fn is_floating_point(&self) -> bool;
fn is_never(&self) -> bool;
fn is_str(&self) -> bool;
fn is_unknown(&self) -> bool;
fn contains_unknown(&self) -> bool;
fn is_ty_var(&self) -> bool;
Expand Down Expand Up @@ -87,6 +88,10 @@ impl TyExt for Ty {
matches!(self.kind(Interner), TyKind::Never)
}

fn is_str(&self) -> bool {
matches!(self.kind(Interner), TyKind::Str)
}

fn is_unknown(&self) -> bool {
matches!(self.kind(Interner), TyKind::Error)
}
Expand Down
13 changes: 12 additions & 1 deletion crates/hir-ty/src/mir/eval.rs
Expand Up @@ -428,6 +428,17 @@ impl MirEvalError {
}
Ok(())
}

pub fn is_panic(&self) -> Option<&str> {
let mut err = self;
while let MirEvalError::InFunction(e, _) = err {
err = e;
}
match err {
MirEvalError::Panic(msg) => Some(msg),
_ => None,
}
}
}

impl std::fmt::Debug for MirEvalError {
Expand Down Expand Up @@ -1138,7 +1149,7 @@ impl Evaluator<'_> {
let mut ty = self.operand_ty(lhs, locals)?;
while let TyKind::Ref(_, _, z) = ty.kind(Interner) {
ty = z.clone();
let size = if ty.kind(Interner) == &TyKind::Str {
let size = if ty.is_str() {
if *op != BinOp::Eq {
never!("Only eq is builtin for `str`");
}
Expand Down
58 changes: 48 additions & 10 deletions crates/hir-ty/src/mir/eval/shim.rs
Expand Up @@ -49,6 +49,7 @@ impl Evaluator<'_> {
if self.not_special_fn_cache.borrow().contains(&def) {
return Ok(false);
}

let function_data = self.db.function_data(def);
let is_intrinsic = match &function_data.abi {
Some(abi) => *abi == Interned::new_str("rust-intrinsic"),
Expand Down Expand Up @@ -131,9 +132,7 @@ impl Evaluator<'_> {
return Ok(true);
}
if let Some(it) = self.detect_lang_function(def) {
let arg_bytes =
args.iter().map(|it| Ok(it.get(self)?.to_owned())).collect::<Result<Vec<_>>>()?;
let result = self.exec_lang_item(it, generic_args, &arg_bytes, locals, span)?;
let result = self.exec_lang_item(it, generic_args, args, locals, span)?;
destination.write_from_bytes(self, &result)?;
return Ok(true);
}
Expand Down Expand Up @@ -311,35 +310,73 @@ impl Evaluator<'_> {

fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
use LangItem::*;
let candidate = self.db.lang_attr(def.into())?;
let attrs = self.db.attrs(def.into());

if attrs.by_key("rustc_const_panic_str").exists() {
// `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE.
return Some(LangItem::BeginPanic);
}

let candidate = attrs.by_key("lang").string_value().and_then(LangItem::from_str)?;
// We want to execute these functions with special logic
// `PanicFmt` is not detected here as it's redirected later.
if [BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
return Some(candidate);
}
if self.db.attrs(def.into()).by_key("rustc_const_panic_str").exists() {
// `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE.
return Some(LangItem::BeginPanic);
}

None
}

fn exec_lang_item(
&mut self,
it: LangItem,
generic_args: &Substitution,
args: &[Vec<u8>],
args: &[IntervalAndTy],
locals: &Locals,
span: MirSpan,
) -> Result<Vec<u8>> {
use LangItem::*;
let mut args = args.iter();
match it {
BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_owned())),
BeginPanic => {
let mut arg = args
.next()
.ok_or(MirEvalError::InternalError(
"argument of BeginPanic is not provided".into(),
))?
.clone();
while let TyKind::Ref(_, _, ty) = arg.ty.kind(Interner) {
if ty.is_str() {
let (pointee, metadata) = arg.interval.get(self)?.split_at(self.ptr_size());
let len = from_bytes!(usize, metadata);

return {
Err(MirEvalError::Panic(
std::str::from_utf8(
self.read_memory(Address::from_bytes(pointee)?, len)?,
)
.unwrap()
.to_owned(),
))
};
}
let size = self.size_of_sized(ty, locals, "begin panic arg")?;
let pointee = arg.interval.get(self)?;
arg = IntervalAndTy {
interval: Interval::new(Address::from_bytes(pointee)?, size),
ty: ty.clone(),
};
}
Err(MirEvalError::Panic(format!(
"unknown-panic-payload: {:?}",
arg.ty.kind(Interner)
)))
}
SliceLen => {
let arg = args.next().ok_or(MirEvalError::InternalError(
"argument of <[T]>::len() is not provided".into(),
))?;
let arg = arg.get(self)?;
let ptr_size = arg.len() / 2;
Ok(arg[ptr_size..].into())
}
Expand All @@ -353,6 +390,7 @@ impl Evaluator<'_> {
let arg = args.next().ok_or(MirEvalError::InternalError(
"argument of drop_in_place is not provided".into(),
))?;
let arg = arg.interval.get(self)?.to_owned();
self.run_drop_glue_deep(
ty.clone(),
locals,
Expand Down
45 changes: 45 additions & 0 deletions crates/hir-ty/src/mir/eval/tests.rs
Expand Up @@ -31,6 +31,7 @@ fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalEr
db.trait_environment(func_id.into()),
)
.map_err(|e| MirEvalError::MirLowerError(func_id, e))?;

let (result, output) = interpret_mir(db, body, false, None);
result?;
Ok((output.stdout().into_owned(), output.stderr().into_owned()))
Expand Down Expand Up @@ -72,6 +73,13 @@ fn check_pass_and_stdio(ra_fixture: &str, expected_stdout: &str, expected_stderr
}
}

fn check_panic(ra_fixture: &str, expected_panic: &str) {
let (db, file_ids) = TestDB::with_many_files(ra_fixture);
let file_id = *file_ids.last().unwrap();
let e = eval_main(&db, file_id).unwrap_err();
assert_eq!(e.is_panic().unwrap_or_else(|| panic!("unexpected error: {:?}", e)), expected_panic);
}

#[test]
fn function_with_extern_c_abi() {
check_pass(
Expand All @@ -87,6 +95,43 @@ fn main() {
);
}

#[test]
fn panic_fmt() {
// panic!
// -> panic_2021 (builtin macro redirection)
// -> #[lang = "panic_fmt"] core::panicking::panic_fmt (hooked by CTFE for redirection)
// -> core::panicking::const_panic_fmt
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
// -> Err(ConstEvalError::Panic)
check_panic(
r#"
//- minicore: fmt, panic
fn main() {
panic!("hello, world!");
}
"#,
"hello, world!",
);
}

#[test]
fn panic_display() {
// panic!
// -> panic_2021 (builtin macro redirection)
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
// -> Err(ConstEvalError::Panic)
check_panic(
r#"
//- minicore: fmt, panic

fn main() {
panic!("{}", "hello, world!");
}
"#,
"hello, world!",
);
}

#[test]
fn drop_basic() {
check_pass(
Expand Down
1 change: 1 addition & 0 deletions crates/ide-diagnostics/src/tests.rs
Expand Up @@ -293,6 +293,7 @@ fn minicore_smoke_test() {
// This should be ignored since we conditionally remove code which creates single item use with braces
config.disabled.insert("unused_braces".to_owned());
config.disabled.insert("unused_variables".to_owned());
config.disabled.insert("remove-unnecessary-else".to_owned());
check_diagnostics_with_config(config, &source);
}

Expand Down
8 changes: 4 additions & 4 deletions crates/ide/src/hover/tests.rs
Expand Up @@ -7864,8 +7864,8 @@ impl Iterator for S {
file_id: FileId(
1,
),
full_range: 6290..6498,
focus_range: 6355..6361,
full_range: 7791..7999,
focus_range: 7856..7862,
name: "Future",
kind: Trait,
container_name: "future",
Expand All @@ -7878,8 +7878,8 @@ impl Iterator for S {
file_id: FileId(
1,
),
full_range: 7128..7594,
focus_range: 7172..7180,
full_range: 8629..9095,
focus_range: 8673..8681,
name: "Iterator",
kind: Trait,
container_name: "iterator",
Expand Down
Expand Up @@ -49,5 +49,5 @@

<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="keyword">let</span> <span class="variable declaration">foo</span> <span class="operator">=</span> <span class="enum_variant default_library library">Some</span><span class="parenthesis">(</span><span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="keyword">let</span> <span class="variable declaration">nums</span> <span class="operator">=</span> <span class="module default_library library">iter</span><span class="operator">::</span><span class="function default_library library">repeat</span><span class="parenthesis">(</span><span class="variable">foo</span><span class="operator">.</span><span class="method consuming default_library library">unwrap</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="keyword">let</span> <span class="variable declaration">nums</span> <span class="operator">=</span> <span class="module default_library library">iter</span><span class="operator">::</span><span class="function default_library library">repeat</span><span class="parenthesis">(</span><span class="variable">foo</span><span class="operator">.</span><span class="method default_library library">unwrap</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span></code></pre>