Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions tslang/lib/TypeScript/OwnershipSignatureGlobals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir::ArrayAttr>(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";
Expand Down
10 changes: 9 additions & 1 deletion tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions tslang/test/tester/own/own_err_global_argument_indirect.ts
Original file line number Diff line number Diff line change
@@ -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));
}
5 changes: 3 additions & 2 deletions tslang/test/tester/own/own_err_global_kept_field.ts
Original file line number Diff line number Diff line change
@@ -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) {}
}
Expand Down
45 changes: 45 additions & 0 deletions tslang/test/tester/own/own_global_argument_other_global.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
Loading