From 4729fe6e668601dc9d7b3b8913d4d5195ede0b87 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 20:50:35 +0000 Subject: [PATCH] -mm=own: casts to any keep field families; interface slots are a method's callers Two callback families stayed open that need not (own spec 59): - A class's callback field once an object of the class became `any`, a union, `unknown` or `object`. Every way back to the field is a cast to the class (an unbox, a union narrowed by instanceof), whose `ts.PropertyRef` the family sees here; another module can cast back only to a class it names, and a class it names is one it can build, which opens the family already. collectFieldFamilies drops the `escaping` set. - A callback parameter of a method an interface's vtable holds. When every slot holding it is closed (interfaceFamilies, spec 31) and it is not interfaceReached, its callers are every call through those slots (interfaceCallsOf), whose arguments join its families with its direct calls'. parameterOf returns a set (FamilyTargets): a value given on through an interface's slot reaches each member, and passes to a closed family only when all of them are. Tests: own_callback_cast and own_callback_interface_slot (own, rc, gc, none; own emits no counting); negatives own_err_callback_cast_field_drops, own_err_callback_interface_slot_drops, own_err_callback_interface_slot_targets. Co-Authored-By: Claude Opus 5.5 --- .../2026-09-24-own-memory-model-design.md | 46 +++- .../OwnershipSignatureCallbacks.cpp | 240 ++++++++++++------ .../lib/TypeScript/OwnershipSignaturePass.h | 7 +- tslang/test/tester/CMakeLists.txt | 9 +- tslang/test/tester/own/own_callback_cast.ts | 90 +++++++ .../tester/own/own_callback_interface_slot.ts | 86 +++++++ .../own/own_err_callback_cast_field_drops.ts | 32 +++ .../own_err_callback_interface_slot_drops.ts | 42 +++ ...own_err_callback_interface_slot_targets.ts | 54 ++++ 9 files changed, 523 insertions(+), 83 deletions(-) create mode 100644 tslang/test/tester/own/own_callback_cast.ts create mode 100644 tslang/test/tester/own/own_callback_interface_slot.ts create mode 100644 tslang/test/tester/own/own_err_callback_cast_field_drops.ts create mode 100644 tslang/test/tester/own/own_err_callback_interface_slot_drops.ts create mode 100644 tslang/test/tester/own/own_err_callback_interface_slot_targets.ts 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)); +}