Require unsafe context to access unsafe statics

e45887341ab82d02db301dbcd593cf9a99bfb29480174367a77287f820698475
Alexis Sellier committed ago 1 parent 7674c012
lib/std/lang/parser.rad +2 -1
847 847
            }
848 848
            return try parseBlockBody(p, true);
849 849
        }
850 850
        if ast::attributesContains(&list, ast::Attribute::Unsafe)
851 851
            and p.current.kind <> scanner::TokenKind::Fn
852 +
            and p.current.kind <> scanner::TokenKind::Static
852 853
        {
853 -
            throw failParsing(p, "`unsafe` is only allowed on functions and blocks");
854 +
            throw failParsing(p, "`unsafe` is only allowed on functions, blocks, and statics");
854 855
        }
855 856
        let allowed: bool =
856 857
            p.current.kind == scanner::TokenKind::Fn or
857 858
            p.current.kind == scanner::TokenKind::Union or
858 859
            p.current.kind == scanner::TokenKind::Record or
lib/std/lang/parser/tests.rad +6 -0
2830 2830
@test unsafe fn testUnsafeBlockAttributesRejected() throws (testing::TestError) {
2831 2831
    let parsed = try? parseStmtsStr("fn run() { export unsafe {} }");
2832 2832
    try testing::expect(parsed == nil);
2833 2833
}
2834 2834
2835 +
/// Unsafe static declarations carry an explicit access requirement.
2836 +
@test unsafe fn testParseUnsafeStatic() throws (testing::TestError) {
2837 +
    let parsed = try? parseStmtsStr("export unsafe static DATA: [u8; 4] = undefined;");
2838 +
    try testing::expect(parsed <> nil);
2839 +
}
2840 +
2835 2841
@test unsafe fn testParseModule() throws (testing::TestError) {
2836 2842
    let r = try! parseStmtsStr("fn f() {} fn g() {}");
2837 2843
2838 2844
    let case ast::NodeValue::Block(module) = r.value
2839 2845
        else throw testing::TestError::Failed;
lib/std/lang/resolver.rad +22 -1
2718 2718
    if not self.inUnsafeContext {
2719 2719
        throw emitError(self, node, ErrorKind::UnsafeOperation);
2720 2720
    }
2721 2721
}
2722 2722
2723 +
/// Require an unsafe context for any access to an unsafe static.
2724 +
unsafe fn checkStaticAccess(self: &mut Resolver, node: *ast::Node, sym: &Symbol)
2725 +
    throws (ResolveError)
2726 +
{
2727 +
    if let case ast::NodeValue::StaticDecl(_) = sym.node.value {
2728 +
        if ast::hasAttribute(sym.attrs, ast::Attribute::Unsafe) {
2729 +
            try requireUnsafe(self, node);
2730 +
        }
2731 +
    }
2732 +
}
2733 +
2723 2734
/// Reject calls from safe code through unsafe function types.
2724 2735
unsafe fn checkUnsafeCall(self: &mut Resolver, node: *ast::Node, info: *FnType)
2725 2736
    throws (ResolveError)
2726 2737
{
2727 2738
    if info.isUnsafe and not self.inUnsafeContext {
2837 2848
    }
2838 2849
    match node.value {
2839 2850
        case ast::NodeValue::Ident(name) => {
2840 2851
            let sym = findAnySymbol(self.scope, name)
2841 2852
                else throw emitError(self, node, ErrorKind::UnresolvedSymbol(name));
2853 +
            try checkStaticAccess(self, node, sym);
2842 2854
            setNodeSymbol(self, node, sym);
2843 2855
            match sym.data {
2844 2856
                case SymbolData::Value { type, .. } =>
2845 2857
                    return setNodeType(self, node, type),
2846 2858
                case SymbolData::Constant { type, value } => {
3312 3324
    isConst: bool
3313 3325
) -> Type throws (ResolveError) {
3314 3326
    let attrs = resolveAttributes(self, attrList);
3315 3327
    let bindingTy = try infer(self, typeNode);
3316 3328
    try ensureStorableType(self, typeNode, bindingTy);
3317 -
    let valueTy = try checkAssignable(self, valueNode, bindingTy);
3329 +
    let wasUnsafe = self.inUnsafeContext;
3330 +
    set self.inUnsafeContext = wasUnsafe or (
3331 +
        not isConst and ast::hasAttribute(attrs, ast::Attribute::Unsafe)
3332 +
    );
3333 +
    let valueTy = try checkAssignable(self, valueNode, bindingTy) catch e {
3334 +
        set self.inUnsafeContext = wasUnsafe;
3335 +
        throw e;
3336 +
    };
3337 +
    set self.inUnsafeContext = wasUnsafe;
3318 3338
3319 3339
    if isConst {
3320 3340
        let mut constVal = constValueEntry(self, valueNode);
3321 3341
        if constVal == nil and not isConstExpr(self, valueNode) {
3322 3342
            throw emitError(self, valueNode, ErrorKind::ConstExprRequired);
6095 6115
unsafe fn resolveScopeAccess(self: &mut Resolver, node: *ast::Node, access: ast::Access) -> Type
6096 6116
    throws (ResolveError)
6097 6117
{
6098 6118
    let scope = self.scope;
6099 6119
    let sym = try resolveAccess(self, node, access, scope);
6120 +
    try checkStaticAccess(self, node, sym);
6100 6121
    let mut ty: Type = undefined;
6101 6122
6102 6123
    match sym.data {
6103 6124
        case SymbolData::Value { type, .. } => {
6104 6125
            setNodeSymbol(self, node, sym);
lib/std/lang/resolver/tests.rad +56 -0
5624 5624
    try expectAnalyzeOk("fn run(s: *[u8]) -> *u8 { return &s[0]; }");
5625 5625
    try expectAnalyzeOk("fn run(s: *[u8]) -> u32 { return s.len + s.cap; }");
5626 5626
    try expectAnalyzeOk("record R { ptr: u32 } fn run(r: R) -> u32 { return r.ptr; }");
5627 5627
}
5628 5628
5629 +
/// Unsafe statics require unsafe permission for reads, writes, and addresses.
5630 +
@test unsafe fn testUnsafeStaticAccessRejected() throws (testing::TestError) {
5631 +
    let programs = &[
5632 +
        "unsafe static VALUE: u32 = 7; fn run() -> u32 { return VALUE; }",
5633 +
        "unsafe static VALUE: u32 = 7; fn run() { set VALUE = 8; }",
5634 +
        "unsafe static VALUE: u32 = 7; fn run() -> *u32 { return &VALUE; }",
5635 +
        "unsafe static VALUE: u32 = 7; fn run() -> *mut u32 { return &mut VALUE; }",
5636 +
        "unsafe static DATA: [u8; 1] = [42]; fn run() -> u8 { return DATA[0]; }",
5637 +
        "unsafe static DATA: [u8; 1] = [42]; fn run() -> u32 { return DATA.len; }",
5638 +
        "record R { value: u32 } unsafe static DATA: R = R { value: 7 }; fn run() -> u32 { return DATA.value; }",
5639 +
    ];
5640 +
    for program in programs {
5641 +
        let mut a = testResolver();
5642 +
        let result = try resolveProgramStr(&mut a, program);
5643 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5644 +
    }
5645 +
}
5646 +
5647 +
/// Unsafe blocks and functions can access unsafe statics.
5648 +
@test unsafe fn testUnsafeStaticAccessAllowed() throws (testing::TestError) {
5649 +
    try expectAnalyzeOk("unsafe static VALUE: u32 = 7; unsafe fn run() -> u32 { set VALUE = 8; return VALUE; }");
5650 +
    try expectAnalyzeOk("unsafe static VALUE: u32 = 7; fn run() -> u32 { unsafe { return VALUE; } }");
5651 +
    try expectAnalyzeOk("unsafe static VALUE: u32 = 7; unsafe fn run() -> *mut u32 { return &mut VALUE; }");
5652 +
    try expectAnalyzeOk("static VALUE: u32 = 7; fn run() -> u32 { return VALUE; }");
5653 +
    try expectAnalyzeOk("constant BYTES: *[u8] = \"x\"; unsafe static DATA: *[u64] = BYTES as *[u64];");
5654 +
}
5655 +
5656 +
/// Imports preserve the unsafe access requirement of a static.
5657 +
@test unsafe fn testUnsafeStaticImportsRejected() throws (testing::TestError) {
5658 +
    let programs = &[
5659 +
        "use root::storage; fn run() -> u32 { return storage::VALUE; }",
5660 +
        "use root::storage::*; fn run() -> u32 { return VALUE; }",
5661 +
    ];
5662 +
    for program in programs {
5663 +
        let mut a = testResolver();
5664 +
        let mut arena = ast::nodeArena(&mut AST_ARENA[..]);
5665 +
        let rootId = try registerModule(&mut MODULE_GRAPH, nil, "root", "export mod storage; mod app;", &mut arena);
5666 +
        let _ = try registerModule(&mut MODULE_GRAPH, rootId, "storage", "export unsafe static VALUE: u32 = 7;", &mut arena);
5667 +
        let _ = try registerModule(&mut MODULE_GRAPH, rootId, "app", program, &mut arena);
5668 +
        let result = try resolveModuleTree(&mut a, rootId);
5669 +
        try expectErrorKind(&result, super::ErrorKind::UnsafeOperation);
5670 +
    }
5671 +
}
5672 +
5673 +
/// An invalid unsafe static initializer does not grant permission to other initializers.
5674 +
@test unsafe fn testUnsafeStaticInitializerRestoresContext() throws (testing::TestError) {
5675 +
    let mut a = testResolver();
5676 +
    let result = try resolveProgramStr(&mut a, "unsafe static BAD: u32 = true; static DATA: *[u64] = &[1 as u8] as *[u64];");
5677 +
    let _ = try expectError(&result);
5678 +
    try testing::expect(not a.inUnsafeContext);
5679 +
5680 +
    let mut b = testResolver();
5681 +
    let next = try resolveProgramStr(&mut b, "constant BYTES: *[u8] = \"x\"; unsafe static VALUE: u32 = 7; static DATA: *[u64] = BYTES as *[u64];");
5682 +
    try expectErrorKind(&next, super::ErrorKind::UnsafeOperation);
5683 +
}
5684 +
5629 5685
/// Unsafe declarations may compose unsafe operations and calls.
5630 5686
@test unsafe fn testUnsafePointerOperationsAllowed() throws (testing::TestError) {
5631 5687
    let program = "record Marker: Once {} unsafe fn load(pointer: *unsafe u32) -> u32 { return *pointer; } unsafe fn run(pointer: *unsafe u32) -> u32 { let next = pointer + 1; let same = pointer == next; return load(pointer); }";
5632 5688
    try expectAnalyzeOk(program);
5633 5689
}
test/tests/unsafe.static.rad added +22 -0
1 +
//! returns: 0
2 +
3 +
/// Storage whose accesses require an unsafe context.
4 +
unsafe static VALUE: u8 = 7;
5 +
6 +
/// Pointer storage initialized before its first read.
7 +
unsafe static POINTER: *u8 = undefined;
8 +
9 +
/// Set the pointer to initialized permanent storage and read its value.
10 +
fn read() -> u8 {
11 +
    unsafe {
12 +
        set VALUE = 8;
13 +
        set POINTER = &VALUE;
14 +
        return *POINTER;
15 +
    }
16 +
}
17 +
18 +
/// Exercise unsafe static access through a safe function.
19 +
@default fn main() -> i32 {
20 +
    if read() <> 8 { return 1; }
21 +
    return 0;
22 +
}