diff --git a/tslang/docs/superpowers/specs/2026-09-24-own-memory-model-design.md b/tslang/docs/superpowers/specs/2026-09-24-own-memory-model-design.md index f8c8e8e2b..7ae60fa58 100644 --- a/tslang/docs/superpowers/specs/2026-09-24-own-memory-model-design.md +++ b/tslang/docs/superpowers/specs/2026-09-24-own-memory-model-design.md @@ -5068,6 +5068,7 @@ part of. Not here: a class whose object becomes an interface or `any` keeps its fields' families open; so does a field given a callback any other way than those above. A class another class extends: §57. +An interface: §58; `any` and a union: §59. ## 57. A callback field of a class others extend, 2026-10-28 @@ -5089,7 +5090,7 @@ down, under own, rc, gc and none; own emits no counting. Negative: `own_err_call whose derived class stores a function that replaces a global array the walk's element may be part of. Not here: a class whose object, or a derived class's, becomes `any` keeps its fields' families open; -an interface: §58. +an interface: §58; `any`: §59. ## 58. A class seen through an interface, 2026-10-28 @@ -5120,4 +5121,45 @@ the field). Not here: a class whose object becomes `any`, a union, `unknown` or `object` keeps its fields' families open; a callback parameter of a method in an interface's vtable is open even where the -method is called on its class. +method is called on its class. Both: §59. + +## 59. Casts to `any` and calls through an interface, 2026-10-28 + +Two families stayed open that need not: a class's callback field once an object of the class became +`any`, a union, `unknown` or `object` (§55-§58), and a callback parameter of a method an interface's +vtable holds (§58). + +- **A class's fields after a cast to `any`, a union, `unknown` or `object`.** Every way back to the + fields is a cast to the class: `x` unboxes, `u instanceof Walker` narrows + (`ts.GetValueFromUnion`), and the field is a `ts.PropertyRef` of the class again, which the family + sees here. Another module can cast back only to a class it names, and a module that names the class + can build it too (`callableFromOutside` of its constructor, which opens the family already). `any` + to an interface uses only the vtables this module made for the class (`castAnyToInterface`, §58); + `any` to a tuple or an object type reads no field of a class (a class object read as one crashes + in every model), and `object` cannot be cast back to a class at all. So the cast no longer opens + the family (`collectFieldFamilies` drops `escaping`). +- **A method an interface's vtable holds** (`...vtbl`) is called through the + interface's slot. When every slot that holds it is closed (`interfaceFamilies`, spec 31: no other + module sees the interface, every value of it is made from a vtable this module reads) and it is not + `interfaceReached`, its callers are every call through those slots (`interfaceCallsOf`): a + `ts.InterfaceSymbolRef` of the slot, not optional, bound and called with its `this` first, as a + method bound to its object is (`boundCalls`). Their arguments join its parameters' families with + those of its direct calls. Any other use of the slot's value opens the family, as before. +- **A value given on through an interface's slot** reaches each member of the slot: `parameterOf` + returns the parameter of each (`FamilyTargets`), and the value is given to a closed family only + when all of them are. A callback parameter given on (`e.each(a, f)` in a function taking `f`) is + forwarded to each, and a function given by name passes to each. + +`own_callback_cast` walks through a class cast to `any` and back, and to a union and narrowed back, +its callback field stored by the constructor and by a method, under own, rc, gc and none; own emits no +counting. `own_callback_interface_slot` calls two classes' methods through an interface, directly, and +with a caller's callback parameter given on through the interface. Negatives: +`own_err_callback_cast_field_drops` (a draining callback stored into a field of a class cast to `any` +and back), `own_err_callback_interface_slot_drops` (a callback given only through the interface still +counts for a direct call that lends the global array it replaces) and +`own_err_callback_interface_slot_targets` (a callback parameter given on through the slot reaches +both classes). + +Not here: a function-typed field of an object literal typed by its receiver (`{ w: x => x + 1 }` given +for `{ w: (x: number) => number }`) is a method bound to the literal object, which nothing owns; own +rejects the call that receives it. diff --git a/tslang/lib/TypeScript/OwnershipSignatureCallbacks.cpp b/tslang/lib/TypeScript/OwnershipSignatureCallbacks.cpp index d9e9187d1..78a4d8cd0 100644 --- a/tslang/lib/TypeScript/OwnershipSignatureCallbacks.cpp +++ b/tslang/lib/TypeScript/OwnershipSignatureCallbacks.cpp @@ -676,39 +676,23 @@ namespace own_signature } void OwnershipSignaturePass::collectFieldFamilies( - mlir::ModuleOp module, llvm::function_ref(mlir::OpOperand &)> parameterOf) + mlir::ModuleOp module, llvm::function_ref parameterOf) { - // the fields of a callback type, by class and position + // the fields of a callback type, by class and position. An object cast to `any`, a union, + // `unknown` or `object` is cast back to its class to reach them (`ts.GetValueFromUnion`, an + // unbox): a `ts.PropertyRef` of the class again, here, or in a module that names the class, + // which builds it too (`callableFromOutside` of its constructor). An interface reaches them + // only through a vtable built from the class (`...vtbl`), here or in a + // module that names it: a field's slot is a `ts.PropertyRef` here, a function field's a thunk + // that loads it and calls it, and another interface's cast remaps those slots; a cast from + // `any` to an interface uses only those vtables (castAnyToInterface). std::map, llvm::SmallVector> fields; - // the classes whose objects go where their fields may be reached otherwise: cast to `any`, - // a union, `unknown` or `object`, from which another module may cast them back. An interface - // reaches them only through a vtable built from the class (`...vtbl`), here - // or in a module that names it: a field's slot is a `ts.PropertyRef` here, a function - // field's a thunk that loads it and calls it, and another interface's cast remaps those slots. - llvm::StringSet<> escaping; - module.walk([&](mlir::Operation *op) { - if (auto fieldOp = mlir::dyn_cast(op)) - { - auto refType = mlir::dyn_cast(fieldOp.getType()); - auto key = fieldFamilyKey(fieldOp.getResult()); - if (key.first && refType && isCallbackType(refType.getElementType())) - { - fields[key].push_back(fieldOp); - } - - return; - } - - auto reachedOtherwise = llvm::any_of(op->getResultTypes(), [](mlir::Type type) { - return mlir::isa(type); - }); - for (auto operand : op->getOperands()) + module.walk([&](mlir_ts::PropertyRefOp fieldOp) { + auto refType = mlir::dyn_cast(fieldOp.getType()); + auto key = fieldFamilyKey(fieldOp.getResult()); + if (key.first && refType && isCallbackType(refType.getElementType())) { - if (auto classType = mlir::dyn_cast(operand.getType()); reachedOtherwise && classType) - { - escaping.insert(classType.getName().getValue()); - } + fields[key].push_back(fieldOp); } }); @@ -719,11 +703,10 @@ namespace own_signature CallbackFamily family; // neither the class nor one extending it (whose object holds the field in its base's - // storage, reached there by the same key) is built by another module, nor goes where its - // fields are reached otherwise + // storage, reached there by the same key) is built by another module auto closedClass = [&](llvm::StringRef name) { auto classConstructor = functions.lookup((name + ".constructor").str()); - return classConstructor && !callableFromOutside(classConstructor) && !escaping.contains(name); + return classConstructor && !callableFromOutside(classConstructor); }; family.closed = closedClass(className) && llvm::all_of(classStorages, [&](auto &entry) { return !extendsClass(classStorages, entry.first(), className) || closedClass(entry.first()); @@ -751,9 +734,9 @@ namespace own_signature family.closed = onlyCalledValue(loadOp.getResult(), forwards); for (auto *forward : forwards) { - auto target = parameterOf(*forward); - family.closed = family.closed && target.first; - family.forwards.push_back(target); + auto targets = parameterOf(*forward); + family.closed = family.closed && !targets.empty(); + family.forwards.append(targets.begin(), targets.end()); } continue; @@ -824,26 +807,11 @@ namespace own_signature return inVTable; }; - // The calls a use of a function's symbol makes: the use itself, a direct call, or those made - // through the method it binds. Null when the symbol goes anywhere else. - auto callsOf = [&](mlir_ts::FuncOp funcOp, mlir::Operation *user, llvm::SmallVectorImpl &found) { - if (auto callee = directCallee(user)) - { - found.push_back(user); - return callee.getValue() == funcOp.getSymName(); - } - - if (!mlir::isa(user) || !notOverridden(funcOp) || - !onlyCalled(user->getResult(0))) - { - // a call through an interface (`ts.InterfaceSymbolRef`) passes what this family can't - // see, from any module the interface reaches - return isClassVTableEntry(user) && !inInterfaceVTable(user) && notOverridden(funcOp); - } - - // `ts.CallInternal(ts.GetMethod(m), ts.GetThis(m), ...)`: its arguments are the method's - // parameters, `this` first - for (auto *boundUser : user->getResult(0).getUsers()) + // `ts.CallInternal(ts.GetMethod(m), ts.GetThis(m), ...)` of a method bound to its object, or + // read out of an interface value (`ts.InterfaceSymbolRef`): its arguments are the method's + // parameters, `this` first + auto boundCalls = [&](mlir::Value bound, bool throughInterface, llvm::SmallVectorImpl &found) { + for (auto *boundUser : bound.getUsers()) { if (mlir::isa(boundUser)) { @@ -859,20 +827,98 @@ namespace own_signature { auto args = callArgs(call); if (!mlir::isa(call) || args.empty() || - !args.front().getDefiningOp()) + !args.front().getDefiningOp() || + (throughInterface && calleeValue(call) != boundUser->getResult(0))) { return false; } - found.push_back(call); + if (!llvm::is_contained(found, call)) + { + found.push_back(call); + } } } return true; }; - // The function a call made through a method's symbol calls, when it is the only one - auto methodCalled = [&](mlir::Operation *call) { + // the calls through each interface's slot, by (interface, vtable position) + std::map, llvm::SmallVector> interfaceCalls; + module.walk([&](mlir_ts::InterfaceSymbolRefOp refOp) { + if (auto interfaceType = mlir::dyn_cast(refOp.getInterfaceVal().getType())) + { + auto position = static_cast(refOp.getIndex()) + INTERFACE_VTABLE_HEADER_SLOTS; + interfaceCalls[{interfaceType.getName().getValue().str(), position}].push_back(refOp); + } + }); + + // A method in an interface's vtable is called through the interface's slot: by every call + // through each closed slot that holds it (interfaceFamilies), which no other module makes + // (spec 59). One an open slot, or a vtable no interface value is made with, holds is reached + // by calls this can't see (interfaceReached). + auto interfaceCallsOf = [&](mlir_ts::FuncOp funcOp, llvm::SmallVectorImpl &found) { + if (interfaceReached.contains(funcOp)) + { + return false; + } + + auto held = false; + for (auto &[key, family] : interfaceFamilies) + { + if (!llvm::is_contained(family.members, funcOp.getSymNameAttr())) + { + continue; + } + + held = true; + if (!family.closed) + { + return false; + } + + for (auto refOp : interfaceCalls[key]) + { + if (refOp.getOptional().value_or(false) || refOp.getIdentifier() != family.field || + !boundCalls(refOp.getResult(), /*throughInterface=*/true, found)) + { + return false; + } + } + } + + return held; + }; + + // The calls a use of a function's symbol makes: the use itself, a direct call, those made + // through the method it binds, or through the interfaces whose vtables hold it. Null when the + // symbol goes anywhere else. + auto callsOf = [&](mlir_ts::FuncOp funcOp, mlir::Operation *user, llvm::SmallVectorImpl &found) { + if (auto callee = directCallee(user)) + { + found.push_back(user); + return callee.getValue() == funcOp.getSymName(); + } + + if (isClassVTableEntry(user) && inInterfaceVTable(user)) + { + return interfaceCallsOf(funcOp, found); + } + + if (!mlir::isa(user) || !notOverridden(funcOp) || + !onlyCalled(user->getResult(0))) + { + return isClassVTableEntry(user) && notOverridden(funcOp); + } + + return boundCalls(user->getResult(0), /*throughInterface=*/false, found); + }; + + // The functions a call made through a method's symbol calls: the one method no other + // overrides, or every member of a closed interface slot (spec 59). None when it may reach + // another. + auto methodsCalled = [&](mlir::Operation *call) { + llvm::SmallVector called; auto getMethodOp = calleeValue(call) ? calleeValue(call).getDefiningOp() : mlir_ts::GetMethodOp(); auto *ref = getMethodOp ? getMethodOp.getBoundFunc().getDefiningOp() : nullptr; mlir::FlatSymbolRefAttr identifier; @@ -884,9 +930,38 @@ namespace own_signature { identifier = refOp.getIdentifierAttr(); } + else if (auto slotOp = mlir::dyn_cast_or_null(ref); slotOp && !slotOp.getOptional().value_or(false)) + { + auto interfaceType = mlir::dyn_cast(slotOp.getInterfaceVal().getType()); + auto found = interfaceType ? interfaceFamilies.find({interfaceType.getName().getValue().str(), + static_cast(slotOp.getIndex()) + INTERFACE_VTABLE_HEADER_SLOTS}) + : interfaceFamilies.end(); + if (found == interfaceFamilies.end() || !found->second.closed || slotOp.getIdentifier() != found->second.field) + { + return called; + } + + for (auto member : found->second.members) + { + auto funcOp = functions.lookup(member.getValue()); + if (!funcOp || interfaceReached.contains(funcOp)) + { + return llvm::SmallVector(); + } + + called.push_back(funcOp); + } + + return called; + } auto funcOp = identifier ? functions.lookup(identifier.getValue()) : mlir_ts::FuncOp(); - return funcOp && notOverridden(funcOp) ? funcOp : mlir_ts::FuncOp(); + if (funcOp && notOverridden(funcOp)) + { + called.push_back(funcOp); + } + + return called; }; auto callsOnly = [&](mlir_ts::FuncOp funcOp, llvm::SmallVectorImpl &calls) { @@ -895,38 +970,51 @@ namespace own_signature llvm::all_of(users, [&](mlir::Operation *user) { return callsOf(funcOp, user, calls); }); }; - // The parameter of the one function a call reaches that `use` is the argument for, by its - // operand's position; null when the call reaches no one function or `use` is not an argument. - auto parameterOf = [&](mlir::OpOperand &use) -> std::pair { + // The parameters of the functions a call reaches that `use` is the argument for, by its + // operand's position: one, or each member of an interface's slot; none when the call may + // reach a function this can't name or `use` is not an argument. + auto parameterOf = [&](mlir::OpOperand &use) -> FamilyTargets { auto *call = use.getOwner(); // ...or the result of the function it is returned from (spec 56) if (flowsToReturn(use)) { auto funcOp = call->getParentOfType(); - return {funcOp ? funcOp.getOperation() : nullptr, RESULT_FAMILY}; + return funcOp ? FamilyTargets{{funcOp.getOperation(), RESULT_FAMILY}} : FamilyTargets(); } // ...or the field of a class it is stored into (spec 55) if (auto storeOp = mlir::dyn_cast(call)) { - return fieldFamilyKey(storeOp.getReference()); + auto key = fieldFamilyKey(storeOp.getReference()); + return key.first ? FamilyTargets{key} : FamilyTargets(); } if (!isCall(call)) { - return {nullptr, -1}; + return {}; } auto callee = directCallee(call); - auto calleeOp = callee ? functions.lookup(callee.getValue()) : methodCalled(call); + auto calleeOps = callee ? llvm::SmallVector() : methodsCalled(call); + if (auto calleeOp = callee ? functions.lookup(callee.getValue()) : mlir_ts::FuncOp()) + { + calleeOps.push_back(calleeOp); + } + auto args = callArgs(call); - if (!calleeOp || args.empty() || use.getOperandNumber() < args.getBeginOperandIndex() || + if (calleeOps.empty() || args.empty() || use.getOperandNumber() < args.getBeginOperandIndex() || use.getOperandNumber() >= args.getBeginOperandIndex() + args.size()) { - return {nullptr, -1}; + return {}; + } + + FamilyTargets targets; + for (auto calleeOp : calleeOps) + { + targets.push_back({calleeOp, static_cast(use.getOperandNumber() - args.getBeginOperandIndex())}); } - return {calleeOp, static_cast(use.getOperandNumber() - args.getBeginOperandIndex())}; + return targets; }; for (auto &entry : functions) @@ -956,9 +1044,9 @@ namespace own_signature CallbackFamily family; for (auto *use : forwards) { - auto target = parameterOf(*use); - family.closed = family.closed && target.first; - family.forwards.push_back(target); + auto targets = parameterOf(*use); + family.closed = family.closed && !targets.empty(); + family.forwards.append(targets.begin(), targets.end()); } for (auto *user : calls) @@ -993,8 +1081,8 @@ namespace own_signature }; auto passesToClosed = [&](mlir::OpOperand &use) { - auto key = parameterOf(use); - return key.first && closedFamily(key); + auto targets = parameterOf(use); + return !targets.empty() && llvm::all_of(targets, [&](auto key) { return closedFamily(key) != nullptr; }); }; // Takes in the members of the parameters given here: false when one is not closed, or gives diff --git a/tslang/lib/TypeScript/OwnershipSignaturePass.h b/tslang/lib/TypeScript/OwnershipSignaturePass.h index 6212424de..6a7a8f304 100644 --- a/tslang/lib/TypeScript/OwnershipSignaturePass.h +++ b/tslang/lib/TypeScript/OwnershipSignaturePass.h @@ -828,6 +828,9 @@ class OwnershipSignaturePass : public mlir::PassWrapper the functions its callers pass there std::map, CallbackFamily> callbackFamilies; + + // the families a value given on reaches: one, or one per member of an interface's slot (spec 59) + using FamilyTargets = llvm::SmallVector, 1>; // the members whose families disagreed (meetFacts), for the error in their bodies llvm::DenseSet demotedCallbacks; // uses of a member's symbol that only pass it to a closed family: no escape (findOpen) @@ -861,9 +864,9 @@ class OwnershipSignaturePass : public mlir::PassWrapper(mlir::OpOperand &)> parameterOf); + llvm::function_ref parameterOf); // The closure function a capture box is bound to, when every box it is bound to holds `cell` in // field `position`. diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 895c8cd9d..b71f551cc 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3974,7 +3974,7 @@ foreach(own_test own_fresh_string own_fresh_array own_return_new own_try_local o own_generator_take own_rest_array_borrows own_string_copies own_captured_string_callback own_callback_and_call own_callback_captured own_callback_shapes own_callback_extended own_callback_interface - own_generic_specializations) + own_generic_specializations own_callback_cast own_callback_interface_slot) tslang_add_test(NAME test-jit-own-${own_test} COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts") tslang_add_test(NAME test-compile-own-${own_test} COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts") # The promise: no counting at all in what own emits. @@ -4012,7 +4012,7 @@ foreach(keeps_mm rc gc none) own_generator_take own_rest_array_borrows own_string_copies own_captured_string_callback own_callback_and_call own_callback_captured own_callback_shapes own_callback_extended own_callback_interface - own_generic_specializations) + own_generic_specializations own_callback_cast own_callback_interface_slot) tslang_add_test(NAME test-jit-${keeps_mm}-${keeps_test} COMMAND test-runner -jit -mm=${keeps_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/${keeps_test}.ts") tslang_add_test(NAME test-compile-${keeps_mm}-${keeps_test} COMMAND test-runner -mm=${keeps_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/${keeps_test}.ts") endforeach() @@ -4715,7 +4715,10 @@ set(own_error_cases "own_err_callback_returned_drops|:21:9: error: '[^']*' borrows an element but is used here after it may be released or overwritten" "own_err_callback_extended_drops|:16:13: error: '[^']*' borrows an element but is used here after it may be released or overwritten" "own_err_callback_interface_param|:34:11: error: a value read out of the global 'pool' is lent to a call that may release or overwrite it" - "own_err_callback_interface_field|:46:11: error: a value read out of the global 'pool' is lent to a call that may release or overwrite it") + "own_err_callback_interface_field|:46:11: error: a value read out of the global 'pool' is lent to a call that may release or overwrite it" + "own_err_callback_cast_field_drops|:16:13: error: '[^']*' borrows an element but is used here after it may be released or overwritten" + "own_err_callback_interface_slot_drops|:41:11: error: a value read out of the global 'pool' is lent to a call that may release or overwrite it" + "own_err_callback_interface_slot_targets|:47:12: error: a value read out of the global 'pool' is lent to a call that may release or overwrite it") foreach(own_error_case ${own_error_cases}) string(FIND "${own_error_case}" "|" own_error_sep) string(SUBSTRING "${own_error_case}" 0 ${own_error_sep} own_error_name) diff --git a/tslang/test/tester/own/own_callback_cast.ts b/tslang/test/tester/own/own_callback_cast.ts new file mode 100644 index 000000000..4b583a838 --- /dev/null +++ b/tslang/test/tester/own/own_callback_cast.ts @@ -0,0 +1,90 @@ +// A callback stored into a class's field keeps its family when the class's objects are cast to `any` +// or to a union (spec 59): each way back is a cast to the class, whose reads of the field are the +// family's again. Each walk reads the element again after the call, so the call must release nothing +// of it. Each step churns, so an element freed too early reads garbage; every model runs it. +class Item { + constructor(public n: number) {} +} + +function churn() { + const junk: Item[] = []; + for (let i = 0; i < 64; i++) junk.push(new Item(999)); +} + +let seen = 0; + +function once(v: Item) { + seen++; +} + +function twice(v: Item) { + seen += 2; +} + +class Walker { + constructor(private f: (v: Item) => void) {} + + use(f: (v: Item) => void) { + this.f = f; + } + + walk(a: Item[]): number { + let s: number = 0; + for (let i = 0; i < a.length; i++) { + const v = a[i]; + this.f(v); + s += v.n; + } + + return s; + } +} + +class Other { + walk(a: Item[]): number { + return 0; + } +} + +function viaAny(x: any, a: Item[]) { + const w = x; + return w.walk(a); +} + +function viaUnion(u: Walker | Other, a: Item[]) { + return u instanceof Walker ? u.walk(a) : -1; +} + +function testAny(a: Item[]) { + seen = 0; + const w = new Walker(once); + let r: number = w.walk(a); + churn(); + w.use(twice); + r += w.walk(a) + viaAny(w, a); + return r * 100 + seen; +} + +function testUnion(a: Item[]) { + seen = 0; + const w = new Walker(once); + let r: number = w.walk(a); + churn(); + w.use(twice); + r += w.walk(a) + viaUnion(w, a); + return r * 100 + seen; +} + +function main() { + let ok = 0; + for (let i = 0; i < 300; i++) { + const a = [new Item(1), new Item(5), new Item(2)]; + if (testAny(a) == 2415) ok++; + churn(); + if (testUnion(a) == 2415) ok++; + churn(); + } + + assert(ok == 600, "ok"); + print("done."); +} diff --git a/tslang/test/tester/own/own_callback_interface_slot.ts b/tslang/test/tester/own/own_callback_interface_slot.ts new file mode 100644 index 000000000..23da83c94 --- /dev/null +++ b/tslang/test/tester/own/own_callback_interface_slot.ts @@ -0,0 +1,86 @@ +// A method an interface's vtable holds takes its callback parameter's family from every call through +// the interface's slot as well as from its direct calls (spec 59): the interface is closed, so no +// other module calls through it. Two classes fill the slot; a caller's own callback parameter is given +// on through the interface to both. Each walk reads the element again after the call, so the call +// must release nothing of it. Each step churns, so an element freed too early reads garbage; every +// model runs it. +class Item { + constructor(public n: number) {} +} + +function churn() { + const junk: Item[] = []; + for (let i = 0; i < 64; i++) junk.push(new Item(999)); +} + +let seen = 0; + +function once(v: Item) { + seen++; +} + +function twice(v: Item) { + seen += 2; +} + +interface Each { + each(a: Item[], f: (v: Item) => void): number; +} + +class Forward implements Each { + each(a: Item[], f: (v: Item) => void): number { + let s: number = 0; + for (let i = 0; i < a.length; i++) { + const v = a[i]; + f(v); + s += v.n; + } + + return s; + } +} + +class Backward implements Each { + each(a: Item[], f: (v: Item) => void): number { + let s: number = 0; + for (let i = a.length - 1; i >= 0; i--) { + const v = a[i]; + f(v); + s += v.n * 10; + } + + return s; + } +} + +function viaEach(e: Each, a: Item[]) { + return e.each(a, once); +} + +function giveOn(e: Each, a: Item[], f: (v: Item) => void) { + return e.each(a, f); +} + +function test(a: Item[]) { + seen = 0; + const fw = new Forward(); + const bw = new Backward(); + let r: number = fw.each(a, twice) + bw.each(a, once); + churn(); + r += viaEach(fw, a) + viaEach(bw, a); + churn(); + r += giveOn(fw, a, twice) + giveOn(bw, a, once); + return r * 100 + seen; +} + +function main() { + let ok = 0; + for (let i = 0; i < 300; i++) { + const a = [new Item(1), new Item(5), new Item(2)]; + if (test(a) == 26424) ok++; + churn(); + } + + assert(ok == 300, "ok"); + print("done."); +} diff --git a/tslang/test/tester/own/own_err_callback_cast_field_drops.ts b/tslang/test/tester/own/own_err_callback_cast_field_drops.ts new file mode 100644 index 000000000..3fe92cc7d --- /dev/null +++ b/tslang/test/tester/own/own_err_callback_cast_field_drops.ts @@ -0,0 +1,32 @@ +// -mm=own rejects: the function stored into `Walker`'s field replaces a global array, releasing what +// it held. The class is cast to `any` and back before `walk` calls it (spec 59), with an element of an +// array that may be that global, and reads the element again after the call. +class Item { + constructor(public n: number) {} +} + +let pool: Item[] = []; + +class Walker { + constructor(private f: (v: Item) => void) {} + + walk(a: Item[]) { + for (let i = 0; i < a.length; i++) { + const v = a[i]; + this.f(v); + print(v.n); + } + } +} + +function viaAny(x: any) { + (x).walk([new Item(1)]); +} + +function main() { + pool = [new Item(7)]; + viaAny(new Walker((v) => { + pool = []; + })); + print(pool.length); +} diff --git a/tslang/test/tester/own/own_err_callback_interface_slot_drops.ts b/tslang/test/tester/own/own_err_callback_interface_slot_drops.ts new file mode 100644 index 000000000..a46c3b2b8 --- /dev/null +++ b/tslang/test/tester/own/own_err_callback_interface_slot_drops.ts @@ -0,0 +1,42 @@ +// -mm=own rejects: a callback given through an interface's slot (spec 59) is in the family of the +// method's parameter for its direct calls too. `drain` replaces the global array, releasing what it +// held, so the direct call that lends that array may have it released under it. +class Item { + constructor(public n: number) {} +} + +let pool: Item[] = []; + +interface Each { + each(a: Item[], f: (v: Item) => void): number; +} + +class Walker implements Each { + each(a: Item[], f: (v: Item) => void): number { + let s: number = 0; + for (let i = 0; i < a.length; i++) { + const v = a[i]; + f(v); + s += v.n; + } + + return s; + } +} + +function once(v: Item) {} + +function drain(v: Item) { + pool = []; +} + +function viaEach(e: Each) { + return e.each([new Item(1)], drain); +} + +function main() { + pool = [new Item(7)]; + const w = new Walker(); + print(viaEach(w)); + print(w.each(pool, once)); +} diff --git a/tslang/test/tester/own/own_err_callback_interface_slot_targets.ts b/tslang/test/tester/own/own_err_callback_interface_slot_targets.ts new file mode 100644 index 000000000..36940cfbb --- /dev/null +++ b/tslang/test/tester/own/own_err_callback_interface_slot_targets.ts @@ -0,0 +1,54 @@ +// -mm=own rejects: a callback parameter given on through an interface's slot reaches every class +// that fills the slot (spec 59). `drain`, given to `giveOn` for one class, is in the family of both +// classes' methods, and replaces the global array `giveOn` lends to them. +class Item { + constructor(public n: number) {} +} + +let pool: Item[] = []; + +interface Each { + each(a: Item[], f: (v: Item) => void): number; +} + +class Forward implements Each { + each(a: Item[], f: (v: Item) => void): number { + let s: number = 0; + for (let i = 0; i < a.length; i++) { + const v = a[i]; + f(v); + s += v.n; + } + + return s; + } +} + +class Backward implements Each { + each(a: Item[], f: (v: Item) => void): number { + let s: number = 0; + for (let i = a.length - 1; i >= 0; i--) { + const v = a[i]; + f(v); + s += v.n; + } + + return s; + } +} + +function once(v: Item) {} + +function drain(v: Item) { + pool = []; +} + +function giveOn(e: Each, f: (v: Item) => void) { + return e.each(pool, f); +} + +function main() { + pool = [new Item(7)]; + print(giveOn(new Forward(), once)); + print(giveOn(new Backward(), drain)); +}