Open
Description
Originally pointed out in this discussion. When installing the super trampoline into a class hierarchy, if a subclass below the injection point overrides the method, the lookup logic in ITKReturnThreadSuper(…)
misroutes to the nearest override, potentially causing infinite recursion.
Reproduction
import ITKSuperBuilder
import XCTest
final class TrampolineTests: XCTestCase {
func testTrampolineEdgeCase() throws {
// Inject our super trampoline into `Level2` for `sayHello()`.
try ITKSuperBuilder.addSuperInstanceMethod(
to: Level2.self,
selector: #selector(Level2.sayHello)
)
// Create a `Level4` instance and call `sayHello()`. We expect this to eventually
// call `Level1`’s implementation exactly once. However, due to the broken lookup
// logic in `ITKReturnThreadSuper(…)`, it will loop forever between Level3 overrides.
let object = Level4()
object.sayHello()
}
}
class Level1: NSObject {
@objc dynamic func sayHello() {
print("Level1 says hello")
}
}
class Level2: Level1 {
// No override here. This is where we install the trampoline.
}
class Level3: Level2 {
@objc dynamic override func sayHello() {
print("Level3 says hello")
super.sayHello()
}
}
class Level4: Level3 {
@objc dynamic override func sayHello() {
print("Level4 says hello")
super.sayHello()
}
}
Expected output:
Level4 says hello
Level3 says hello
Level1 says hello
Actual output:
Level4 says hello
Level3 says hello
Level3 says hello
Level3 says hello
… (infinite repetition)