Check pointer target mutability for writes
682efa2140bc35aaf8a48b04df2939aa573a13e8e9834863d0694aa630b6540b
1 parent
65221249
lib/std/lang/resolver.rad
+26 -30
| 65 | 65 | /// A single method signature within a trait. |
|
| 66 | 66 | export record TraitMethod: Copy { |
|
| 67 | 67 | /// Method name. |
|
| 68 | 68 | name: *[u8], |
|
| 69 | 69 | /// Function type for the method, excluding the receiver. |
|
| 70 | - | fnType: *FnType, |
|
| 70 | + | fnType: *mut FnType, |
|
| 71 | 71 | /// Whether the receiver is mutable. |
|
| 72 | 72 | mutable: bool, |
|
| 73 | 73 | /// Pointer-like class used by the receiver. |
|
| 74 | 74 | receiverClass: types::PointerClass, |
|
| 75 | 75 | /// V-table slot index. |
| 97 | 97 | /// Name of the concrete type. |
|
| 98 | 98 | concreteTypeName: *[u8], |
|
| 99 | 99 | /// Method name. |
|
| 100 | 100 | name: *[u8], |
|
| 101 | 101 | /// Function type excluding the receiver. |
|
| 102 | - | fnType: *FnType, |
|
| 102 | + | fnType: *mut FnType, |
|
| 103 | 103 | /// Whether the receiver is mutable. |
|
| 104 | 104 | mutable: bool, |
|
| 105 | 105 | /// Pointer-like class used by the receiver. |
|
| 106 | 106 | receiverClass: types::PointerClass, |
|
| 107 | 107 | /// Symbol for the method. |
| 312 | 312 | /// Eg. `[i32; 32]`. |
|
| 313 | 313 | Array(ArrayType), |
|
| 314 | 314 | /// Eg. `?T`. |
|
| 315 | 315 | Optional(*Type), |
|
| 316 | 316 | /// Eg. `fn id(i32) -> i32`. |
|
| 317 | - | Fn(*FnType), |
|
| 317 | + | Fn(*mut FnType), |
|
| 318 | 318 | /// Named, ie. user-defined types, includes union variants. |
|
| 319 | 319 | Nominal(*NominalType), |
|
| 320 | 320 | /// Owning trait object. An erased type with v-table. |
|
| 321 | 321 | TraitObject { |
|
| 322 | 322 | /// Ownership and safety class. |
| 851 | 851 | /// Stack of loop contexts for nested loops. |
|
| 852 | 852 | loopStack: [LoopCtx; MAX_LOOP_DEPTH], |
|
| 853 | 853 | /// Current loop depth, indexes into loop stack. |
|
| 854 | 854 | loopDepth: u32, |
|
| 855 | 855 | /// Signature of the function currently being analyzed. |
|
| 856 | - | currentFn: ?*unsafe FnType, |
|
| 856 | + | currentFn: ?*unsafe mut FnType, |
|
| 857 | 857 | /// Current module being analyzed. |
|
| 858 | 858 | currentMod: u16, |
|
| 859 | 859 | /// Whether the current lexical context permits unsafe operations. |
|
| 860 | 860 | inUnsafeContext: bool, |
|
| 861 | 861 | /// Configuration for semantic analysis. |
| 927 | 927 | ||
| 928 | 928 | return entry; |
|
| 929 | 929 | } |
|
| 930 | 930 | ||
| 931 | 931 | /// Allocate a function type descriptor and return a pointer to it. |
|
| 932 | - | unsafe fn allocFnType(self: &mut Resolver, info: FnType) -> *FnType { |
|
| 932 | + | unsafe fn allocFnType(self: &mut Resolver, info: FnType) -> *mut FnType { |
|
| 933 | 933 | let entry = try! alloc::alloc( |
|
| 934 | 934 | &mut self.arena, @sizeOf(FnType), @alignOf(FnType) |
|
| 935 | 935 | ) as *mut FnType; |
|
| 936 | 936 | ||
| 937 | 937 | set *entry = info; |
| 1182 | 1182 | } |
|
| 1183 | 1183 | } |
|
| 1184 | 1184 | } |
|
| 1185 | 1185 | ||
| 1186 | 1186 | /// Set the expected return type for a new function body. |
|
| 1187 | - | unsafe fn enterFn(self: &mut Resolver, node: *ast::Node, ty: &FnType) { |
|
| 1187 | + | unsafe fn enterFn(self: &mut Resolver, node: *ast::Node, ty: &mut FnType) { |
|
| 1188 | 1188 | assert self.currentFn == nil, "enterFn: already in a function"; |
|
| 1189 | - | set self.currentFn = ty as *unsafe FnType; |
|
| 1189 | + | set self.currentFn = ty as *unsafe mut FnType; |
|
| 1190 | 1190 | enterScope(self, node); |
|
| 1191 | 1191 | } |
|
| 1192 | 1192 | ||
| 1193 | 1193 | /// Clear the expected return type when leaving a function body. |
|
| 1194 | 1194 | fn exitFn(self: &mut Resolver) { |
| 3382 | 3382 | throwList: &[], |
|
| 3383 | 3383 | isUnsafe: ast::hasAttribute(attrMask, ast::Attribute::Unsafe), |
|
| 3384 | 3384 | localCount: 0, |
|
| 3385 | 3385 | }; |
|
| 3386 | 3386 | // Enter the function scope to process parameters. |
|
| 3387 | - | enterFn(self, node, &fnType); |
|
| 3387 | + | enterFn(self, node, &mut fnType); |
|
| 3388 | 3388 | ||
| 3389 | 3389 | if decl.sig.params.len > MAX_FN_PARAMS { |
|
| 3390 | 3390 | exitFn(self); |
|
| 3391 | 3391 | throw emitError(self, node, ErrorKind::FnParamOverflow(CountMismatch { |
|
| 3392 | 3392 | expected: MAX_FN_PARAMS, |
| 3456 | 3456 | ||
| 3457 | 3457 | /// Resolve a function or method body and restore the enclosing context. |
|
| 3458 | 3458 | unsafe fn resolveExecutableBody( |
|
| 3459 | 3459 | self: &mut Resolver, |
|
| 3460 | 3460 | node: *ast::Node, |
|
| 3461 | - | fnType: *FnType, |
|
| 3461 | + | fnType: *mut FnType, |
|
| 3462 | 3462 | receiverName: ?*ast::Node, |
|
| 3463 | 3463 | params: *mut [*ast::Node], |
|
| 3464 | 3464 | body: *ast::Node, |
|
| 3465 | 3465 | ) throws (ResolveError) { |
|
| 3466 | 3466 | let wasUnsafe = self.inUnsafeContext; |
| 5572 | 5572 | if let method = findMethod(self, subjectTy, methodName) { |
|
| 5573 | 5573 | // Reject mutable-receiver methods on immutable bindings. |
|
| 5574 | 5574 | // If the parent is already a mutable pointer, the receiver is fine. |
|
| 5575 | 5575 | // Otherwise, check that the parent can yield a mutable borrow. |
|
| 5576 | 5576 | if method.mutable { |
|
| 5577 | - | let mut isMutPtr = false; |
|
| 5578 | - | if let case Type::Pointer { mutable, .. } = parentTy { |
|
| 5579 | - | set isMutPtr = mutable; |
|
| 5580 | - | } |
|
| 5581 | - | if not isMutPtr and not (try canBorrowMutFrom(self, access.parent)) { |
|
| 5577 | + | if not try canMutateThrough(self, access.parent) { |
|
| 5582 | 5578 | throw emitError(self, access.parent, ErrorKind::ImmutableBinding); |
|
| 5583 | 5579 | } |
|
| 5584 | 5580 | } |
|
| 5585 | 5581 | // Check arguments (excluding receiver). |
|
| 5586 | 5582 | try checkCallArgs(self, node, call, method.fnType, ctx); |
| 5665 | 5661 | // Slice assignment: `slice[range] = value`. |
|
| 5666 | 5662 | if let case ast::NodeValue::Subscript { container, index } = assign.left.value { |
|
| 5667 | 5663 | if let case ast::NodeValue::Range(range) = index.value { |
|
| 5668 | 5664 | try infer(self, index); |
|
| 5669 | 5665 | let containerTy = try infer(self, container); |
|
| 5670 | - | if not try canBorrowMutFrom(self, container) { |
|
| 5666 | + | if not try canMutateThrough(self, container) { |
|
| 5671 | 5667 | throw emitError(self, container, ErrorKind::ImmutableBinding); |
|
| 5672 | 5668 | } |
|
| 5673 | 5669 | let subjectTy = autoDeref(containerTy); |
|
| 5674 | 5670 | try checkSliceRangeIndices(self, range); |
|
| 5675 | 5671 |
| 6244 | 6240 | throw emitError(self, access.parent, ErrorKind::ExpectedRecord); |
|
| 6245 | 6241 | } |
|
| 6246 | 6242 | } |
|
| 6247 | 6243 | } |
|
| 6248 | 6244 | ||
| 6245 | + | /// Check target mutability for implicit pointer access. |
|
| 6246 | + | unsafe fn canMutateThrough(self: &mut Resolver, node: *ast::Node) -> bool |
|
| 6247 | + | throws (ResolveError) |
|
| 6248 | + | { |
|
| 6249 | + | let ty = try infer(self, node); |
|
| 6250 | + | match ty { |
|
| 6251 | + | case Type::Pointer { mutable, .. } => return mutable, |
|
| 6252 | + | case Type::Slice { mutable, .. } => return mutable, |
|
| 6253 | + | else => return try canBorrowMutFrom(self, node), |
|
| 6254 | + | } |
|
| 6255 | + | } |
|
| 6256 | + | ||
| 6249 | 6257 | /// Determine whether an expression can yield a mutable location for borrowing. |
|
| 6250 | 6258 | unsafe fn canBorrowMutFrom(self: &mut Resolver, node: *ast::Node) -> bool |
|
| 6251 | 6259 | throws (ResolveError) |
|
| 6252 | 6260 | { |
|
| 6253 | 6261 | match node.value { |
|
| 6254 | 6262 | case ast::NodeValue::Ident(name) => { |
|
| 6255 | 6263 | let sym = findValueSymbol(self.scope, name) |
|
| 6256 | 6264 | else return false; |
|
| 6257 | 6265 | let case SymbolData::Value { mutable, .. } = sym.data |
|
| 6258 | 6266 | else return false; |
|
| 6259 | - | // Check if the binding itself is mutable, or if it's a mutable pointer. |
|
| 6260 | - | if mutable { |
|
| 6261 | - | return true; |
|
| 6262 | - | } |
|
| 6263 | - | // Check if the type is a mutable pointer or slice. |
|
| 6264 | - | let ty = typeFor(self, node) else return false; |
|
| 6265 | - | if let case Type::Pointer { mutable, .. } = ty { |
|
| 6266 | - | return mutable; |
|
| 6267 | - | } |
|
| 6268 | - | if let case Type::Slice { mutable, .. } = ty { |
|
| 6269 | - | return mutable; |
|
| 6270 | - | } |
|
| 6271 | - | return false; |
|
| 6267 | + | return mutable; |
|
| 6272 | 6268 | } |
|
| 6273 | 6269 | case ast::NodeValue::FieldAccess(access) => { |
|
| 6274 | 6270 | let parentTy = try infer(self, access.parent); |
|
| 6275 | 6271 | if let case Type::Slice { .. } = autoDeref(parentTy) { |
|
| 6276 | 6272 | try requireUnsafe(self, node); |
|
| 6277 | 6273 | } |
|
| 6278 | - | return try canBorrowMutFrom(self, access.parent); |
|
| 6274 | + | return try canMutateThrough(self, access.parent); |
|
| 6279 | 6275 | } |
|
| 6280 | 6276 | case ast::NodeValue::ScopeAccess(_) => { |
|
| 6281 | 6277 | // Module-qualified access to a top-level symbol. A `static` |
|
| 6282 | 6278 | // binds as a mutable value; a `constant` does not. |
|
| 6283 | 6279 | let _ = try infer(self, node); |
| 6296 | 6292 | ||
| 6297 | 6293 | if let case Type::Slice { mutable, .. } = subjectTy { |
|
| 6298 | 6294 | return mutable; |
|
| 6299 | 6295 | } |
|
| 6300 | 6296 | if let case Type::Array(_) = subjectTy { |
|
| 6301 | - | return try canBorrowMutFrom(self, container); |
|
| 6297 | + | return try canMutateThrough(self, container); |
|
| 6302 | 6298 | } |
|
| 6303 | 6299 | return false; |
|
| 6304 | 6300 | } |
|
| 6305 | 6301 | case ast::NodeValue::ArrayLit(_), |
|
| 6306 | 6302 | ast::NodeValue::ArrayRepeatLit(_) => |
lib/std/lang/resolver/tests.rad
+26 -1
| 5469 | 5469 | @test unsafe fn testPointerArithmeticRequiresUnsafe() throws (testing::TestError) { |
|
| 5470 | 5470 | let programs = &[ |
|
| 5471 | 5471 | "fn run(p: *u8) -> *u8 { return p + 1; }", |
|
| 5472 | 5472 | "fn run(p: *u8) -> *u8 { return 1 + p; }", |
|
| 5473 | 5473 | "fn run(p: *u8) -> *u8 { return p - 1; }", |
|
| 5474 | - | "fn run(p: *mut u8) { set p += 1; }", |
|
| 5474 | + | "fn run(input: *mut u8) { let mut p = input; set p += 1; }", |
|
| 5475 | 5475 | "static DATA: [u8; 1] = [42]; fn run() -> u8 { let p = &DATA[0]; return *(p + 1); }", |
|
| 5476 | 5476 | ]; |
|
| 5477 | 5477 | for program in programs { |
|
| 5478 | 5478 | let mut a = testResolver(); |
|
| 5479 | 5479 | let result = try resolveProgramStr(&mut a, program); |
| 5927 | 5927 | let mut a = testResolver(); |
|
| 5928 | 5928 | let result = try resolveProgramStr(&mut a, program); |
|
| 5929 | 5929 | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("u")); |
|
| 5930 | 5930 | } |
|
| 5931 | 5931 | } |
|
| 5932 | + | ||
| 5933 | + | /// Binding mutability does not permit writes through immutable pointers. |
|
| 5934 | + | @test unsafe fn testMutableBindingImmutableTargetRejected() throws (testing::TestError) { |
|
| 5935 | + | let programs = &[ |
|
| 5936 | + | "record R: Copy { n: u8 } fn run(input: *R) { let mut p = input; set p.n = 1; }", |
|
| 5937 | + | "record R: Copy { n: u8 } fn run(input: *R) { let mut p = input; let q = &mut p.n; }", |
|
| 5938 | + | "static DATA: [u8; 1] = [0]; fn run() { let mut p = &DATA; set p[0] = 1; }", |
|
| 5939 | + | "static DATA: [u8; 1] = [0]; fn run() { let mut p = &DATA; set p[..] = 1; }", |
|
| 5940 | + | "static DATA: [u8; 1] = [0]; fn run() { let mut p = &DATA; let q = &mut p[0]; }", |
|
| 5941 | + | "record R: Copy { n: u8 } fn (r: &mut R) change() { set r.n = 1; } fn run(input: *R) { let mut p = input; p.change(); }", |
|
| 5942 | + | "fn run(p: *mut u8, q: *mut u8) { set p = q; }", |
|
| 5943 | + | ]; |
|
| 5944 | + | for program in programs { |
|
| 5945 | + | let mut a = testResolver(); |
|
| 5946 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 5947 | + | try expectErrorKind(&result, super::ErrorKind::ImmutableBinding); |
|
| 5948 | + | } |
|
| 5949 | + | } |
|
| 5950 | + | ||
| 5951 | + | /// Mutable targets support access through fixed and mutable pointer bindings. |
|
| 5952 | + | @test unsafe fn testMutablePointerTargetsAllowed() throws (testing::TestError) { |
|
| 5953 | + | try expectAnalyzeOk("record R: Copy { n: u8 } fn (r: &mut R) change() { set r.n = 2; } fn run(p: *mut R) { set p.n = 1; p.change(); let q = &mut p.n; set *q = 3; }"); |
|
| 5954 | + | try expectAnalyzeOk("static DATA: [u8; 2] = [0, 0]; fn run() { let p = &mut DATA; set p[0] = 1; set p[..] = 2; let q = &mut p[0]; set *q = 3; }"); |
|
| 5955 | + | try expectAnalyzeOk("fn run(first: *u8, second: *u8) { let mut p = first; set p = second; }"); |
|
| 5956 | + | } |