diff --git a/tslang/lib/TypeScript/OwnershipSignatureGlobals.cpp b/tslang/lib/TypeScript/OwnershipSignatureGlobals.cpp index b679c02d4..e587678d1 100644 --- a/tslang/lib/TypeScript/OwnershipSignatureGlobals.cpp +++ b/tslang/lib/TypeScript/OwnershipSignatureGlobals.cpp @@ -499,6 +499,17 @@ namespace own_signature continue; } + // only a store to the global releases its block: a callee that assigns other globals, and + // none it cannot name (pinAssignedGlobals, run before), leaves this one's value as it is + if (!call.op->hasAttr(OWN_ASSIGNS_ANY_GLOBAL_ATTR_NAME)) + { + auto assigned = call.op->getAttrOfType(OWN_ASSIGNS_GLOBALS_ATTR_NAME); + if (!assigned || !llvm::is_contained(assigned.getValue(), mlir::StringAttr::get(&getContext(), global.getGlobalName()))) + { + continue; + } + } + call.op->emitError("a value read out of the global '") << global.getGlobalName() << "' is lent to a call that may release or overwrite it; -mm=own cannot prove it outlives the call"; diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 4253ea47e..09592312d 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3620,6 +3620,13 @@ foreach(immortal_mm rc gc none) endforeach() endforeach() +# A value read out of an assigned global, lent to a call that assigns only other globals, outlives the +# call; own refuses a call that may assign that global or one it cannot name. +foreach(other_global_mm own rc gc none) + tslang_add_test(NAME test-jit-${other_global_mm}-own_global_argument_other_global COMMAND test-runner -jit -mm=${other_global_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_argument_other_global.ts") + tslang_add_test(NAME test-compile-${other_global_mm}-own_global_argument_other_global COMMAND test-runner -mm=${other_global_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_argument_other_global.ts") +endforeach() + # A global whose one store runs in a loop is mortal (spec 25): its stores are not marked, so each # releases the value before it, which an immortal header would leak. The `if` arm's store runs once. add_test(NAME test-own-global-loop-store-mortal @@ -4093,7 +4100,8 @@ set(own_error_cases "own_err_global_method_transitive|:19:11: error: a value read out of the global 'G' is lent to a call that may release or overwrite it" "own_err_global_this_field_move|a value read out of the global 'GK' is lent to a call that may release or overwrite it" "own_err_global_argument_field_let|:21:5: error: a value read out of the global 'G' is lent to a call that may release or overwrite it" - "own_err_global_kept_field|a value read out of the global 'g' is lent to a call that may release or overwrite it" + "own_err_global_kept_field|:25:5: error: the value read from 'g' is kept where its end at an assignment of 'g' cannot be checked" + "own_err_global_argument_indirect|:20:11: error: a value read out of the global 'G' is lent to a call that may release or overwrite it" "own_err_global_let_call|borrows the global 'names', but -mm=own cannot prove the global keeps its value" "own_err_global_from_global|:8:5: error: the initializer of global 'k' reads a value another owner keeps" "own_err_global_from_field|:12:7: error: the initializer of global 'k' reads a value another owner keeps" diff --git a/tslang/test/tester/own/own_err_global_argument_indirect.ts b/tslang/test/tester/own/own_err_global_argument_indirect.ts new file mode 100644 index 000000000..ca6795a40 --- /dev/null +++ b/tslang/test/tester/own/own_err_global_argument_indirect.ts @@ -0,0 +1,21 @@ +// A call through a function value may run any code, an assignment of the global among it: the +// value read out of G cannot be lent to it. +class N { + constructor(public x: number) {} +} + +let G = new N(1); +let F: () => void = () => {}; + +function viaFunctionValue(n: N) { + F(); + return n.x; +} + +function main() { + F = () => { + G = new N(3); + }; + G = new N(2); + print(viaFunctionValue(G)); +} diff --git a/tslang/test/tester/own/own_err_global_kept_field.ts b/tslang/test/tester/own/own_err_global_kept_field.ts index d52380ecb..85f668ae5 100644 --- a/tslang/test/tester/own/own_err_global_kept_field.ts +++ b/tslang/test/tester/own/own_err_global_kept_field.ts @@ -1,6 +1,7 @@ // -mm=own rejects: `keep` stores its parameter `c` into a field, and `main` lends it the global `g`, -// which `reset` gives another value elsewhere: `g` is not immortal (spec 25), and a call that may -// drop cannot be lent a value read out of it. +// which `reset` gives another value elsewhere: `g` is not immortal (spec 25), and what `keep` stores +// outlives the next assignment of `g` (checkGlobalRead). `keep` assigns no global, so the call itself +// may be lent the value. class C { constructor(public x: number) {} } diff --git a/tslang/test/tester/own/own_global_argument_other_global.ts b/tslang/test/tester/own/own_global_argument_other_global.ts new file mode 100644 index 000000000..db7ceac4d --- /dev/null +++ b/tslang/test/tester/own/own_global_argument_other_global.ts @@ -0,0 +1,45 @@ +// A value read out of an assigned global, lent to a call that assigns other globals but not that one: +// only a store to the global releases its block, so the value outlives the call. Own refused every +// call that might drop anything; it now refuses one that may assign this global, or any it cannot +// name. Each read follows allocations of the same size, so a freed block would be reused. +class N { + tag = ""; + constructor(public x: number) { + this.tag = "n" + x; + } +} + +let G = new N(1); +let H = new N(5); + +function churn() { + const keep: N[] = []; + for (let i = 0; i < 1000; i++) keep.push(new N(777)); + return keep.length; +} + +function setH(x: number) { + H = new N(x); +} + +function other(n: N) { + H = new N(n.x + 10); + churn(); + assert(n.tag == "n" + n.x, "the lent value, after the callee assigned another global"); + return n.x; +} + +function deeper(n: N) { + setH(n.x + 20); + churn(); + assert(n.tag == "n" + n.x, "the lent value, after a callee's callee assigned another global"); + return n.x; +} + +function main() { + G = new N(2); + assert(other(G) == 2 && H.x == 12, "a callee that assigns another global"); + G = new N(3); + assert(deeper(G) == 3 && H.x == 23, "a callee's callee that assigns another global"); + print("done."); +}