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.
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; |