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

chore: require safety doc comment for unsafe instead of //@safety #6992

Merged
merged 3 commits into from
Jan 9, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions compiler/noirc_frontend/src/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
}
ast::LValue::Dereference(_lv, span) => {
// TODO: this is a dummy statement for now, but we should
// somehow track the derefence and update the pointed to

Check warning on line 311 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (derefence)
// variable
ast::Statement {
kind: ast::StatementKind::Expression(uint_expr(0, *span)),
Expand Down Expand Up @@ -523,8 +523,8 @@
__debug_var_assign_oracle(var_id, value);
}
pub fn __debug_var_assign<T>(var_id: u32, value: T) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_var_assign_inner(var_id, value);
}}
Expand All @@ -536,8 +536,8 @@
__debug_var_drop_oracle(var_id);
}
pub fn __debug_var_drop(var_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_var_drop_inner(var_id);
}}
Expand All @@ -549,8 +549,8 @@
__debug_fn_enter_oracle(fn_id);
}
pub fn __debug_fn_enter(fn_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_fn_enter_inner(fn_id);
}}
Expand All @@ -562,8 +562,8 @@
__debug_fn_exit_oracle(fn_id);
}
pub fn __debug_fn_exit(fn_id: u32) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_fn_exit_inner(fn_id);
}}
Expand All @@ -575,8 +575,8 @@
__debug_dereference_assign_oracle(var_id, value);
}
pub fn __debug_dereference_assign<T>(var_id: u32, value: T) {
/// Safety: debug context
unsafe {
//@safety: debug context
{
__debug_dereference_assign_inner(var_id, value);
}}
Expand All @@ -603,8 +603,8 @@
__debug_oracle_member_assign_{n}(var_id, value, {vars});
}}
pub fn __debug_member_assign_{n}<T, Index>(var_id: u32, value: T, {var_sig}) {{
/// Safety: debug context
unsafe {{
//@safety: debug context
__debug_inner_member_assign_{n}(var_id, value, {vars});
}}
}}
Expand Down Expand Up @@ -715,9 +715,9 @@
ast::Pattern::Tuple(patterns, _) => {
stack.extend(patterns.iter().map(|pattern| (pattern, false)));
}
ast::Pattern::Struct(_, pids, _) => {

Check warning on line 718 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (pids)
stack.extend(pids.iter().map(|(_, pattern)| (pattern, is_mut)));

Check warning on line 719 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (pids)
vars.extend(pids.iter().map(|(id, _)| (id.clone(), false)));

Check warning on line 720 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (pids)
}
ast::Pattern::Interned(_, _) => (),
}
Expand All @@ -728,7 +728,7 @@
fn pattern_to_string(pattern: &ast::Pattern) -> String {
match pattern {
ast::Pattern::Identifier(id) => id.0.contents.clone(),
ast::Pattern::Mutable(mpat, _, _) => format!("mut {}", pattern_to_string(mpat.as_ref())),

Check warning on line 731 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (mpat)

Check warning on line 731 in compiler/noirc_frontend/src/debug/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (mpat)
ast::Pattern::Tuple(elements, _) => format!(
"({})",
elements.iter().map(pattern_to_string).collect::<Vec<String>>().join(", ")
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ impl<'context> Elaborator<'context> {
ExpressionKind::Comptime(comptime, _) => {
return self.elaborate_comptime_block(comptime, expr.span)
}
ExpressionKind::Unsafe(block_expression, _) => {
self.elaborate_unsafe_block(block_expression, expr.span)
ExpressionKind::Unsafe(block_expression, span) => {
self.elaborate_unsafe_block(block_expression, span)
}
ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)),
ExpressionKind::Interned(id) => {
Expand Down
22 changes: 3 additions & 19 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
done: false,
skip_comments: true,
skip_whitespaces: true,
max_integer: BigInt::from_biguint(num_bigint::Sign::Plus, FieldElement::modulus())

Check warning on line 50 in compiler/noirc_frontend/src/lexer/lexer.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (biguint)
- BigInt::one(),
}
}
Expand Down Expand Up @@ -726,7 +726,7 @@
}

fn parse_comment(&mut self, start: u32) -> SpannedTokenResult {
let mut doc_style = match self.peek_char() {
let doc_style = match self.peek_char() {
Some('!') => {
self.next_char();
Some(DocStyle::Inner)
Expand All @@ -735,17 +735,9 @@
self.next_char();
Some(DocStyle::Outer)
}
Some('@') => Some(DocStyle::Safety),
_ => None,
};
let mut comment = self.eat_while(None, |ch| ch != '\n');
if doc_style == Some(DocStyle::Safety) {
if comment.starts_with("@safety") {
comment = comment["@safety".len()..].to_string();
} else {
doc_style = None;
}
}
let comment = self.eat_while(None, |ch| ch != '\n');

if !comment.is_ascii() {
let span = Span::from(start..self.position);
Expand All @@ -760,7 +752,7 @@
}

fn parse_block_comment(&mut self, start: u32) -> SpannedTokenResult {
let mut doc_style = match self.peek_char() {
let doc_style = match self.peek_char() {
Some('!') => {
self.next_char();
Some(DocStyle::Inner)
Expand All @@ -769,7 +761,6 @@
self.next_char();
Some(DocStyle::Outer)
}
Some('@') => Some(DocStyle::Safety),
_ => None,
};

Expand All @@ -796,13 +787,6 @@
ch => content.push(ch),
}
}
if doc_style == Some(DocStyle::Safety) {
if content.starts_with("@safety") {
content = content["@safety".len()..].to_string();
} else {
doc_style = None;
}
}
if depth == 0 {
if !content.is_ascii() {
let span = Span::from(start..self.position);
Expand Down Expand Up @@ -1410,7 +1394,7 @@
// (expected_token_discriminator, strings_to_lex)
// expected_token_discriminator matches a given token when
// std::mem::discriminant returns the same discriminant for both.
fn blns_base64_to_statements(base64_str: String) -> Vec<(Option<Token>, Vec<String>)> {

Check warning on line 1397 in compiler/noirc_frontend/src/lexer/lexer.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (blns)
use base64::engine::general_purpose;
use std::borrow::Cow;
use std::io::Cursor;
Expand Down Expand Up @@ -1468,7 +1452,7 @@
fn test_big_list_of_naughty_strings() {
use std::mem::discriminant;

let blns_contents = include_str!("./blns/blns.base64.json");

Check warning on line 1455 in compiler/noirc_frontend/src/lexer/lexer.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (blns)
let blns_base64: Vec<String> =
serde_json::from_str(blns_contents).expect("BLNS json invalid");
for blns_base64_str in blns_base64 {
Expand Down
3 changes: 0 additions & 3 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ impl Display for FmtStrFragment {
pub enum DocStyle {
Outer,
Inner,
Safety,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
Expand Down Expand Up @@ -425,13 +424,11 @@ impl fmt::Display for Token {
Token::LineComment(ref s, style) => match style {
Some(DocStyle::Inner) => write!(f, "//!{s}"),
Some(DocStyle::Outer) => write!(f, "///{s}"),
Some(DocStyle::Safety) => write!(f, "//@safety{s}"),
None => write!(f, "//{s}"),
},
Token::BlockComment(ref s, style) => match style {
Some(DocStyle::Inner) => write!(f, "/*!{s}*/"),
Some(DocStyle::Outer) => write!(f, "/**{s}*/"),
Some(DocStyle::Safety) => write!(f, "/*@safety{s}*/"),
None => write!(f, "/*{s}*/"),
},
Token::Quote(ref stream) => {
Expand Down
16 changes: 13 additions & 3 deletions compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ pub enum ParserErrorReason {
WrongNumberOfAttributeArguments { name: String, min: usize, max: usize, found: usize },
#[error("The `deprecated` attribute expects a string argument")]
DeprecatedAttributeExpectsAStringArgument,
#[error("Unsafe block must start with a safety comment")]
#[error("Unsafe block must have a safety doc comment above it")]
MissingSafetyComment,
#[error("Unsafe block must start with a safety comment")]
UnsafeDocCommentDoesNotStartWithSafety,
#[error("Missing parameters for function definition")]
MissingParametersForFunctionDefinition,
}
Expand Down Expand Up @@ -283,10 +285,18 @@ impl<'a> From<&'a ParserError> for Diagnostic {
error.span,
),
ParserErrorReason::MissingSafetyComment => Diagnostic::simple_warning(
"Missing Safety Comment".into(),
"Unsafe block must start with a safety comment: //@safety".into(),
"Unsafe block must have a safety doc comment above it".into(),
"The doc comment must start with the \"Safety: \" word".into(),
error.span,
),
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety => {
Diagnostic::simple_warning(
"Unsafe block must start with a safety comment".into(),
"The doc comment above this unsafe block must start with the \"Safety: \" word"
.into(),
error.span,
)
}
ParserErrorReason::MissingParametersForFunctionDefinition => {
Diagnostic::simple_error(
"Missing parameters for function definition".into(),
Expand Down
22 changes: 22 additions & 0 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ pub struct Parser<'a> {
next_token: SpannedToken,
current_token_span: Span,
previous_token_span: Span,

/// The current statement's doc comments.
/// This is used to eventually know if an `unsafe { ... }` expression is documented
/// in its containing statement. For example:
///
/// ```noir
/// /// Safety: test
/// let x = unsafe { call() };
/// ```
statement_doc_comments: Option<StatementDocComments>,
}

#[derive(Debug)]
pub(crate) struct StatementDocComments {
pub(crate) doc_comments: Vec<String>,
pub(crate) start_span: Span,
pub(crate) end_span: Span,

/// Were these doc comments "read" by an unsafe statement?
/// If not, these doc comments aren't documenting anything and they produce an error.
pub(crate) read: bool,
}

impl<'a> Parser<'a> {
Expand All @@ -107,6 +128,7 @@ impl<'a> Parser<'a> {
next_token: eof_spanned_token(),
current_token_span: Default::default(),
previous_token_span: Default::default(),
statement_doc_comments: None,
};
parser.read_two_first_tokens();
parser
Expand Down
83 changes: 62 additions & 21 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
MemberAccessExpression, MethodCallExpression, Statement, TypePath, UnaryOp, UnresolvedType,
},
parser::{labels::ParsingRuleLabel, parser::parse_many::separated_by_comma, ParserErrorReason},
token::{DocStyle, Keyword, SpannedToken, Token, TokenKind},
token::{Keyword, Token, TokenKind},
};

use super::{
Expand Down Expand Up @@ -267,6 +267,21 @@ impl<'a> Parser<'a> {
}

fn parse_atom_kind(&mut self, allow_constructors: bool) -> Option<ExpressionKind> {
let span_before_doc_comments = self.current_token_span;
let doc_comments = self.parse_outer_doc_comments();
let has_doc_comments = !doc_comments.is_empty();

if let Some(kind) = self.parse_unsafe_expr(&doc_comments, span_before_doc_comments) {
return Some(kind);
}

if has_doc_comments {
self.push_error(
ParserErrorReason::DocCommentDoesNotDocumentAnything,
self.span_since(span_before_doc_comments),
);
}

if let Some(literal) = self.parse_literal() {
return Some(literal);
}
Expand All @@ -275,10 +290,6 @@ impl<'a> Parser<'a> {
return Some(kind);
}

if let Some(kind) = self.parse_unsafe_expr() {
return Some(kind);
}

if let Some(kind) = self.parse_path_expr(allow_constructors) {
return Some(kind);
}
Expand Down Expand Up @@ -370,26 +381,43 @@ impl<'a> Parser<'a> {
}

/// UnsafeExpression = 'unsafe' Block
fn parse_unsafe_expr(&mut self) -> Option<ExpressionKind> {
fn parse_unsafe_expr(
&mut self,
doc_comments: &[String],
span_before_doc_comments: Span,
) -> Option<ExpressionKind> {
let start_span = self.current_token_span;

if !self.eat_keyword(Keyword::Unsafe) {
return None;
}

let next_token = self.next_token.token();
if matches!(
next_token,
Token::LineComment(_, Some(DocStyle::Safety))
| Token::BlockComment(_, Some(DocStyle::Safety))
) {
//Checks the safety comment is there, and skip it
let span = self.current_token_span;
self.eat_left_brace();
self.token = SpannedToken::new(Token::LeftBrace, span);
} else {
self.push_error(ParserErrorReason::MissingSafetyComment, self.current_token_span);
if doc_comments.is_empty() {
if let Some(statement_doc_comments) = &mut self.statement_doc_comments {
statement_doc_comments.read = true;

let doc_comments = &statement_doc_comments.doc_comments;
let span_before_doc_comments = statement_doc_comments.start_span;
let span_after_doc_comments = statement_doc_comments.end_span;

if !doc_comments[0].trim().to_lowercase().starts_with("safety:") {
self.push_error(
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety,
Span::from(
span_before_doc_comments.start()..span_after_doc_comments.start(),
),
);
}
} else {
self.push_error(ParserErrorReason::MissingSafetyComment, start_span);
}
} else if !doc_comments[0].trim().to_lowercase().starts_with("safety:") {
self.push_error(
ParserErrorReason::UnsafeDocCommentDoesNotStartWithSafety,
self.span_since(span_before_doc_comments),
);
}

let start_span = self.current_token_span;
if let Some(block) = self.parse_block() {
Some(ExpressionKind::Unsafe(block, self.span_since(start_span)))
} else {
Expand Down Expand Up @@ -971,8 +999,21 @@ mod tests {

#[test]
fn parses_unsafe_expression() {
let src = "unsafe { //@safety: test
1 }";
let src = "
/// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);
let ExpressionKind::Unsafe(block, _) = expr.kind else {
panic!("Expected unsafe expression");
};
assert_eq!(block.statements.len(), 1);
}

#[test]
fn parses_unsafe_expression_with_doc_comment() {
let src = "
/// Safety: test
unsafe { 1 }";
let expr = parse_expression_no_errors(src);
let ExpressionKind::Unsafe(block, _) = expr.kind else {
panic!("Expected unsafe expression");
Expand Down
Loading
Loading