Support scoped unsafe statement blocks
e5805fe572a889649b9491e9f46707ef4e85489fadcbbaf14c8e189b5786fd03
1 parent
a1fd8da6
compiler/radiance.rad
+2 -2
| 736 | 736 | })); |
|
| 737 | 737 | ||
| 738 | 738 | // Build: `return testing::runAllTests(&[...]);` |
|
| 739 | 739 | let retStmt = ast::synthNode(arena, ast::NodeValue::Return { value: callExpr }); |
|
| 740 | 740 | let bodyStmts = ast::nodeSlice(arena, 1).append(retStmt, a); |
|
| 741 | - | let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts })); |
|
| 741 | + | let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts, isUnsafe: false })); |
|
| 742 | 742 | ||
| 743 | 743 | // Build: `unsafe fn #testMain() -> i32` |
|
| 744 | 744 | let fnName = ast::synthNode(arena, ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, "#testMain"))); |
|
| 745 | 745 | let returnType = ast::synthNode(arena, ast::NodeValue::TypeSig(ast::TypeSig::Integer { |
|
| 746 | 746 | width: 4, sign: ast::Signedness::Signed, |
| 770 | 770 | ) { |
|
| 771 | 771 | let case ast::NodeValue::Block(block) = blockNode.value else { |
|
| 772 | 772 | panic "injectIntoBlock: expected Block node"; |
|
| 773 | 773 | }; |
|
| 774 | 774 | let stmts = block.statements.append(decl, alloc::arenaAllocator(&mut arena.arena)); |
|
| 775 | - | set blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts }); |
|
| 775 | + | set blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts, isUnsafe: block.isUnsafe }); |
|
| 776 | 776 | } |
|
| 777 | 777 | ||
| 778 | 778 | /// Write a self-contained RV64 image containing text and data sections. |
|
| 779 | 779 | unsafe fn writeImage( |
|
| 780 | 780 | code: *[u32], |
lib/std/lang/ast.rad
+4 -2
| 246 | 246 | ||
| 247 | 247 | /// Compound statement block with optional dedicated scope. |
|
| 248 | 248 | export record Block: Copy { |
|
| 249 | 249 | /// Statements that belong to this block. |
|
| 250 | 250 | statements: *mut [*Node], |
|
| 251 | + | /// Whether this block permits unsafe operations. |
|
| 252 | + | isUnsafe: bool, |
|
| 251 | 253 | } |
|
| 252 | 254 | ||
| 253 | 255 | /// Function call expression. |
|
| 254 | 256 | export record Call: Copy { |
|
| 255 | 257 | /// Callee expression. |
| 856 | 858 | let a = alloc::arenaAllocator(&mut arena.arena); |
|
| 857 | 859 | let fnName = synthNode(arena, NodeValue::Ident(name)); |
|
| 858 | 860 | let params: *mut [*Node] = &mut []; |
|
| 859 | 861 | let throwList: *mut [*Node] = &mut []; |
|
| 860 | 862 | let fnSig = FnSig { params, returnType: nil, throwList }; |
|
| 861 | - | let fnBody = synthNode(arena, NodeValue::Block(Block { statements: bodyStmts })); |
|
| 863 | + | let fnBody = synthNode(arena, NodeValue::Block(Block { statements: bodyStmts, isUnsafe: false })); |
|
| 862 | 864 | let fnDecl = synthNode(arena, NodeValue::FnDecl(FnDecl { |
|
| 863 | 865 | name: fnName, sig: fnSig, body: fnBody, attrs: nil, |
|
| 864 | 866 | })); |
|
| 865 | 867 | let mut rootStmts: *mut [*Node] = &mut []; |
|
| 866 | 868 | rootStmts.append(fnDecl, a); |
|
| 867 | - | let modBody = synthNode(arena, NodeValue::Block(Block { statements: rootStmts })); |
|
| 869 | + | let modBody = synthNode(arena, NodeValue::Block(Block { statements: rootStmts, isUnsafe: false })); |
|
| 868 | 870 | ||
| 869 | 871 | return SynthFnMod { modBody, fnBody }; |
|
| 870 | 872 | } |
lib/std/lang/ast/printer.rad
+2 -1
| 366 | 366 | set len += 1; |
|
| 367 | 367 | return sexpr::list(a, head, &children[..len]); |
|
| 368 | 368 | } |
|
| 369 | 369 | case super::NodeValue::Block(blk) => { |
|
| 370 | 370 | let children = nodeListToExprs(a, &blk.statements[..]); |
|
| 371 | - | return sexpr::block(a, "block", &[], children); |
|
| 371 | + | let name = "unsafe" if blk.isUnsafe else "block"; |
|
| 372 | + | return sexpr::block(a, name, &[], children); |
|
| 372 | 373 | } |
|
| 373 | 374 | case super::NodeValue::Let(decl) => { |
|
| 374 | 375 | let mut head = "let"; |
|
| 375 | 376 | if decl.mutable { set head = "let-mut"; } |
|
| 376 | 377 | return sexpr::list(a, head, &[ |
lib/std/lang/parser.rad
+19 -3
| 837 | 837 | // TODO: Why is `parseStmt` checking for attributes? |
|
| 838 | 838 | // We should have a `parseDecl` which is top-level, and `parseStmt` which |
|
| 839 | 839 | // is inside functions. |
|
| 840 | 840 | let attrs = parseAttributes(p); |
|
| 841 | 841 | if let list = attrs { |
|
| 842 | + | if ast::attributesContains(&list, ast::Attribute::Unsafe) |
|
| 843 | + | and p.current.kind == scanner::TokenKind::LBrace |
|
| 844 | + | { |
|
| 845 | + | if list.list.len <> 1 { |
|
| 846 | + | throw failParsing(p, "unsafe blocks cannot have declaration attributes"); |
|
| 847 | + | } |
|
| 848 | + | return try parseBlockBody(p, true); |
|
| 849 | + | } |
|
| 842 | 850 | if ast::attributesContains(&list, ast::Attribute::Unsafe) |
|
| 843 | 851 | and p.current.kind <> scanner::TokenKind::Fn |
|
| 844 | 852 | { |
|
| 845 | - | throw failParsing(p, "`unsafe` is only allowed on functions"); |
|
| 853 | + | throw failParsing(p, "`unsafe` is only allowed on functions and blocks"); |
|
| 846 | 854 | } |
|
| 847 | 855 | let allowed: bool = |
|
| 848 | 856 | p.current.kind == scanner::TokenKind::Fn or |
|
| 849 | 857 | p.current.kind == scanner::TokenKind::Union or |
|
| 850 | 858 | p.current.kind == scanner::TokenKind::Record or |
| 968 | 976 | } |
|
| 969 | 977 | ||
| 970 | 978 | /// Parse a block of statements enclosed in curly braces. |
|
| 971 | 979 | export unsafe fn parseBlock(p: &mut Parser) -> *ast::Node |
|
| 972 | 980 | throws (ParseError) |
|
| 981 | + | { |
|
| 982 | + | return try parseBlockBody(p, false); |
|
| 983 | + | } |
|
| 984 | + | ||
| 985 | + | /// Parse a statement block with the specified unsafe permission. |
|
| 986 | + | unsafe fn parseBlockBody(p: &mut Parser, isUnsafe: bool) -> *ast::Node |
|
| 987 | + | throws (ParseError) |
|
| 973 | 988 | { |
|
| 974 | 989 | let start = p.current; |
|
| 975 | 990 | let mut blk = mkBlock(p, 64); |
|
| 991 | + | set blk.isUnsafe = isUnsafe; |
|
| 976 | 992 | ||
| 977 | 993 | if not consume(p, scanner::TokenKind::LBrace) { |
|
| 978 | 994 | throw failParsing(p, "expected `{`"); |
|
| 979 | 995 | } |
|
| 980 | 996 | try parseStmtsUntil(p, scanner::TokenKind::RBrace, &mut blk); |
| 983 | 999 | return node(p, ast::NodeValue::Block(blk)); |
|
| 984 | 1000 | } |
|
| 985 | 1001 | ||
| 986 | 1002 | /// Create an empty block with no statements. |
|
| 987 | 1003 | unsafe fn mkBlock(p: &mut Parser, cap: u32) -> ast::Block { |
|
| 988 | - | return ast::Block { statements: ast::nodeSlice(&mut *p.arena, cap) }; |
|
| 1004 | + | return ast::Block { statements: ast::nodeSlice(&mut *p.arena, cap), isUnsafe: false }; |
|
| 989 | 1005 | } |
|
| 990 | 1006 | ||
| 991 | 1007 | /// Create a block containing a single statement node. |
|
| 992 | 1008 | unsafe fn mkBlockWith(p: &mut Parser, node: *ast::Node) -> ast::Block { |
|
| 993 | 1009 | let stmts = ast::nodeSlice(&mut *p.arena, 1).append(node, p.allocator); |
|
| 994 | - | return ast::Block { statements: stmts }; |
|
| 1010 | + | return ast::Block { statements: stmts, isUnsafe: false }; |
|
| 995 | 1011 | } |
|
| 996 | 1012 | ||
| 997 | 1013 | /// Parse the branch that follows `else` in let-else style constructs. |
|
| 998 | 1014 | /// |
|
| 999 | 1015 | /// Allows either a block, a single statement like `return`, |
lib/std/lang/parser/tests.rad
+23 -0
| 2807 | 2807 | @test unsafe fn testParseMixedBraceInitializer() throws (testing::TestError) { |
|
| 2808 | 2808 | let parsed: ?*ast::Node = try? parseExprStr("Pt { x: 1, 2 }"); |
|
| 2809 | 2809 | try testing::expect(parsed == nil); |
|
| 2810 | 2810 | } |
|
| 2811 | 2811 | ||
| 2812 | + | /// Unsafe statements have a block body. |
|
| 2813 | + | @test unsafe fn testParseUnsafeBlock() throws (testing::TestError) { |
|
| 2814 | + | let parsed = try? parseStmtsStr("fn run() { unsafe { return; } }"); |
|
| 2815 | + | let root = parsed else throw testing::TestError::Failed; |
|
| 2816 | + | let case ast::NodeValue::Block(module) = root.value |
|
| 2817 | + | else throw testing::TestError::Failed; |
|
| 2818 | + | let case ast::NodeValue::FnDecl(decl) = module.statements[0].value |
|
| 2819 | + | else throw testing::TestError::Failed; |
|
| 2820 | + | let fnBody = decl.body else throw testing::TestError::Failed; |
|
| 2821 | + | let case ast::NodeValue::Block(body) = fnBody.value |
|
| 2822 | + | else throw testing::TestError::Failed; |
|
| 2823 | + | let case ast::NodeValue::Block(inner) = body.statements[0].value |
|
| 2824 | + | else throw testing::TestError::Failed; |
|
| 2825 | + | try testing::expect(not body.isUnsafe); |
|
| 2826 | + | try testing::expect(inner.isUnsafe); |
|
| 2827 | + | } |
|
| 2828 | + | ||
| 2829 | + | /// Unsafe blocks do not accept declaration attributes. |
|
| 2830 | + | @test unsafe fn testUnsafeBlockAttributesRejected() throws (testing::TestError) { |
|
| 2831 | + | let parsed = try? parseStmtsStr("fn run() { export unsafe {} }"); |
|
| 2832 | + | try testing::expect(parsed == nil); |
|
| 2833 | + | } |
|
| 2834 | + | ||
| 2812 | 2835 | @test unsafe fn testParseModule() throws (testing::TestError) { |
|
| 2813 | 2836 | let r = try! parseStmtsStr("fn f() {} fn g() {}"); |
|
| 2814 | 2837 | ||
| 2815 | 2838 | let case ast::NodeValue::Block(module) = r.value |
|
| 2816 | 2839 | else throw testing::TestError::Failed; |
lib/std/lang/resolver.rad
+16 -12
| 625 | 625 | InvalidRefPosition, |
|
| 626 | 626 | /// A reference cannot be bound to a local. |
|
| 627 | 627 | RefBinding, |
|
| 628 | 628 | /// Call arguments contain overlapping incompatible loans. |
|
| 629 | 629 | BorrowConflict(*[u8]), |
|
| 630 | - | /// Unsafe pointer operation outside an `unsafe` function. |
|
| 630 | + | /// Unsafe operation outside an unsafe context. |
|
| 631 | 631 | UnsafeOperation, |
|
| 632 | - | /// Safe code cannot call an `unsafe` function. |
|
| 632 | + | /// An unsafe call requires an unsafe context. |
|
| 633 | 633 | UnsafeCall, |
|
| 634 | 634 | /// Internal error. |
|
| 635 | 635 | Internal, |
|
| 636 | 636 | } |
|
| 637 | 637 |
| 850 | 850 | loopDepth: u32, |
|
| 851 | 851 | /// Signature of the function currently being analyzed. |
|
| 852 | 852 | currentFn: ?*unsafe FnType, |
|
| 853 | 853 | /// Current module being analyzed. |
|
| 854 | 854 | currentMod: u16, |
|
| 855 | - | /// Whether the current function permits unsafe operations. |
|
| 856 | - | inUnsafeFn: bool, |
|
| 855 | + | /// Whether the current lexical context permits unsafe operations. |
|
| 856 | + | inUnsafeContext: bool, |
|
| 857 | 857 | /// Configuration for semantic analysis. |
|
| 858 | 858 | config: Config, |
|
| 859 | 859 | /// Unified arena for symbols, scopes, and nominal type. |
|
| 860 | 860 | arena: alloc::Arena, |
|
| 861 | 861 | /// Combined semantic metadata table indexed by node ID. |
| 1009 | 1009 | pkgScope: storage.pkgScope, |
|
| 1010 | 1010 | loopStack: undefined, |
|
| 1011 | 1011 | loopDepth: 0, |
|
| 1012 | 1012 | currentFn: nil, |
|
| 1013 | 1013 | currentMod: 0, |
|
| 1014 | - | inUnsafeFn: false, |
|
| 1014 | + | inUnsafeContext: false, |
|
| 1015 | 1015 | config, |
|
| 1016 | 1016 | arena, |
|
| 1017 | 1017 | nodeData: NodeDataTable { entries: storage.nodeData }, |
|
| 1018 | 1018 | types: nil, |
|
| 1019 | 1019 | errors: @sliceOf(storage.errors.ptr, 0, storage.errors.len), |
| 2711 | 2711 | // Ignore non-declaration nodes. |
|
| 2712 | 2712 | } |
|
| 2713 | 2713 | } |
|
| 2714 | 2714 | } |
|
| 2715 | 2715 | ||
| 2716 | - | /// Require the current function to be unsafe. |
|
| 2716 | + | /// Require an unsafe function or block. |
|
| 2717 | 2717 | unsafe fn requireUnsafe(self: &mut Resolver, node: *ast::Node) throws (ResolveError) { |
|
| 2718 | - | if not self.inUnsafeFn { |
|
| 2718 | + | if not self.inUnsafeContext { |
|
| 2719 | 2719 | throw emitError(self, node, ErrorKind::UnsafeOperation); |
|
| 2720 | 2720 | } |
|
| 2721 | 2721 | } |
|
| 2722 | 2722 | ||
| 2723 | 2723 | /// Reject calls from safe code through unsafe function types. |
|
| 2724 | 2724 | unsafe fn checkUnsafeCall(self: &mut Resolver, node: *ast::Node, info: *FnType) |
|
| 2725 | 2725 | throws (ResolveError) |
|
| 2726 | 2726 | { |
|
| 2727 | - | if info.isUnsafe and not self.inUnsafeFn { |
|
| 2727 | + | if info.isUnsafe and not self.inUnsafeContext { |
|
| 2728 | 2728 | throw emitError(self, node, ErrorKind::UnsafeCall); |
|
| 2729 | 2729 | } |
|
| 2730 | 2730 | } |
|
| 2731 | 2731 | ||
| 2732 | 2732 | /// Visit a top-level definition, recursing into sub-modules. |
| 3036 | 3036 | /// Analyze a block node, allocating a nested lexical scope. |
|
| 3037 | 3037 | unsafe fn resolveBlock(self: &mut Resolver, node: *ast::Node, block: ast::Block) -> Type |
|
| 3038 | 3038 | throws (ResolveError) |
|
| 3039 | 3039 | { |
|
| 3040 | 3040 | enterScope(self, node); |
|
| 3041 | + | let wasUnsafe = self.inUnsafeContext; |
|
| 3042 | + | set self.inUnsafeContext = wasUnsafe or block.isUnsafe; |
|
| 3041 | 3043 | let blockTy = try visitList(self, block.statements) catch { |
|
| 3042 | 3044 | // One of the statements in the block failed analysis. We simply proceed |
|
| 3043 | 3045 | // without checking the rest of the block statements. Return `Never` to |
|
| 3044 | 3046 | // avoid spurious `FnMissingReturn` errors. |
|
| 3045 | 3047 | exitScope(self); |
|
| 3048 | + | set self.inUnsafeContext = wasUnsafe; |
|
| 3046 | 3049 | return setNodeType(self, node, Type::Never); |
|
| 3047 | 3050 | }; |
|
| 3048 | 3051 | exitScope(self); |
|
| 3052 | + | set self.inUnsafeContext = wasUnsafe; |
|
| 3049 | 3053 | ||
| 3050 | 3054 | return setNodeType(self, node, blockTy); |
|
| 3051 | 3055 | } |
|
| 3052 | 3056 | ||
| 3053 | 3057 | /// Analyze a `let` declaration and bind its identifier. |
| 3432 | 3436 | fnType: *FnType, |
|
| 3433 | 3437 | receiverName: ?*ast::Node, |
|
| 3434 | 3438 | params: *mut [*ast::Node], |
|
| 3435 | 3439 | body: *ast::Node, |
|
| 3436 | 3440 | ) throws (ResolveError) { |
|
| 3437 | - | let wasUnsafe = self.inUnsafeFn; |
|
| 3438 | - | set self.inUnsafeFn = fnType.isUnsafe; |
|
| 3441 | + | let wasUnsafe = self.inUnsafeContext; |
|
| 3442 | + | set self.inUnsafeContext = fnType.isUnsafe; |
|
| 3439 | 3443 | // Enter function scope. |
|
| 3440 | 3444 | enterFn(self, node, fnType); // Enter function scope for body analysis. |
|
| 3441 | 3445 | ||
| 3442 | 3446 | let missingReturn = try checkExecutableBody(self, fnType, receiverName, params, body) catch e { |
|
| 3443 | 3447 | exitFn(self); |
|
| 3444 | - | set self.inUnsafeFn = wasUnsafe; |
|
| 3448 | + | set self.inUnsafeContext = wasUnsafe; |
|
| 3445 | 3449 | throw e; |
|
| 3446 | 3450 | }; |
|
| 3447 | 3451 | exitFn(self); |
|
| 3448 | - | set self.inUnsafeFn = wasUnsafe; |
|
| 3452 | + | set self.inUnsafeContext = wasUnsafe; |
|
| 3449 | 3453 | if missingReturn { |
|
| 3450 | 3454 | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3451 | 3455 | } |
|
| 3452 | 3456 | } |
|
| 3453 | 3457 |
lib/std/lang/resolver/printer.rad
+2 -2
| 537 | 537 | } |
|
| 538 | 538 | case super::ErrorKind::BorrowConflict(name) => { |
|
| 539 | 539 | printQuoted("conflicting call-scoped loans of '", name); |
|
| 540 | 540 | } |
|
| 541 | 541 | case super::ErrorKind::UnsafeOperation => { |
|
| 542 | - | io::print("unsafe pointer operation requires an unsafe function"); |
|
| 542 | + | io::print("unsafe operation requires an unsafe function or block"); |
|
| 543 | 543 | } |
|
| 544 | 544 | case super::ErrorKind::UnsafeCall => { |
|
| 545 | - | io::print("calling an unsafe function requires an unsafe function"); |
|
| 545 | + | io::print("calling an unsafe function requires an unsafe function or block"); |
|
| 546 | 546 | } |
|
| 547 | 547 | case super::ErrorKind::Internal => { |
|
| 548 | 548 | io::print("internal compiler error"); |
|
| 549 | 549 | } |
|
| 550 | 550 | case super::ErrorKind::RecordFieldOutOfOrder { .. } => { |
lib/std/lang/resolver/tests.rad
+46 -0
| 5553 | 5553 | try expectAnalyzeOk("fn change(n: &mut u32) { set *n = 0; } unsafe fn run(s: *mut [u8]) { change(&mut s.len); }"); |
|
| 5554 | 5554 | try expectAnalyzeOk("fn run(s: *mut [u8]) { set s[0] = 1; }"); |
|
| 5555 | 5555 | try expectAnalyzeOk("record R { len: u32 } fn run(r: &mut R) { set r.len = 1; }"); |
|
| 5556 | 5556 | } |
|
| 5557 | 5557 | ||
| 5558 | + | /// Unsafe blocks permit operations within a safe function. |
|
| 5559 | + | @test unsafe fn testUnsafeBlocksAllowed() throws (testing::TestError) { |
|
| 5560 | + | try expectAnalyzeOk("fn run(p: *u8) -> *u8 { unsafe { return p + 1; } }"); |
|
| 5561 | + | try expectAnalyzeOk("unsafe fn read(p: *unsafe u8) -> u8 { return *p; } fn run(p: *unsafe u8) -> u8 { unsafe { return read(p); } }"); |
|
| 5562 | + | try expectAnalyzeOk("fn run(p: *u8) -> *[u8] { unsafe { { return @sliceOf(p, 1); } } }"); |
|
| 5563 | + | try expectAnalyzeOk("fn run(p: *u8) -> *u64 { unsafe { unsafe { return p as *u64; } } }"); |
|
| 5564 | + | try expectAnalyzeOk("unsafe fn run(p: *u8) -> *u8 { unsafe {} return p + 1; }"); |
|
| 5565 | + | } |
|
| 5566 | + | ||
| 5567 | + | /// Unsafe permission ends at the closing brace and at function boundaries. |
|
| 5568 | + | @test unsafe fn testUnsafeBlockContextRestored() throws (testing::TestError) { |
|
| 5569 | + | let programs = &[ |
|
| 5570 | + | "fn run(p: *u8) -> *u8 { unsafe { let q = p + 1; } return p + 1; }", |
|
| 5571 | + | "fn run(p: *u8) -> *u8 { unsafe { unsafe {} } return p + 1; }", |
|
| 5572 | + | "unsafe fn first(p: *u8) -> *u8 { return p + 1; } fn second(p: *u8) -> *u8 { return p + 1; }", |
|
| 5573 | + | ]; |
|
| 5574 | + | for program in programs { |
|
| 5575 | + | let mut a = testResolver(); |
|
| 5576 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 5577 | + | try expectErrorKind(&result, super::ErrorKind::UnsafeOperation); |
|
| 5578 | + | } |
|
| 5579 | + | let mut a = testResolver(); |
|
| 5580 | + | let result = try resolveProgramStr(&mut a, "unsafe fn act() {} fn run() { unsafe { act(); } act(); }"); |
|
| 5581 | + | try expectErrorKind(&result, super::ErrorKind::UnsafeCall); |
|
| 5582 | + | } |
|
| 5583 | + | ||
| 5584 | + | /// Resolution errors do not extend an unsafe block's permission. |
|
| 5585 | + | @test unsafe fn testUnsafeBlockErrorRestoresContext() throws (testing::TestError) { |
|
| 5586 | + | let mut a = testResolver(); |
|
| 5587 | + | let result = try resolveProgramStr(&mut a, "fn run(p: *u8) { unsafe { missing; } let q = p + 1; }"); |
|
| 5588 | + | let mut found = false; |
|
| 5589 | + | for err in result.diagnostics.errors { |
|
| 5590 | + | if let case super::ErrorKind::UnsafeOperation = err.kind { |
|
| 5591 | + | set found = true; |
|
| 5592 | + | } |
|
| 5593 | + | } |
|
| 5594 | + | try testing::expect(found); |
|
| 5595 | + | } |
|
| 5596 | + | ||
| 5597 | + | /// Unsafe blocks preserve reference lifetime checks. |
|
| 5598 | + | @test unsafe fn testUnsafeBlockPreservesReferences() throws (testing::TestError) { |
|
| 5599 | + | let mut a = testResolver(); |
|
| 5600 | + | let result = try resolveProgramStr(&mut a, "fn run() { let n: u32 = 1; unsafe { let p = &n; } }"); |
|
| 5601 | + | try expectErrorKind(&result, super::ErrorKind::RefBinding); |
|
| 5602 | + | } |
|
| 5603 | + | ||
| 5558 | 5604 | /// Unsafe declarations may compose unsafe operations and calls. |
|
| 5559 | 5605 | @test unsafe fn testUnsafePointerOperationsAllowed() throws (testing::TestError) { |
|
| 5560 | 5606 | 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); }"; |
|
| 5561 | 5607 | try expectAnalyzeOk(program); |
|
| 5562 | 5608 | } |
test/tests/unsafe.block.bounds.rad
added
+14 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | ||
| 3 | + | /// Supply a runtime array index. |
|
| 4 | + | fn index(n: u32) -> u32 { |
|
| 5 | + | return n; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | /// An unsafe block retains bounds checks for array indexing. |
|
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | let data: [i32; 1] = [42]; |
|
| 11 | + | unsafe { |
|
| 12 | + | return data[index(1)]; |
|
| 13 | + | } |
|
| 14 | + | } |
test/tests/unsafe.block.rad
added
+32 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Storage for checked and unchecked accesses. |
|
| 4 | + | static DATA: [u8; 3] = [11, 22, 33]; |
|
| 5 | + | ||
| 6 | + | /// Read a stored pointer under the caller's unsafe contract. |
|
| 7 | + | unsafe fn read(pointer: *u8) -> u8 { |
|
| 8 | + | return *pointer; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Read the second byte while retaining a safe interface. |
|
| 12 | + | fn second() -> u8 { |
|
| 13 | + | unsafe { |
|
| 14 | + | return read(&DATA[0] + 1); |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Exercise unsafe blocks with mutation and loop exits. |
|
| 19 | + | @default fn main() -> i32 { |
|
| 20 | + | if second() <> 22 { return 1; } |
|
| 21 | + | let mut count: u32 = 0; |
|
| 22 | + | for i in 0..3 { |
|
| 23 | + | unsafe { |
|
| 24 | + | set *(&mut DATA[0] + i) = 44; |
|
| 25 | + | set count += 1; |
|
| 26 | + | if i == 0 { continue; } |
|
| 27 | + | if i == 1 { break; } |
|
| 28 | + | } |
|
| 29 | + | } |
|
| 30 | + | if count <> 2 or DATA[1] <> 44 or DATA[2] <> 33 { return 2; } |
|
| 31 | + | return 0; |
|
| 32 | + | } |