Don't use proc macro

I really don't like the new syntax though,
much as I don't like proc_macro it may be coming back.
pull/3/head
Bradlee Speice 2018-11-14 23:31:10 -05:00
parent af2781b453
commit 000d0032a9
10 changed files with 54 additions and 284 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
*.swp

View File

@ -17,6 +17,4 @@ repository = "https://github.com/bspeice/qadapt.git"
[dependencies]
libc = "0.2"
spin = "0.4"
thread-id = "3.3"
qadapt-macro = { version = "0.3.0", path = "./qadapt-macro" }
thread-id = "3.3"

View File

@ -1,3 +0,0 @@
/target
**/*.rs.bk
Cargo.lock

View File

@ -1,18 +0,0 @@
[package]
name = "qadapt-macro"
version = "0.3.0"
authors = ["Bradlee Speice <bradlee@speice.io>"]
description = "The Quick And Dirty Allocation Profiling Tool - Support Macros"
license = "Apache-2.0"
readme = "README.md"
categories = [
"development-tools",
"development-tools::debugging",
"development-tools::profiling",
"development-tools::testing",
"memory-management"
]
repository = "https://github.com/bspeice/qadapt.git"
[lib]
proc-macro = true

View File

@ -1,6 +0,0 @@
# QADAPT - Helper macros
Helper macros to use with the QADAPT allocator system
This crate is intended for managing the QADAPT allocator,
and is unusable on its own.

View File

@ -1,136 +0,0 @@
//! Helper macros to use with the QADAPT allocator system
//!
//! This crate is intended for managing the QADAPT allocator,
//! and is unusable on its own.
//!
// TODO: This causes issues, but I can't track down why
// #![deny(missing_docs)]
extern crate proc_macro;
use proc_macro::Delimiter;
use proc_macro::Spacing;
use proc_macro::Span;
use proc_macro::TokenStream;
use proc_macro::TokenTree;
use std::iter::FromIterator;
type TT = proc_macro::TokenTree;
type TS = proc_macro::TokenStream;
type G = proc_macro::Group;
type I = proc_macro::Ident;
type P = proc_macro::Punct;
fn release_guard(fn_name: &str) -> TokenStream {
let rel = I::new("release", Span::call_site());
let not_rel: Vec<TokenTree> = vec![
I::new("not", Span::call_site()).into(),
G::new(Delimiter::Parenthesis, TokenTree::Ident(rel).into()).into()
];
let cfg_not_rel: Vec<TokenTree> = vec![
I::new("cfg", Span::call_site()).into(),
G::new(Delimiter::Parenthesis, TS::from_iter(not_rel.into_iter())).into()
];
let guarded: Vec<TokenTree> = vec![
P::new('#', Spacing::Alone).into(),
G::new(Delimiter::Bracket, TS::from_iter(cfg_not_rel.into_iter())).into(),
P::new(':', Spacing::Joint).into(),
P::new(':', Spacing::Alone).into(),
I::new("qadapt", Span::call_site()).into(),
P::new(':', Spacing::Joint).into(),
P::new(':', Spacing::Alone).into(),
I::new(fn_name, Span::call_site()).into(),
G::new(Delimiter::Parenthesis, TokenStream::new()).into(),
];
TS::from_iter(guarded.into_iter())
}
fn protected_body(fn_name: &str, args: G) -> TokenTree {
let mut args_filtered = Vec::new();
let mut waiting_for_comma = false;
let mut in_type = 0;
for tt in args.stream().into_iter() {
match tt {
TokenTree::Ident(ref _i) if !waiting_for_comma && in_type == 0 => {
args_filtered.push(tt.clone());
waiting_for_comma = true;
}
TokenTree::Punct(ref p) if p.as_char() == '<' => in_type += 1,
TokenTree::Punct(ref p) if p.as_char() == '>' => in_type -= 1,
TokenTree::Punct(ref p) if p.as_char() == ',' && in_type == 0 => {
waiting_for_comma = false;
args_filtered.push(tt.clone())
}
_ => ()
}
}
let args_group = G::new(Delimiter::Parenthesis, TS::from_iter(args_filtered));
let tt: Vec<TT> = vec![
G::new(Delimiter::Brace, release_guard("enter_protected")).into(),
I::new("let", Span::call_site()).into(),
I::new("__ret__", Span::call_site()).into(),
P::new('=', Spacing::Alone).into(),
I::new(fn_name, Span::call_site()).into(),
args_group.into(),
P::new(';', Spacing::Alone).into(),
G::new(Delimiter::Brace, release_guard("exit_protected")).into(),
I::new("__ret__", Span::call_site()).into(),
];
G::new(Delimiter::Brace, TS::from_iter(tt)).into()
}
/// Set up the QADAPT allocator to trigger a panic if any allocations happen during
/// calls to this function.
///
/// QADAPT will only track allocations in the thread that calls this function;
/// if (for example) this function receives the results of an allocation in a
/// separate thread, QADAPT will not trigger a panic.
#[proc_macro_attribute]
pub fn allocate_panic(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut original_fn: Vec<TokenTree> = Vec::new();
let mut protected_fn: Vec<TokenTree> = Vec::new();
let mut item_iter = item.into_iter();
// First, get the function name we're replacing
let mut fn_name = None;
let mut fn_args = None;
while let Some(tt) = item_iter.next() {
match tt {
TokenTree::Ident(ref i) if i.to_string() == "fn" => {
original_fn.push(tt.clone());
protected_fn.push(tt.clone());
}
TokenTree::Ident(ref i) if fn_args.is_none() => {
let changed_name = format!("__{}__", i.to_owned());
original_fn.push(TokenTree::Ident(I::new(&changed_name, Span::call_site())));
protected_fn.push(tt.clone());
fn_name = Some(changed_name);
}
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Parenthesis && fn_args.is_none() => {
original_fn.push(tt.clone());
protected_fn.push(tt.clone());
fn_args = Some(g.clone());
}
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Brace => {
original_fn.push(tt.clone());
protected_fn.push(protected_body(
&fn_name.take().unwrap(),
fn_args.take().unwrap(),
));
}
tt => {
original_fn.push(tt.clone());
protected_fn.push(tt.clone());
}
}
}
let mut full = Vec::new();
full.push(TS::from_iter(original_fn));
full.push(TS::from_iter(protected_fn));
let ts = TS::from_iter(full.into_iter());
ts
}

View File

@ -12,15 +12,11 @@
//! for some helper macros to make working with QADAPT a bit easier.
#![deny(missing_docs)]
extern crate libc;
extern crate qadapt_macro;
extern crate spin;
// thread_id is necessary because `std::thread::current()` panics if we have not yet
// allocated a `thread_local!{}` it depends on.
extern crate thread_id;
// Re-export the proc macros to use by other code
pub use qadapt_macro::*;
use libc::c_void;
use libc::free;
use libc::malloc;
@ -31,6 +27,7 @@ use std::thread;
thread_local! {
static PROTECTION_LEVEL: RwLock<usize> = RwLock::new(0);
static IS_STARTED: RwLock<bool> = RwLock::new(false);
}
/// The QADAPT allocator itself
@ -43,6 +40,10 @@ pub fn enter_protected() {
return;
}
IS_STARTED
.try_with(|b| assert!(*b.read(), "QADAPT not in use"))
.unwrap_or_else(|_e| ());
PROTECTION_LEVEL
.try_with(|v| {
*v.write() += 1;
@ -70,6 +71,17 @@ pub fn exit_protected() {
.unwrap_or_else(|_e| ());
}
/// Given a closure, run a function inside a guarded allocation block
pub fn do_protected<T, C>(mut c: C) -> T
where
C: FnMut() -> T
{
enter_protected();
let ret = c();
exit_protected();
ret
}
static INTERNAL_ALLOCATION: RwLock<usize> = RwLock::new(usize::max_value());
/// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected()
@ -108,6 +120,9 @@ unsafe impl GlobalAlloc for QADAPT {
return malloc(layout.size()) as *mut u8;
}
IS_STARTED.try_with(|b| *b.write() = true)
.unwrap_or_else(|_e| ());
// Because accessing PROTECTION_LEVEL has the potential to trigger an allocation,
// we need to spin until we can claim the INTERNAL_ALLOCATION lock for our thread.
claim_internal_alloc();
@ -131,7 +146,7 @@ unsafe impl GlobalAlloc for QADAPT {
}
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if alloc_immediate() {
return free(ptr as *mut c_void);
}
@ -148,15 +163,27 @@ unsafe impl GlobalAlloc for QADAPT {
// Tripped a bad dealloc, but make sure further memory access during unwind
// doesn't have issues
PROTECTION_LEVEL.with(|v| *v.write() = 0);
/*
panic!(
"Unexpected deallocation for size {}, protection level: {}",
layout.size(),
v
)
*/
}
_ => (),
}
}
}
#[macro_export]
macro_rules! alloc_panic{
($fn_body:expr) => {{
#[cfg(any(test, debug))]
{
::qadapt::do_protected(|| $fn_body)
}
#[cfg(not(any(test, debug)))]
{
$fn_body
}
}}
}

View File

@ -1,4 +1,4 @@
#![feature(asm)]
#[macro_use]
extern crate qadapt;
use qadapt::enter_protected;
@ -9,17 +9,14 @@ use qadapt::QADAPT;
#[global_allocator]
static Q: QADAPT = QADAPT;
pub fn black_box<T>(dummy: T) -> T {
// Taken from test lib, need to mark the arg as non-introspectable
unsafe { asm!("" : : "r"(&dummy)) }
dummy
}
#[test]
fn test_copy() {
enter_protected();
black_box(0u8);
exit_protected();
let z = alloc_panic!({
let zero = 0;
zero
});
assert_eq!(z, 0);
}
#[test]
@ -41,16 +38,8 @@ fn unit_result(b: bool) -> Result<(), ()> {
#[test]
fn test_unit_result() {
enter_protected();
#[allow(unused)]
{
black_box(unit_result(true));
}
black_box(unit_result(true)).unwrap();
#[allow(unused)]
{
black_box(unit_result(false));
}
black_box(unit_result(false)).unwrap_err();
unit_result(true).unwrap();
unit_result(false).unwrap_err();
exit_protected();
}
@ -80,14 +69,14 @@ fn test_vec_push_capacity() {
#[test]
fn test_vec_with_zero() {
enter_protected();
let _v: Vec<u8> = black_box(Vec::with_capacity(0));
let _v: Vec<u8> = Vec::with_capacity(0);
exit_protected();
}
#[test]
fn test_vec_new() {
enter_protected();
let _v: Vec<u8> = black_box(Vec::new());
let _v: Vec<u8> = Vec::new();
exit_protected();
}

View File

@ -1,89 +0,0 @@
extern crate qadapt;
use std::io;
use qadapt::allocate_panic;
use qadapt::QADAPT;
#[global_allocator]
static Q: QADAPT = QADAPT;
#[allocate_panic]
fn no_allocate() {
#[cfg(not(release))]
{
let _v = 0;
}
assert_eq!(::qadapt::protection_level(), 1);
let _v: Vec<()> = Vec::with_capacity(0);
}
#[test]
fn macro_no_allocate() {
no_allocate();
}
#[allocate_panic]
fn allocates() {
assert_eq!(::qadapt::protection_level(), 1);
// Without boxing, release profile can actually optimize out the allocation
let mut v = Box::new(Vec::new());
v.push(1);
}
#[test]
#[should_panic]
fn macro_allocates() {
allocates();
}
#[allocate_panic]
fn no_allocate_ret() -> bool {
return true;
}
#[test]
fn macro_return() {
assert!(no_allocate_ret());
}
#[allocate_panic]
fn no_allocate_implicit_ret() -> bool {
true
}
#[test]
fn macro_implicit_return() {
assert!(no_allocate_implicit_ret());
}
#[allocate_panic]
fn no_allocate_arg(b: bool) -> bool {
b
}
#[test]
fn macro_allocate_arg() {
no_allocate_arg(true);
no_allocate_arg(false);
}
#[allocate_panic]
fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 {
i
}
#[test]
fn macro_allocate_args() {
no_allocate_args(true, 0, -1);
no_allocate_args(false, 4, -90);
}
#[allocate_panic]
fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> {
Ok(r)
}
#[test]
fn macro_return_result() {
return_result(Ok(16)).unwrap().unwrap();
}

7
tests/usage.rs Normal file
View File

@ -0,0 +1,7 @@
extern crate qadapt;
#[test]
#[should_panic]
fn panic_not_using_qadapt() {
::qadapt::enter_protected();
}