Merge pull request #1994 from AleoHQ/remove-unsafe

Remove all uses of unsafe
This commit is contained in:
Collin Chin 2022-08-05 13:50:53 -07:00 committed by GitHub
commit 46f21c68a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 96 additions and 292 deletions

View File

@ -16,7 +16,7 @@
use crate::Program;
use leo_span::Symbol;
use leo_span::{symbol::with_session_globals, Symbol};
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -26,13 +26,18 @@ pub fn serialize<S: Serializer>(
imported_modules: &IndexMap<Vec<Symbol>, Program>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let joined: IndexMap<String, Program> = imported_modules
.into_iter()
.map(|(package, program)| {
let package = package.iter().map(|x| x.as_str().to_string()).collect::<Vec<_>>();
(package.join("."), program.clone())
})
.collect();
let joined: IndexMap<String, Program> = with_session_globals(|s| {
imported_modules
.into_iter()
.map(|(package, program)| {
let package = package
.iter()
.map(|x| x.as_str(s, |s| s.to_owned()))
.collect::<Vec<_>>();
(package.join("."), program.clone())
})
.collect()
});
joined.serialize(serializer)
}

View File

@ -20,6 +20,7 @@
//! The [`Ast`] type is intended to be parsed and modified by different passes
//! of the Leo compiler. The Leo compiler can generate a set of R1CS constraints from any [`Ast`].
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod access;
pub use self::access::*;

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![allow(clippy::module_inception)]
#![allow(clippy::upper_case_acronyms)]
#![doc = include_str!("../README.md")]

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
mod algorithms;

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
use leo_errors::{emitter::Handler, Result};
use leo_span::symbol::create_session_if_not_set_then;

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
use leo_ast::Ast;
use leo_errors::emitter::Handler;
use leo_span::symbol::create_session_if_not_set_then;

View File

@ -19,6 +19,7 @@
//! This module contains the [`parse_ast()`] method which calls the underlying [`parse()`]
//! method to create a new program ast.
#![forbid(unsafe_code)]
#![allow(clippy::vec_init_then_push)]
#![doc = include_str!("../README.md")]

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod code_generation;

View File

@ -1,175 +0,0 @@
// Copyright (C) 2019-2022 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
// Copyright Rust project developers under MIT or APACHE-2.0.
use core::alloc::Layout;
use core::cell::{Cell, RefCell};
use core::mem::{self, MaybeUninit};
use core::{cmp, ptr, slice};
use std::iter;
// The arenas start with PAGE-sized chunks, and then each new chunk is twice as
// big as its predecessor, up until we reach HUGE_PAGE-sized chunks, whereupon
// we stop growing. This scales well, from arenas that are barely used up to
// arenas that are used for 100s of MiBs. Note also that the chosen sizes match
// the usual sizes of pages and huge pages on Linux.
const PAGE: usize = 4096;
const HUGE_PAGE: usize = 2 * 1024 * 1024;
pub struct DroplessArena {
/// A pointer to the start of the free space.
start: Cell<*mut u8>,
/// A pointer to the end of free space.
///
/// The allocation proceeds from the end of the chunk towards the start.
/// When this pointer crosses the start pointer, a new chunk is allocated.
end: Cell<*mut u8>,
/// A vector of arena chunks.
chunks: RefCell<Vec<TypedArenaChunk<u8>>>,
}
unsafe impl Send for DroplessArena {}
impl Default for DroplessArena {
#[inline]
fn default() -> DroplessArena {
DroplessArena {
start: Cell::new(ptr::null_mut()),
end: Cell::new(ptr::null_mut()),
chunks: Default::default(),
}
}
}
impl DroplessArena {
#[inline(never)]
#[cold]
fn grow(&self, additional: usize) {
unsafe {
let mut chunks = self.chunks.borrow_mut();
let new_cap = if let Some(last_chunk) = chunks.last_mut() {
// If the previous chunk's len is less than HUGE_PAGE bytes,
// then this chunk will be at least double the previous chunk's size.
last_chunk.storage.len().min(HUGE_PAGE / 2) * 2
} else {
PAGE
};
// Also ensure that this chunk can fit `additional`.
let new_cap = cmp::max(additional, new_cap);
let mut chunk = TypedArenaChunk::<u8>::new(new_cap);
self.start.set(chunk.start());
self.end.set(chunk.end());
chunks.push(chunk);
}
}
/// Allocates a byte slice with specified layout from the current memory chunk.
/// Returns `None` if there is no free space left to satisfy the request.
#[inline]
fn alloc_raw_without_grow(&self, layout: Layout) -> Option<*mut u8> {
let start = self.start.get() as usize;
let end = self.end.get() as usize;
let align = layout.align();
let bytes = layout.size();
let new_end = end.checked_sub(bytes)? & !(align - 1);
if start <= new_end {
let new_end = new_end as *mut u8;
self.end.set(new_end);
Some(new_end)
} else {
// There's no more space since we're growing towards the start.
None
}
}
#[inline]
pub fn alloc_raw(&self, layout: Layout) -> *mut u8 {
assert!(layout.size() != 0);
loop {
if let Some(a) = self.alloc_raw_without_grow(layout) {
break a;
}
// No free space left. Allocate a new chunk to satisfy the request.
// On failure the grow will panic or abort.
self.grow(layout.size());
}
}
/// Allocates a slice of objects that are copied into the `DroplessArena`,
/// returning a mutable reference to it.
/// Will panic if passed a zero-sized type.
///
/// Panics:
///
/// - Zero-sized types
/// - Zero-length slices
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &mut [T] {
assert!(!mem::needs_drop::<T>());
assert!(mem::size_of::<T>() != 0);
assert!(!slice.is_empty());
let mem = self.alloc_raw(Layout::for_value::<[T]>(slice)) as *mut T;
unsafe {
mem.copy_from_nonoverlapping(slice.as_ptr(), slice.len());
slice::from_raw_parts_mut(mem, slice.len())
}
}
}
struct TypedArenaChunk<T> {
/// The raw storage for the arena chunk.
storage: Box<[MaybeUninit<T>]>,
}
impl<T> TypedArenaChunk<T> {
#[inline]
unsafe fn new(capacity: usize) -> TypedArenaChunk<T> {
TypedArenaChunk {
// HACK(Centril) around `Box::new_uninit_slice` not being stable.
storage: iter::repeat_with(MaybeUninit::<T>::uninit).take(capacity).collect(),
}
}
// Returns a pointer to the first allocated object.
#[inline]
fn start(&mut self) -> *mut T {
// HACK(Centril) around `MaybeUninit::slice_as_mut_ptr` not being stable.
self.storage.as_mut_ptr() as *mut T
}
// Returns a pointer to the end of the allocated space.
#[inline]
fn end(&mut self) -> *mut T {
unsafe {
if mem::size_of::<T>() == 0 {
// A pointer as large as possible for zero-sized elements.
!0 as *mut T
} else {
self.start().add(self.storage.len())
}
}
}
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
mod dropless;
#![forbid(unsafe_code)]
pub mod symbol;
pub use symbol::{sym, Symbol};

View File

@ -368,12 +368,10 @@ fn normalize_newlines(src: &mut String) {
}
// Account for removed `\r`.
// After `set_len`, `buf` is guaranteed to contain utf-8 again.
// After `buf.truncate(..)`, `buf` is guaranteed to contain utf-8 again.
let new_len = buf.len() - gap_len;
unsafe {
buf.set_len(new_len);
*src = String::from_utf8_unchecked(buf);
}
buf.truncate(new_len);
*src = String::from_utf8(buf).unwrap();
fn find_crlf(src: &[u8]) -> Option<usize> {
let mut search_idx = 0;
@ -414,9 +412,8 @@ fn analyze_source_file(src: &str, source_file_start_pos: BytePos) -> (Vec<BytePo
let src_bytes = src.as_bytes();
while i < src.len() {
// SAFETY: We verified that i < src.len().
let i_usize = i as usize;
let byte = unsafe { *src_bytes.get_unchecked(i_usize) };
let byte = src_bytes[i_usize];
// How much to advance to get to the next UTF-8 char in the string.
let mut char_len = 1;

View File

@ -14,11 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::dropless::DroplessArena;
use crate::source_map::SourceMap;
use core::borrow::Borrow;
use core::cmp::PartialEq;
use core::convert::AsRef;
use core::hash::{Hash, Hasher};
use core::num::NonZeroU32;
use core::ops::Deref;
use core::{fmt, str};
@ -26,8 +26,6 @@ use fxhash::FxBuildHasher;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cell::RefCell;
use std::intrinsics::transmute;
use std::marker::PhantomData;
/// A helper for `symbols` defined below.
/// The macro's job is to bind conveniently usable `const` items to the symbol names provided.
@ -240,8 +238,10 @@ impl Symbol {
/// Returns the corresponding `Symbol` for the given `index`.
pub const fn new(index: u32) -> Self {
let index = index.saturating_add(1);
// SAFETY: per above addition, we know `index > 0` always applies.
Self(unsafe { NonZeroU32::new_unchecked(index) })
Self(match NonZeroU32::new(index) {
None => unreachable!(),
Some(x) => x,
})
}
/// Maps a string to its interned representation.
@ -249,13 +249,9 @@ impl Symbol {
with_session_globals(|session_globals| session_globals.symbol_interner.intern(string))
}
/// Convert to effectively a `&'static str`.
/// This is a slowish operation because it requires locking the symbol interner.
pub fn as_str(self) -> SymbolStr {
with_session_globals(|session_globals| {
let symbol_str = session_globals.symbol_interner.get(self);
SymbolStr::new(unsafe { std::mem::transmute::<&str, &str>(symbol_str) })
})
/// Convert to effectively a `&'static str` given the `SessionGlobals`.
pub fn as_str<R>(self, s: &SessionGlobals, with: impl FnOnce(&str) -> R) -> R {
s.symbol_interner.get(self, with)
}
/// Converts this symbol to the raw index.
@ -268,82 +264,19 @@ impl Symbol {
}
fn serde_from_symbol<S: Serializer>(index: &NonZeroU32, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&Self(*index).as_str())
with_session_globals(|sg| Self(*index).as_str(sg, |s| ser.serialize_str(s)))
}
}
impl fmt::Debug for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.as_str(), f)
with_session_globals(|s| self.as_str(s, |s| fmt::Debug::fmt(s, f)))
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.as_str(), f)
}
}
/// An alternative to [`Symbol`], useful when the chars within the symbol need to
/// be accessed. It deliberately has limited functionality and should only be
/// used for temporary values.
///
/// Because the interner outlives any thread which uses this type, we can
/// safely treat `string` which points to interner data, as an immortal string,
/// as long as this type never crosses between threads.
#[derive(Clone, Eq, PartialOrd, Ord)]
pub struct SymbolStr {
string: &'static str,
/// Ensures the type is neither `Sync` nor `Send`,
/// so that we satisfy "never crosses between threads" per above.
not_sync_send: PhantomData<*mut ()>,
}
impl SymbolStr {
/// Create a `SymbolStr` from a `&'static str`.
pub fn new(string: &'static str) -> Self {
Self {
string,
not_sync_send: PhantomData,
}
}
}
// This impl allows a `SymbolStr` to be directly equated with a `String` or `&str`.
impl<T: Deref<Target = str>> PartialEq<T> for SymbolStr {
fn eq(&self, other: &T) -> bool {
self.string == other.deref()
}
}
/// This impl means that if `ss` is a `SymbolStr`:
/// - `*ss` is a `str`;
/// - `&*ss` is a `&str` (and `match &*ss { ... }` is a common pattern).
/// - `&ss as &str` is a `&str`, which means that `&ss` can be passed to a
/// function expecting a `&str`.
impl Deref for SymbolStr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.string
}
}
impl AsRef<str> for SymbolStr {
fn as_ref(&self) -> &str {
self.string
}
}
impl fmt::Debug for SymbolStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.string, f)
}
}
impl fmt::Display for SymbolStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.string, f)
with_session_globals(|s| self.as_str(s, |s| fmt::Display::fmt(s, f)))
}
}
@ -383,13 +316,53 @@ pub fn with_session_globals<R>(f: impl FnOnce(&SessionGlobals) -> R) -> R {
SESSION_GLOBALS.with(f)
}
/// An interned string,
/// either prefilled "at compile time" (`Static`),
/// or created at runtime (`Owned`).
#[derive(Eq)]
enum InternedStr {
/// String is stored "at compile time", i.e. prefilled.
Static(&'static str),
/// String is constructed and stored during runtime.
Owned(Box<str>),
}
impl Borrow<str> for InternedStr {
fn borrow(&self) -> &str {
self.deref()
}
}
impl Deref for InternedStr {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::Static(s) => s,
Self::Owned(s) => s,
}
}
}
impl PartialEq for InternedStr {
fn eq(&self, other: &InternedStr) -> bool {
self.deref() == other.deref()
}
}
impl Hash for InternedStr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.deref().hash(state);
}
}
/// The inner interner.
/// This construction is used to get interior mutability in `Interner`.
struct InnerInterner {
/// Arena used to allocate the strings, giving us `&'static str`s from it.
arena: DroplessArena,
// /// Arena used to allocate the strings, giving us `&'static str`s from it.
// arena: DroplessArena,
/// Registration of strings and symbol index allocation is done in this set.
set: IndexSet<&'static str, FxBuildHasher>,
set: IndexSet<InternedStr, FxBuildHasher>,
}
/// A symbol-to-string interner.
@ -406,8 +379,8 @@ impl Interner {
/// Returns an interner prefilled with `init`.
fn prefill(init: &[&'static str]) -> Self {
let inner = InnerInterner {
arena: <_>::default(),
set: init.iter().copied().collect(),
// arena: <_>::default(),
set: init.iter().copied().map(InternedStr::Static).collect(),
};
Self {
inner: RefCell::new(inner),
@ -416,30 +389,19 @@ impl Interner {
/// Interns `string`, returning a `Symbol` corresponding to it.
fn intern(&self, string: &str) -> Symbol {
let InnerInterner { arena, set } = &mut *self.inner.borrow_mut();
let InnerInterner { set } = &mut *self.inner.borrow_mut();
if let Some(sym) = set.get_index_of(string) {
// Already internet, return that symbol.
// Already interned, return that symbol.
return Symbol::new(sym as u32);
}
// SAFETY: `from_utf8_unchecked` is safe since we just allocated a `&str`,
// which is known to be UTF-8.
let bytes = arena.alloc_slice(string.as_bytes());
let string: &str = unsafe { str::from_utf8_unchecked(bytes) };
unsafe fn transmute_lt<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
transmute(x)
}
// SAFETY: Extending to `'static` is fine. Accesses only happen while the arena is alive.
let string: &'static _ = unsafe { transmute_lt(string) };
Symbol::new(set.insert_full(string).0 as u32)
Symbol::new(set.insert_full(InternedStr::Owned(string.into())).0 as u32)
}
/// Returns the corresponding string for the given symbol.
fn get(&self, symbol: Symbol) -> &str {
self.inner.borrow().set.get_index(symbol.as_u32() as usize).unwrap()
fn get<R>(&self, symbol: Symbol, with: impl FnOnce(&str) -> R) -> R {
let set = &self.inner.borrow().set;
with(set.get_index(symbol.as_u32() as usize).unwrap())
}
}

View File

@ -39,6 +39,8 @@
// ```
//
#![forbid(unsafe_code)]
use abnf::types::{Node, Rule};
use anyhow::{anyhow, Result};
use std::collections::{HashMap, HashSet};

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![deny(clippy::all, clippy::missing_docs_in_private_items)]
#![doc = include_str!("../README.md")]

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod commands;

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub mod build;

View File

@ -22,6 +22,7 @@
//! To regenerate the tests after a syntax change or failing test, delete the [`tests/expectations/`]
//! directory and run the [`parser_tests()`] test in [`parser/src/test.rs`].
#![forbid(unsafe_code)]
#![cfg(not(doctest))] // Don't doctest the markdown.
#![doc = include_str!("../README.md")]