bonsai-ninja-security 0.3.1

Security rulepack loader, matcher, and source/sink/sanitizer wrapper for bonsai-ninja.
use super::*;

#[test]
fn match_attribution_recovers_outer_java_method_after_nested_lambda() {
    let source = r#"
class Example {
    void method() {
        Runnable callback = () -> { consume("inside"); };
        consume("after");
    }
}
"#;
    let registry = std::sync::Arc::new(bonsai_lang_api::LanguageRegistry::new());
    registry.register(std::sync::Arc::new(bonsai_lang_java::JavaAdapter::new()));
    let ws = Workspace::new(registry);
    ws.vfs()
        .write("Example.java".to_string(), std::sync::Arc::<str>::from(source));
    let file = ws.vfs().all_files()[0];
    let _ = ws.db().decl_index(file);
    let start = source.find("consume(\"after\")").expect("outer call") as u64;
    let hit = RuleMatch {
        origin: MatchOrigin::Rulepack,
        rule_id: "java.test.sink".to_string(),
        language: "java".to_string(),
        file: "Example.java".to_string(),
        line: 5,
        column: 9,
        span: Span::new(file, start, start + "consume(\"after\")".len() as u64),
        match_text: "consume".to_string(),
        enclosing_fn: None,
    };

    let func = func_id_for_match(&ws, &hit).expect("outer method attribution");
    let global = ws.compiler_linkage_index();
    assert_eq!(
        global
            .decl_of(SymbolId::new(func.raw()))
            .expect("attributed declaration")
            .name,
        "method"
    );
}

#[test]
fn match_attribution_owns_calls_inside_recovered_c_preprocessor_predicate() {
    let source = r#"int process(int first, int second, int tail, int value) {
#ifdef FIRST_SHAPE
    if (first ||
#else
    if (second ||
#endif
        tail) {
        consume(value);
    }
    return value;
}
int unrelated(
"#;
    let registry = std::sync::Arc::new(bonsai_lang_api::LanguageRegistry::new());
    registry.register(std::sync::Arc::new(bonsai_lang_c::CAdapter::new()));
    let ws = Workspace::new(registry);
    ws.vfs()
        .write("unit.c".to_string(), std::sync::Arc::<str>::from(source));
    let file = ws.vfs().all_files()[0];
    let _ = ws.db().decl_index(file);
    let start = source.find("consume(value)").expect("call") as u64;
    let hit = RuleMatch {
        origin: MatchOrigin::Rulepack,
        rule_id: "c.test.sink".to_string(),
        language: "c".to_string(),
        file: "unit.c".to_string(),
        line: 7,
        column: 9,
        span: Span::new(file, start, start + "consume(value)".len() as u64),
        match_text: "consume".to_string(),
        enclosing_fn: None,
    };

    let attributed = [&hit]
        .into_iter()
        .filter_map(|matched| func_id_for_match(&ws, matched))
        .collect::<Vec<_>>();
    assert_eq!(
        attributed.len(),
        1,
        "a compiler-owned recovered call must not contribute an unattributed sink match"
    );
    let global = ws.compiler_linkage_index();
    assert_eq!(
        global
            .decl_of(SymbolId::new(attributed[0].raw()))
            .expect("attributed declaration")
            .name,
        "process"
    );
}