facebook / infer

A static analyzer for Java, C, C++, and Objective-C
http://fbinfer.com/
MIT License
14.8k stars 2k forks source link

[Biabduction] [Objective-C] Retain Cycle not work when wrapping cycles. #1762

Open skyleaworlder opened 1 year ago

skyleaworlder commented 1 year ago

Please include the following information:

1. Only Objective-C code

@JumpMasterJJ and I try to use infer biabduction to analyze Objective-C "Retain Cycle". This is our code:

```objective-c #import @class Child; @class Parent; struct ParentWrapper { Parent *parent; }; struct ChildWrapper { Child *child; }; @interface Parent : NSObject { @public struct ChildWrapper childWrapper; } -(void) dealloc; @end @interface Child : NSObject { @public struct ParentWrapper parentWrapper; } -(void) dealloc; @end @implementation Parent -(void) dealloc { NSLog(@"this is parent dealloc function"); } @end @implementation Child -(void) dealloc { NSLog(@"this is parent dealloc function"); } @end int foo(int argc, const char* argv[]) { Parent *parent = [[Parent alloc] init]; Child *child = [[Child alloc] init]; parent->childWrapper.child = child; child->parentWrapper.parent = parent; return 0; } int main(int argc, const char* argv[]) { foo(argc, argv); return 0; } ```

You can easily know there is a reference counting problem in the above code. And I can confirm it since I also compiled it without infer. It didn't print log due to retain cycles:

clang -fobjc-arc -framework Foundation main.m -o main

./main
# No output

If I remove child->parentWrapper.parent = parent;, it can print logs successfully:

```objective-c #import @class Child; @class Parent; struct ParentWrapper { Parent *parent; }; struct ChildWrapper { Child *child; }; @interface Parent : NSObject { @public struct ChildWrapper childWrapper; } -(void) dealloc; @end @interface Child : NSObject { @public struct ParentWrapper parentWrapper; } -(void) dealloc; @end @implementation Parent -(void) dealloc { NSLog(@"this is parent dealloc function"); } @end @implementation Child -(void) dealloc { NSLog(@"this is parent dealloc function"); } @end int foo(int argc, const char* argv[]) { Parent *parent = [[Parent alloc] init]; Child *child = [[Child alloc] init]; parent->childWrapper.child = child; // child->parentWrapper.parent = parent; return 0; } int main(int argc, const char* argv[]) { foo(argc, argv); return 0; } ```

It can print:

./main
2023-05-17 19:32:49.180 main[25913:168343] this is parent dealloc function
2023-05-17 19:32:49.180 main[25913:168343] this is parent dealloc function

2. With infer biabduction

We ran for the first piece of code:

infer run --biabduction --debug -- clang -fobjc-arc -framework Foundation main.m -o main

But I got:

1/1 [################################################################################] 100% 514ms

No issues found

3. Potential problem

We also try to write some other pieces of Objective-C program, like this:

```objective-c #import @class Child; @interface Parent : NSObject { @public Child *child; } @end @interface Child : NSObject { @public Parent *parent; } @end @implementation Parent @end @implementation Child @end int foo(int argc, const char* argv[]) { Parent *parent = [[Parent alloc] init]; Child *child = [[Child alloc] init]; parent->child = child; child->parent = parent; return 0; } int main(int argc, const char* argv[]) { return 0; } ```

Apparently, it also has the same kind of problem. And we ran infer:

infer run --biabduction --debug -- clang -fobjc-arc -framework Foundation main-simple.m -o main-simple

1/1 [################################################################################] 100% 800ms

main-simple.m:25: error: Biabduction Retain Cycle
  Retain cycle at line 25, column 5 involving the following objects:
 (1) `child` of type `Child*` --> `parent`, last assigned on line 25
 (2) `parent` of type `Parent*` --> `child`, last assigned on line 24.
  23. 
  24.     parent->child = child;
  25.     child->parent = parent;
          ^
  26.     return 0;
  27. }

Found 1 issue
                          Issue Type(ISSUED_TYPE_ID): #
  Biabduction Retain Cycle(BIABDUCTION_RETAIN_CYCLE): 1

So we think that it's related to "wrapper", which means that current Retain Cycle checker might be not able to handle nested data structure.