Skip to content

Commit a63c964

Browse files
committed
Reject panicking calls in no_panic functions
1 parent 41c5ad8 commit a63c964

4 files changed

Lines changed: 145 additions & 0 deletions

File tree

src/analyzer.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ impl Analyzer<'_> {
7575
self.check_generic_constraints();
7676
self.check_pure_bodies();
7777
self.check_no_block_bodies();
78+
self.check_no_panic_bodies();
7879
self.check_noalloc_allocations();
7980
self.check_try_operator_result_returns();
8081
self.check_resource_fields();
@@ -927,6 +928,95 @@ impl Analyzer<'_> {
927928
}
928929
}
929930

931+
fn check_no_panic_bodies(&mut self) {
932+
let items = self.syntax_program.items.clone();
933+
for item in &items {
934+
let Item::Function(function) = item else {
935+
continue;
936+
};
937+
if !function_has_effect(function, "no_panic") {
938+
continue;
939+
}
940+
self.check_no_panic_block(&function.name, &function.body);
941+
}
942+
}
943+
944+
fn check_no_panic_block(&mut self, function_name: &str, block: &Block) {
945+
for statement in &block.statements {
946+
self.check_no_panic_stmt(function_name, statement);
947+
}
948+
}
949+
950+
fn check_no_panic_stmt(&mut self, function_name: &str, statement: &Stmt) {
951+
match statement {
952+
Stmt::Let(stmt) => {
953+
if let Some(value) = &stmt.value {
954+
self.check_no_panic_expr(function_name, value);
955+
}
956+
}
957+
Stmt::Return(stmt) => {
958+
if let Some(value) = &stmt.value {
959+
self.check_no_panic_expr(function_name, value);
960+
}
961+
}
962+
Stmt::Expr(value) => self.check_no_panic_expr(function_name, value),
963+
Stmt::With(stmt) => {
964+
self.check_no_panic_expr(function_name, &stmt.resource);
965+
self.check_no_panic_block(function_name, &stmt.body);
966+
}
967+
Stmt::If(stmt) => {
968+
self.check_no_panic_expr(function_name, &stmt.condition);
969+
self.check_no_panic_block(function_name, &stmt.then_body);
970+
if let Some(else_body) = &stmt.else_body {
971+
self.check_no_panic_block(function_name, else_body);
972+
}
973+
}
974+
Stmt::Loop(stmt) => {
975+
if let Some(condition) = &stmt.condition {
976+
self.check_no_panic_expr(function_name, condition);
977+
}
978+
self.check_no_panic_block(function_name, &stmt.body);
979+
}
980+
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Unknown(_) => {}
981+
}
982+
}
983+
984+
fn check_no_panic_expr(&mut self, function_name: &str, expr: &Expr) {
985+
match expr {
986+
Expr::Call {
987+
callee, args, span, ..
988+
} => {
989+
match self.hir.resolve_call(callee) {
990+
CallResolution::Resolved { signature, kind } => {
991+
if !matches!(kind, ResolvedCalleeKind::Constructor { .. })
992+
&& !signature.effects.iter().any(|effect| effect == "no_panic")
993+
{
994+
self.panic_call_diagnostic(function_name, callee, span);
995+
}
996+
}
997+
CallResolution::EnumVariant | CallResolution::Unknown => {}
998+
}
999+
for arg in args {
1000+
self.check_no_panic_expr(function_name, &arg.value);
1001+
}
1002+
}
1003+
Expr::Effect { value, .. } | Expr::Manage { value, .. } | Expr::Try { value, .. } => {
1004+
self.check_no_panic_expr(function_name, value);
1005+
}
1006+
Expr::Binary { left, right, .. } => {
1007+
self.check_no_panic_expr(function_name, left);
1008+
self.check_no_panic_expr(function_name, right);
1009+
}
1010+
Expr::Field { base, .. } => self.check_no_panic_expr(function_name, base),
1011+
Expr::Index { base, index, .. } => {
1012+
self.check_no_panic_expr(function_name, base);
1013+
self.check_no_panic_expr(function_name, index);
1014+
}
1015+
Expr::Closure { body, .. } => self.check_no_panic_block(function_name, body),
1016+
Expr::Ident(_, _) | Expr::Number(_, _) | Expr::String(_, _) | Expr::Unknown(_) => {}
1017+
}
1018+
}
1019+
9301020
fn check_duplicate_declarations(&mut self) {
9311021
for duplicate in self.hir.duplicate_symbols() {
9321022
self.diagnostics.push(
@@ -1360,6 +1450,33 @@ impl Analyzer<'_> {
13601450
),
13611451
);
13621452
}
1453+
1454+
fn panic_call_diagnostic(
1455+
&mut self,
1456+
function_name: &str,
1457+
callee: &Callee,
1458+
span: &crate::diagnostic::Span,
1459+
) {
1460+
self.diagnostics.push(
1461+
Diagnostic::error(
1462+
code::INVALID_NO_PANIC_CALL,
1463+
format!(
1464+
"`{function_name}` is declared no_panic but calls possibly panicking function `{}`.",
1465+
callee_display(callee)
1466+
),
1467+
span.clone(),
1468+
"possibly panicking call in no_panic function",
1469+
)
1470+
.with_cause(
1471+
"A `no_panic` function may only call constructors, enum variants, or functions also declared `effects(no_panic)`.",
1472+
)
1473+
.with_fix(
1474+
"remove_no_panic_or_call_no_panic",
1475+
"Remove `no_panic`, or call only APIs whose signatures are declared `effects(no_panic)`.",
1476+
"manual",
1477+
),
1478+
);
1479+
}
13631480
}
13641481

13651482
fn resource_pool_namespace_arg(namespace: &str) -> Option<&str> {

src/diagnostic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod code {
1919
pub const UNKNOWN_FILE_FEATURE: &str = "RS0016";
2020
pub const DUPLICATE_FILE_FEATURE: &str = "RS0017";
2121
pub const INVALID_NO_BLOCK_CALL: &str = "RS0018";
22+
pub const INVALID_NO_PANIC_CALL: &str = "RS0019";
2223
pub const FEATURE_VIOLATION: &str = "RS0101";
2324
pub const UNNAMED_ARGUMENT: &str = "RS0201";
2425
pub const MISSING_DATA_EFFECT: &str = "RS0202";
@@ -311,6 +312,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
311312
title: "invalid no_block call",
312313
explanation: "`effects(no_block)` is a guarantee that the function does not block the current isolate. Calls inside it must target constructors, enum variants, or functions also declared `effects(no_block)`.",
313314
},
315+
DiagnosticExplanation {
316+
code: code::INVALID_NO_PANIC_CALL,
317+
title: "invalid no_panic call",
318+
explanation: "`effects(no_panic)` is a guarantee that the function does not intentionally panic. Calls inside it must target constructors, enum variants, or functions also declared `effects(no_panic)`.",
319+
},
314320
DiagnosticExplanation {
315321
code: code::FEATURE_VIOLATION,
316322
title: "feature violation",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// expect: RS0019
2+
3+
fn may_panic(value: Int) -> Int {
4+
return value
5+
}
6+
7+
fn promised_no_panic(value: Int) -> Int
8+
effects(no_panic)
9+
{
10+
return may_panic(value: value)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fn checked_id(value: Int) -> Int
2+
effects(no_panic)
3+
{
4+
return value
5+
}
6+
7+
fn checked_twice(value: Int) -> Int
8+
effects(no_panic)
9+
{
10+
return checked_id(value: value)
11+
}

0 commit comments

Comments
 (0)