pub(crate) mod analyzer;
pub(crate) mod check;
pub(crate) mod collect;
pub(crate) mod csharp;
pub mod diag;
pub(crate) mod errors;
pub(crate) mod modules;
pub mod program;
pub(crate) mod scope;
pub mod types;
pub(crate) mod validate;
pub use diag::{Diagnostic, Diagnostics};
pub use program::*;
pub use types::{PType, PermissionOrigin, TypeKind};
use crate::syntax::ast;
use crate::syntax::{FileId, SourceMap};
use analyzer::Analyzer;
#[derive(Debug, Clone)]
pub struct Options {
pub continue_on_error: bool,
pub verifier: bool,
}
impl Default for Options {
fn default() -> Options {
Options { continue_on_error: true, verifier: false }
}
}
#[derive(Debug, Clone, Default)]
pub struct AnalysisError {
pub thrown: Option<Diagnostic>,
pub collected: Vec<Diagnostic>,
pub warnings: Vec<String>,
}
impl AnalysisError {
pub fn messages(&self, sm: &SourceMap) -> Vec<String> {
let mut out = Vec::new();
if let Some(t) = &self.thrown {
out.push(t.render(sm));
}
let d = Diagnostics { errors: self.collected.clone(), warnings: Vec::new(), continue_on_error: true };
out.extend(d.sorted_errors(sm).iter().map(|e| e.render(sm)));
out
}
}
#[derive(Debug, Clone)]
pub struct Analysis {
pub program: Program,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum CompileError {
Parse {
file: FileId,
message: String,
lexer: bool,
},
NotSupported {
file: FileId,
line: usize,
col: usize,
keyword: String,
},
Analysis(AnalysisError),
}
pub fn compile(sm: &SourceMap, opts: &Options) -> Result<Analysis, CompileError> {
let mut programs = Vec::new();
for (i, f) in sm.files().iter().enumerate() {
let id = FileId(i as u32);
let tokens = crate::syntax::lexer::lex(id, &f.text).map_err(|e| {
let lc = f.line_col(e.span.start);
CompileError::Parse {
file: id,
message: format!("line {}:{} {}", lc.line, lc.col - 1, e.message),
lexer: true,
}
})?;
if !opts.verifier {
if let Some(t) = tokens.iter().find(|t| t.kind.is_verifier_only()) {
let lc = f.line_col(t.span.start);
return Err(CompileError::NotSupported {
file: id,
line: lc.line as usize,
col: (lc.col - 1) as usize,
keyword: sm.text(t.span).to_string(),
});
}
}
let p = crate::syntax::parse(id, &f.text).map_err(|e| {
let lc = f.line_col(e.span.start);
CompileError::Parse {
file: id,
message: format!("line {}:{} {}", lc.line, lc.col - 1, e.message),
lexer: false,
}
})?;
programs.push(p);
}
analyze(sm, &programs, opts).map_err(CompileError::Analysis)
}
pub fn analyze(sm: &SourceMap, programs: &[ast::Program], opts: &Options) -> Result<Analysis, AnalysisError> {
let mut a = Analyzer::new(sm, opts.continue_on_error);
let r = run(&mut a, programs);
let thrown = r.err();
if thrown.is_some() || a.diags.has_errors() {
return Err(AnalysisError { thrown, collected: a.diags.errors, warnings: a.diags.warnings });
}
for (i, n) in a.fun_next_slot.iter().enumerate() {
a.prog.funs[i].frame_size = *n;
}
let names = csharp::names(&a.prog);
for (m, name) in names.machines.into_iter().enumerate() {
a.prog.machines[m].cs_name = name;
}
for (f, name) in names.funs.into_iter().enumerate() {
a.prog.funs[f].cs_name = name;
}
use sha2::{Digest, Sha256};
let mut sources: Vec<_> = sm.files().iter().map(|f| f.text.as_bytes()).collect();
sources.sort();
let mut digest = Sha256::new();
digest.update((sources.len() as u64).to_le_bytes());
for source in sources {
digest.update((source.len() as u64).to_le_bytes());
digest.update(source);
}
a.prog.source_fingerprint = Some(format!("sha256:{:x}", digest.finalize()));
Ok(Analysis { program: a.prog, warnings: a.diags.warnings })
}
fn run<'a>(a: &mut Analyzer<'a>, programs: &'a [ast::Program]) -> Result<(), Diagnostic> {
for p in programs {
a.collect_stubs(p)?;
}
a.prog.sort_universal_events();
let mut machine_iter = 0usize;
let mut cursor = collect::FunCursor::default();
for p in programs {
a.collect_declarations(p, &mut machine_iter, &mut cursor)?;
}
for m in 0..a.prog.machines.len() {
let r = a.validate_machine(MachineId(m as u32));
a.diags.tolerant(r)?;
}
let declared = a.prog.funs.len();
let mut order: Vec<FunId> =
(0..declared as u32).map(FunId).filter(|f| a.prog.funs[f.idx()].owner.is_none()).collect();
for m in 0..a.prog.machines.len() {
let mut methods: Vec<FunId> = (0..declared as u32)
.map(FunId)
.filter(|f| a.prog.funs[f.idx()].owner == Some(MachineId(m as u32)))
.collect();
methods.sort_by_key(|f| a.prog.funs[f.idx()].span.start);
order.extend(methods);
}
for id in order {
let r = a.populate_fun(id).and_then(|_| a.check_all_paths_return(id));
a.diags.tolerant(r)?;
}
let mut f = declared;
while f < a.prog.funs.len() {
let id = FunId(f as u32);
let r = a.populate_fun(id).and_then(|_| a.check_all_paths_return(id));
a.diags.tolerant(r)?;
f += 1;
}
for m in 0..a.prog.machines.len() {
let r = a.validate_no_static_handlers(MachineId(m as u32));
a.diags.tolerant(r)?;
}
if a.diags.has_errors() {
return Ok(());
}
a.propagate_flags();
a.check_capabilities()?;
for f in 0..a.prog.funs.len() {
a.check_control_flow(FunId(f as u32))?;
}
for m in 0..a.prog.machines.len() {
a.infer_creates(MachineId(m as u32));
}
if a.diags.has_errors() {
return Ok(());
}
a.populate_module_system()?;
Ok(())
}
#[cfg(test)]
mod fingerprint_tests {
use super::*;
fn fingerprint(files: &[(&str, &str)]) -> String {
let mut sm = SourceMap::new();
for (name, contents) in files {
sm.add(*name, *contents);
}
compile(&sm, &Options::default()).unwrap().program.source_fingerprint.unwrap()
}
#[test]
fn source_identity_ignores_paths_and_load_order_but_detects_changed_guards() {
let first = "event E: int; spec S observes E { start state A { on E do (n: int) { assert n > 0; } } }";
let second = first.replace("n > 0", "n > 1");
let helper = "event Other;";
assert_eq!(
fingerprint(&[("/a/spec.p", first), ("/a/helper.p", helper)]),
fingerprint(&[("/b/helper.p", helper), ("/b/spec.p", first)])
);
assert_ne!(fingerprint(&[("spec.p", first)]), fingerprint(&[("spec.p", &second)]));
}
}