johnno1962 / InjectionIII

Re-write of Injection for Xcode in (mostly) Swift
MIT License
4.02k stars 319 forks source link

found objects does not initialize #436

Closed qmkCamel closed 1 year ago

qmkCamel commented 1 year ago

Hi, found a case that some object does not initialize as expect. The demo code is below.

  1. change the DemoViewController code to trigger recompile the DemoViewController
  2. reenter DemoViewController, found the label is null, but the contentLabel is OK
@interface DemoViewController ()

@property (nonatomic, strong) UILabel *contentLabel;

@end

@implementation DemoViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UILabel *label = [[UILabel alloc] init];
    label.frame = CGRectMake(0, 200, 100, 100);
    label.backgroundColor = [UIColor whiteColor];
    label.text = @"label";
    NSLog(@"label = %@", label); // label is null, not initialize
    [self.view addSubview:label];

    [self.view addSubview:self.contentLabel]; 
    NSLog(@"label = %@", self.contentLabel); // contentLabel is null, not initialize
}

- (UILabel *)contentLabel {
    if (!_contentLabel) {
        _contentLabel = [UILabel new];
        _contentLabel.frame = CGRectMake(100, 100, 100, 100);
        _contentLabel.backgroundColor = [UIColor whiteColor];
        _contentLabel.text = @"123";
    }
    return _contentLabel;
}

@end
johnno1962 commented 1 year ago

Hi, I can't replicate what you're seeing but it looks like where you message a class e.g. [UILabel new] etc. isn't initialised correctly. Are you sure you're running the most recent version of the InjectionIII.app 4.5.6?

qmkCamel commented 1 year ago

The version is as below. 

It occurs on device hot reload. And if I stay in the DemoViewController the label can initialize correctly. But If I pop to previous vc and reenter the DemoViewController that will happens.

johnno1962 commented 1 year ago

I can't replicate what you say is happening. Any chance you could post your test project or email it to me at github @ johnholdsworth.com?

qmkCamel commented 1 year ago

hot_reload_demo.zip This is the demo. I use the HotReloading as a local package in order to change the host. Thanks a lot ~

johnno1962 commented 1 year ago

Hi, I know this sounds strange but you should remove the comented code // .... [[NSNotificationCenter defaultCenter] addObser completely from the file. Injection takes this as a class reference and gives you the error: 🔥 ⚠️ Number of class refs 3 does not equal ["KKLabel", "UIColor", "NSNotificationCenter", "UILabel"] which means class references are not being initialised. If I can think of a regular expression that ignores commented code when looking for class references I may look at this but I'm not sure this is a priority.

qmkCamel commented 1 year ago

Got it, I noticed the error but ignored that. Thanks again ~

johnno1962 commented 1 year ago

I've pushed 7dcd68483705959cb07298e79722943f4def4b45 which addresses this (need to update the server side as well). There isn't really a tidy way to look for class references for Objective-C. I'll roll a new version of the app later today.

johnno1962 commented 1 year ago

4.5.7 released

qmkCamel commented 1 year ago

Get class references with regular expression may not cover some case, such as

So analyze the dylib is a possible way?

I've pushed 7dcd68483705959cb07298e79722943f4def4b45 which addresses this (need to update the server side as well). There isn't really a tidy way to look for class references for Objective-C. I'll roll a new version of the app later today.

johnno1962 commented 1 year ago

Using regex to extract class references obviously isn't going to always work. If you look at the assembly language, an Objective-C class reference isn't recorded systematically as it is for Swift which is parsed out in Unhide.mm. The information required is going to be in the object file somewhere but I'm not familiar with LINKEDIT segments etc and if I tried to introduce "proper" parsing, that too would be subject to bugs, limitations etc. At the moment (as very few people are interested in device injection) I'm seeing how far we can get without delving into the dynamic linker sources and it seems that is "quite far".

qmkCamel commented 1 year ago

I remember the old version just dlopen the dylib. So the new version aims to enable the swift injection?

johnno1962 commented 1 year ago

Since iOS 10 you can't just dlopen() a dylib in an area of the disk that can be written to so you can't inject. "Device injection" side of HotReloading seeks to simulate as much as possible of what dlopen does by reading into memory are arranging for the executable section to be executable. Pointers to functions are not a problem as you can use a thing called "fishhook" but pointers to classes (as crops up in Objective-C when you reference a class) is more of a problem. I'm trying to avoid writing my own dynamic linker so I'm confining myself to "best effort" implementation at the moment.

qmkCamel commented 1 year ago

Got it, but after iOS 13 dlopen is allowed again during debug mode.

johnno1962 commented 1 year ago

Seriously? You seem to be right! SO, this has all been a colossal waste of time then?

johnno1962 commented 1 year ago

Don't know what happened there 🤦‍♂️. Must have not been code signing properly. I have a bit of code to back out.

johnno1962 commented 1 year ago

@zenangst

johnno1962 commented 1 year ago

So, after the embarrassment of yesterday it's easy enough to switch back to using dlopen() for device injection. I've updated the HotReloading project to remove InjectionScratch and uploaded a new release candidate of the InjectionIII.app if you want to give it/them a try together. Thanks so much for letting me know this was a possibility again; On-device injection is back open for business. If you notice anything broken let me know!

zenangst commented 1 year ago

Wow, so dlopen is back in the game! 👏

johnno1962 commented 1 year ago

Yeah, can't believe it. I was sure I checked that reasonably recently but must have been holding it wrong.

johnno1962 commented 1 year ago

I'll close this for now. Thanks again for your patience putting me right.