Skip to content

Commit 41c5ad8

Browse files
committed
Reject blocking calls in no_block functions
1 parent 841f717 commit 41c5ad8

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
@@ -74,6 +74,7 @@ impl Analyzer<'_> {
7474
self.check_signature_explicitness();
7575
self.check_generic_constraints();
7676
self.check_pure_bodies();
77+
self.check_no_block_bodies();
7778
self.check_noalloc_allocations();
7879
self.check_try_operator_result_returns();
7980
self.check_resource_fields();
@@ -837,6 +838,95 @@ impl Analyzer<'_> {
837838
}
838839
}
839840

841+
fn check_no_block_bodies(&mut self) {
842+
let items = self.syntax_program.items.clone();
843+
for item in &items {
844+
let Item::Function(function) = item else {
845+
continue;
846+
};
847+
if !function_has_effect(function, "no_block") {
848+
continue;
849+
}
850+
self.check_no_block_block(&function.name, &function.body);
851+
}
852+
}
853+
854+
fn check_no_block_block(&mut self, function_name: &str, block: &Block) {
855+
for statement in &block.statements {
856+
self.check_no_block_stmt(function_name, statement);
857+
}
858+
}
859+
860+
fn check_no_block_stmt(&mut self, function_name: &str, statement: &Stmt) {
861+
match statement {
862+
Stmt::Let(stmt) => {
863+
if let Some(value) = &stmt.value {
864+
self.check_no_block_expr(function_name, value);
865+
}
866+
}
867+
Stmt::Return(stmt) => {
868+
if let Some(value) = &stmt.value {
869+
self.check_no_block_expr(function_name, value);
870+
}
871+
}
872+
Stmt::Expr(value) => self.check_no_block_expr(function_name, value),
873+
Stmt::With(stmt) => {
874+
self.check_no_block_expr(function_name, &stmt.resource);
875+
self.check_no_block_block(function_name, &stmt.body);
876+
}
877+
Stmt::If(stmt) => {
878+
self.check_no_block_expr(function_name, &stmt.condition);
879+
self.check_no_block_block(function_name, &stmt.then_body);
880+
if let Some(else_body) = &stmt.else_body {
881+
self.check_no_block_block(function_name, else_body);
882+
}
883+
}
884+
Stmt::Loop(stmt) => {
885+
if let Some(condition) = &stmt.condition {
886+
self.check_no_block_expr(function_name, condition);
887+
}
888+
self.check_no_block_block(function_name, &stmt.body);
889+
}
890+
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Unknown(_) => {}
891+
}
892+
}
893+
894+
fn check_no_block_expr(&mut self, function_name: &str, expr: &Expr) {
895+
match expr {
896+
Expr::Call {
897+
callee, args, span, ..
898+
} => {
899+
match self.hir.resolve_call(callee) {
900+
CallResolution::Resolved { signature, kind } => {
901+
if !matches!(kind, ResolvedCalleeKind::Constructor { .. })
902+
&& !signature.effects.iter().any(|effect| effect == "no_block")
903+
{
904+
self.blocking_call_diagnostic(function_name, callee, span);
905+
}
906+
}
907+
CallResolution::EnumVariant | CallResolution::Unknown => {}
908+
}
909+
for arg in args {
910+
self.check_no_block_expr(function_name, &arg.value);
911+
}
912+
}
913+
Expr::Effect { value, .. } | Expr::Manage { value, .. } | Expr::Try { value, .. } => {
914+
self.check_no_block_expr(function_name, value);
915+
}
916+
Expr::Binary { left, right, .. } => {
917+
self.check_no_block_expr(function_name, left);
918+
self.check_no_block_expr(function_name, right);
919+
}
920+
Expr::Field { base, .. } => self.check_no_block_expr(function_name, base),
921+
Expr::Index { base, index, .. } => {
922+
self.check_no_block_expr(function_name, base);
923+
self.check_no_block_expr(function_name, index);
924+
}
925+
Expr::Closure { body, .. } => self.check_no_block_block(function_name, body),
926+
Expr::Ident(_, _) | Expr::Number(_, _) | Expr::String(_, _) | Expr::Unknown(_) => {}
927+
}
928+
}
929+
840930
fn check_duplicate_declarations(&mut self) {
841931
for duplicate in self.hir.duplicate_symbols() {
842932
self.diagnostics.push(
@@ -1243,6 +1333,33 @@ impl Analyzer<'_> {
12431333
),
12441334
);
12451335
}
1336+
1337+
fn blocking_call_diagnostic(
1338+
&mut self,
1339+
function_name: &str,
1340+
callee: &Callee,
1341+
span: &crate::diagnostic::Span,
1342+
) {
1343+
self.diagnostics.push(
1344+
Diagnostic::error(
1345+
code::INVALID_NO_BLOCK_CALL,
1346+
format!(
1347+
"`{function_name}` is declared no_block but calls possibly blocking function `{}`.",
1348+
callee_display(callee)
1349+
),
1350+
span.clone(),
1351+
"possibly blocking call in no_block function",
1352+
)
1353+
.with_cause(
1354+
"A `no_block` function may only call constructors, enum variants, or functions also declared `effects(no_block)`.",
1355+
)
1356+
.with_fix(
1357+
"remove_no_block_or_call_no_block",
1358+
"Remove `no_block`, or call only APIs whose signatures are declared `effects(no_block)`.",
1359+
"manual",
1360+
),
1361+
);
1362+
}
12461363
}
12471364

12481365
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
@@ -18,6 +18,7 @@ pub mod code {
1818
pub const UNSUPPORTED_SYNTAX: &str = "RS0015";
1919
pub const UNKNOWN_FILE_FEATURE: &str = "RS0016";
2020
pub const DUPLICATE_FILE_FEATURE: &str = "RS0017";
21+
pub const INVALID_NO_BLOCK_CALL: &str = "RS0018";
2122
pub const FEATURE_VIOLATION: &str = "RS0101";
2223
pub const UNNAMED_ARGUMENT: &str = "RS0201";
2324
pub const MISSING_DATA_EFFECT: &str = "RS0202";
@@ -305,6 +306,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
305306
title: "duplicate file feature",
306307
explanation: "Each review-relevant capability may appear at most once in a `features:` header. Duplicate entries are rejected instead of silently folded away.",
307308
},
309+
DiagnosticExplanation {
310+
code: code::INVALID_NO_BLOCK_CALL,
311+
title: "invalid no_block call",
312+
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)`.",
313+
},
308314
DiagnosticExplanation {
309315
code: code::FEATURE_VIOLATION,
310316
title: "feature violation",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// expect: RS0018
2+
3+
fn may_block(value: Int) -> Int {
4+
return value
5+
}
6+
7+
fn promised_nonblocking(value: Int) -> Int
8+
effects(no_block)
9+
{
10+
return may_block(value: value)
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fn add_one(value: Int) -> Int
2+
effects(no_block)
3+
{
4+
return value + 1
5+
}
6+
7+
fn add_two(value: Int) -> Int
8+
effects(no_block)
9+
{
10+
return add_one(value: value) + 1
11+
}

0 commit comments

Comments
 (0)