所有分析的源码都来自于支付宝开放平台:App支付客户端 DEMO&SDK[1]
偶然发现支付宝的 SDK 存在一个很有意思的函数 APMutableStringRemoveLastComma
。该函数会判断可变字符串尾部是否等于 ,
;如果相等,则进行移除。
而且,很奇怪的地方是,当我们通过导出的 bitcode
代码进行分析时,会发现该函数会 重复调用 字符串的 length
方法获取长度,而没有采用调用一次并缓存的方式进行性能优化。
bitcode
源码函数分析:
@"__ir_hidden#1_" = private global [7 x i8] c"length\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@"__ir_hidden#2_" = private externally_initialized global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @"__ir_hidden#1_", i64 0, i64 0), section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip"
@"__ir_hidden#3694_" = private global [20 x i8] c"substringWithRange:\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@"__ir_hidden#3695_" = private externally_initialized global i8* getelementptr inbounds ([20 x i8], [20 x i8]* @"__ir_hidden#3694_", i64 0, i64 0), section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip"
@__CFConstantStringClassReference = external global [0 x i32]
@"__ir_hidden#41_" = private unnamed_addr constant [2 x i8] c",\00", section "__TEXT,__cstring,cstring_literals", align 1
@"__ir_hidden#42_" = private constant %"__ir_hidden#3693_" { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @"__ir_hidden#41_", i32 0, i32 0), i64 1 }, section "__DATA,__cfstring"
@"__ir_hidden#3696_" = private global [17 x i8] c"isEqualToString:\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@"__ir_hidden#3697_" = private externally_initialized global i8* getelementptr inbounds ([17 x i8], [17 x i8]* @"__ir_hidden#3696_", i64 0, i64 0), section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip"
@"__ir_hidden#3973_" = private unnamed_addr constant [1 x i8] zeroinitializer, section "__TEXT,__cstring,cstring_literals", align 1
@"__ir_hidden#3974_" = private constant %"__ir_hidden#3693_" { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @"__ir_hidden#3973_", i32 0, i32 0), i64 0 }, section "__DATA,__cfstring"
@"__ir_hidden#3741_" = private global [37 x i8] c"replaceCharactersInRange:withString:\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@"__ir_hidden#3742_" = private externally_initialized global i8* getelementptr inbounds ([37 x i8], [37 x i8]* @"__ir_hidden#3741_", i64 0, i64 0), section "__DATA, __objc_selrefs, literal_pointers, no_dead_strip"
@llvm.compiler.used = appending global [8 x i8*] [i8* getelementptr inbounds ([7 x i8], [7 x i8]* @"__ir_hidden#1_", i32 0, i32 0), i8* bitcast (i8** @"__ir_hidden#2_" to i8*), i8* getelementptr inbounds ([20 x i8], [20 x i8]* @"__ir_hidden#3694_", i32 0, i32 0), i8* bitcast (i8** @"__ir_hidden#3695_" to i8*), i8* getelementptr inbounds ([17 x i8], [17 x i8]* @"__ir_hidden#3696_", i32 0, i32 0), i8* bitcast (i8** @"__ir_hidden#3697_" to i8*), i8* getelementptr inbounds ([37 x i8], [37 x i8]* @"__ir_hidden#3741_", i32 0, i32 0), i8* bitcast (i8** @"__ir_hidden#3742_" to i8*)], section "llvm.metadata"
; Function Attrs: optsize ssp
define void @APMutableStringRemoveLastComma(%0*) #0 {
%2 = bitcast %0* %0 to i8*
; 先对入参 NSMutableString 进行 retain 操作
%3 = tail call i8* @llvm.objc.retain(i8* %2)
; 这里实际上是获取 @selector(length) 方法,准备进行调用实例方法
%4 = load i8*, i8** @"__ir_hidden#2_", align 8, !invariant.load !8
; 调用 @selector(length) 方法
%5 = tail call i64 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i64 (i8*, i8*)*)(i8* %2, i8* %4) #3, !clang.arc.no_objc_arc_exceptions !8
; 将返回值与 0 比较
%6 = icmp eq i64 %5, 0
; 如果相等,进入 22: 分支,如果不等,进入 7: 分支
br i1 %6, label %22, label %7
7: ; preds = %1
; 再次获取 @selector(length) 方法,准备进行调用实例方法
%8 = load i8*, i8** @"__ir_hidden#2_", align 8, !invariant.load !8
; 再次调用 @selector(length) 方法
%9 = tail call i64 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i64 (i8*, i8*)*)(i8* %2, i8* %8) #3, !clang.arc.no_objc_arc_exceptions !8
; 将结果减 1
%10 = add i64 %9, -1
; 获取 @selector(substringWithRange:) 方法
%11 = load i8*, i8** @"__ir_hidden#3695_", align 8, !invariant.load !8
; 准备参数 NSRange ,分别是 {length-1,1}
%12 = insertvalue [2 x i64] undef, i64 %10, 0
%13 = insertvalue [2 x i64] %12, i64 1, 1
; 获取子串
%14 = tail call %1* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %1* (i8*, i8*, [2 x i64])*)(i8* %2, i8* %11, [2 x i64] %13) #3, !clang.arc.no_objc_arc_exceptions !8
%15 = bitcast %1* %14 to i8*
; 讲方法的返回值放入自动释放池
%16 = tail call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %15)
; 获取 @selector(isEqualToString:) 方法
%17 = load i8*, i8** @"__ir_hidden#3697_", align 8, !invariant.load !8
; 子串与 ',' 判等
%18 = tail call zeroext i1 bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i1 (i8*, i8*, %1*)*)(i8* %15, i8* %17, %1* bitcast (%"__ir_hidden#3693_"* @"__ir_hidden#42_" to %1*)) #3, !clang.arc.no_objc_arc_exceptions !8
; 如果相等,进入 19: 分支,否则进入 21:分支
br i1 %18, label %19, label %21
19: ; preds = %7
; 获取 @selector(replaceCharactersInRange:withString:) 方法
%20 = load i8*, i8** @"__ir_hidden#3742_", align 8, !invariant.load !8
; 将尾部替换为空字符串,和函数名 MutableStringRemoveLastComma 对应
tail call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, [2 x i64], %1*)*)(i8* %2, i8* %20, [2 x i64] %13, %1* bitcast (%"__ir_hidden#3693_"* @"__ir_hidden#3974_" to %1*)) #3, !clang.arc.no_objc_arc_exceptions !8
br label %21
21: ; preds = %19, %7
; 销毁子串
tail call void @llvm.objc.release(i8* %15)
br label %22
22: ; preds = %21, %1
; 释放对入参的引用
tail call void @llvm.objc.release(i8* %2)
; return 操作
ret void
}
下面的代码是根据上面的
bitcode
反推得到,不代表支付宝 SDK 原始的代码
void APMutableStringRemoveLastComma(NSMutableString *input) {
if ([input length]==0) {
return;
} else {
NSUInteger location = [input length];
location = location - 1;
NSRange range = NSMakeRange(location, 1);
NSString *tail = [input substringWithRange:range];
BOOL isEqual = [tail isEqualToString:@","];
if (isEqual) {
[input replaceCharactersInRange:range withString:@""];
}
}
}
void APMutableStringRemoveLastComma(NSMutableString *input) {
NSUInteger length = [input length];
if (length==0) {
return;
} else {
NSUInteger location = length - 1;
NSRange range = NSMakeRange(location, 1);
NSString *tail = [input substringWithRange:range];
BOOL isEqual = [tail isEqualToString:@","];
if (isEqual) {
[input replaceCharactersInRange:range withString:@""];
}
}
}
[1]
App支付客户端 DEMO&SDK: https://opendocs.alipay.com/open/54/104509