Require unsafe context to access unsafe statics
e45887341ab82d02db301dbcd593cf9a99bfb29480174367a77287f820698475
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 | + | } |