CFNetworkCopyProxiesForAutoConfigurationScript and CFNetworkExecuteProxyAutoConfigurationURL cause the memory leak

Hi Experts,

I created a simple application with the following code to retrieve the proxy info based on PAC file or PAC URL such as Xcode:

  NSString *strURL = @"http://example.com";
  NSString *pacFileData = @"function FindProxyForURL(url, host) {if (dnsDomainIs(host, \"example.com\") || dnsDomainIs(host, \"www.example.com\")) {return \"PROXY 192.168.1.2:8888;DIRECT\";}return \"DIRECT\";}";
  CFStringRef tempPacdata = CFStringCreateCopy(kCFAllocatorDefault, (__bridge CFStringRef)pacFileData);
  CFURLRef tempURLref = CFURLCreateWithString(kCFAllocatorDefault, (__bridge CFStringRef)strURL, nil);
  CFArrayRef proxies = CFNetworkCopyProxiesForAutoConfigurationScript(tempPacdata, tempURLref, &err);

  strURL = nil;
  if (proxies != NULL)
  {
    CFRelease(proxies);
    proxies = NULL;
  }

  if (tempPacdata != NULL)
  {
    CFRelease(tempPacdata);
    tempPacdata = NULL;
  }
  if (tempURLref != NULL)
  {
    CFRelease(tempURLref);
    tempURLref = NULL;
  }

The output of leaks:

Report Version:  7
Analysis Tool:   /usr/bin/leaks

Physical footprint:         2700K
Physical footprint (peak):  2700K
----

leaks Report Version: 4.0
Process 43271: 783 nodes malloced for 85 KB
Process 43271: 17 leaks for 1232 total leaked bytes.

    17 (1.20K) ROOT CYCLE: <CFRunLoopSource 0x6000002f8000> [192]
       16 (1.02K) ROOT CYCLE: 0x600000ef80e0 [224]
          CYCLE BACK TO <CFRunLoopSource 0x6000002f8000> [192]
          5 (352 bytes) ROOT CYCLE: 0x6000019f4400 [128]
             2 (80 bytes) ROOT CYCLE: 0x6000037e8220 [32]
                1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0x6000039e8030> [48]  CFNetwork  <unknown-symbol> 0x7ff819c39000 + 1605859  0x7ff819dc10e3
             1 (32 bytes) ROOT CYCLE: 0x6000037e8240 [32]
             1 (112 bytes) <NSURL 0x600001cf4000> [112]  "http://example.com/"
          9 (432 bytes) <CFArray 0x6000022f87c0> [64]  item count: 2
             5 (240 bytes) <NSDictionary 0x6000022f8780> [64]  item count: 3
                1 (48 bytes) <CFString 0x6000039f8c60> [48]  length: 16  "kCFProxyTypeHTTP"
                1 (48 bytes) <CFString 0x6000039f8d50> [48]  length: 21  "kCFProxyPortNumberKey"
                1 (48 bytes) _list --> <CFString 0x6000039f8cf0> [48]  length: 19  "kCFProxyHostNameKey"
                1 (32 bytes) <CFString 0x6000037fab00> [32]  length: 11  "192.168.1.2"
             3 (128 bytes) <NSDictionary 0x6000037fab20> [32]
                1 (48 bytes) _key --> <CFString 0x6000039f8c00> [48]  length: 15  "kCFProxyTypeKey"
                1 (48 bytes) _obj --> <CFString 0x6000039f8cc0> [48]  length: 16  "kCFProxyTypeNone"
          1 (32 bytes) 0x6000037fab40 [32]

Does that mean some bugs in those APIs?

Replies

Does that mean some bugs in those APIs?

Probably. Those APIs don’t get a lot of footfall. However, it’s hard to be sure because the code you posted is incomplete. It doesn’t show the final disposition of proxies or err, both of which would potentially trigger leaks.

Pasted in below is my version of your code. Note that this learns heavily on Objective-C’s automatic memory management, using toll-free bridging and CFBridgingRelease to minimise the amount of manual memory management in play.

My tests suggests that this also leaks. Run it in your environment and confirm that. Once you do, I encourage you to file a bug with your test project. Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


static void test(void) {
    NSURL * url = [NSURL URLWithString:@"http://example.com"];
    NSString * pac = @"function FindProxyForURL(url, host) {if (dnsDomainIs(host, \"example.com\") || dnsDomainIs(host, \"www.example.com\")) {return \"PROXY 192.168.1.2:8888;DIRECT\";}return \"DIRECT\";}";
    CFErrorRef error = NULL;
    NSArray * proxies = CFBridgingRelease(
        CFNetworkCopyProxiesForAutoConfigurationScript(
            (__bridge CFStringRef) pac,
            (__bridge CFURLRef) url,
            &error
        )
    );
    if (proxies == nil) {
        NSError * nsError = CFBridgingRelease(error);
        NSLog(@"did fail, error: %@", nsError);
        return;
    }
    NSLog(@"success, proxies: %@", proxies);
}
Add a Comment

Thanks, @eskimo for your help. Yes, I still see the leaks caused by cycle reference?

Report Version: 7
Analysis Tool:  /usr/bin/leaks

Physical footprint:     2720K
Physical footprint (peak): 2720K
----

leaks Report Version: 4.0
Process 43967: 787 nodes malloced for 85 KB
Process 43967: 8 leaks for 800 total leaked bytes.

  8 (800 bytes) ROOT CYCLE: <CFRunLoopSource 0x600003034000> [192]
    7 (608 bytes) ROOT CYCLE: 0x600003c30000 [224]
     CYCLE BACK TO <CFRunLoopSource 0x600003034000> [192]
     5 (352 bytes) ROOT CYCLE: 0x600002b34a80 [128]
       2 (80 bytes) ROOT CYCLE: 0x600000536c80 [32]
        1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0x600000b34c00> [48] CFNetwork <unknown-symbol> 0x7ff819c39000 + 1605859 0x7ff819dc10e3
       1 (32 bytes) ROOT CYCLE: 0x600000536ce0 [32]
       1 (112 bytes) <NSURL 0x600002e344d0> [112] "http://example.com/"
     1 (32 bytes) 0x600000538140 [32]

Btw: If I use __bridge_transfer to replace the CFBridgingRelease in your code, then I get more leaks including the proxies.

CFArrayRef proxiesArray = CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)pac, (__bridge CFURLRef) url, &err);
NSArray *proxies = (__bridge_transfer NSArray *)proxiesArray;

more leaks:

Report Version: 7
Analysis Tool:  /usr/bin/leaks

Physical footprint:     2764K
Physical footprint (peak): 2764K
----

leaks Report Version: 4.0
Process 44061: 786 nodes malloced for 85 KB
Process 44061: 17 leaks for 1232 total leaked bytes.

  17 (1.20K) ROOT CYCLE: <CFRunLoopSource 0x600003888000> [192]
    16 (1.02K) ROOT CYCLE: 0x60000348c000 [224]
     CYCLE BACK TO <CFRunLoopSource 0x600003888000> [192]
     5 (352 bytes) ROOT CYCLE: 0x60000239c280 [128]
       2 (80 bytes) ROOT CYCLE: 0x600000d9c220 [32]
        1 (48 bytes) ROOT CYCLE: <__NSMallocBlock__ 0x60000039c000> [48] CFNetwork <unknown-symbol> 0x7ff819c39000 + 1605859 0x7ff819dc10e3
       1 (32 bytes) ROOT CYCLE: 0x600000d9c280 [32]
       1 (112 bytes) <NSURL 0x6000026980e0> [112] "http://example.com/"
     9 (432 bytes) <CFArray 0x600001884200> [64] item count: 2
       5 (240 bytes) <NSDictionary 0x6000018841c0> [64] item count: 3
        1 (48 bytes) <CFString 0x600000384060> [48] length: 21 "kCFProxyPortNumberKey"
        1 (48 bytes) <CFString 0x6000003840c0> [48] length: 16 "kCFProxyTypeHTTP"
        1 (48 bytes) _list --> <CFString 0x600000384090> [48] length: 19 "kCFProxyHostNameKey"
        1 (32 bytes) <CFString 0x600000d84060> [32] length: 11 "192.168.1.2"
       3 (128 bytes) <NSDictionary 0x600000d84080> [32]
        1 (48 bytes) _key --> <CFString 0x600000384030> [48] length: 15 "kCFProxyTypeKey"
        1 (48 bytes) _obj --> <CFString 0x6000003840f0> [48] length: 16 "kCFProxyTypeNone"
     1 (32 bytes) 0x600000d840a0 [32]

What's the difference between __bridge_transfer and CFBridgingRelease? Both are used to transfer ownership of a Core Foundation object to an Objective-C object, right?

What's the difference between __bridge_transfer and CFBridgingRelease?

Assuming you have ARC enabled, CFBridgingRelease is defined as:

NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
    return (__bridge_transfer id)X;
}

I just find the CFBridgingRelease easier to remember.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Add a Comment