Skip to content

Commit

Permalink
Merge pull request #818 from welcommand/master
Browse files Browse the repository at this point in the history
Support more block feature without libffi
  • Loading branch information
bang590 authored Apr 22, 2018
2 parents 5a8dad7 + e64e5a3 commit e80a557
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 42 deletions.
10 changes: 10 additions & 0 deletions Demo/iOSDemo/JSPatchDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
2D6D12FD1B0B8CF20095A435 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D6D12FA1B0B8CF20095A435 /* Images.xcassets */; };
2D6D12FF1B0B8CF20095A435 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D6D12FC1B0B8CF20095A435 /* main.m */; };
2D6D13011B0B8CFF0095A435 /* demo.js in Resources */ = {isa = PBXBuildFile; fileRef = 2D6D13001B0B8CFF0095A435 /* demo.js */; };
2DA3C42B206A1892005877CB /* newBlockTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA3C42A206A1892005877CB /* newBlockTest.m */; };
2DA3C434206B5569005877CB /* newBlockTest.js in Resources */ = {isa = PBXBuildFile; fileRef = 2DA3C433206B5569005877CB /* newBlockTest.js */; };
360BCF4E1D82DCFC00202977 /* JPDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 360BCF4D1D82DCFC00202977 /* JPDispatch.m */; };
360BCF541D82EFCD00202977 /* JPProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 360BCF531D82EFCD00202977 /* JPProtocol.m */; };
3613C3861C6329F300E915CB /* JPEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 3613C3611C6329F300E915CB /* JPEngine.m */; };
Expand Down Expand Up @@ -93,6 +95,9 @@
2D6D12FB1B0B8CF20095A435 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2D6D12FC1B0B8CF20095A435 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
2D6D13001B0B8CFF0095A435 /* demo.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = demo.js; sourceTree = "<group>"; };
2DA3C429206A1892005877CB /* newBlockTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = newBlockTest.h; sourceTree = "<group>"; };
2DA3C42A206A1892005877CB /* newBlockTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = newBlockTest.m; sourceTree = "<group>"; };
2DA3C433206B5569005877CB /* newBlockTest.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newBlockTest.js; sourceTree = "<group>"; };
360BCF4C1D82DCFC00202977 /* JPDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPDispatch.h; sourceTree = "<group>"; };
360BCF4D1D82DCFC00202977 /* JPDispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPDispatch.m; sourceTree = "<group>"; };
360BCF521D82EFCD00202977 /* JPProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPProtocol.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -473,6 +478,9 @@
36CE19231CB3E709007D73AC /* JPPerformanceTest.h */,
36CE19241CB3E709007D73AC /* JPPerformanceTest.m */,
36CE19261CB3E72B007D73AC /* performanceTest.js */,
2DA3C429206A1892005877CB /* newBlockTest.h */,
2DA3C42A206A1892005877CB /* newBlockTest.m */,
2DA3C433206B5569005877CB /* newBlockTest.js */,
6C72B7941C352BA80086C98D /* newProtocolTest.h */,
6C72B7951C352BA80086C98D /* newProtocolTest.m */,
6C9C0EE31C25A7C700FCAAC5 /* newProtocolTest.js */,
Expand Down Expand Up @@ -603,6 +611,7 @@
E1B89EAE1B228818000645C2 /* multithreadTest.js in Resources */,
369492601CFEFE42003F44CA /* jsCFunctionTest.js in Resources */,
36CE19271CB3E72B007D73AC /* performanceTest.js in Resources */,
2DA3C434206B5569005877CB /* newBlockTest.js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -664,6 +673,7 @@
E1B89EA31B218986000645C2 /* JPInheritanceTestObjects.m in Sources */,
4F9EBFC91D50749200EC72C1 /* JPNumberTest.m in Sources */,
DE94AE2B1AF246C000E461D4 /* JSPatchTests.m in Sources */,
2DA3C42B206A1892005877CB /* newBlockTest.m in Sources */,
3694925F1CFEFE42003F44CA /* JPCFunctionTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
1 change: 1 addition & 0 deletions Demo/iOSDemo/JSPatchTests/JPPerformanceTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@

- (void)testJSCallMallocJPMemory;
- (void)testJSCallMallocJPCFunction;

@end
11 changes: 10 additions & 1 deletion Demo/iOSDemo/JSPatchTests/JPPerformanceTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "JPPerformanceTest.h"
#import <UIKit/UIKit.h>

@implementation JPPerformanceTest

Expand All @@ -20,6 +21,7 @@ - (void)testJSCallJSMethodWithLargeDictionaryParam{}
- (void)testJSCallJSMethodWithLargeDictionaryParamAutoConvert{}
- (void)testJSCallJSMethodWithParam{}


- (void)testOCCallEmptyMethod {
for (int i = 0; i < 10000; i ++) {
[self emptyMethodToOverride];
Expand Down Expand Up @@ -49,7 +51,6 @@ - (void)initTestPerformanceObj {
if (!testPerformanceObj) testPerformanceObj = [[NSObject alloc] init];
}
- (void)emptyMethod {

}

- (void)methodWithParamObject:(NSObject *)obj {
Expand All @@ -69,4 +70,12 @@ - (NSObject *)methodReturnObjectToOverride {
return nil;
}

- (void)allArgSumWithBlock:(double (^)(CGFloat arg0, CGPoint arg1, NSInteger arg2, id arg3))block {

NSNumber *arg3 = [NSNumber numberWithDouble:3.3];
double sum = block(3.2, (CGPoint){1.1,1.2}, 10,arg3);
NSLog(@"==== sum = %@",@(sum));
}


@end
9 changes: 9 additions & 0 deletions Demo/iOSDemo/JSPatchTests/JSPatchTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import "JPPerformanceTest.h"
#import "JPCFunctionTest.h"
#import "JPNumberTest.h"
#import "newBlockTest.h"

@interface JSPatchTests : XCTestCase

Expand Down Expand Up @@ -479,6 +480,14 @@ - (void)testJSCallMallocJPCFunction
}];
}

- (void)testNewBlock {
[self loadPatch:@"newBlockTest"];
newBlockTest *obj = [[newBlockTest alloc] init];
[obj removeJPBlock];
[obj testJSBlockToOCCall];
XCTAssert(obj.success, @"testJSBlockToOCCall");
}

- (void)testNewProtocol{
[self loadPatch:@"newProtocolTest"];

Expand Down
20 changes: 20 additions & 0 deletions Demo/iOSDemo/JSPatchTests/newBlockTest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// newBlockTest.h
// JSPatchTests
//
// Created by WELCommand on 2018/3/27.
// Copyright © 2018年 bang. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface newBlockTest : NSObject

@property (nonatomic, assign) BOOL success;

- (void)removeJPBlock;

- (void)testJSBlockToOCCall;

@end
7 changes: 7 additions & 0 deletions Demo/iOSDemo/JSPatchTests/newBlockTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defineClass("newBlockTest", {
testJSBlockToOCCall: function() {
self.performBlock(block("CGFloat, int, CGPoint, double, CGFloat, NSNumber*, NSString*, NSInteger", function(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
return arg1 + arg2.x + arg2.y + arg3 + arg4 + arg5 + arg6.doubleValue() + arg7;
}));
}
}, {});
31 changes: 31 additions & 0 deletions Demo/iOSDemo/JSPatchTests/newBlockTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// newBlockTest.m
// JSPatchTests
//
// Created by WELCommand on 2018/3/27.
// Copyright © 2018年 bang. All rights reserved.
//

#import "newBlockTest.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "JPEngine.h"

@implementation newBlockTest

- (void)testJSBlockToOCCall {}


+ (void)main:(JSContext *)context
{
context[@"__genBlock"] = nil;
}

- (void)removeJPBlock {
[JPEngine addExtensions:@[@"newBlockTest"]];
}

- (void)performBlock:(CGFloat (^)(int arg1, CGPoint arg2, double arg3, CGFloat arg4, NSNumber *arg5, NSString *arg6, NSInteger arg7))block {
_success = (block(1, (CGPoint){3.3, 3.3}, 1.1, 1.1, @(11), @"4.4", 17) == (CGFloat)(1 + 3.3 + 3.3 + 1.1 + 1.1 + 11 + 4.4 + 17)) && (block(1, (CGPoint){3.3, 3.3}, 1.1, 1.1, @(11), @"4.4", 17) == (CGFloat)(1 + 3.3 + 3.3 + 1.1 + 1.1 + 11 + 4.4 + 17));
}

@end
3 changes: 1 addition & 2 deletions Demo/iOSDemo/JSPatchTests/performanceTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,4 @@ defineClass('JPPerformanceTest', {
var p = malloc(10)
}
}

})
})
157 changes: 118 additions & 39 deletions JSPatch/JPEngine.m
Original file line number Diff line number Diff line change
Expand Up @@ -656,33 +656,37 @@ static void addMethodToProtocol(Protocol* protocol, NSString *selectorName, NSSt

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{

#ifdef DEBUG
_JSLastCallStack = [NSThread callStackSymbols];
#endif
BOOL deallocFlag = NO;
id slf = assignSlf;
BOOL isBlock = [[assignSlf class] isSubclassOfClass : NSClassFromString(@"NSBlock")];

NSMethodSignature *methodSignature = [invocation methodSignature];
NSInteger numberOfArguments = [methodSignature numberOfArguments];

NSString *selectorName = NSStringFromSelector(invocation.selector);
NSString *selectorName = isBlock ? @"" : NSStringFromSelector(invocation.selector);
NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);
JSValue *jsFunc = isBlock ? objc_getAssociatedObject(assignSlf, "_JSValue")[@"cb"] : getJSFunctionInObjectHierachy(slf, JPSelectorName);
if (!jsFunc) {
JPExecuteORIGForwardInvocation(slf, selector, invocation);
return;
}

NSMutableArray *argList = [[NSMutableArray alloc] init];
if ([slf class] == slf) {
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
[argList addObject:[JPBoxing boxWeakObj:slf]];
if (!isBlock) {
if ([slf class] == slf) {
[argList addObject:[JSValue valueWithObject:@{@"__clsName": NSStringFromClass([slf class])} inContext:_context]];
} else if ([selectorName isEqualToString:@"dealloc"]) {
[argList addObject:[JPBoxing boxAssignObj:slf]];
deallocFlag = YES;
} else {
[argList addObject:[JPBoxing boxWeakObj:slf]];
}
}

for (NSUInteger i = 2; i < numberOfArguments; i++) {
for (NSUInteger i = isBlock ? 1 : 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {

Expand Down Expand Up @@ -1415,6 +1419,14 @@ static id invokeVariableParameterMethod(NSMutableArray *origArgumentsList, NSMet
return results;
}

NSMethodSignature *block_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector) {
uint8_t *p = (uint8_t *)((__bridge void *)self);
p += sizeof(void *) * 2 + sizeof(int32_t) *2 + sizeof(uintptr_t) * 2;
const char **signature = (const char **)p;
return [NSMethodSignature signatureWithObjCTypes:*signature];
}


static id getArgument(id valObj){
if (valObj == _nilObj ||
([valObj isKindOfClass:[NSNumber class]] && strcmp([valObj objCType], "c") == 0 && ![valObj boolValue])) {
Expand All @@ -1427,35 +1439,102 @@ static id getArgument(id valObj){

static id genCallbackBlock(JSValue *jsVal)
{
#define BLK_TRAITS_ARG(_idx, _paramName) \
if (_idx < argTypes.count) { \
NSString *argType = trim(argTypes[_idx]); \
if (blockTypeIsScalarPointer(argType)) { \
[list addObject:formatOCToJS([JPBoxing boxPointer:_paramName])]; \
} else if (blockTypeIsObject(trim(argTypes[_idx]))) { \
[list addObject:formatOCToJS((__bridge id)_paramName)]; \
} else { \
[list addObject:formatOCToJS([NSNumber numberWithLongLong:(long long)_paramName])]; \
} \
}

NSArray *argTypes = [[jsVal[@"args"] toString] componentsSeparatedByString:@","];
if (argTypes.count > [jsVal[@"argCount"] toInt32]) {
argTypes = [argTypes subarrayWithRange:NSMakeRange(1, argTypes.count - 1)];
}
id cb = ^id(void *p0, void *p1, void *p2, void *p3, void *p4, void *p5) {
NSMutableArray *list = [[NSMutableArray alloc] init];
BLK_TRAITS_ARG(0, p0)
BLK_TRAITS_ARG(1, p1)
BLK_TRAITS_ARG(2, p2)
BLK_TRAITS_ARG(3, p3)
BLK_TRAITS_ARG(4, p4)
BLK_TRAITS_ARG(5, p5)
JSValue *ret = [jsVal[@"cb"] callWithArguments:list];
return formatJSToOC(ret);
};
void (^block)(void) = ^(void){};
uint8_t *p = (uint8_t *)((__bridge void *)block);
p += sizeof(void *) + sizeof(int32_t) *2;
void(**invoke)(void) = (void (**)(void))p;

p += sizeof(void *) + sizeof(uintptr_t) * 2;
const char **signature = (const char **)p;

static NSMutableDictionary *typeSignatureDict;
if (!typeSignatureDict) {
typeSignatureDict = [NSMutableDictionary new];
#define JP_DEFINE_TYPE_SIGNATURE(_type) \
[typeSignatureDict setObject:@[[NSString stringWithUTF8String:@encode(_type)], @(sizeof(_type))] forKey:@#_type];\

JP_DEFINE_TYPE_SIGNATURE(id);
JP_DEFINE_TYPE_SIGNATURE(BOOL);
JP_DEFINE_TYPE_SIGNATURE(int);
JP_DEFINE_TYPE_SIGNATURE(void);
JP_DEFINE_TYPE_SIGNATURE(char);
JP_DEFINE_TYPE_SIGNATURE(short);
JP_DEFINE_TYPE_SIGNATURE(unsigned short);
JP_DEFINE_TYPE_SIGNATURE(unsigned int);
JP_DEFINE_TYPE_SIGNATURE(long);
JP_DEFINE_TYPE_SIGNATURE(unsigned long);
JP_DEFINE_TYPE_SIGNATURE(long long);
JP_DEFINE_TYPE_SIGNATURE(unsigned long long);
JP_DEFINE_TYPE_SIGNATURE(float);
JP_DEFINE_TYPE_SIGNATURE(double);
JP_DEFINE_TYPE_SIGNATURE(bool);
JP_DEFINE_TYPE_SIGNATURE(size_t);
JP_DEFINE_TYPE_SIGNATURE(CGFloat);
JP_DEFINE_TYPE_SIGNATURE(CGSize);
JP_DEFINE_TYPE_SIGNATURE(CGRect);
JP_DEFINE_TYPE_SIGNATURE(CGPoint);
JP_DEFINE_TYPE_SIGNATURE(CGVector);
JP_DEFINE_TYPE_SIGNATURE(NSRange);
JP_DEFINE_TYPE_SIGNATURE(NSInteger);
JP_DEFINE_TYPE_SIGNATURE(Class);
JP_DEFINE_TYPE_SIGNATURE(SEL);
JP_DEFINE_TYPE_SIGNATURE(void*);
JP_DEFINE_TYPE_SIGNATURE(void *);
}

NSString *types = [jsVal[@"args"] toString];
NSArray *lt = [types componentsSeparatedByString:@","];

NSString *funcSignature = @"@?0";

NSInteger size = sizeof(void *);
for (NSInteger i = 1; i < lt.count;) {
NSString *t = trim(lt[i]);
NSString *tpe = typeSignatureDict[typeSignatureDict[t] ? t : @"id"][0];
if (i == 0) {
funcSignature =[[NSString stringWithFormat:@"%@%@",tpe, [@(size) stringValue]] stringByAppendingString:funcSignature];
break;
}

funcSignature = [funcSignature stringByAppendingString:[NSString stringWithFormat:@"%@%@", tpe, [@(size) stringValue]]];
size += [typeSignatureDict[typeSignatureDict[t] ? t : @"id"][1] integerValue];

i = (i != lt.count - 1) ? i + 1 : 0;
}

IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
if ([funcSignature UTF8String][0] == '{') {
//In some cases that returns struct, we should use the '_stret' API:
//http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
//NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:[funcSignature UTF8String]];
if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
}
#endif
*invoke = (void *)msgForwardIMP;

const char *fs = [funcSignature UTF8String];
char *s = malloc(strlen(fs));
strcpy(s, fs);
*signature = s;

objc_setAssociatedObject(block, "_JSValue", jsVal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"NSBlock");
#define JP_HOOK_METHOD(selector, func) {Method method = class_getInstanceMethod([NSObject class], selector); \
BOOL success = class_addMethod(cls, selector, (IMP)func, method_getTypeEncoding(method)); \
if (!success) { class_replaceMethod(cls, selector, (IMP)func, method_getTypeEncoding(method));}}

JP_HOOK_METHOD(@selector(methodSignatureForSelector:), block_methodSignatureForSelector);
JP_HOOK_METHOD(@selector(forwardInvocation:), JPForwardInvocation);
});

return cb;
return block;
}

#pragma mark - Struct
Expand Down

0 comments on commit e80a557

Please sign in to comment.