compiler: Retain and check conditional call argument loans
7f92c2e1ce7245fcb8b37e4114564678614c5bed21cd9632e2036ff3833b2901
Track explicit reference arguments until the call starts and compare each possible conditional argument place with earlier arguments. This prevents a later argument from mutating or aliasing storage borrowed by an earlier one. The checker releases these temporary loans after the call.
1 parent
d5fdaec0
lib/std/lang/resolver.rad
+108 -61
| 839 | 839 | len: u32, |
|
| 840 | 840 | /// Whether further projections can identify distinct storage. |
|
| 841 | 841 | precise: bool, |
|
| 842 | 842 | } |
|
| 843 | 843 | ||
| 844 | - | /// A reference binding that protects its source for one lexical scope. |
|
| 844 | + | /// Storage protected by a reference binding or a pending call argument. |
|
| 845 | 845 | record LocalLoan: Copy { |
|
| 846 | - | /// Local symbol through which the source can be accessed. |
|
| 847 | - | binding: *unsafe mut Symbol, |
|
| 848 | - | /// Storage retained by the reference. |
|
| 849 | - | place: BorrowPlace, |
|
| 846 | + | /// Local symbol that provides access, or nil for a pending call argument. |
|
| 847 | + | binding: ?*unsafe mut Symbol, |
|
| 848 | + | /// Expression that supplies the borrowed storage. |
|
| 849 | + | source: *ast::Node, |
|
| 850 | 850 | /// Whether other reads of the source are excluded. |
|
| 851 | 851 | exclusive: bool, |
|
| 852 | 852 | } |
|
| 853 | 853 | ||
| 854 | 854 | /// Function-local exact-use checker state. |
| 7964 | 7964 | ||
| 7965 | 7965 | /// Reject accesses that conflict with a reference in an active lexical scope. |
|
| 7966 | 7966 | unsafe fn checkLocalLoans(checker: &mut LinearChecker, node: *ast::Node, exclusive: bool) |
|
| 7967 | 7967 | throws (ResolveError) |
|
| 7968 | 7968 | { |
|
| 7969 | - | let place = borrowPlace(checker.resolver, node); |
|
| 7969 | + | try checkLoans(checker.resolver, &checker.locals[..checker.localLen], node, exclusive); |
|
| 7970 | + | } |
|
| 7971 | + | ||
| 7972 | + | /// Reject accesses that conflict with any of the supplied loans. |
|
| 7973 | + | unsafe fn checkLoans( |
|
| 7974 | + | self: &mut Resolver, loans: &[LocalLoan], node: *ast::Node, exclusive: bool |
|
| 7975 | + | ) throws (ResolveError) { |
|
| 7976 | + | match node.value { |
|
| 7977 | + | case ast::NodeValue::CondExpr(cond) => { |
|
| 7978 | + | try checkLoans(self, loans, cond.thenExpr, exclusive); |
|
| 7979 | + | try checkLoans(self, loans, cond.elseExpr, exclusive); |
|
| 7980 | + | return; |
|
| 7981 | + | } |
|
| 7982 | + | case ast::NodeValue::As(cast) => { |
|
| 7983 | + | try checkLoans(self, loans, cast.value, exclusive); |
|
| 7984 | + | return; |
|
| 7985 | + | } |
|
| 7986 | + | else => {} |
|
| 7987 | + | } |
|
| 7988 | + | let place = borrowPlace(self, node); |
|
| 7970 | 7989 | let root = place.root else return; |
|
| 7971 | - | for i in 0..checker.localLen { |
|
| 7972 | - | let loan = checker.locals[i]; |
|
| 7973 | - | if (exclusive or loan.exclusive) and placesOverlap(&place, &loan.place) |
|
| 7974 | - | and not usesLocalLoan(checker.resolver, node, loan.binding) |
|
| 7975 | - | { |
|
| 7976 | - | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 7990 | + | for loan in loans { |
|
| 7991 | + | if not (exclusive or loan.exclusive) { |
|
| 7992 | + | continue; |
|
| 7993 | + | } |
|
| 7994 | + | if let binding = loan.binding; usesLocalLoan(self, node, binding) { |
|
| 7995 | + | continue; |
|
| 7996 | + | } |
|
| 7997 | + | if borrowOverlaps(self, &place, loan.source) { |
|
| 7998 | + | throw emitError(self, node, ErrorKind::BorrowConflict(root.name)); |
|
| 7977 | 7999 | } |
|
| 7978 | 8000 | } |
|
| 7979 | 8001 | } |
|
| 7980 | 8002 | ||
| 7981 | 8003 | /// Retain existing storage until its immutable reference binding leaves scope. |
| 7994 | 8016 | throw emitError(checker.resolver, node, ErrorKind::Internal); |
|
| 7995 | 8017 | } |
|
| 7996 | 8018 | let sym = symbolFor(checker.resolver, node) else panic "reference without binding"; |
|
| 7997 | 8019 | let exclusive = isExclusiveArgument(ty); |
|
| 7998 | 8020 | try checkLocalLoans(checker, binding.value, exclusive); |
|
| 7999 | - | set checker.locals[checker.localLen] = LocalLoan { binding: sym, place, exclusive }; |
|
| 8021 | + | set checker.locals[checker.localLen] = LocalLoan { |
|
| 8022 | + | binding: sym, source: binding.value, exclusive, |
|
| 8023 | + | }; |
|
| 8000 | 8024 | set checker.localLen += 1; |
|
| 8001 | 8025 | } |
|
| 8002 | 8026 | ||
| 8003 | 8027 | /// Protect a pattern source until its reference bindings leave scope. |
|
| 8004 | 8028 | unsafe fn addPatternLoan(checker: &mut LinearChecker, subject: *ast::Node) |
| 8293 | 8317 | checker: &mut LinearChecker, |
|
| 8294 | 8318 | env: &mut LinearEnv, |
|
| 8295 | 8319 | node: *ast::Node, |
|
| 8296 | 8320 | call: ast::Call, |
|
| 8297 | 8321 | ) throws (ResolveError) { |
|
| 8322 | + | let localStart = checker.localLen; |
|
| 8298 | 8323 | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 8299 | 8324 | case NodeExtra::SliceAppend { .. }, NodeExtra::SliceDelete { .. } => { |
|
| 8300 | 8325 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
|
| 8301 | 8326 | else panic "slice mutation without receiver"; |
|
| 8302 | 8327 | try checkPatternLoan(checker, access.parent); |
| 8304 | 8329 | } |
|
| 8305 | 8330 | else => {} |
|
| 8306 | 8331 | } |
|
| 8307 | 8332 | try checkLinearNode(checker, env, call.callee, LinearUse::Observe); |
|
| 8308 | 8333 | let mut fnInfo: ?*FnType = nil; |
|
| 8334 | + | let mut receiverClass: ?types::PointerClass = nil; |
|
| 8335 | + | let mut receiverMutable = false; |
|
| 8309 | 8336 | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 8310 | - | case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => |
|
| 8311 | - | set fnInfo = traitInfo.methods[methodIndex].fnType, |
|
| 8312 | - | case NodeExtra::MethodCall { method } => set fnInfo = method.fnType, |
|
| 8337 | + | case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => { |
|
| 8338 | + | let method = &traitInfo.methods[methodIndex]; |
|
| 8339 | + | set fnInfo = method.fnType; |
|
| 8340 | + | set receiverClass = method.receiverClass; |
|
| 8341 | + | set receiverMutable = method.mutable; |
|
| 8342 | + | } |
|
| 8343 | + | case NodeExtra::MethodCall { method } => { |
|
| 8344 | + | set fnInfo = method.fnType; |
|
| 8345 | + | set receiverClass = method.receiverClass; |
|
| 8346 | + | set receiverMutable = method.mutable; |
|
| 8347 | + | } |
|
| 8313 | 8348 | else => { |
|
| 8314 | 8349 | if let calleeTy = typeFor(checker.resolver, call.callee) { |
|
| 8315 | 8350 | if let case Type::Fn(info) = calleeTy { |
|
| 8316 | 8351 | set fnInfo = info; |
|
| 8317 | 8352 | } |
| 8322 | 8357 | for arg in call.args { |
|
| 8323 | 8358 | try checkLinearNode(checker, env, arg, LinearUse::Consume); |
|
| 8324 | 8359 | } |
|
| 8325 | 8360 | return; |
|
| 8326 | 8361 | }; |
|
| 8327 | - | let mut places: [BorrowPlace; MAX_FN_PARAMS + 1] = undefined; |
|
| 8328 | - | let mut exclusive: [bool; MAX_FN_PARAMS + 1] = undefined; |
|
| 8329 | - | let mut placesLen: u32 = 0; |
|
| 8362 | + | let mut arguments: [LocalLoan; MAX_FN_PARAMS + 1] = undefined; |
|
| 8363 | + | let mut argumentsLen: u32 = 0; |
|
| 8330 | 8364 | ||
| 8331 | 8365 | // Method function types exclude their implicit receiver. Account for it |
|
| 8332 | 8366 | // explicitly so owning receivers are consumed and reference receivers |
|
| 8333 | 8367 | // participate in call-scoped loan conflict checks. |
|
| 8334 | 8368 | if let case ast::NodeValue::FieldAccess(access) = call.callee.value { |
|
| 8335 | - | let mut receiverClass = types::PointerClass::Unsafe; |
|
| 8336 | - | let mut receiverMutable = false; |
|
| 8337 | - | let mut haveReceiver = false; |
|
| 8338 | - | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 8339 | - | case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => { |
|
| 8340 | - | let method = &traitInfo.methods[methodIndex]; |
|
| 8341 | - | set receiverClass = method.receiverClass; |
|
| 8342 | - | set receiverMutable = method.mutable; |
|
| 8343 | - | set haveReceiver = true; |
|
| 8344 | - | } |
|
| 8345 | - | case NodeExtra::MethodCall { method } => { |
|
| 8346 | - | set receiverClass = method.receiverClass; |
|
| 8347 | - | set receiverMutable = method.mutable; |
|
| 8348 | - | set haveReceiver = true; |
|
| 8349 | - | } |
|
| 8350 | - | else => {} |
|
| 8351 | - | } |
|
| 8352 | - | if haveReceiver { |
|
| 8353 | - | try checkLocalLoans(checker, access.parent, |
|
| 8354 | - | receiverMutable or receiverClass == types::PointerClass::Owned); |
|
| 8355 | - | if receiverMutable or receiverClass == types::PointerClass::Owned { |
|
| 8369 | + | if let receiverClass = receiverClass { |
|
| 8370 | + | let loan = LocalLoan { |
|
| 8371 | + | binding: nil, source: access.parent, |
|
| 8372 | + | exclusive: receiverClass == types::PointerClass::Owned or receiverMutable, |
|
| 8373 | + | }; |
|
| 8374 | + | try checkLocalLoans(checker, access.parent, loan.exclusive); |
|
| 8375 | + | if loan.exclusive { |
|
| 8356 | 8376 | try checkPatternLoan(checker, access.parent); |
|
| 8357 | 8377 | } |
|
| 8358 | 8378 | if receiverClass <> types::PointerClass::Unsafe { |
|
| 8359 | - | let place = borrowPlace(checker.resolver, access.parent); |
|
| 8360 | - | if place.root <> nil { |
|
| 8361 | - | set places[placesLen] = place; |
|
| 8362 | - | set exclusive[placesLen] = |
|
| 8363 | - | receiverClass == types::PointerClass::Owned or receiverMutable; |
|
| 8364 | - | set placesLen += 1; |
|
| 8365 | - | } |
|
| 8379 | + | set arguments[argumentsLen] = loan; |
|
| 8380 | + | set argumentsLen += 1; |
|
| 8366 | 8381 | } |
|
| 8367 | 8382 | if receiverClass == types::PointerClass::Ref { |
|
| 8368 | 8383 | try checkLinearNode(checker, env, access.parent, LinearUse::Borrow); |
|
| 8384 | + | try retainCallLoan(checker, loan); |
|
| 8369 | 8385 | } else if receiverClass == types::PointerClass::Owned { |
|
| 8370 | 8386 | try checkLinearNode(checker, env, access.parent, LinearUse::Consume); |
|
| 8371 | 8387 | } |
|
| 8372 | 8388 | } |
|
| 8373 | 8389 | } |
|
| 8374 | 8390 | ||
| 8375 | 8391 | for arg, i in call.args { |
|
| 8376 | 8392 | let expected = *info.paramTypes[i]; |
|
| 8377 | - | let argExclusive = isExclusiveArgument(expected); |
|
| 8378 | - | if argExclusive { |
|
| 8393 | + | let loan = LocalLoan { binding: nil, source: arg, exclusive: isExclusiveArgument(expected) }; |
|
| 8394 | + | if loan.exclusive { |
|
| 8379 | 8395 | try checkPatternLoan(checker, arg); |
|
| 8380 | 8396 | } |
|
| 8381 | - | let place = borrowPlace(checker.resolver, arg); |
|
| 8382 | 8397 | if not isUnsafePointerType(expected) { |
|
| 8383 | - | if let rootSym = place.root { |
|
| 8384 | - | for j in 0..placesLen { |
|
| 8385 | - | if (exclusive[j] or argExclusive) and placesOverlap(&places[j], &place) { |
|
| 8386 | - | throw emitError(checker.resolver, arg, ErrorKind::BorrowConflict(rootSym.name)); |
|
| 8387 | - | } |
|
| 8388 | - | } |
|
| 8389 | - | set places[placesLen] = place; |
|
| 8390 | - | set exclusive[placesLen] = argExclusive; |
|
| 8391 | - | set placesLen += 1; |
|
| 8392 | - | } |
|
| 8398 | + | try checkLoans(checker.resolver, &arguments[..argumentsLen], arg, loan.exclusive); |
|
| 8399 | + | set arguments[argumentsLen] = loan; |
|
| 8400 | + | set argumentsLen += 1; |
|
| 8393 | 8401 | } |
|
| 8394 | - | try checkLocalLoans(checker, arg, argExclusive); |
|
| 8402 | + | try checkLocalLoans(checker, arg, loan.exclusive); |
|
| 8395 | 8403 | if isRefType(expected) { |
|
| 8396 | 8404 | try checkLinearNode(checker, env, arg, LinearUse::Borrow); |
|
| 8405 | + | try retainCallLoan(checker, loan); |
|
| 8397 | 8406 | } else { |
|
| 8398 | 8407 | try checkLinearNode(checker, env, arg, LinearUse::Consume); |
|
| 8399 | 8408 | } |
|
| 8400 | 8409 | } |
|
| 8410 | + | set checker.localLen = localStart; |
|
| 8401 | 8411 | if *info.returnType == Type::Never and info.throwList.len == 0 { |
|
| 8402 | 8412 | set env.terminated = true; |
|
| 8403 | 8413 | } |
|
| 8404 | 8414 | } |
|
| 8405 | 8415 | ||
| 8416 | + | /// Return whether an expression can address the supplied storage. |
|
| 8417 | + | unsafe fn borrowOverlaps(self: &mut Resolver, place: &BorrowPlace, node: *ast::Node) -> bool { |
|
| 8418 | + | match node.value { |
|
| 8419 | + | case ast::NodeValue::CondExpr(cond) => |
|
| 8420 | + | return borrowOverlaps(self, place, cond.thenExpr) |
|
| 8421 | + | or borrowOverlaps(self, place, cond.elseExpr), |
|
| 8422 | + | case ast::NodeValue::As(cast) => return borrowOverlaps(self, place, cast.value), |
|
| 8423 | + | else => { |
|
| 8424 | + | let other = borrowPlace(self, node); |
|
| 8425 | + | return placesOverlap(place, &other); |
|
| 8426 | + | } |
|
| 8427 | + | } |
|
| 8428 | + | } |
|
| 8429 | + | ||
| 8430 | + | /// Return whether evaluating an argument creates an explicit address borrow. |
|
| 8431 | + | fn createsExplicitBorrow(node: *ast::Node) -> bool { |
|
| 8432 | + | match node.value { |
|
| 8433 | + | case ast::NodeValue::AddressOf(_) => return true, |
|
| 8434 | + | case ast::NodeValue::As(cast) => return createsExplicitBorrow(cast.value), |
|
| 8435 | + | case ast::NodeValue::CondExpr(cond) => |
|
| 8436 | + | return createsExplicitBorrow(cond.thenExpr) or createsExplicitBorrow(cond.elseExpr), |
|
| 8437 | + | else => return false, |
|
| 8438 | + | } |
|
| 8439 | + | } |
|
| 8440 | + | ||
| 8441 | + | /// Retain an explicit address argument until its call begins. |
|
| 8442 | + | unsafe fn retainCallLoan(checker: &mut LinearChecker, loan: LocalLoan) throws (ResolveError) { |
|
| 8443 | + | if not createsExplicitBorrow(loan.source) { |
|
| 8444 | + | return; |
|
| 8445 | + | } |
|
| 8446 | + | if checker.localLen >= MAX_LINEAR_BINDINGS { |
|
| 8447 | + | throw emitError(checker.resolver, loan.source, ErrorKind::Internal); |
|
| 8448 | + | } |
|
| 8449 | + | set checker.locals[checker.localLen] = loan; |
|
| 8450 | + | set checker.localLen += 1; |
|
| 8451 | + | } |
|
| 8452 | + | ||
| 8406 | 8453 | /// Check a pattern conditional. Linear scrutinees require an exhaustive match. |
|
| 8407 | 8454 | unsafe fn checkLinearIfLet( |
|
| 8408 | 8455 | checker: &mut LinearChecker, |
|
| 8409 | 8456 | env: &mut LinearEnv, |
|
| 8410 | 8457 | node: *ast::Node, |
lib/std/lang/resolver/tests.rad
+87 -0
| 6445 | 6445 | let mut res = testResolver(); |
|
| 6446 | 6446 | let result = try resolveProgramStr(&mut res, program); |
|
| 6447 | 6447 | try expectNoErrors(&result); |
|
| 6448 | 6448 | } |
|
| 6449 | 6449 | } |
|
| 6450 | + | ||
| 6451 | + | /// Earlier reference arguments protect their storage during later arguments. |
|
| 6452 | + | @test unsafe fn testCallArgumentLoans() throws (testing::TestError) { |
|
| 6453 | + | for program in [ |
|
| 6454 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (p: &mut u32) { outer(&mut *p, inner(p)); }", |
|
| 6455 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32) { outer(&*p, inner(p)); }", |
|
| 6456 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} unsafe fn f (p: &mut u32) { outer(&mut *p, inner(p)); }", |
|
| 6457 | + | "record R { n: u32 } fn (r: &mut R) call(n: u32) {} fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn f (r: &mut R) { (&mut *r).call(inner(&mut r.n)); }", |
|
| 6458 | + | ] { |
|
| 6459 | + | let mut res = testResolver(); |
|
| 6460 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6461 | + | let error = try expectError(&result); |
|
| 6462 | + | let case super::ErrorKind::BorrowConflict(_) = error.kind |
|
| 6463 | + | else throw testing::TestError::Failed; |
|
| 6464 | + | } |
|
| 6465 | + | } |
|
| 6466 | + | ||
| 6467 | + | /// Conditional explicit arguments protect every possible borrowed place. |
|
| 6468 | + | @test unsafe fn testConditionalCallArgumentLoans() throws (testing::TestError) { |
|
| 6469 | + | for program in [ |
|
| 6470 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(p)); }", |
|
| 6471 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(q)); }", |
|
| 6472 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *q, &mut *p); }", |
|
| 6473 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(q)); }", |
|
| 6474 | + | "fn outer(p: &mut u32, q: &mut u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *q, &mut *q); }", |
|
| 6475 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer((&*p if flag else &*q) as &u32, inner(p)); }", |
|
| 6476 | + | "record R { n: u32 } fn (r: &R) call(n: u32) {} fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn f (p: &mut R, q: &mut R, flag: bool) { (&*p if flag else &*q).call(inner(&mut q.n)); }", |
|
| 6477 | + | ] { |
|
| 6478 | + | let mut res = testResolver(); |
|
| 6479 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6480 | + | let error = try expectError(&result); |
|
| 6481 | + | let case super::ErrorKind::BorrowConflict(_) = error.kind |
|
| 6482 | + | else throw testing::TestError::Failed; |
|
| 6483 | + | } |
|
| 6484 | + | } |
|
| 6485 | + | ||
| 6486 | + | /// Conditional argument alternatives cannot overlap another exclusive argument. |
|
| 6487 | + | @test unsafe fn testConditionalCallArgumentOverlap() throws (testing::TestError) { |
|
| 6488 | + | for program in [ |
|
| 6489 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, p); }", |
|
| 6490 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q); }", |
|
| 6491 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p, p if flag else q); }", |
|
| 6492 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(q, p if flag else q); }", |
|
| 6493 | + | "fn outer(p: &mut u32, q: &mut u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, p); }", |
|
| 6494 | + | "fn outer(p: &u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q); }", |
|
| 6495 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q if flag else p); }", |
|
| 6496 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, r: &mut u32, a: bool, b: bool) { outer((p if a else q) if b else r, q); }", |
|
| 6497 | + | "fn outer(p: &u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer((p if flag else q) as &u32, p); }", |
|
| 6498 | + | "record R { n: u32 } fn (r: &mut R) call(p: &R) {} fn f (p: &mut R, q: &mut R, flag: bool) { (p if flag else q).call(p); }", |
|
| 6499 | + | "trait R { fn (&mut R) call(p: &opaque R); } fn f (p: &mut opaque R, q: &mut opaque R, flag: bool) { (p if flag else q).call(q); }", |
|
| 6500 | + | ] { |
|
| 6501 | + | let mut res = testResolver(); |
|
| 6502 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6503 | + | let error = try expectError(&result); |
|
| 6504 | + | let case super::ErrorKind::BorrowConflict(_) = error.kind |
|
| 6505 | + | else throw testing::TestError::Failed; |
|
| 6506 | + | } |
|
| 6507 | + | } |
|
| 6508 | + | ||
| 6509 | + | /// Conditional argument alternatives permit shared access and disjoint places. |
|
| 6510 | + | @test unsafe fn testConditionalCallArgumentSeparation() throws (testing::TestError) { |
|
| 6511 | + | for program in [ |
|
| 6512 | + | "fn outer(p: &u32, q: &u32) {} fn f (p: &u32, q: &u32, flag: bool) { outer(p if flag else q, p); }", |
|
| 6513 | + | "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, r: &mut u32, flag: bool) { outer(p if flag else q, r); }", |
|
| 6514 | + | "record R { a: u32, b: u32, c: u32 } fn outer(p: &mut u32, q: &mut u32) {} fn f (r: &mut R, flag: bool) { outer(&mut r.a if flag else &mut r.b, &mut r.c); }", |
|
| 6515 | + | "record R { n: u32 } fn (r: &mut R) call(p: &R) {} fn f (p: &mut R, q: &mut R, r: &R, flag: bool) { (p if flag else q).call(r); }", |
|
| 6516 | + | ] { |
|
| 6517 | + | let mut res = testResolver(); |
|
| 6518 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6519 | + | try expectNoErrors(&result); |
|
| 6520 | + | } |
|
| 6521 | + | } |
|
| 6522 | + | ||
| 6523 | + | /// Call loans allow shared reads, separate fields, and access after the call. |
|
| 6524 | + | @test unsafe fn testCallArgumentLoanScopes() throws (testing::TestError) { |
|
| 6525 | + | for program in [ |
|
| 6526 | + | "fn read(p: &u32) -> u32 { return *p; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, read(p)); set *p = 3; set *q = 4; }", |
|
| 6527 | + | "record R { a: u32, b: u32, c: u32 } fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (r: &mut R, flag: bool) { outer(&mut r.a if flag else &mut r.b, inner(&mut r.c)); set r.a = 3; }", |
|
| 6528 | + | "fn outer(p: &mut u32, n: u32) {} fn f (p: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *p, 0); set *p = 3; }", |
|
| 6529 | + | "fn read(p: &u32) -> u32 { return *p; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32) { outer(&*p, read(p)); set *p = 3; }", |
|
| 6530 | + | "record R { a: u32, b: u32 } fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (r: &mut R) { outer(&mut r.a, inner(&mut r.b)); set r.a = 3; }", |
|
| 6531 | + | ] { |
|
| 6532 | + | let mut res = testResolver(); |
|
| 6533 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6534 | + | try expectNoErrors(&result); |
|
| 6535 | + | } |
|
| 6536 | + | } |
lib/std/vec.rad
+6 -4
| 65 | 65 | /// Returns false if the vector is at capacity. |
|
| 66 | 66 | export unsafe fn push(vec: &mut RawVec, elem: &opaque) -> bool { |
|
| 67 | 67 | if vec.len >= capacity(vec) { |
|
| 68 | 68 | return false; |
|
| 69 | 69 | } |
|
| 70 | - | let off: u32 = vec.len * vec.stride; |
|
| 71 | - | copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride)); |
|
| 70 | + | let stride = vec.stride; |
|
| 71 | + | let off: u32 = vec.len * stride; |
|
| 72 | + | copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride)); |
|
| 72 | 73 | set vec.len += 1; |
|
| 73 | 74 | ||
| 74 | 75 | return true; |
|
| 75 | 76 | } |
|
| 76 | 77 |
| 95 | 96 | /// Returns false if index is out of bounds. |
|
| 96 | 97 | export unsafe fn put(vec: &mut RawVec, index: u32, elem: &opaque) -> bool { |
|
| 97 | 98 | if index >= vec.len { |
|
| 98 | 99 | return false; |
|
| 99 | 100 | } |
|
| 100 | - | let off: u32 = index * vec.stride; |
|
| 101 | - | copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride)); |
|
| 101 | + | let stride = vec.stride; |
|
| 102 | + | let off: u32 = index * stride; |
|
| 103 | + | copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride)); |
|
| 102 | 104 | ||
| 103 | 105 | return true; |
|
| 104 | 106 | } |
|
| 105 | 107 | ||
| 106 | 108 | /// Copy bytes from source to destination. |
test/tests/mutref.loop.bug.rad
+8 -4
| 19 | 19 | /// merge tries to `load` through it as if it were a pointer. |
|
| 20 | 20 | fn testZeroIter(n: u32) -> u32 { |
|
| 21 | 21 | let mut val: u32 = 42; |
|
| 22 | 22 | let mut i: u32 = 0; |
|
| 23 | 23 | while i < n { |
|
| 24 | - | store(&mut val, val + 1); |
|
| 24 | + | let next = val + 1; |
|
| 25 | + | store(&mut val, next); |
|
| 25 | 26 | set i += 1; |
|
| 26 | 27 | } |
|
| 27 | 28 | return val; |
|
| 28 | 29 | } |
|
| 29 | 30 | ||
| 30 | 31 | /// Multiple iterations: accumulate via &mut pointer in a loop. |
|
| 31 | 32 | fn testMultiIter() -> u32 { |
|
| 32 | 33 | let mut acc: u32 = 0; |
|
| 33 | 34 | let mut i: u32 = 0; |
|
| 34 | 35 | while i < 5 { |
|
| 35 | - | store(&mut acc, acc + i); |
|
| 36 | + | let next = acc + i; |
|
| 37 | + | store(&mut acc, next); |
|
| 36 | 38 | set i += 1; |
|
| 37 | 39 | } |
|
| 38 | 40 | return acc; |
|
| 39 | 41 | } |
|
| 40 | 42 |
| 42 | 44 | fn testMultipleVars() -> u32 { |
|
| 43 | 45 | let mut a: u32 = 0; |
|
| 44 | 46 | let mut b: u32 = 100; |
|
| 45 | 47 | let mut i: u32 = 0; |
|
| 46 | 48 | while i < 3 { |
|
| 47 | - | store(&mut a, a + 1); |
|
| 48 | - | store(&mut b, b - 1); |
|
| 49 | + | let nextA = a + 1; |
|
| 50 | + | store(&mut a, nextA); |
|
| 51 | + | let nextB = b - 1; |
|
| 52 | + | store(&mut b, nextB); |
|
| 49 | 53 | set i += 1; |
|
| 50 | 54 | } |
|
| 51 | 55 | return a + b; |
|
| 52 | 56 | } |
|
| 53 | 57 |