Skip to content

Commit f72b2bf

Browse files
committed
Support for unsafe blocks and functions
1 parent a8c713e commit f72b2bf

33 files changed

Lines changed: 769 additions & 116 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/analyzer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ semver = "1.0.0"
1818
salsa = "0.16.1"
1919
parking_lot_core = { version = "=0.8.0" } # used by salsa; version pinned for wasm compatibility
2020
indexmap = "1.6.2"
21-
21+
if_chain = "1.0.1"
2222

2323
[dev-dependencies]
2424
insta = "1.7.1"

crates/analyzer/src/db/queries/contracts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ pub fn contract_init_function(
162162
if let Some((id, span)) = first_def {
163163
// `__init__` must be `pub`.
164164
// Return type is checked in `queries::functions::function_signature`.
165-
if !id.data(db).ast.kind.is_pub {
165+
if !id.is_public(db) {
166166
diagnostics.push(errors::fancy_error(
167167
"`__init__` function is not public",
168168
vec![Label::primary(span, "`__init__` function must be public")],

crates/analyzer/src/db/queries/functions.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::traversal::types::type_desc;
99
use fe_common::diagnostics::Label;
1010
use fe_parser::ast;
1111
use fe_parser::node::Node;
12+
use if_chain::if_chain;
1213
use std::collections::HashMap;
1314
use std::convert::TryInto;
1415
use std::rc::Rc;
@@ -25,6 +26,17 @@ pub fn function_signature(
2526
let mut scope = ItemScope::new(db, function.module(db));
2627
let contract = function.contract(db);
2728

29+
if_chain! {
30+
if contract.is_some();
31+
if let Some(pub_span) = function.pub_span(db);
32+
if let Some(unsafe_span) = function.unsafe_span(db);
33+
then {
34+
scope.error("public contract functions can't be unsafe",
35+
pub_span + unsafe_span,
36+
"a contract function can be either `pub` or `unsafe`, but not both");
37+
}
38+
}
39+
2840
let mut self_decl = SelfDecl::None;
2941
let mut names = HashMap::new();
3042
let params = def
@@ -161,7 +173,14 @@ pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis<Rc<F
161173
}
162174
}
163175

164-
let mut block_scope = BlockScope::new(&scope, BlockScopeType::Function);
176+
let mut block_scope = BlockScope::new(
177+
&scope,
178+
if function.unsafe_span(db).is_some() {
179+
BlockScopeType::Unsafe
180+
} else {
181+
BlockScopeType::Function
182+
},
183+
);
165184

166185
// If `traverse_statements` fails, we can be confident that a diagnostic
167186
// has been emitted, either while analyzing this fn body or while analyzing

crates/analyzer/src/namespace/items.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,12 +514,18 @@ impl FunctionId {
514514
pub fn module(&self, db: &dyn AnalyzerDb) -> ModuleId {
515515
self.data(db).module
516516
}
517-
pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool {
518-
self.data(db).ast.kind.is_pub
519-
}
520517
pub fn is_pure(&self, db: &dyn AnalyzerDb) -> bool {
521518
self.signature(db).self_decl == SelfDecl::None
522519
}
520+
pub fn is_public(&self, db: &dyn AnalyzerDb) -> bool {
521+
self.pub_span(db).is_some()
522+
}
523+
pub fn pub_span(&self, db: &dyn AnalyzerDb) -> Option<Span> {
524+
self.data(db).ast.kind.pub_
525+
}
526+
pub fn unsafe_span(&self, db: &dyn AnalyzerDb) -> Option<Span> {
527+
self.data(db).ast.kind.unsafe_
528+
}
523529
pub fn signature(&self, db: &dyn AnalyzerDb) -> Rc<types::FunctionSignature> {
524530
db.function_signature(*self).value
525531
}

crates/analyzer/src/namespace/scopes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ pub enum BlockScopeType {
189189
Function,
190190
IfElse,
191191
Loop,
192+
Unsafe,
192193
}
193194

194195
impl AnalyzerContext for BlockScope<'_, '_> {

crates/analyzer/src/traversal/expressions.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::builtins::{
55
use crate::context::{AnalyzerContext, CallType, ExpressionAttributes, Location, NamedThing};
66
use crate::errors::{FatalError, IndexingError, NotFixedSize};
77
use crate::namespace::items::{ContractId, FunctionId, Item};
8-
use crate::namespace::scopes::BlockScope;
8+
use crate::namespace::scopes::{BlockScope, BlockScopeType};
99
use crate::namespace::types::{
1010
Array, Base, Contract, FeString, Integer, SelfDecl, Struct, Tuple, Type, TypeDowncast, U256,
1111
};
@@ -19,6 +19,7 @@ use fe_common::Span;
1919
use fe_parser::ast as fe;
2020
use fe_parser::ast::UnaryOperator;
2121
use fe_parser::node::Node;
22+
use if_chain::if_chain;
2223
use num_bigint::BigInt;
2324
use std::convert::TryInto;
2425
use std::ops::RangeInclusive;
@@ -732,7 +733,7 @@ fn expr_call(
732733
func_name,
733734
self_span,
734735
} => expr_call_self_attribute(scope, &func_name, func.span, self_span, args),
735-
CallType::Pure(func_id) => expr_call_pure(scope, func_id, args),
736+
CallType::Pure(func_id) => expr_call_pure(scope, func.span, func_id, args),
736737
CallType::ValueAttribute => expr_call_value_attribute(scope, func, args),
737738
CallType::TypeAttribute { typ, func_name } => {
738739
expr_call_type_attribute(scope, typ, &func_name, func.span, args)
@@ -963,6 +964,27 @@ fn resolve_self(scope: &mut BlockScope, use_span: Span) -> Result<ContractId, Fa
963964
Ok(contract)
964965
}
965966

967+
fn check_for_unsafe_call_outside_unsafe(
968+
scope: &mut BlockScope,
969+
fn_name: &str,
970+
call_name_span: Span,
971+
function: FunctionId,
972+
) {
973+
if_chain! {
974+
if !scope.inherits_type(BlockScopeType::Unsafe);
975+
if let Some(unsafe_span) = function.unsafe_span(scope.db());
976+
then {
977+
let def_name_span = function.name_span(scope.db());
978+
scope.fancy_error(&format!("unsafe function `{}` can only be called in an unsafe function or block",
979+
fn_name),
980+
vec![Label::primary(call_name_span, "call to unsafe function"),
981+
Label::secondary(unsafe_span + def_name_span, format!("`{}` is defined here as unsafe", fn_name))],
982+
vec!["Hint: put this call in an `unsafe` block if you're confident that it's safe to use here".into()],
983+
);
984+
}
985+
}
986+
}
987+
966988
fn expr_call_self_attribute(
967989
scope: &mut BlockScope,
968990
func_name: &str,
@@ -974,6 +996,8 @@ fn expr_call_self_attribute(
974996
let contract = resolve_self(scope, self_span)?;
975997

976998
if let Some(func) = contract.self_function(scope.db(), func_name) {
999+
check_for_unsafe_call_outside_unsafe(scope, func_name, name_span, func);
1000+
9771001
let sig = func.signature(scope.root.db);
9781002
validate_named_args(
9791003
scope,
@@ -1013,18 +1037,20 @@ fn expr_call_self_attribute(
10131037

10141038
fn expr_call_pure(
10151039
scope: &mut BlockScope,
1040+
call_name_span: Span,
10161041
function: FunctionId,
10171042
args: &Node<Vec<Node<fe::CallArg>>>,
10181043
) -> Result<ExpressionAttributes, FatalError> {
10191044
assert!(function.is_pure(scope.db()));
10201045

10211046
let fn_name = function.name(scope.db());
1022-
let name_span = function.name_span(scope.db());
1047+
check_for_unsafe_call_outside_unsafe(scope, &fn_name, call_name_span, function);
1048+
10231049
let sig = function.signature(scope.db());
10241050
validate_named_args(
10251051
scope,
10261052
&fn_name,
1027-
name_span,
1053+
function.name_span(scope.db()),
10281054
args,
10291055
&sig.params,
10301056
LabelPolicy::AllowAnyUnlabeled,

crates/analyzer/src/traversal/functions.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn func_stmt(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(), Fa
2929
For { .. } => for_loop(scope, stmt),
3030
While { .. } => while_loop(scope, stmt),
3131
If { .. } => if_statement(scope, stmt),
32+
Unsafe { .. } => unsafe_block(scope, stmt),
3233
Assert { .. } => assert(scope, stmt),
3334
Expr { value } => expressions::expr(scope, value, None).map(|_| ()),
3435
Pass => Ok(()),
@@ -110,6 +111,22 @@ fn if_statement(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(),
110111
}
111112
}
112113

114+
fn unsafe_block(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(), FatalError> {
115+
match &stmt.kind {
116+
fe::FuncStmt::Unsafe(body) => {
117+
if scope.inherits_type(BlockScopeType::Unsafe) {
118+
scope.error(
119+
"unnecessary `unsafe` block",
120+
stmt.span,
121+
"this `unsafe` block is nested inside another `unsafe` context",
122+
);
123+
}
124+
traverse_statements(&mut scope.new_child(BlockScopeType::Unsafe), body)
125+
}
126+
_ => unreachable!(),
127+
}
128+
}
129+
113130
fn while_loop(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(), FatalError> {
114131
match &stmt.kind {
115132
fe::FuncStmt::While { test, body } => {

crates/analyzer/tests/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,5 @@ test_file! { call_to_pure_fn_on_self }
257257
test_file! { missing_self }
258258
test_file! { self_not_first }
259259
test_file! { self_in_standalone_fn }
260+
test_file! { unsafe_misuse }
261+
test_file! { unsafe_nesting }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
source: crates/analyzer/tests/errors.rs
3+
expression: "error_string(&path, &src)"
4+
5+
---
6+
error: unsafe function `mod_priv` can only be called in an unsafe function or block
7+
┌─ compile_errors/unsafe_misuse.fe:10:3
8+
9+
3 │ unsafe fn mod_priv(): # OK
10+
│ ------------------ `mod_priv` is defined here as unsafe
11+
·
12+
10 │ mod_priv() # BAD
13+
│ ^^^^^^^^ call to unsafe function
14+
15+
= Hint: put this call in an `unsafe` block if you're confident that it's safe to use here
16+
17+
error: public contract functions can't be unsafe
18+
┌─ compile_errors/unsafe_misuse.fe:18:3
19+
20+
18 │ pub unsafe fn pub_self(self): # BAD
21+
│ ^^^^^^^^^^ a contract function can be either `pub` or `unsafe`, but not both
22+
23+
error: public contract functions can't be unsafe
24+
┌─ compile_errors/unsafe_misuse.fe:20:3
25+
26+
20 │ pub unsafe fn pub_noself(): # BAD
27+
│ ^^^^^^^^^^ a contract function can be either `pub` or `unsafe`, but not both
28+
29+
error: unsafe function `priv_self` can only be called in an unsafe function or block
30+
┌─ compile_errors/unsafe_misuse.fe:38:5
31+
32+
31 │ unsafe fn priv_self(self): # OK
33+
│ ------------------- `priv_self` is defined here as unsafe
34+
·
35+
38 │ self.priv_self() # BAD
36+
│ ^^^^^^^^^^^^^^ call to unsafe function
37+
38+
= Hint: put this call in an `unsafe` block if you're confident that it's safe to use here
39+
40+
error: unsafe function `priv_nonself` can only be called in an unsafe function or block
41+
┌─ compile_errors/unsafe_misuse.fe:39:5
42+
43+
24 │ unsafe fn priv_nonself(): # OK
44+
│ ---------------------- `priv_nonself` is defined here as unsafe
45+
·
46+
39 │ priv_nonself() # BAD
47+
│ ^^^^^^^^^^^^ call to unsafe function
48+
49+
= Hint: put this call in an `unsafe` block if you're confident that it's safe to use here
50+
51+

0 commit comments

Comments
 (0)