Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

OC 与 C++ 混编导致 UTF8String 野指针崩溃 #2397

Open
littlechaochao opened this issue Jul 31, 2024 · 1 comment
Open

OC 与 C++ 混编导致 UTF8String 野指针崩溃 #2397

littlechaochao opened this issue Jul 31, 2024 · 1 comment
Assignees

Comments

@littlechaochao
Copy link

littlechaochao commented Jul 31, 2024

【版本信息】

4.3.57

【平台信息】

iOS 原生

【预期的表现】

连续调用 PAGImageView 的 - (void)setPathAsync:completionBlock: 不会产生崩溃

【实际的情况】

1 当第一次调用 PAGImageView 的 - (void)setPathAsync:completionBlock: 时,下面函数会去下载 PAG 文件:

+ (PAGFile*)Load:(NSString*)path {
  if (path == nil) {
    return nil;
  }
  if ([PAGFileImpl IsNetWorkPath:path]) {
    NSData* cacheData = [PAGDiskCacheImpl ReadFile:path];
    if (cacheData == nil) {
      NSError* error = nil;
      cacheData = [NSData dataWithContentsOfURL:[NSURL URLWithString:path]
                                        options:NSDataReadingUncached
                                          error:&error]; // 同步网络下载
      if (error == nil && cacheData != nil) {
        [PAGDiskCacheImpl WritFile:path data:cacheData];
      }
    }
    return [PAGFileImpl Load:cacheData.bytes size:cacheData.length path:path];
  }
  auto pagFile = pag::PAGFile::Load([path UTF8String]);
  if (pagFile == nullptr) {
    return nil;
  }
  return (PAGFile*)[PAGLayerImpl ToPAGLayer:pagFile];
}

2 如果此时同一个 PAGImageView 对象触再次触发 - (void)setPathAsync:completionBlock: 调用,下面函数会将 PAGImageView 持有的文件 path 释放掉:

- (void)setPathAsync:(NSString*)path
        maxFrameRate:(float)maxFrameRate
     completionBlock:(void (^)(PAGFile*))callback {
  if (filePath != nil) {
    [filePath release]; // 释放之前的 filePath
    filePath = nil;
  }
  filePath = [path retain];
  [PAGFile LoadAsync:path
      completionBlock:^(PAGFile* pagFile) {
        [self setComposition:pagFile maxFrameRate:maxFrameRate];
        callback(pagFile);
      }];
}

这样造成的结果是,第一步的网络下载成功之后,调用后面的 [PAGDiskCacheImpl WritFile:path data:cacheData]; 方法访问了 path 变量,此时这个 path 变量已经被 release 了,会崩在这个方法的内部:

+ (BOOL)WritFile:(NSString*)key data:(NSData*)data {
  if (key == nil || data == nil) {
    return false;
  }
  std::string cacheKey = [key UTF8String]; // 会崩在 UTF8String 这里
  auto cacheDatas = tgfx::Data::MakeWithoutCopy(data.bytes, data.length);
  return pag::DiskCache::WriteFile(cacheKey, cacheDatas);
}

原因是下载是在一个 C++ block 里面执行的:

+ (void)LoadAsync:(NSString*)path completionBlock:(void (^)(PAGFile*))callback {
  if (path == nil) {
    callback(nil);
    return;
  }
  void (^copyCallback)(PAGFile*) = Block_copy(callback);
  tgfx::Task::Run([callBack = copyCallback, path]() { // 在这个 Block 里面执行下载
    PAGFile* file = [PAGFileImpl Load:path]; // 下载函数
    callBack(file);
    Block_release(callBack);
  });
}

但是 C++ block 捕获 path 变量并不会增加 OC 对象的引用计数,导致前面第 2 次调用 - (void)setPathAsync:completionBlock: 释放了 path,访问了野指针崩溃

同时,如果直接调用 PAGFile 的 LoadAsync:completionBlock:方法,这个问题也容易出现,原因和上面是一样的。

【Demo及附件】

Demo 运行复现
内存访问错误demo.zip

@GabrielOmarBatistuta
Copy link

有解决办法了吗?我也遇到同样的问题了

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants