Check pointer target mutability for writes

682efa2140bc35aaf8a48b04df2939aa573a13e8e9834863d0694aa630b6540b
Alexis Sellier committed ago 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 +
}