Reject incompatible pointer coercions

65221249ab3ca9f3fb3ab8508872f6904f95008a44d8b18649a19deec65c6ff1
Require equal pointee and slice element types for storage coercions.
Protect pattern source storage through its reference binding scope,
including in unsafe contexts. Check reference bindings during
the ownership traversal.
Alexis Sellier committed ago 1 parent 5ef2ba40
lib/std/lang/resolver.rad +107 -24
813 813
/// Read loop arrays only at indices below `loopDepth`.
814 814
/// `enterLinearLoop` initializes each slot before it increases `loopDepth`.
815 815
record LinearChecker: Copy {
816 816
    /// Resolver that owns the symbols and diagnostics.
817 817
    resolver: *unsafe mut Resolver,
818 +
    /// Source symbols protected by active pattern references.
819 +
    loans: [*mut Symbol; MAX_LINEAR_BINDINGS],
820 +
    /// Number of initialized entries in `loans`.
821 +
    loanLen: u32,
818 822
    /// Binding count at entry to each active loop.
819 823
    loopMarks: [u32; MAX_LINEAR_LOOP_DEPTH],
820 824
    /// Available bindings at entry to each active loop.
821 825
    loopAvailable: [u64; MAX_LINEAR_LOOP_DEPTH],
822 826
    /// Available bindings shared by the exits from each active loop.
1777 1781
    );
1778 1782
}
1779 1783
1780 1784
/// Check if the `from` type is assignable to the `to` type, and return a
1781 1785
/// coercion plan if so.
1786 +
/// Referenced storage requires equal element types. Function values may gain
1787 +
/// an unsafe call requirement.
1782 1788
unsafe fn isAssignable(self: &mut Resolver, to: Type, from: Type, rval: *ast::Node) -> ?Coercion {
1783 -
    return isAssignableValue(self, to, from, rval, true);
1784 -
}
1785 -
1786 -
/// Check assignment while preserving function safety in referenced storage.
1787 -
unsafe fn isAssignableValue(
1788 -
    self: &mut Resolver, to: Type, from: Type, rval: *ast::Node,
1789 -
    allowFnSafetyCoercion: bool
1790 -
) -> ?Coercion {
1791 1789
    if to == Type::Unknown or from == Type::Unknown {
1792 1790
        return nil;
1793 1791
    }
1794 1792
    if from == Type::Undefined {
1795 1793
        // TODO: Don't let `undefined` be used in place of functions and other
1818 1816
            return Coercion::Identity;
1819 1817
        }
1820 1818
        if lhsMutable and not rhsMutable {
1821 1819
            return nil;
1822 1820
        }
1823 -
        return isAssignableValue(self, *lhsTarget, *rhsTarget, rval, false);
1821 +
        if typesEqual(*lhsTarget, *rhsTarget) {
1822 +
            return Coercion::Identity;
1823 +
        }
1824 +
        return nil;
1824 1825
    }
1825 1826
    if let case Type::TraitObject { class: lhsClass, traitInfo: lhsTraitInfo, mutable: lhsMutable } = to {
1826 1827
        if let case Type::Pointer { class: rhsClass, target: rhsTarget, mutable: rhsMutable } = from {
1827 1828
            if not pointerClassesAssignable(lhsClass, rhsClass)
1828 1829
                or (lhsMutable and not rhsMutable)
1856 1857
        }
1857 1858
        // Allow coercion from `*[T]` to `*[opaque]`, and mutable counterparts.
1858 1859
        if *lhsItem == Type::Opaque {
1859 1860
            return Coercion::Identity;
1860 1861
        }
1861 -
        return isAssignableValue(self, *lhsItem, *rhsItem, rval, false);
1862 +
        if typesEqual(*lhsItem, *rhsItem) {
1863 +
            return Coercion::Identity;
1864 +
        }
1865 +
        return nil;
1862 1866
    }
1863 1867
    match to {
1864 1868
        case Type::Array(lhs) => {
1865 1869
            let case Type::Array(rhs) = from
1866 1870
                else return nil;
1882 1886
                        return Coercion::Identity;
1883 1887
                    }
1884 1888
                    return nil;
1885 1889
                }
1886 1890
                case ast::NodeValue::ArrayRepeatLit(repeat) => {
1887 -
                    return isAssignableValue(self, *lhs.item, *rhs.item, repeat.item, allowFnSafetyCoercion);
1891 +
                    return isAssignable(self, *lhs.item, *rhs.item, repeat.item);
1888 1892
                }
1889 1893
                else => {
1890 1894
                    if typesEqual(*lhs.item, *rhs.item) {
1891 1895
                        return Coercion::Identity;
1892 1896
                    }
1897 1901
1898 1902
        case Type::Optional(inner) => {
1899 1903
            if from == Type::Nil {
1900 1904
                return Coercion::OptionalLift(to);
1901 1905
            }
1902 -
            if let _ = isAssignableValue(self, *inner, from, rval, allowFnSafetyCoercion) {
1906 +
            if let _ = isAssignable(self, *inner, from, rval) {
1903 1907
                return Coercion::OptionalLift(to);
1904 1908
            }
1905 1909
            if let case Type::Optional(fromInner) = from {
1906 -
                return isAssignableValue(self, *inner, *fromInner, rval, allowFnSafetyCoercion);
1910 +
                return isAssignable(self, *inner, *fromInner, rval);
1907 1911
            }
1908 1912
            return nil;
1909 1913
        }
1910 1914
1911 1915
        case Type::Fn(toInfo) => {
1912 1916
            // Allow function type structural matching.
1913 1917
            if let case Type::Fn(fromInfo) = from {
1914 1918
                if fnTypeEqual(toInfo, fromInfo) or (
1915 -
                    allowFnSafetyCoercion and toInfo.isUnsafe and not fromInfo.isUnsafe
1919 +
                    toInfo.isUnsafe and not fromInfo.isUnsafe
1916 1920
                    and fnSignatureEqual(toInfo, fromInfo)
1917 1921
                ) {
1918 1922
                    return Coercion::Identity;
1919 1923
                }
1920 1924
            }
7692 7696
}
7693 7697
7694 7698
/// Find the local root borrowed or consumed by an argument expression.
7695 7699
fn linearRootSymbol(self: &mut Resolver, node: *ast::Node) -> ?*mut Symbol {
7696 7700
    match node.value {
7697 -
        case ast::NodeValue::Ident(_) => return symbolFor(self, node),
7701 +
        case ast::NodeValue::Ident(_), ast::NodeValue::ScopeAccess(_) =>
7702 +
            return symbolFor(self, node),
7703 +
        case ast::NodeValue::As(expr) => return linearRootSymbol(self, expr.value),
7698 7704
        case ast::NodeValue::AddressOf(addr) => return linearRootSymbol(self, addr.target),
7699 7705
        case ast::NodeValue::FieldAccess(access) =>
7700 7706
            return linearRootSymbol(self, access.parent),
7701 7707
        case ast::NodeValue::Subscript { container, .. } =>
7702 7708
            return linearRootSymbol(self, container),
7703 7709
        case ast::NodeValue::Deref(target) => return linearRootSymbol(self, target),
7704 7710
        else => return nil,
7705 7711
    }
7706 7712
}
7707 7713
7714 +
/// Protect a pattern source until its reference bindings leave scope.
7715 +
unsafe fn addPatternLoan(checker: &mut LinearChecker, subject: *ast::Node)
7716 +
    throws (ResolveError)
7717 +
{
7718 +
    let root = linearRootSymbol(&mut *checker.resolver, subject) else return;
7719 +
    if checker.loanLen >= MAX_LINEAR_BINDINGS {
7720 +
        throw emitError(&mut *checker.resolver, subject, ErrorKind::Internal);
7721 +
    }
7722 +
    set checker.loans[checker.loanLen] = root;
7723 +
    set checker.loanLen += 1;
7724 +
}
7725 +
7726 +
/// Reject a write, mutable loan, or ownership transfer of a pattern source.
7727 +
unsafe fn checkPatternLoan(checker: &mut LinearChecker, node: *ast::Node)
7728 +
    throws (ResolveError)
7729 +
{
7730 +
    let root = linearRootSymbol(&mut *checker.resolver, node) else return;
7731 +
    for i in 0..checker.loanLen {
7732 +
        if checker.loans[i] == root {
7733 +
            throw emitError(&mut *checker.resolver, node, ErrorKind::BorrowConflict(root.name));
7734 +
        }
7735 +
    }
7736 +
}
7737 +
7738 +
/// Return whether a parameter can mutate or consume its argument's storage.
7739 +
fn isExclusiveArgument(ty: Type) -> bool {
7740 +
    match ty {
7741 +
        case Type::Pointer { mutable, .. } => return mutable,
7742 +
        case Type::Slice { mutable, .. } => return mutable,
7743 +
        case Type::TraitObject { mutable, .. } => return mutable,
7744 +
        else => return isMoveOnly(ty),
7745 +
    }
7746 +
}
7747 +
7708 7748
/// Add the value identifiers introduced by a pattern.
7749 +
/// Return whether the pattern introduces references to its source storage.
7709 7750
unsafe fn addLinearPatternBindings(
7710 7751
    checker: &mut LinearChecker,
7711 7752
    env: &mut LinearEnv,
7712 7753
    pattern: *ast::Node,
7713 -
) throws (ResolveError) {
7754 +
) -> bool throws (ResolveError) {
7755 +
    let mut hasReferences = false;
7714 7756
    match pattern.value {
7715 -
        case ast::NodeValue::Ident(_) => try addLinearBinding(checker, env, pattern),
7757 +
        case ast::NodeValue::Ident(_) => {
7758 +
            try addLinearBinding(checker, env, pattern);
7759 +
            if let ty = typeFor(&mut *checker.resolver, pattern) {
7760 +
                return isRefType(ty);
7761 +
            }
7762 +
        }
7716 7763
        case ast::NodeValue::Call(call) => {
7717 7764
            for arg in call.args {
7718 -
                try addLinearPatternBindings(checker, env, arg);
7765 +
                if try addLinearPatternBindings(checker, env, arg) {
7766 +
                    set hasReferences = true;
7767 +
                }
7719 7768
            }
7720 7769
        }
7721 7770
        case ast::NodeValue::RecordLit(lit) => {
7722 7771
            for fieldNode in lit.fields {
7723 7772
                let case ast::NodeValue::RecordLitField(field) = fieldNode.value
7724 7773
                    else panic "addLinearPatternBindings: expected field";
7725 -
                try addLinearPatternBindings(checker, env, field.value);
7774 +
                if try addLinearPatternBindings(checker, env, field.value) {
7775 +
                    set hasReferences = true;
7776 +
                }
7726 7777
            }
7727 7778
        }
7728 7779
        case ast::NodeValue::ArrayLit(items) => {
7729 7780
            for item in items {
7730 -
                try addLinearPatternBindings(checker, env, item);
7781 +
                if try addLinearPatternBindings(checker, env, item) {
7782 +
                    set hasReferences = true;
7783 +
                }
7731 7784
            }
7732 7785
        }
7733 7786
        else => {}
7734 7787
    }
7788 +
    return hasReferences;
7735 7789
}
7736 7790
7737 7791
/// Check a lexical block and exact-use of locals introduced in it.
7738 7792
unsafe fn checkLinearBlock(
7739 7793
    checker: &mut LinearChecker,
7882 7936
    for prongNode in matchExpr.prongs {
7883 7937
        let case ast::NodeValue::MatchProng(prong) = prongNode.value
7884 7938
            else panic "checkLinearMatch: expected prong";
7885 7939
        let mut branch = base;
7886 7940
        let bindingsStart = branch.len;
7941 +
        let loanStart = checker.loanLen;
7887 7942
        match prong.arm {
7888 7943
            case ast::ProngArm::Case(patterns) => {
7889 7944
                for pattern in patterns {
7890 -
                    try addLinearPatternBindings(checker, &mut branch, pattern);
7945 +
                    if try addLinearPatternBindings(checker, &mut branch, pattern) {
7946 +
                        try addPatternLoan(checker, matchExpr.subject);
7947 +
                    }
7891 7948
                }
7892 7949
            }
7893 7950
            case ast::ProngArm::Binding(binding) => {
7894 -
                try addLinearPatternBindings(checker, &mut branch, binding);
7951 +
                if try addLinearPatternBindings(checker, &mut branch, binding) {
7952 +
                    try addPatternLoan(checker, matchExpr.subject);
7953 +
                }
7895 7954
            }
7896 7955
            case ast::ProngArm::Else => {}
7897 7956
        }
7898 7957
        if prong.guard <> nil {
7899 7958
            for i in bindingsStart..branch.len {
7908 7967
        if let guard = prong.guard {
7909 7968
            try checkLinearNode(checker, &mut branch, guard, LinearUse::Consume);
7910 7969
        }
7911 7970
        try checkLinearNode(checker, &mut branch, prong.body, LinearUse::Discard);
7912 7971
        try finishLinearScope(checker, &mut branch, bindingsStart);
7972 +
        set checker.loanLen = loanStart;
7913 7973
        if haveResult {
7914 7974
            let previous = result;
7915 7975
        try joinLinearBranches(checker, &mut result, &previous, &branch, node);
7916 7976
        } else {
7917 7977
            set result = branch;
7974 8034
                set haveReceiver = true;
7975 8035
            }
7976 8036
            else => {}
7977 8037
        }
7978 8038
        if haveReceiver {
8039 +
            if receiverMutable or receiverClass == types::PointerClass::Owned {
8040 +
                try checkPatternLoan(checker, access.parent);
8041 +
            }
7979 8042
            if receiverClass <> types::PointerClass::Unsafe {
7980 8043
                let root = linearRootSymbol(&mut *checker.resolver, access.parent);
7981 8044
                if let rootSym = root {
7982 8045
                    set roots[rootsLen] = rootSym;
7983 8046
                    set exclusive[rootsLen] =
7993 8056
        }
7994 8057
    }
7995 8058
7996 8059
    for arg, i in call.args {
7997 8060
        let expected = *info.paramTypes[i];
8061 +
        if isExclusiveArgument(expected) {
8062 +
            try checkPatternLoan(checker, arg);
8063 +
        }
7998 8064
        let root = linearRootSymbol(&mut *checker.resolver, arg);
7999 8065
        let mut argExclusive = isMoveOnly(expected);
8000 8066
        if let case Type::Pointer { class: types::PointerClass::Ref, mutable, .. } = expected {
8001 8067
            set argExclusive = mutable;
8002 8068
        } else if let case Type::Slice { class: types::PointerClass::Ref, mutable, .. } = expected {
8055 8121
        LinearUse::Consume,
8056 8122
    );
8057 8123
    let base = *env;
8058 8124
    let mut thenEnv = base;
8059 8125
    let bindingsStart = thenEnv.len;
8060 -
    try addLinearPatternBindings(checker, &mut thenEnv, conditional.pattern.pattern);
8126 +
    let loanStart = checker.loanLen;
8127 +
    if try addLinearPatternBindings(checker, &mut thenEnv, conditional.pattern.pattern) {
8128 +
        try addPatternLoan(checker, conditional.pattern.scrutinee);
8129 +
    }
8061 8130
    if let guard = conditional.pattern.guard {
8062 8131
        try checkLinearNode(checker, &mut thenEnv, guard, LinearUse::Consume);
8063 8132
    }
8064 8133
    try checkLinearNode(checker, &mut thenEnv, conditional.thenBranch, LinearUse::Discard);
8065 8134
    try finishLinearScope(checker, &mut thenEnv, bindingsStart);
8135 +
    set checker.loanLen = loanStart;
8066 8136
    let mut elseEnv = base;
8067 8137
    if let branch = conditional.elseBranch {
8068 8138
        try checkLinearNode(checker, &mut elseEnv, branch, LinearUse::Discard);
8069 8139
    }
8070 8140
    try joinLinearBranches(checker, env, &thenEnv, &elseEnv, node);
8081 8151
        return;
8082 8152
    }
8083 8153
    match node.value {
8084 8154
        case ast::NodeValue::Ident(_) => {
8085 8155
            if usage == LinearUse::Consume {
8156 +
                if let ty = typeFor(&mut *checker.resolver, node); isExclusiveArgument(ty) {
8157 +
                    try checkPatternLoan(checker, node);
8158 +
                }
8086 8159
                try consumeLinearIdent(checker, env, node);
8087 8160
            }
8088 8161
        }
8089 8162
        case ast::NodeValue::ExprStmt(expr) => {
8090 8163
            if let exprTy = typeFor(&mut *checker.resolver, expr) {
8116 8189
            if isUndefined {
8117 8190
                markLinearBindingUnavailable(&mut *checker.resolver, env, node);
8118 8191
            }
8119 8192
        }
8120 8193
        case ast::NodeValue::Assign(assign) => {
8194 +
            try checkPatternLoan(checker, assign.left);
8121 8195
            let mut target: ?u32 = nil;
8122 8196
            let mut targetLinear = false;
8123 8197
            if let leftTy = typeFor(&mut *checker.resolver, assign.left) {
8124 8198
                if isMoveOnly(leftTy) {
8125 8199
                    set targetLinear = isLinear(leftTy);
8150 8224
                set env.available |= (1 as u64) << (index as u64);
8151 8225
            }
8152 8226
        }
8153 8227
        case ast::NodeValue::Call(call) => try checkLinearCall(checker, env, node, call),
8154 8228
        case ast::NodeValue::AddressOf(addr) => {
8229 +
            if addr.mutable {
8230 +
                try checkPatternLoan(checker, addr.target);
8231 +
            }
8155 8232
            try checkLinearNode(checker, env, addr.target, LinearUse::Borrow);
8156 8233
        }
8157 8234
        case ast::NodeValue::Deref(target) => {
8158 8235
            if let resultTy = typeFor(&mut *checker.resolver, node) {
8159 8236
                if isMoveOnly(resultTy) and usage == LinearUse::Consume {
8357 8434
                whileStmt.pattern.scrutinee,
8358 8435
                LinearUse::Consume,
8359 8436
            );
8360 8437
            let mut conditionExit = bodyEnv;
8361 8438
            let start = bodyEnv.len;
8362 -
            try addLinearPatternBindings(checker, &mut bodyEnv, whileStmt.pattern.pattern);
8439 +
            let loanStart = checker.loanLen;
8440 +
            if try addLinearPatternBindings(checker, &mut bodyEnv, whileStmt.pattern.pattern) {
8441 +
                try addPatternLoan(checker, whileStmt.pattern.scrutinee);
8442 +
            }
8363 8443
            if let guard = whileStmt.pattern.guard {
8364 8444
                try checkLinearNode(checker, &mut bodyEnv, guard, LinearUse::Consume);
8365 8445
                let mut guardExit = bodyEnv;
8366 8446
                try finishLinearScope(checker, &mut guardExit, start);
8367 8447
                let previous = conditionExit;
8374 8454
                );
8375 8455
            }
8376 8456
            setLinearLoopNaturalExit(checker, &conditionExit);
8377 8457
            try checkLinearNode(checker, &mut bodyEnv, whileStmt.body, LinearUse::Discard);
8378 8458
            try finishLinearScope(checker, &mut bodyEnv, start);
8459 +
            set checker.loanLen = loanStart;
8379 8460
            try checkLinearLoopBackEdge(checker, &bodyEnv, whileStmt.body);
8380 8461
            exitLinearLoop(checker);
8381 8462
            set *env = conditionExit;
8382 8463
            if let elseBranch = whileStmt.elseBranch {
8383 8464
                let mut elseEnv = conditionExit;
8490 8571
    params: *mut [*ast::Node],
8491 8572
    body: *ast::Node,
8492 8573
) throws (ResolveError) {
8493 8574
    let mut checker = LinearChecker {
8494 8575
        resolver: self as *unsafe mut Resolver,
8576 +
        loans: undefined,
8577 +
        loanLen: 0,
8495 8578
        loopMarks: undefined,
8496 8579
        loopAvailable: undefined,
8497 8580
        loopExitAvailable: undefined,
8498 8581
        loopHasNaturalExit: undefined,
8499 8582
        loopBreakSeen: undefined,
lib/std/lang/resolver/printer.rad +1 -1
534 534
        }
535 535
        case super::ErrorKind::RefBinding => {
536 536
            io::print("references cannot be bound to locals");
537 537
        }
538 538
        case super::ErrorKind::BorrowConflict(name) => {
539 -
            printQuoted("conflicting call-scoped loans of '", name);
539 +
            printQuoted("conflicting borrow of '", name);
540 540
        }
541 541
        case super::ErrorKind::UnsafeOperation => {
542 542
            io::print("unsafe operation requires an unsafe function or block");
543 543
        }
544 544
        case super::ErrorKind::UnsafeCall => {
lib/std/lang/resolver/tests.rad +98 -0
5829 5829
        "fn replace(slot: &mut unsafe fn()) {} fn load() {} fn run() { let mut callback: fn() = load; replace(&mut callback); }");
5830 5830
    let err = try expectError(&result);
5831 5831
    let case super::ErrorKind::TypeMismatch(_) = err.kind
5832 5832
        else throw testing::TestError::Failed;
5833 5833
}
5834 +
5835 +
/// Referenced storage must preserve its exact element type in every context.
5836 +
@test unsafe fn testPointerStorageCoercionsRejected() throws (testing::TestError) {
5837 +
    let programs = &[
5838 +
        "fn replace(p: &mut ?*u8) { set *p = nil; } static DATA: u8 = 7; fn run() { let mut p = &DATA; replace(&mut p); }",
5839 +
        "fn replace(p: &mut ?*u8) { set *p = nil; } static DATA: u8 = 7; unsafe fn run() { let mut p = &DATA; replace(&mut p); }",
5840 +
        "fn replace(p: &mut ?*u8) { set *p = nil; } static DATA: u8 = 7; fn run() { let mut p = &DATA; unsafe { replace(&mut p); } }",
5841 +
        "fn take(p: *?u32) {} fn run(p: *u32) { take(p); }",
5842 +
        "unsafe fn take(p: *unsafe mut ?*u8) {} unsafe fn run(p: *unsafe mut *u8) { take(p); }",
5843 +
        "fn take(p: *[?*u8]) {} fn run(p: *[*u8]) { take(p); }",
5844 +
        "unsafe fn take(p: *mut [?*u8]) {} unsafe fn run(p: *mut [*u8]) { take(p); }",
5845 +
        "fn take(p: &mut **u8) {} fn run(p: &mut *mut *u8) { take(p); }",
5846 +
    ];
5847 +
    for program in programs {
5848 +
        let mut a = testResolver();
5849 +
        let result = try resolveProgramStr(&mut a, program);
5850 +
        let err = try expectError(&result);
5851 +
        let case super::ErrorKind::TypeMismatch(_) = err.kind
5852 +
            else throw testing::TestError::Failed;
5853 +
    }
5854 +
    try expectAnalyzeOk("fn take(p: &mut ?*u8) {} fn run() { let mut p: ?*u8 = nil; take(&mut p); }");
5855 +
    try expectAnalyzeOk("fn run(p: *mut u8) -> *u8 { return p; }");
5856 +
    try expectAnalyzeOk("fn run(p: *u8) -> *opaque { return p; }");
5857 +
}
5858 +
5859 +
/// Casts cannot add or remove optionality in referenced storage.
5860 +
@test unsafe fn testPointerOptionalStorageCastsRejected() throws (testing::TestError) {
5861 +
    let programs = &[
5862 +
        "fn run(p: **u8) { p as *?*u8; }",
5863 +
        "unsafe fn run(p: *mut *u8) { p as *mut ?*u8; }",
5864 +
        "fn run(p: *mut *u8) { unsafe { p as *mut ?*u8; } }",
5865 +
        "unsafe fn run(p: *mut ?*u8) { p as *mut *u8; }",
5866 +
        "unsafe fn run(p: *unsafe mut *u8) { p as *unsafe mut ?*u8; }",
5867 +
        "unsafe fn run(p: *mut [*u8]) { p as *mut [?*u8]; }",
5868 +
    ];
5869 +
    for program in programs {
5870 +
        let mut a = testResolver();
5871 +
        let result = try resolveProgramStr(&mut a, program);
5872 +
        let err = try expectError(&result);
5873 +
        let case super::ErrorKind::InvalidAsCast(_) = err.kind
5874 +
            else throw testing::TestError::Failed;
5875 +
    }
5876 +
}
5877 +
5878 +
/// Pattern references prevent replacement of their source in every context.
5879 +
@test unsafe fn testPatternLoanMutationRejected() throws (testing::TestError) {
5880 +
    let programs = &[
5881 +
        "union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); match &u { case U::A(p) => { set u = U::B(1); *p; } else => {} } }",
5882 +
        "union U: Copy { A(u64), B(u64) } unsafe fn run() { let mut u = U::A(7); match &u { case U::A(p) => { set u = U::B(1); *p; } else => {} } }",
5883 +
        "union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); match &mut u { case U::A(p) => { unsafe { set u = U::B(1); } *p; } else => {} } }",
5884 +
        "union U: Copy { A(u64), B(u64) } fn change(u: &mut U) { set *u = U::B(1); } fn run() { let mut u = U::A(7); match &u { case U::A(p) => { change(&mut u); *p; } else => {} } }",
5885 +
        "union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); if let case U::A(p) = &u { set u = U::B(1); *p; } }",
5886 +
        "union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); while let case U::A(p) = &u { set u = U::B(1); *p; } }",
5887 +
        "union U: Copy { A(u64), B(u64) } fn run(u: *mut U) { match u { case U::A(p) => { let moved = u; *p; } else => {} } }",
5888 +
    ];
5889 +
    for program in programs {
5890 +
        let mut a = testResolver();
5891 +
        let result = try resolveProgramStr(&mut a, program);
5892 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("u"));
5893 +
    }
5894 +
}
5895 +
5896 +
/// Pattern loans end at their scope and permit writes through mutable payload references.
5897 +
@test unsafe fn testPatternLoanScopeAllowed() throws (testing::TestError) {
5898 +
    try expectAnalyzeOk("union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); match &u { case U::A(p) => { *p; } else => {} } set u = U::B(1); }");
5899 +
    try expectAnalyzeOk("union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); match &mut u { case U::A(p) => { set *p = 8; } else => {} } set u = U::B(1); }");
5900 +
    try expectAnalyzeOk("union U: Copy { A(u64), B(u64) } fn run() { let mut u = U::A(7); if let case U::A(p) = &u { *p; } else { set u = U::A(1); } set u = U::B(1); }");
5901 +
}
5902 +
5903 +
/// Replacing a borrowed pointer payload cannot forge an address.
5904 +
@test unsafe fn testBorrowedPointerPayloadReplacementRejected() throws (testing::TestError) {
5905 +
    let programs = &[
5906 +
        "static DATA: u8 = 7; union U: Copy { A(*u8), B(u64) } fn run() -> u8 { let mut u = U::A(&DATA); match &u { case U::A(p) => { set u = U::B(1); return **p; } else => return 0, } }",
5907 +
        "static DATA: u8 = 7; union U: Copy { A(*u8), B(u64) } unsafe fn run() -> u8 { let mut u = U::A(&DATA); match &u { case U::A(p) => { set u = U::B(1); return **p; } else => return 0, } }",
5908 +
    ];
5909 +
    for program in programs {
5910 +
        let mut a = testResolver();
5911 +
        let result = try resolveProgramStr(&mut a, program);
5912 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("u"));
5913 +
    }
5914 +
}
5915 +
5916 +
/// Guards, nested patterns, and unsafe calls must preserve pattern source storage.
5917 +
@test unsafe fn testPatternLoanIndirectMutationRejected() throws (testing::TestError) {
5918 +
    let programs = &[
5919 +
        "fn run() { let mut u: ?u64 = 7; match &u { p => { set u = nil; *p; } else => {} } }",
5920 +
        "union U: Copy { A(u64), B(u64) } fn change(u: &mut U) -> bool { set *u = U::B(1); return true; } fn run() { let mut u = U::A(7); match &u { case U::A(p) if change(&mut u) => { *p; } else => {} } }",
5921 +
        "union U: Copy { A(u64), B(u64) } unsafe fn change(u: *unsafe mut U) { set *u = U::B(1); } unsafe fn run(u: *unsafe mut U) { match u { case U::A(p) => { change(u); *p; } else => {} } }",
5922 +
        "union U: Copy { A(u64), B(u64) } record R: Copy { value: U } fn run() { let mut u = R { value: U::A(7) }; match &u.value { case U::A(p) => { set u.value = U::B(1); *p; } else => {} } }",
5923 +
        "union U: Copy { A(u64), B(u64) } fn run() { let mut u = [U::A(7)]; match &u[0] { case U::A(p) => { set u[0] = U::B(1); *p; } else => {} } }",
5924 +
        "union U: Copy { A(u64), B(u64) } unsafe fn (u: *unsafe mut U) change() { set *u = U::B(1); } unsafe fn run(u: *unsafe mut U) { match u { case U::A(p) => { u.change(); *p; } else => {} } }",
5925 +
    ];
5926 +
    for program in programs {
5927 +
        let mut a = testResolver();
5928 +
        let result = try resolveProgramStr(&mut a, program);
5929 +
        try expectErrorKind(&result, super::ErrorKind::BorrowConflict("u"));
5930 +
    }
5931 +
}
test/tests/match.borrow.scope.rad added +25 -0
1 +
//! returns: 0
2 +
3 +
/// Values read and written through pattern references.
4 +
union Value: Copy { Number(u32), Empty }
5 +
6 +
/// Mutate the payload, then replace the union after the match.
7 +
@default fn main() -> i32 {
8 +
    let mut value = Value::Number(7);
9 +
    match &mut value {
10 +
        case Value::Number(n) => { set *n = 9; }
11 +
        else => return 1,
12 +
    }
13 +
    match &value {
14 +
        case Value::Number(n) => { if *n <> 9 { return 2; } }
15 +
        else => return 3,
16 +
    }
17 +
    set value = Value::Empty;
18 +
    let mut pointer: ?*u8 = nil;
19 +
    clear(&mut pointer);
20 +
    if pointer <> nil { return 4; }
21 +
    return 0;
22 +
}
23 +
24 +
/// Clear storage that has an optional pointer type.
25 +
fn clear(pointer: &mut ?*u8) { set *pointer = nil; }
test/tests/union.match.ref.rad +2 -1
32 32
/// While-let on a mutable union reference.
33 33
fn whileLetRef(ptr: *mut Option) -> u32 {
34 34
    let mut sum: u32 = 0;
35 35
    while let case Option::Some(val) = ptr {
36 36
        set sum += *val;
37 -
        set *ptr = Option::None;
37 +
        break;
38 38
    }
39 +
    set *ptr = Option::None;
39 40
    return sum;
40 41
}
41 42
42 43
/// Match on an optional reference with binding pattern.
43 44
fn optionalRef(ptr: ?*u32) -> u32 {
test/tests/union.match.ref.ril +4 -4
50 50
fn w32 $whileLetRef(w64 %0) {
51 51
  @entry0
52 52
    jmp @while1(%0, 0);
53 53
  @while1(w64 %1, w32 %4)
54 54
    load w8 %2 %1 0;
55 -
    br.eq w8 %2 1 @body2 @merge3;
55 +
    br.eq w8 %2 1 @body2 @merge3(%4);
56 56
  @body2
57 57
    add w64 %3 %1 4;
58 58
    load w32 %5 %3 0;
59 59
    add w32 %6 %4 %5;
60 +
    jmp @merge3(%6);
61 +
  @merge3(w32 %9)
60 62
    reserve %7 8 4;
61 63
    store w8 0 %7 0;
62 64
    blit %1 %7 8;
63 -
    jmp @while1(%1, %6);
64 -
  @merge3
65 -
    ret %4;
65 +
    ret %9;
66 66
}
67 67
68 68
fn w32 $optionalRef(w64 %0) {
69 69
  @entry0
70 70
    br.ne w32 %0 0 @then1 @else2;