Assert no memory leaks by stubbing malloc/free in the test suite

This commit is contained in:
Max Brunsfeld 2019-01-21 14:22:35 -08:00
parent 6105bf9909
commit 196339aaa9
7 changed files with 146 additions and 18 deletions

7
Cargo.lock generated
View file

@ -502,6 +502,11 @@ name = "smallbitvec"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "spin"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.7.0"
@ -585,6 +590,7 @@ dependencies = [
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
"smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tree-sitter 0.3.5",
]
@ -702,6 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "225de307c6302bec3898c51ca302fc94a7a1697ef0845fcee6448f33c032249c"
"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811"
"checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"

View file

@ -34,3 +34,6 @@ features = ["preserve_order"]
[dependencies.log]
version = "0.4.6"
features = ["std"]
[dev-dependencies]
spin = "0.5"

View file

@ -0,0 +1,104 @@
#![cfg(test)]
#![allow(dead_code)]
use spin::Mutex;
use std::collections::HashMap;
use std::os::raw::{c_ulong, c_void};
#[derive(Debug, PartialEq, Eq, Hash)]
struct Allocation(*const c_void);
unsafe impl Send for Allocation {}
unsafe impl Sync for Allocation {}
#[derive(Default)]
struct AllocationRecorder {
enabled: bool,
allocation_count: u64,
outstanding_allocations: HashMap<Allocation, u64>,
}
lazy_static! {
static ref RECORDER: Mutex<AllocationRecorder> = Mutex::new(AllocationRecorder::default());
}
extern "C" {
fn malloc(size: c_ulong) -> *mut c_void;
fn calloc(count: c_ulong, size: c_ulong) -> *mut c_void;
fn realloc(ptr: *mut c_void, size: c_ulong) -> *mut c_void;
fn free(ptr: *mut c_void);
}
pub fn start_recording() {
let mut recorder = RECORDER.lock();
recorder.enabled = true;
recorder.allocation_count = 0;
recorder.outstanding_allocations.clear();
}
pub fn stop_recording() {
let mut recorder = RECORDER.lock();
recorder.enabled = false;
if !recorder.outstanding_allocations.is_empty() {
panic!(
"Leaked allocation indices: {:?}",
recorder
.outstanding_allocations
.iter()
.map(|e| e.1)
.collect::<Vec<_>>()
);
}
}
fn record_alloc(ptr: *mut c_void) {
let mut recorder = RECORDER.lock();
if recorder.enabled {
let count = recorder.allocation_count;
recorder.allocation_count += 1;
recorder
.outstanding_allocations
.insert(Allocation(ptr), count);
}
}
fn record_dealloc(ptr: *mut c_void) {
let mut recorder = RECORDER.lock();
if recorder.enabled {
recorder.outstanding_allocations.remove(&Allocation(ptr));
}
}
#[no_mangle]
extern "C" fn ts_record_malloc(size: c_ulong) -> *const c_void {
let result = unsafe { malloc(size) };
record_alloc(result);
result
}
#[no_mangle]
extern "C" fn ts_record_calloc(count: c_ulong, size: c_ulong) -> *const c_void {
let result = unsafe { calloc(count, size) };
record_alloc(result);
result
}
#[no_mangle]
extern "C" fn ts_record_realloc(ptr: *mut c_void, size: c_ulong) -> *const c_void {
record_dealloc(ptr);
let result = unsafe { realloc(ptr, size) };
record_alloc(result);
result
}
#[no_mangle]
extern "C" fn ts_record_free(ptr: *mut c_void) {
record_dealloc(ptr);
unsafe { free(ptr) };
}
#[no_mangle]
extern "C" fn ts_record_allocations_toggle() {
let mut recorder = RECORDER.lock();
recorder.enabled = !recorder.enabled;
}

View file

@ -1,9 +1,10 @@
use super::allocations;
use super::fixtures::{fixtures_dir, get_language, get_test_language};
use crate::generate;
use crate::test::{parse_tests, print_diff, print_diff_key, TestEntry};
use crate::util;
use std::fs;
use tree_sitter::{LogType, Parser};
use tree_sitter::{Language, LogType, Parser};
const LANGUAGES: &'static [&'static str] = &[
"bash",
@ -27,8 +28,6 @@ lazy_static! {
#[test]
fn test_real_language_corpus_files() {
let mut log_session = None;
let mut parser = get_parser(&mut log_session, "log1.html");
let grammars_dir = fixtures_dir().join("grammars");
let mut did_fail = false;
@ -44,8 +43,7 @@ fn test_real_language_corpus_files() {
let language = get_language(language_name);
let corpus_dir = grammars_dir.join(language_name).join("corpus");
let test = parse_tests(&corpus_dir).unwrap();
parser.set_language(language).unwrap();
did_fail |= run_mutation_tests(&mut parser, test);
did_fail |= run_mutation_tests(language, test);
}
if did_fail {
@ -55,8 +53,6 @@ fn test_real_language_corpus_files() {
#[test]
fn test_error_corpus_files() {
let mut log_session = None;
let mut parser = get_parser(&mut log_session, "log2.html");
let corpus_dir = fixtures_dir().join("error_corpus");
let mut did_fail = false;
@ -74,8 +70,7 @@ fn test_error_corpus_files() {
let test = parse_tests(&entry.path()).unwrap();
let language = get_language(&language_name);
parser.set_language(language).unwrap();
did_fail |= run_mutation_tests(&mut parser, test);
did_fail |= run_mutation_tests(language, test);
}
if did_fail {
@ -85,8 +80,6 @@ fn test_error_corpus_files() {
#[test]
fn test_feature_corpus_files() {
let mut log_session = None;
let mut parser = get_parser(&mut log_session, "log3.html");
let test_grammars_dir = fixtures_dir().join("test_grammars");
let mut did_fail = false;
@ -132,8 +125,7 @@ fn test_feature_corpus_files() {
let c_code = generate_result.unwrap().1;
let language = get_test_language(language_name, c_code, &test_path);
let test = parse_tests(&corpus_path).unwrap();
parser.set_language(language).unwrap();
did_fail |= run_mutation_tests(&mut parser, test);
did_fail |= run_mutation_tests(language, test);
}
}
@ -142,7 +134,7 @@ fn test_feature_corpus_files() {
}
}
fn run_mutation_tests(parser: &mut Parser, test: TestEntry) -> bool {
fn run_mutation_tests(language: Language, test: TestEntry) -> bool {
match test {
TestEntry::Example {
name,
@ -157,23 +149,30 @@ fn run_mutation_tests(parser: &mut Parser, test: TestEntry) -> bool {
eprintln!(" example: {:?}", name);
allocations::start_recording();
let mut log_session = None;
let mut parser = get_parser(&mut log_session, "log.html");
parser.set_language(language).unwrap();
let tree = parser
.parse_utf8(&mut |byte_offset, _| &input[byte_offset..], None)
.unwrap();
let actual = tree.root_node().to_sexp();
drop(tree);
drop(parser);
if actual != output {
print_diff_key();
print_diff(&actual, &output);
println!("");
true
} else {
allocations::stop_recording();
false
}
}
TestEntry::Group { children, .. } => {
let mut result = false;
for child in children {
result |= run_mutation_tests(parser, child);
result |= run_mutation_tests(language, child);
}
result
}

View file

@ -1,3 +1,4 @@
mod allocations;
mod corpuses;
mod fixtures;
mod parser_api;

View file

@ -1,6 +1,6 @@
extern crate cc;
use std::env;
use std::{env, fs};
use std::path::{Path, PathBuf};
fn main() {
@ -20,13 +20,27 @@ fn main() {
}
let mut config = cc::Build::new();
println!("cargo:rerun-if-env-changed=TREE_SITTER_TEST");
if env::var("TREE_SITTER_TEST").is_ok() {
config.define("TREE_SITTER_TEST", "");
}
let src_path = Path::new("src");
for entry in fs::read_dir(&src_path).unwrap() {
let entry = entry.unwrap();
let path = src_path.join(entry.file_name());
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
}
config
.define("UTF8PROC_STATIC", "")
.flag_if_supported("-std=c99")
.flag_if_supported("-Wno-unused-parameter")
.include("include")
.include("utf8proc")
.file(Path::new("src").join("lib.c"))
.file(src_path.join("lib.c"))
.compile("tree-sitter");
}

View file

@ -855,7 +855,7 @@ char *ts_subtree_string(Subtree self, const TSLanguage *language, bool include_a
language, true,
include_all, 0, false
) + 1;
char *result = ts_malloc(size * sizeof(char));
char *result = malloc(size * sizeof(char));
ts_subtree__write_to_string(self, result, size, language, true, include_all, 0, false);
return result;
}