Add unsafe keyword for pointer-integer boundary crossing
985a1b9224f9ad669ee4b5f0f93fdff28618d6113b1e089bdf0d8a26016b7898
Phase 5: add unsafe fn/block to control integer-to-pointer casts.
Language changes:
- 'unsafe fn' declares an unsafe function
- 'unsafe { ... }' introduces an unsafe block
- Integer-to-pointer casts (e.g. addr as *i32) are only allowed
inside unsafe functions or unsafe blocks
- The resolver rejects int-to-ptr casts outside unsafe context
with 'unsafe operation outside of unsafe context'
Implementation:
- scanner.rad: Add Unsafe keyword token
- ast.rad: Add Unsafe attribute variant, UnsafeBlock node
- parser.rad: Parse 'unsafe fn' and 'unsafe { }', add UnsafeBlock
to expectsSemicolon whitelist
- resolver.rad: Track unsafeDepth, add isUnsafe to FnType, allow
numeric-to-pointer casts in isValidCast but enforce unsafe context
in resolveAs. Add UnsafeRequired/UnsafeCallRequired errors.
- lower.rad: Handle UnsafeBlock as transparent to lowering
- il.rad: Update WordToPtr doc referencing unsafe
- Add unsafe.basic test with int-to-ptr roundtrip
1 parent
2f7beb74
lib/std/lang/ast.rad
+5 -0
| 45 | 45 | Extern = 0b100, |
|
| 46 | 46 | /// Test-only declaration attribute. |
|
| 47 | 47 | Test = 0b1000, |
|
| 48 | 48 | /// Compiler intrinsic attribute. |
|
| 49 | 49 | Intrinsic = 0b10000, |
|
| 50 | + | /// Unsafe function/method attribute. |
|
| 51 | + | Unsafe = 0b100000, |
|
| 50 | 52 | } |
|
| 51 | 53 | ||
| 52 | 54 | /// Ordered collection of attribute nodes applied to a declaration. |
|
| 53 | 55 | pub record Attributes { |
|
| 54 | 56 | list: *mut [*Node], |
| 643 | 645 | /// Argument list. |
|
| 644 | 646 | args: *mut [*Node], |
|
| 645 | 647 | }, |
|
| 646 | 648 | /// Block expression or statement body. |
|
| 647 | 649 | Block(Block), |
|
| 650 | + | /// Unsafe block expression: `unsafe { ... }`. |
|
| 651 | + | /// The inner node is a Block. |
|
| 652 | + | UnsafeBlock(*Node), |
|
| 648 | 653 | /// Call expression, eg. `f(x)`. |
|
| 649 | 654 | Call(Call), |
|
| 650 | 655 | /// Field access expression (e.g. `foo.bar`). |
|
| 651 | 656 | FieldAccess(Access), |
|
| 652 | 657 | /// Scope access expression (e.g. `foo::bar`). |
lib/std/lang/ast/printer.rad
+4 -0
| 305 | 305 | case super::Attribute::Pub => return sexpr::sym("@pub"), |
|
| 306 | 306 | case super::Attribute::Default => return sexpr::sym("@default"), |
|
| 307 | 307 | case super::Attribute::Extern => return sexpr::sym("@extern"), |
|
| 308 | 308 | case super::Attribute::Test => return sexpr::sym("@test"), |
|
| 309 | 309 | case super::Attribute::Intrinsic => return sexpr::sym("@intrinsic"), |
|
| 310 | + | case super::Attribute::Unsafe => return sexpr::sym("unsafe"), |
|
| 310 | 311 | } |
|
| 311 | 312 | } |
|
| 312 | 313 | case super::NodeValue::Try(t) => { |
|
| 313 | 314 | let mut head = "try"; |
|
| 314 | 315 | if t.shouldPanic { head = "try!"; } |
| 336 | 337 | } |
|
| 337 | 338 | case super::NodeValue::Block(blk) => { |
|
| 338 | 339 | let children = nodeListToExprs(a, &blk.statements[..]); |
|
| 339 | 340 | return sexpr::block(a, "block", &[], children); |
|
| 340 | 341 | } |
|
| 342 | + | case super::NodeValue::UnsafeBlock(inner) => { |
|
| 343 | + | return sexpr::list(a, "unsafe", &[toExpr(a, inner)]); |
|
| 344 | + | } |
|
| 341 | 345 | case super::NodeValue::Let(decl) => { |
|
| 342 | 346 | let mut head = "let"; |
|
| 343 | 347 | if decl.mutable { head = "let-mut"; } |
|
| 344 | 348 | return sexpr::list(a, head, &[ |
|
| 345 | 349 | toExpr(a, decl.ident), |
lib/std/lang/lower.rad
+8 -0
| 3697 | 3697 | } |
|
| 3698 | 3698 | match node.value { |
|
| 3699 | 3699 | case ast::NodeValue::Block(_) => { |
|
| 3700 | 3700 | try lowerBlock(self, node); |
|
| 3701 | 3701 | } |
|
| 3702 | + | case ast::NodeValue::UnsafeBlock(inner) => { |
|
| 3703 | + | try lowerNode(self, inner); |
|
| 3704 | + | } |
|
| 3702 | 3705 | case ast::NodeValue::Return { value } => { |
|
| 3703 | 3706 | try lowerReturnStmt(self, node, value); |
|
| 3704 | 3707 | } |
|
| 3705 | 3708 | case ast::NodeValue::Throw { expr } => { |
|
| 3706 | 3709 | try lowerThrowStmt(self, expr); |
| 6966 | 6969 | } |
|
| 6967 | 6970 | case ast::NodeValue::Block(_) => { |
|
| 6968 | 6971 | try lowerBlock(self, node); |
|
| 6969 | 6972 | val = il::Val::Undef; |
|
| 6970 | 6973 | } |
|
| 6974 | + | case ast::NodeValue::UnsafeBlock(inner) => { |
|
| 6975 | + | // Unsafe blocks are transparent to the lowerer; safety is |
|
| 6976 | + | // enforced by the resolver. |
|
| 6977 | + | val = try lowerExpr(self, inner); |
|
| 6978 | + | } |
|
| 6971 | 6979 | case ast::NodeValue::ExprStmt(expr) => { |
|
| 6972 | 6980 | let _ = expr; |
|
| 6973 | 6981 | val = il::Val::Undef; |
|
| 6974 | 6982 | } |
|
| 6975 | 6983 | // Lower these as statements. |
lib/std/lang/parser.rad
+29 -0
| 643 | 643 | ast::NodeValue::WhileLet(_), |
|
| 644 | 644 | ast::NodeValue::For(_), |
|
| 645 | 645 | ast::NodeValue::Loop { .. }, |
|
| 646 | 646 | ast::NodeValue::Match(_), |
|
| 647 | 647 | ast::NodeValue::Block(_), |
|
| 648 | + | ast::NodeValue::UnsafeBlock(_), |
|
| 648 | 649 | ast::NodeValue::FnDecl(_), |
|
| 649 | 650 | ast::NodeValue::RecordDecl(_), |
|
| 650 | 651 | ast::NodeValue::UnionDecl(_), |
|
| 651 | 652 | ast::NodeValue::TraitDecl { .. }, |
|
| 652 | 653 | ast::NodeValue::InstanceDecl { .. }, |
| 746 | 747 | if p.context != Context::Normal { |
|
| 747 | 748 | throw failParsing(p, "unexpected `{` in this context"); |
|
| 748 | 749 | } |
|
| 749 | 750 | return try parseRecordLit(p, nil); |
|
| 750 | 751 | } |
|
| 752 | + | case scanner::TokenKind::Unsafe => { |
|
| 753 | + | advance(p); // Consume `unsafe`. |
|
| 754 | + | let block = try parseBlock(p); |
|
| 755 | + | return node(p, ast::NodeValue::UnsafeBlock(block)); |
|
| 756 | + | } |
|
| 751 | 757 | else => { |
|
| 752 | 758 | throw failParsing(p, "expected expression"); |
|
| 753 | 759 | } |
|
| 754 | 760 | } |
|
| 755 | 761 | } |
| 867 | 873 | )); |
|
| 868 | 874 | } |
|
| 869 | 875 | return node(p, ast::NodeValue::ExprStmt(expr)); |
|
| 870 | 876 | } |
|
| 871 | 877 | ||
| 878 | + | /// Merge an attribute node into an existing (possibly nil) attribute list. |
|
| 879 | + | fn mergeAttr(p: *mut Parser, attrs: ?ast::Attributes, attrNode: *ast::Node) -> ?ast::Attributes { |
|
| 880 | + | if let a = attrs { |
|
| 881 | + | a.list.append(attrNode, p.allocator); |
|
| 882 | + | return a; |
|
| 883 | + | } |
|
| 884 | + | let mut list = ast::nodeSlice(p.arena, 4); |
|
| 885 | + | list.append(attrNode, p.allocator); |
|
| 886 | + | return ast::Attributes { list }; |
|
| 887 | + | } |
|
| 888 | + | ||
| 872 | 889 | /// Parse leading attributes attached to the next declaration statement. |
|
| 873 | 890 | fn parseAttributes(p: *mut Parser) -> ?ast::Attributes { |
|
| 874 | 891 | let mut attrs = ast::nodeSlice(p.arena, 4); |
|
| 875 | 892 | ||
| 876 | 893 | if let attr = tryParseAnnotation(p) { |
| 994 | 1011 | return try parseConst(p, attrs); |
|
| 995 | 1012 | } |
|
| 996 | 1013 | case scanner::TokenKind::Static => { |
|
| 997 | 1014 | return try parseStatic(p, attrs); |
|
| 998 | 1015 | } |
|
| 1016 | + | case scanner::TokenKind::Unsafe => { |
|
| 1017 | + | advance(p); // Consume `unsafe`. |
|
| 1018 | + | if check(p, scanner::TokenKind::Fn) { |
|
| 1019 | + | // Add the Unsafe attribute and parse as a normal fn decl. |
|
| 1020 | + | let attrNode = nodeAttribute(p, ast::Attribute::Unsafe); |
|
| 1021 | + | let mut unsafeAttrs = mergeAttr(p, attrs, attrNode); |
|
| 1022 | + | return try parseFnDecl(p, unsafeAttrs); |
|
| 1023 | + | } |
|
| 1024 | + | // `unsafe { ... }` block expression. |
|
| 1025 | + | let block = try parseBlock(p); |
|
| 1026 | + | return node(p, ast::NodeValue::UnsafeBlock(block)); |
|
| 1027 | + | } |
|
| 999 | 1028 | case scanner::TokenKind::Fn => { |
|
| 1000 | 1029 | return try parseFnDecl(p, attrs); |
|
| 1001 | 1030 | } |
|
| 1002 | 1031 | case scanner::TokenKind::Union => { |
|
| 1003 | 1032 | return try parseUnionDecl(p, attrs); |
lib/std/lang/resolver.rad
+61 -2
| 259 | 259 | pub record FnType { |
|
| 260 | 260 | paramTypes: *[*Type], |
|
| 261 | 261 | returnType: *Type, |
|
| 262 | 262 | throwList: *[*Type], |
|
| 263 | 263 | localCount: u32, |
|
| 264 | + | /// Whether the function is declared `unsafe`. |
|
| 265 | + | isUnsafe: bool, |
|
| 264 | 266 | } |
|
| 265 | 267 | ||
| 266 | 268 | /// Describes a type computed during semantic analysis. |
|
| 267 | 269 | pub union Type { |
|
| 268 | 270 | /// A type that couldn't be decided. |
| 572 | 574 | FnThrowOverflow(CountMismatch), |
|
| 573 | 575 | /// Trait declaration has too many methods. |
|
| 574 | 576 | TraitMethodOverflow(CountMismatch), |
|
| 575 | 577 | /// Instance declaration is missing a required supertrait instance. |
|
| 576 | 578 | MissingSupertraitInstance(*[u8]), |
|
| 579 | + | /// Unsafe operation outside of an `unsafe` block or function. |
|
| 580 | + | UnsafeRequired, |
|
| 581 | + | /// Call to an unsafe function outside of an `unsafe` context. |
|
| 582 | + | UnsafeCallRequired, |
|
| 577 | 583 | /// Internal error. |
|
| 578 | 584 | Internal, |
|
| 579 | 585 | } |
|
| 580 | 586 | ||
| 581 | 587 | /// Diagnostics returned by the analyzer. |
| 744 | 750 | loopStack: [LoopCtx; MAX_LOOP_DEPTH], |
|
| 745 | 751 | /// Current loop depth, indexes into loop stack. |
|
| 746 | 752 | loopDepth: u32, |
|
| 747 | 753 | /// Signature of the function currently being analyzed. |
|
| 748 | 754 | currentFn: ?*FnType, |
|
| 755 | + | /// Unsafe context depth. Non-zero inside `unsafe fn` or `unsafe { }` blocks. |
|
| 756 | + | unsafeDepth: u32, |
|
| 749 | 757 | /// Current module being analyzed. |
|
| 750 | 758 | currentMod: u16, |
|
| 751 | 759 | /// Configuration for semantic analysis. |
|
| 752 | 760 | config: Config, |
|
| 753 | 761 | /// Unified arena for symbols, scopes, and nominal type. |
| 902 | 910 | scope: storage.pkgScope, |
|
| 903 | 911 | pkgScope: storage.pkgScope, |
|
| 904 | 912 | loopStack: undefined, |
|
| 905 | 913 | loopDepth: 0, |
|
| 906 | 914 | currentFn: nil, |
|
| 915 | + | unsafeDepth: 0, |
|
| 907 | 916 | currentMod: 0, |
|
| 908 | 917 | config, |
|
| 909 | 918 | arena, |
|
| 910 | 919 | nodeData: NodeDataTable { entries: storage.nodeData }, |
|
| 911 | 920 | types: nil, |
| 2457 | 2466 | try infer(self, node); |
|
| 2458 | 2467 | } |
|
| 2459 | 2468 | case ast::NodeValue::InstanceDecl { traitName, targetType, methods } => { |
|
| 2460 | 2469 | try resolveInstanceDecl(self, node, traitName, targetType, methods); |
|
| 2461 | 2470 | } |
|
| 2462 | - | case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, body, attrs } => { |
|
| 2471 | + | case ast::NodeValue::MethodDecl { name, receiverName, receiverType, sig, attrs, .. } => { |
|
| 2463 | 2472 | try resolveMethodDecl(self, node, name, receiverName, receiverType, sig, attrs); |
|
| 2464 | 2473 | } |
|
| 2465 | 2474 | else => { |
|
| 2466 | 2475 | // Ignore non-declaration nodes. |
|
| 2467 | 2476 | } |
| 2568 | 2577 | }, |
|
| 2569 | 2578 | case ast::NodeValue::Call(call) => return try resolveCall(self, node, call, CallCtx::Normal), |
|
| 2570 | 2579 | case ast::NodeValue::FieldAccess(access) => return try resolveFieldAccess(self, node, access), |
|
| 2571 | 2580 | case ast::NodeValue::BinOp(binop) => return try resolveBinOp(self, node, binop), |
|
| 2572 | 2581 | case ast::NodeValue::Block(block) => return try resolveBlock(self, node, block), |
|
| 2582 | + | case ast::NodeValue::UnsafeBlock(inner) => { |
|
| 2583 | + | self.unsafeDepth += 1; |
|
| 2584 | + | let ty = try visit(self, inner, hint) catch e { |
|
| 2585 | + | self.unsafeDepth -= 1; |
|
| 2586 | + | throw e; |
|
| 2587 | + | }; |
|
| 2588 | + | self.unsafeDepth -= 1; |
|
| 2589 | + | return setNodeType(self, node, ty); |
|
| 2590 | + | } |
|
| 2573 | 2591 | case ast::NodeValue::Let(decl) => return try resolveLet(self, node, decl), |
|
| 2574 | 2592 | case ast::NodeValue::ConstDecl(decl) => return try resolveConstOrStatic( |
|
| 2575 | 2593 | self, node, decl.ident, decl.type, decl.value, decl.attrs, true |
|
| 2576 | 2594 | ), |
|
| 2577 | 2595 | case ast::NodeValue::StaticDecl(decl) => return try resolveConstOrStatic( |
| 3023 | 3041 | let mut fnType = FnType { |
|
| 3024 | 3042 | paramTypes: &[], |
|
| 3025 | 3043 | returnType: allocType(self, retTy), |
|
| 3026 | 3044 | throwList: &[], |
|
| 3027 | 3045 | localCount: 0, |
|
| 3046 | + | isUnsafe: ast::hasAttribute(attrMask, ast::Attribute::Unsafe), |
|
| 3028 | 3047 | }; |
|
| 3029 | 3048 | // Enter the function scope to process parameters. |
|
| 3030 | 3049 | enterFn(self, node, &fnType); |
|
| 3031 | 3050 | ||
| 3032 | 3051 | if decl.sig.params.len > MAX_FN_PARAMS { |
| 3090 | 3109 | if let body = decl.body { |
|
| 3091 | 3110 | if isExtern { |
|
| 3092 | 3111 | throw emitError(self, node, ErrorKind::FnUnexpectedBody); |
|
| 3093 | 3112 | } |
|
| 3094 | 3113 | enterFn(self, node, fnType); // Enter function scope for body analysis. |
|
| 3095 | - | ||
| 3114 | + | if fnType.isUnsafe { |
|
| 3115 | + | self.unsafeDepth += 1; |
|
| 3116 | + | } |
|
| 3096 | 3117 | let bodyTy = try checkAssignable(self, body, Type::Void) catch e { |
|
| 3118 | + | if fnType.isUnsafe { |
|
| 3119 | + | self.unsafeDepth -= 1; |
|
| 3120 | + | } |
|
| 3097 | 3121 | exitFn(self); |
|
| 3098 | 3122 | throw e; |
|
| 3099 | 3123 | }; |
|
| 3100 | 3124 | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3125 | + | if fnType.isUnsafe { |
|
| 3126 | + | self.unsafeDepth -= 1; |
|
| 3127 | + | } |
|
| 3101 | 3128 | exitFn(self); |
|
| 3102 | 3129 | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3103 | 3130 | } |
|
| 3131 | + | if fnType.isUnsafe { |
|
| 3132 | + | self.unsafeDepth -= 1; |
|
| 3133 | + | } |
|
| 3104 | 3134 | exitFn(self); |
|
| 3105 | 3135 | } else if not isExtern { |
|
| 3106 | 3136 | throw emitError(self, node, ErrorKind::FnMissingBody); |
|
| 3107 | 3137 | } |
|
| 3108 | 3138 | } |
| 3349 | 3379 | let fnType = FnType { |
|
| 3350 | 3380 | paramTypes: ¶mTypes[..], |
|
| 3351 | 3381 | returnType: retType, |
|
| 3352 | 3382 | throwList: &throwList[..], |
|
| 3353 | 3383 | localCount: 0, |
|
| 3384 | + | isUnsafe: false, |
|
| 3354 | 3385 | }; |
|
| 3355 | 3386 | traitType.methods.append(TraitMethod { |
|
| 3356 | 3387 | name: methodName, |
|
| 3357 | 3388 | fnType: allocFnType(self, fnType), |
|
| 3358 | 3389 | mutable, |
| 3532 | 3563 | let fnType = FnType { |
|
| 3533 | 3564 | paramTypes: ¶mTypes[..], |
|
| 3534 | 3565 | returnType: tm.fnType.returnType, |
|
| 3535 | 3566 | throwList: tm.fnType.throwList, |
|
| 3536 | 3567 | localCount: 0, |
|
| 3568 | + | isUnsafe: false, |
|
| 3537 | 3569 | }; |
|
| 3538 | 3570 | ||
| 3539 | 3571 | // Create a symbol for the instance method without binding it into the |
|
| 3540 | 3572 | // module scope. Instance methods are dispatched via v-table, so they |
|
| 3541 | 3573 | // must not pollute the enclosing scope. |
| 3612 | 3644 | let case SymbolData::Value { type: Type::Fn(fnType), .. } = sym.data |
|
| 3613 | 3645 | else panic "resolveMethodBody: expected value symbol"; |
|
| 3614 | 3646 | ||
| 3615 | 3647 | // Enter function scope. |
|
| 3616 | 3648 | enterFn(self, node, fnType); |
|
| 3649 | + | if fnType.isUnsafe { |
|
| 3650 | + | self.unsafeDepth += 1; |
|
| 3651 | + | } |
|
| 3617 | 3652 | ||
| 3618 | 3653 | // Bind the receiver parameter. |
|
| 3619 | 3654 | let receiverTy = *fnType.paramTypes[0]; |
|
| 3620 | 3655 | try bindValueIdent(self, receiverName, receiverName, receiverTy, false, 0, 0) catch e { |
|
| 3656 | + | if fnType.isUnsafe { self.unsafeDepth -= 1; } |
|
| 3621 | 3657 | exitFn(self); |
|
| 3622 | 3658 | throw e; |
|
| 3623 | 3659 | }; |
|
| 3624 | 3660 | // Bind the remaining parameters from the signature. |
|
| 3625 | 3661 | for paramNode in sig.params { |
|
| 3626 | 3662 | let paramTy = try infer(self, paramNode) catch e { |
|
| 3663 | + | if fnType.isUnsafe { self.unsafeDepth -= 1; } |
|
| 3627 | 3664 | exitFn(self); |
|
| 3628 | 3665 | throw e; |
|
| 3629 | 3666 | }; |
|
| 3630 | 3667 | } |
|
| 3631 | 3668 | ||
| 3632 | 3669 | // Resolve the body. |
|
| 3633 | 3670 | let retTy = *fnType.returnType; |
|
| 3634 | 3671 | let bodyTy = try checkAssignable(self, body, Type::Void) catch e { |
|
| 3672 | + | if fnType.isUnsafe { self.unsafeDepth -= 1; } |
|
| 3635 | 3673 | exitFn(self); |
|
| 3636 | 3674 | throw e; |
|
| 3637 | 3675 | }; |
|
| 3638 | 3676 | if retTy != Type::Void and bodyTy != Type::Never { |
|
| 3677 | + | if fnType.isUnsafe { self.unsafeDepth -= 1; } |
|
| 3639 | 3678 | exitFn(self); |
|
| 3640 | 3679 | throw emitError(self, body, ErrorKind::FnMissingReturn); |
|
| 3641 | 3680 | } |
|
| 3681 | + | if fnType.isUnsafe { self.unsafeDepth -= 1; } |
|
| 3642 | 3682 | exitFn(self); |
|
| 3643 | 3683 | } |
|
| 3644 | 3684 | ||
| 3645 | 3685 | /// Resolve a standalone method declaration (signature only). |
|
| 3646 | 3686 | /// Validates the receiver type and registers the method in the method table. |
| 3716 | 3756 | } |
|
| 3717 | 3757 | ||
| 3718 | 3758 | let retTypePtr = allocType(self, returnType); |
|
| 3719 | 3759 | let throwList = &throwTypes[..]; |
|
| 3720 | 3760 | ||
| 3761 | + | let isUnsafe = ast::hasAttribute(resolveAttributes(self, attrs), ast::Attribute::Unsafe); |
|
| 3762 | + | ||
| 3721 | 3763 | // Full function type (receiver + params) for lowering. |
|
| 3722 | 3764 | let fullFnType = FnType { |
|
| 3723 | 3765 | paramTypes: ¶mTypes[..], returnType: retTypePtr, throwList, localCount: 0, |
|
| 3766 | + | isUnsafe, |
|
| 3724 | 3767 | }; |
|
| 3725 | 3768 | let fnTy = Type::Fn(allocFnType(self, fullFnType)); |
|
| 3726 | 3769 | ||
| 3727 | 3770 | // Function type excluding receiver, for call arg checking. |
|
| 3728 | 3771 | let checkFnType = FnType { |
|
| 3729 | 3772 | paramTypes: ¶mTypes[1..], returnType: retTypePtr, throwList, localCount: 0, |
|
| 3773 | + | isUnsafe, |
|
| 3730 | 3774 | }; |
|
| 3731 | 3775 | ||
| 3732 | 3776 | // Compute attribute mask. |
|
| 3733 | 3777 | let mut attrMask: u32 = 0; |
|
| 3734 | 3778 | if let a = attrs { |
| 5882 | 5926 | if let case Type::Slice { .. } = source { |
|
| 5883 | 5927 | // Disallow slice to numeric; slices are fat pointers. |
|
| 5884 | 5928 | } else if isAddressType(source) and isNumericType(target) { |
|
| 5885 | 5929 | return true; |
|
| 5886 | 5930 | } |
|
| 5931 | + | // Allow numeric to pointer (requires unsafe context; checked in resolveAs). |
|
| 5932 | + | if isNumericType(source) { |
|
| 5933 | + | if let case Type::Pointer { .. } = target { |
|
| 5934 | + | return true; |
|
| 5935 | + | } |
|
| 5936 | + | } |
|
| 5887 | 5937 | // Allow pointer casts if one side is `*opaque` or target types are castable. |
|
| 5888 | 5938 | if let case Type::Pointer { target: sourceTarget, .. } = source { |
|
| 5889 | 5939 | if let case Type::Pointer { target: targetTarget, .. } = target { |
|
| 5890 | 5940 | if isOpaquePointer(source) or isOpaquePointer(target) { |
|
| 5891 | 5941 | return true; |
| 5918 | 5968 | ||
| 5919 | 5969 | assert sourceTy != Type::Unknown; |
|
| 5920 | 5970 | assert targetTy != Type::Unknown; |
|
| 5921 | 5971 | ||
| 5922 | 5972 | if isValidCast(sourceTy, targetTy) { |
|
| 5973 | + | // Integer-to-pointer cast requires unsafe context. |
|
| 5974 | + | if isNumericType(sourceTy) { |
|
| 5975 | + | if let case Type::Pointer { .. } = targetTy { |
|
| 5976 | + | if self.unsafeDepth == 0 { |
|
| 5977 | + | throw emitError(self, node, ErrorKind::UnsafeRequired); |
|
| 5978 | + | } |
|
| 5979 | + | } |
|
| 5980 | + | } |
|
| 5923 | 5981 | // Propagate constant value through the cast, adjusting integer |
|
| 5924 | 5982 | // metadata to match the target type. |
|
| 5925 | 5983 | if let value = constValueEntry(self, expr.value) { |
|
| 5926 | 5984 | if let case ConstValue::Int(i) = value { |
|
| 5927 | 5985 | if let range = integerRange(targetTy) { |
| 6552 | 6610 | let fnType = FnType { |
|
| 6553 | 6611 | paramTypes: ¶mTypes[..], |
|
| 6554 | 6612 | returnType: retType, |
|
| 6555 | 6613 | throwList: &throwList[..], |
|
| 6556 | 6614 | localCount: 0, |
|
| 6615 | + | isUnsafe: false, |
|
| 6557 | 6616 | }; |
|
| 6558 | 6617 | return Type::Fn(allocFnType(self, fnType)); |
|
| 6559 | 6618 | } |
|
| 6560 | 6619 | case ast::TypeSig::TraitObject { traitName, mutable } => { |
|
| 6561 | 6620 | let sym = try resolveNamePath(self, traitName); |
lib/std/lang/resolver/printer.rad
+6 -0
| 483 | 483 | case super::ErrorKind::TraitMethodOverflow(m) => |
|
| 484 | 484 | printMismatch("too many trait methods", "maximum", m), |
|
| 485 | 485 | case super::ErrorKind::MissingSupertraitInstance(name) => { |
|
| 486 | 486 | printQuoted("missing instance for supertrait '", name); |
|
| 487 | 487 | } |
|
| 488 | + | case super::ErrorKind::UnsafeRequired => { |
|
| 489 | + | io::print("unsafe operation outside of unsafe context"); |
|
| 490 | + | } |
|
| 491 | + | case super::ErrorKind::UnsafeCallRequired => { |
|
| 492 | + | io::print("call to unsafe function requires unsafe context"); |
|
| 493 | + | } |
|
| 488 | 494 | case super::ErrorKind::Internal => { |
|
| 489 | 495 | io::print("internal compiler error"); |
|
| 490 | 496 | } |
|
| 491 | 497 | case super::ErrorKind::RecordFieldOutOfOrder { .. } => { |
|
| 492 | 498 | io::print("record field out of order"); |
lib/std/lang/scanner.rad
+6 -2
| 102 | 102 | // Trait-related tokens. |
|
| 103 | 103 | Trait, Instance, |
|
| 104 | 104 | ||
| 105 | 105 | // Type-related tokens. |
|
| 106 | 106 | I8, I16, I32, I64, U8, U16, U32, U64, |
|
| 107 | - | Opaque, Fn, Bool, Union, Record, As |
|
| 107 | + | Opaque, Fn, Bool, Union, Record, As, |
|
| 108 | + | ||
| 109 | + | // Safety. |
|
| 110 | + | Unsafe, |
|
| 108 | 111 | } |
|
| 109 | 112 | ||
| 110 | 113 | /// A reserved keyword. |
|
| 111 | 114 | record Keyword { |
|
| 112 | 115 | /// Keyword string. |
| 114 | 117 | /// Corresponding token. |
|
| 115 | 118 | tok: TokenKind, |
|
| 116 | 119 | } |
|
| 117 | 120 | ||
| 118 | 121 | /// Sorted keyword table for binary search. |
|
| 119 | - | const KEYWORDS: [Keyword; 51] = [ |
|
| 122 | + | const KEYWORDS: [Keyword; 52] = [ |
|
| 120 | 123 | { name: "align", tok: TokenKind::Align }, |
|
| 121 | 124 | { name: "and", tok: TokenKind::And }, |
|
| 122 | 125 | { name: "as", tok: TokenKind::As }, |
|
| 123 | 126 | { name: "assert", tok: TokenKind::Assert }, |
|
| 124 | 127 | { name: "bool", tok: TokenKind::Bool }, |
| 164 | 167 | { name: "u32", tok: TokenKind::U32 }, |
|
| 165 | 168 | { name: "u64", tok: TokenKind::U64 }, |
|
| 166 | 169 | { name: "u8", tok: TokenKind::U8 }, |
|
| 167 | 170 | { name: "undefined", tok: TokenKind::Undefined }, |
|
| 168 | 171 | { name: "union", tok: TokenKind::Union }, |
|
| 172 | + | { name: "unsafe", tok: TokenKind::Unsafe }, |
|
| 169 | 173 | { name: "use", tok: TokenKind::Use }, |
|
| 170 | 174 | { name: "while", tok: TokenKind::While }, |
|
| 171 | 175 | ]; |
|
| 172 | 176 | ||
| 173 | 177 | /// Describes where source code originated from. |
test/tests/unsafe.basic.rad
added
+28 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// An unsafe function that converts an address to a pointer and reads it. |
|
| 4 | + | unsafe fn unsafeRead(addr: u64) -> i32 { |
|
| 5 | + | let p = addr as *i32; |
|
| 6 | + | return *p; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | let x: i32 = 42; |
|
| 11 | + | let addr = &x as u64; |
|
| 12 | + | ||
| 13 | + | // Calling unsafe fn and int-to-ptr cast inside unsafe block. |
|
| 14 | + | let mut val: i32 = 0; |
|
| 15 | + | unsafe { |
|
| 16 | + | val = unsafeRead(addr); |
|
| 17 | + | } |
|
| 18 | + | assert val == 42; |
|
| 19 | + | ||
| 20 | + | // Direct int-to-ptr cast in unsafe block. |
|
| 21 | + | let mut p: *i32 = &x; |
|
| 22 | + | unsafe { |
|
| 23 | + | p = addr as *i32; |
|
| 24 | + | } |
|
| 25 | + | assert *p == 42; |
|
| 26 | + | ||
| 27 | + | return 0; |
|
| 28 | + | } |
test/tests/unsafe.basic.ril
added
+26 -0
| 1 | + | fn w32 $unsafeRead(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | wtp %1 %0; |
|
| 4 | + | sload w32 %2 %1 0; |
|
| 5 | + | ret %2; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn w32 $main() { |
|
| 9 | + | @entry0 |
|
| 10 | + | reserve %0 4 4; |
|
| 11 | + | store w32 42 %0 0; |
|
| 12 | + | ptw %1 %0; |
|
| 13 | + | call w32 %2 $unsafeRead(%1); |
|
| 14 | + | br.eq w32 %2 42 @assert.ok2 @assert.fail1; |
|
| 15 | + | @assert.fail1 |
|
| 16 | + | unreachable; |
|
| 17 | + | @assert.ok2 |
|
| 18 | + | wtp %3 %1; |
|
| 19 | + | sload w32 %4 %3 0; |
|
| 20 | + | br.eq w32 %4 42 @assert.ok4 @assert.fail3; |
|
| 21 | + | @assert.fail3 |
|
| 22 | + | unreachable; |
|
| 23 | + | @assert.ok4 |
|
| 24 | + | ret 0; |
|
| 25 | + | } |
|
| 26 | + |