[preanalysis] Replace overridden method calls in ObjC

Summary:
This diff replaces overridden method calls in ObjC when possible, ie the first parameter of the
method has a sub-class type of the method's class.  For example,

when `MyEnumerator` is a sub-class of `NSEnumerator` and there is overridden `nextObject`,

```
[my_enumerator nextObject]
```

in Sil, it was translated to like

```
NSEnumerator.nextObject(my_enumerator : MyEnumerator*)
```

and the analyzer missed the overridden method.  This diff replaces the function call to

```
MyEnumerator.nextObject(my_enumerator : MyEnumerator*)
```

Reviewed By: ezgicicek

Differential Revision: D24477290

fbshipit-source-id: 6842a76f8
master
Sungkeun Cho 4 years ago committed by Facebook GitHub Bot
parent 067020f4df
commit 43b98fc0cf

@ -35,6 +35,13 @@ module AddAbstractionInstructions = struct
Procdesc.iter_nodes do_node pdesc
end
let objc_get_first_arg_typ = function
| [(_, {Typ.desc= Tptr ({desc= Tstruct ((ObjcClass _ | ObjcProtocol _) as objc_class)}, _)})] ->
Some objc_class
| _ ->
None
(** In ObjC, [NSObject.copy] returns the object returned by [copyWithZone:] on the given class. This
method must be implemented if the class complies with [NSCopying] protocol. Since we don't have
access to NSObject's code, to follow calls into [copyWithZone:], we replace such [copy] calls
@ -50,13 +57,6 @@ module ReplaceObjCCopy = struct
type copy_kind =
{protocol: string; method_name: string; method_with_zone: string; is_mutable: bool}
let get_first_arg_typ = function
| [(_, {Typ.desc= Tptr ({desc= Tstruct objc_class}, _)})] ->
Some objc_class
| _ ->
None
let get_copy_kind_opt pname =
let matches_nsobject_proc method_name =
String.equal (Procname.get_method pname) method_name
@ -88,8 +88,8 @@ module ReplaceObjCCopy = struct
let get_replaced_instr {protocol; method_name; method_with_zone; is_mutable} pdesc tenv params
instr ret_id_typ loc flags =
match get_first_arg_typ params with
ret_id_typ loc flags =
match objc_get_first_arg_typ params with
| Some cl ->
let class_name = Typ.Name.name cl in
if
@ -103,25 +103,69 @@ module ReplaceObjCCopy = struct
used by Objective-C. We still need to satisfy the
signature though. *)
L.(debug Capture Verbose) "REPLACING %s with '%s'@\n" method_name method_with_zone ;
Sil.Call
(ret_id_typ, function_exp, params @ [(Exp.null, Typ.pointer_to_objc_nszone)], loc, flags)
)
else instr
Some
(Sil.Call
( ret_id_typ
, function_exp
, params @ [(Exp.null, Typ.pointer_to_objc_nszone)]
, loc
, flags )) )
else None
| _ ->
instr
None
let process tenv pdesc =
let instr_replace_copy_method _node (instr : Sil.instr) =
match instr with
| Call (ret_id_typ, Const (Cfun pn), params, loc, flags) ->
get_copy_kind_opt pn
|> Option.value_map ~default:instr ~f:(fun copy_kind ->
get_replaced_instr copy_kind pdesc tenv params instr ret_id_typ loc flags )
let process tenv pdesc ret_id_typ callee params loc flags =
get_copy_kind_opt callee
|> Option.bind ~f:(fun copy_kind ->
get_replaced_instr copy_kind pdesc tenv params ret_id_typ loc flags )
end
module ReplaceObjCOverridden = struct
let may_be_super_call class_name_opt object_name =
Option.exists class_name_opt ~f:(Typ.Name.equal object_name)
let get_overridden_method_opt tenv ~caller_class_name ~callee params =
let open IOption.Let_syntax in
let* sup_class_name = Procname.get_class_type_name callee in
let* sub_class_name = objc_get_first_arg_typ params in
if
PatternMatch.is_subtype tenv sub_class_name sup_class_name
&& not (may_be_super_call caller_class_name sub_class_name)
then
let callee' = Procname.replace_class callee sub_class_name in
if Option.is_some (Procdesc.load callee') then Some callee' else None
else None
let process tenv caller ret_id_typ callee params loc flags =
get_overridden_method_opt tenv
~caller_class_name:(Procname.get_class_type_name caller)
~callee params
|> Option.map ~f:(fun overridden_method ->
Logging.d_printfln_escaped "Replace overridden method %a to %a" Procname.pp callee
Procname.pp overridden_method ;
Sil.Call (ret_id_typ, Const (Cfun overridden_method), params, loc, flags) )
end
module ReplaceObjCMethodCall = struct
let process tenv pdesc caller =
let replace_method instr =
match (instr : Sil.instr) with
| Call (ret_id_typ, Const (Cfun callee), params, loc, flags) ->
IOption.if_none_evalopt
(ReplaceObjCCopy.process tenv pdesc ret_id_typ callee params loc flags) ~f:(fun () ->
ReplaceObjCOverridden.process tenv caller ret_id_typ callee params loc flags )
|> Option.value ~default:instr
| _ ->
instr
in
Procdesc.replace_instrs pdesc ~f:instr_replace_copy_method |> ignore
Procdesc.replace_instrs pdesc ~f:(fun node instr ->
NodePrinter.with_session node ~kind:`ComputePre
~pp_name:(fun fmt -> Format.pp_print_string fmt "Replace ObjC method")
~f:(fun () -> replace_method instr) )
|> ignore
end
(** Find synthetic (including access and bridge) Java methods in the procedure and inline them in
@ -466,7 +510,7 @@ let do_preanalysis exe_env pdesc =
if not (Procname.is_java proc_name) then (
ClosuresSubstitution.process_closure_call summary ;
ClosureSubstSpecializedMethod.process summary ;
ReplaceObjCCopy.process tenv pdesc ) ;
ReplaceObjCMethodCall.process tenv pdesc proc_name ) ;
Liveness.process summary tenv ;
AddAbstractionInstructions.process pdesc ;
if Procname.is_java proc_name then Devirtualizer.process summary tenv ;

@ -0,0 +1,10 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface MyEnumerator : NSEnumerator<NSObject*>
@end

@ -0,0 +1,21 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "MyEnumerator.h"
@implementation MyEnumerator {
int n;
}
- (NSString*)nextObject {
for (int i = 0; i < n; i++) {
}
// NSEnumerator.nextObject should not be replaced
return [super nextObject];
}
@end

@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "MyEnumerator.h"
// init array
@ -214,3 +215,8 @@ void nsarray_copy_linear(NSArray* array) {
for (int i = 0; i < copy.count; i++) {
}
}
void call_my_enumerator_next_object_linear(MyEnumerator* enumerator) {
// NSEnumerator.nextObject should be replaced to MyEnumerator.nextObject
NSString* s = [enumerator nextObject];
}

@ -1,4 +1,7 @@
${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.indexOfObject:inSortedRange:options:usingComparator:[objc_blocknsarray_binary_search_log_FN_1], 0, OnUIThread:false, []
codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.dealloc, 1, OnUIThread:false, []
codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.nextObject, 6 + 3 ⋅ self->n + 3 ⋅ (1+max(0, self->n)), OnUIThread:false, [{1+max(0, self->n)},Loop,{self->n},Loop]
codetoanalyze/objc/performance/NSArray.m, call_my_enumerator_next_object_linear, 9 + 3 ⋅ enumerator->n.ub + 3 ⋅ (1+max(0, enumerator->n.ub)), OnUIThread:false, [{1+max(0, enumerator->n.ub)},Call to MyEnumerator.nextObject,Loop,{enumerator->n.ub},Call to MyEnumerator.nextObject,Loop]
codetoanalyze/objc/performance/NSArray.m, call_nsarray_enumerator_param_linear, 6, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, multiple_nsarray_enumerators_param_linear, 12 + 5 ⋅ (enumerator2.length + enumerator1.length + 2), OnUIThread:false, [{enumerator2.length + enumerator1.length + 2},Loop]
codetoanalyze/objc/performance/NSArray.m, nsarray_access_constant, 50, OnUIThread:false, []

Loading…
Cancel
Save