Giter Site home page Giter Site logo

ypliang19 / mango Goto Github PK

View Code? Open in Web Editor NEW
1.2K 21.0 212.0 1.33 MB

MangoFix is a DSL which syntax is very similar to Objective-C,MangoFix is also an iOS App hotfix SDK. You can use MangoFix method replace any Objective-C or Swift method.

License: MIT License

Objective-C 63.67% Lex 1.63% Yacc 11.38% C 13.97% Ruby 0.31% Modula-3 6.70% Swift 2.35%
hotfix mangofix jspatch ios swift-hotfix

mango's Issues

Grammar conflicts

I've just added mango.[yl] to https://mingodad.github.io/parsertl-playground/playground/ an Yacc/Lex compatible online editor/tester (select MangoFix parser from Examples then click Parse to see a parser tree for the content in Input source editor).

I fixed some conflicts there see definition, annotation_list, annotation_list_opt, declare_struct ... , but still there is some conflicts:

state 48 SHIFT (type_specifier -> IDENTIFIER . ASTERISK)/REDUCE (primary_expression -> IDENTIFIER) conflict.
state 192 SHIFT (type_specifier -> IDENTIFIER . ASTERISK)/REDUCE (primary_expression -> IDENTIFIER) conflict.
state 234:COLON REDUCE (selector_1 -> IDENTIFIER)/REDUCE (primary_expression -> primary_expression DOT IDENTIFIER) conflict.
state 234:LP REDUCE (selector_1 -> IDENTIFIER)/REDUCE (primary_expression -> primary_expression DOT IDENTIFIER) conflict.
state 235:COLON REDUCE (selector_1 -> key_work_identifier)/REDUCE (primary_expression -> primary_expression DOT key_work_identifier) conflict.
state 235:LP REDUCE (selector_1 -> key_work_identifier)/REDUCE (primary_expression -> primary_expression DOT key_work_identifier) conflict.

The rule primary_expression is too broad and as it's now accept things like:

    12("str");
    "str"(23.5);
    nil("str");
    NULL("str");

I hope https://mingodad.github.io/parsertl-playground/playground/ can help debug/develop/test/document this project grammar.

The repository is here https://github.com/mingodad/parsertl-playground .

Any feedback is welcome !

审核能过吗

假如只添加这个库,然后随便写一个修改某个页面的颜色,这样可以通过审核吗

super.viewDidLoad() 不加括号会崩溃

@YPLiang19 看文档里有写 OC 无参方法的调用可以省略后面的括号,但是super.viewDidLoad这种写法会崩溃,貌似死循环:

class SuperMyController:UIViewController{

  • (void)viewDidLoad {
    super.viewDidLoad;
    }

image

image

和jspatch除了dsl区别在哪里?

看了作者关于原理的介绍,除了一个使用jsbridge一个使用dsl解释器去解析,到oc层面都是用的runtime机制,包括创建函数等好像和jspatch一是一样的,那么核心区别在哪里呢?

之前我们用了jspatch,妥妥地被拒了,jspatch有自己的分发平台,使用mango得自己搭建服务器,接入成本还是很高的,所以希望能够详细了解下,如果被拒的风险依旧很大的话就不适用我们上架的这个情况

有几个问题

  • 是否可以支持static、const修饰符
  • dispatch_after、dispatch_once两个常用方法无法使用
  • 为对象添加属性时,如果属性是基本类型的话会无法赋值,比如下面的代码
class SuperMyController:UIViewController{
@property (assign, nonatomic) int count;
- (void)viewDidLoad {
    super.viewDidLoad();

    self.count = 12;
    NSLog(@"count=" + self.count); //输出 count=0
}

原IMP丢弃,stackBlock crash , 子类调用不到被修复的父类方法

首先感谢作者实现了一种全新的hotfix方式,点赞!
我在使用过程中发现了几个问题,希望作者能优化下,让mango越来越棒!

问题一:被修复的类会丢弃原来的IMP

execute.m 原来的处理会丢弃旧的IMP,处理有点粗暴。我将旧IMP绑定到ORIGxxx。


static void replace_method(MANInterpreter *interpreter,Class clazz, MANMethodDefinition *method){
   // 省略其他代码
    //保留原来的IMP
    Class c2 = method.classMethod ? objc_getMetaClass(class_getName(clazz)) : clazz;
    class_replaceMethod(c2, @selector(forwardInvocation:), (IMP)mango_forward_invocation,"v@:@");
    IMP originalIMP =class_replaceMethod(c2, sel, _objc_msgForward, typeEncoding);
    class_addMethod(c2, NSSelectorFromString([NSString stringWithFormat:@"ORIG%@",func.name]), originalIMP, typeEncoding);
	if (needFreeTypeEncoding) {
		free((void *)typeEncoding);
	}
}

之后就可以在DSL中使用self.ORIGxxxx带调用到原来的方法。

问题二:父类修复某一方法,子类使用super调用不到

MANMethodMapTable.h
当子类使用[super xxx] ,传递class实际是子类的class,getMethodMapTableItemWith:sel: 中返回nil,类似调用了空函数。

-(MANMethodMapTableItem *)getMethodMapTableItemWith:(Class)clazz classMethod:(BOOL)classMethod sel:(SEL)sel{
    //需要递归找到所有父类的方法,同时产生新的问题:子类好父类hotfix同一个方法,方法内部使用super时会造成死循环。JSPatch取最顶层IMP调用,虽然没有死循环,但是丢掉了继承调用链。
    Class fixClass = clazz;
    do {
        NSString *index = [NSString stringWithFormat:@"%d_%@_%@,",classMethod,NSStringFromClass(fixClass),NSStringFromSelector(sel)];
        MANMethodMapTableItem* item = _dic[index];
        if (item || [NSStringFromClass(fixClass) isEqualToString:@"NSObject"]) {
            return item;
        }
        fixClass =  class_getSuperclass(fixClass);
    } while (fixClass);
    
    return nil;
    
}

问题三:当修复的方法有参数是stack block时,会造成crash

ARC之后有种写法还是会产生StackBlock:

SubModel* model = [[SubModel alloc] initWithTitle:@"Title" image:@"image"];
   [model log];
   int a = 1;
   void(^block)(void) = ^{  //此时block是 Malloc
       NSLog(@"run block %d" , a);
   };
   [model testBlock:^{  // 此时block是 Stack
       NSLog(@"run block %d" , a);
   }];

DSL中的fix代码

class Model : NSObject {
    -(void)log{
        NSLog(@"Fix Model Log");
        self.ORIGlog();
    }
    -(void)testBlock:(Block)block
    {
        NSLog(@"Fix testBlock");
        self.ORIGtestBlock:(block);
    }
}

Crash :
block

crash

在ARC模式下,会自动插入Stack转Malloc的操作,DSL会丢失这个特性,就出现了上述问题。

我在代码中打了一个补丁,暂时满足需求:
MANValue.h

- (instancetype)initWithCValuePointer:(void *)cValuePointer typeEncoding:(const char *)typeEncoding bridgeTransfer:(BOOL)bridgeTransfer  {
	typeEncoding = removeTypeEncodingPrefix((char *)typeEncoding);
	MANValue *retValue = [[MANValue alloc] init];
	
	switch (*typeEncoding) {
			//省略部分代码
        case '@':{
            retValue.type = man_create_type_specifier(MAN_TYPE_OBJECT);
            if (bridgeTransfer) {
                retValue.objectValue = (__bridge_transfer id)(*(void **)cValuePointer);
            } else if (0 == strcmp(typeEncoding, "@?")) { // **如果是block,将stack变为malloc**
                id block = (__bridge id)(*(void **)cValuePointer);
                block = [block copy];
                retValue.objectValue = block;
            }else{
                retValue.objectValue = (__bridge id)(*(void **)cValuePointer);
            }
            
            break;
        }
		//省略部分代码
	}
	
	return retValue;
}

问题四:内存偶尔有点高

MANInterpreter.h

NSString *currentThread = [[NSThread currentThread] description]; 
修改为
 NSString *currentThread = [NSString stringWithFormat:@"%p",[NSThread currentThread]];
完整代码:
- (MANStack *)stack{
    NSString *currentThread = [NSString stringWithFormat:@"%p",[NSThread currentThread]];
    [_lock lock];
    if (!_stacksDic[currentThread]) {
        _stacksDic[(id)currentThread] = [[MANStack alloc] init];
    }
    MANStack* value = _stacksDic[currentThread];
    [_lock unlock];
    return value;
}

这个修改只能解决一点点问题,还需要作者从全局出发找到优化方法。

测试代码

完整DSL:

class Model : NSObject {
    -(void)log{
        NSLog(@"Fix Model Log");
        self.ORIGlog();
    }
    -(void)testBlock:(Block)block
    {
        NSLog(@"Fix testBlock");
        self.ORIGtestBlock:(block);
    }
}

Model.h

@interface Model : NSObject
@property(nonatomic,copy) NSString* title;
@property(nonatomic,copy) NSString* image;
-(instancetype)initWithTitle:(NSString*)t image:(NSString *)i;
-(void)log;
-(void)testBlock:(void(^)(void))block;
@end

Model.m

#import "Model.h"

@implementation Model
-(instancetype)initWithTitle:(NSString*)t image:(NSString *)i
{
    self = [super init];
    if (self) {
        self.title = t;
        self.image = i;
    }
    return self;
}

-(void)log
{
    NSLog(@"title = %@ image = %@" , self.title , self.image);
}
-(void)testBlock:(void(^)(void))block
{
    if(block){
        block();
    }
    
}

@end

我的测试代码是在tag1.0.1上测试的。反馈中虽然给出了自己的修改,但是并不是最好的。希望作者能抽空修复下这些问题,以后有新版本发布也方便我们升级。多谢!!

block属性变量赋值导致内存泄漏

场景:native存在block类型的属性,在hotfix里面如果此block赋值则会导致泄漏

如列子
//native 声明的属性
@Property(nonatomic , copy) void(^testBlock)(void);

//hotfix
{
//此代码运行后 当前对象会内存泄漏
self.testBlock = ^ {
NSLog(@"block go");
};
}

关于类型转换的问题

为什么字符串和NSNumber不能转换int, float和double这种基本类型。@"123".intValue()打印出来的是

关于 super 直接使用 objc_msgSendSuper 实现的问题

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */

根据苹果关于 objc_msgSend 的注释,对于部分返回 struct 的方法,是否应该使用 objc_msgSendSuper_stret ?

MangoFix性能如何?

iPhone8 Plus上测试后发现MangoFix的启动速度是JSPatch的10倍,运行速度也达到JSPatch的近2~5倍。
MangFix测试结果:
image

JSPath测试结果:
image

How to use it?

Hello:
I looked at the documentation and the demo, but I still didn't understand how to perform the hotfix operation. After I looked at the loading script in the delegate, it seemed that the demo example ran without any difference.

How to use it?

关于RSA加密的问题

我在升级到1.4的时候发现,MFContext 创建需要传的是私钥,而公钥用来加密,那就是需要把私钥保存到客户端本地,相当于公开,而公钥是私有的。
而据我所知,OpenSSL 生成的私钥是可以直接提取公钥的,客户端就可以同时获取到私钥和公钥,所以这样的加密是基本没有意义的。
而根据我的查询JSPatch 就是使用私钥加密MD5来保证安全性的,https://jspatch.com/Docs/security,也可以看看知乎相关讨论 https://www.zhihu.com/question/25912483
在我看来RSA的算法中私钥公钥其实一样,OpenSSL 区分为公钥私钥后才决定了私钥为什么要私有。所以客户端应该保存的是公钥,用来解密,而私钥是私有的用来加密。
感谢你开发的工具,确实很好用,希望能够完善这个安全问题,谢谢

脚本带 if (xx && xx) 或 if (xx || xx) 时可能存在内存泄漏

在脚本里写这样的代码,会导致内存泄漏:

- (void)test {
    if (info && info.xx) {
        //...
    }
}

而换成这样就没问题:

- (void)test {
    if (info) {
        if (info.xx) {
            //...
        }
    }
}

可以在 MangoFixDemo 工程中复现这个问题。原生代码如下:

// ViewController.m
@interface MGTestInfo : NSObject

@property (nonatomic, strong) NSString *name;

@end

@implementation MGTestInfo

- (void)dealloc {
    NSLog(@"MGTestInfo dealloc, name: %@", self.name);
}

@end

@implementation ViewController

//......

- (void)notificationExample{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTestChanged:) name:@"kNotificationTestChange" object:nil];
    });
    
    static int index = 0;
    MGTestInfo *info = [MGTestInfo new];
    info.name = [NSString stringWithFormat:@"name_%d", ++ index];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"kNotificationTestChange" object:info];
}

- (void)onTestChanged:(NSNotification *)notification{
    self.resultView.text = [notification.object name];
}

//......

脚本代码:

// demo.mg
class ViewController:UIViewController {

    - (void)onTestChanged:(NSNotification*)noti {
        MGTestInfo *info = noti.object;
        if (info && info.name.isEqualToString:(@"name_2")) {
            return;
        }
        self.ORGonTestChanged:(noti);
    }
}

运行之后,没法调到 [MGTestInfo dealloc] 方法。如果分开写两个 if 则没问题。

原因可能是这里,eval_logic_and_expression,其实现如下:

static void eval_logic_and_expression(MFInterpreter *inter, MFScopeChain *scope, MFBinaryExpression *expr){
	eval_expression(inter, scope, expr.left);
	MFValue *leftValue = [inter.stack peekStack:0];
	MFValue *resultValue = [MFValue new];
	resultValue.type = mf_create_type_specifier(MF_TYPE_BOOL);
	if (!leftValue.isSubtantial) {
		resultValue.uintValue = NO;
		[inter.stack pop];
	}else{
		eval_expression(inter, scope, expr.right);
		MFValue *rightValue = [inter.stack peekStack:0];
		if (!rightValue.isSubtantial) {
			resultValue.uintValue = NO;
		}else{
			resultValue.uintValue = YES;
		}
		[inter.stack pop];
	}
	[inter.stack push:resultValue];
}

可以看到,第一句 eval_expression(inter, scope, expr.left); 执行之后,可能没有配对地调用 [inter.stack pop];eval_logic_or_expression 也存在同样的问题。

如果在 else 里面最后加一句 [inter.stack pop]; 则问题解决。

请问一下,这里是真的少调用了 pop,还是有其他我不了解的原因故意没加那句 pop?

连续解析多个DSL文件问题

如果一个MFInterpreter实例解析过错误的DSL文件,再用该实例解析其他正确的DSL文件,则其他正确DSL文件也不会生效。

我在已下文件方法中使用yyrestart就可以了~

MFInterpreter.h

- (void)compileSoruceWithString:(NSString *)source{
	extern void nac_set_source_string(char const *source);
	nac_set_source_string([source UTF8String]);
	
	extern int yyparse(void);
        extern void yyrestart  (FILE * input_file );
	if (yyparse()) {
        yyrestart(NULL); /** 解析出错时,重置yylex */
        return;
	}
	
}

Terminating app due to uncaught exception 'MFRuntimeErrorNotFoundCFunction'

工程:MangoFixDemo
真机:iPhone X (14.2)

crash1:
*** Terminating app due to uncaught exception 'MFRuntimeErrorNotFoundCFunction', reason: 'error location line number: 223, not found CFunction: NSSearchPathForDirectoriesInDomains'

demo.mg -> line 223

crash2:
*** Terminating app due to uncaught exception 'MFRuntimeErrorNotFoundCFunction', reason: 'error location line number: 268, not found CFunction: testNativeCStringFunc'

demo.mg -> line 268

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.