我怎样才能安全地处理这个varargs函数?

时间:2017-03-10 21:10:25

标签: objective-c variadic-functions

我编写了一种方法,可以使用格式字符串轻松设置URL的路径部分。最初,我只是将格式字符串和args直接传递给initWithFormat:,但有人开始传递给我带有空格的args。在转到initWithFormat:之前,我将方法更改为对参数进行百分比编码。

我可以将其称为[request setUrlWithFormat:@"users/%@/timesheets", username],其中username可以是bmauterb mauter

- (void) setUrlWithFormat:(NSString *)format, ... {

    // loop through varargs and cleanse them for the URL path
    va_list args;
    va_start(args, format);
    NSMutableArray *cleaned = [[NSMutableArray alloc] init];
    for(NSString *s = format; s != nil; s = va_arg(args, NSString *)) {
        if (s == format) continue;
        [cleaned addObject:[s stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]];
    }
    va_end(args);

    // put the cleansed values back into a varargs list
    __unsafe_unretained id  *argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * cleaned.count);
    for (NSInteger i = 0; i < cleaned.count; i++) argList[i] = cleaned[i];
    NSString* result = [[NSString alloc] initWithFormat:format, *argList];
    free(argList);

    [self setUrl:result];
}

有时我在第一个for循环行上使用EXC_BAD_ACCESS崩溃。有时我会在initWithString:行崩溃。大部分时间它都很完美。

更新:再次感谢@uliwitness。如果其他人想看到我最终得到的东西,请点击:

- (void) setUrlWithFormat:(NSString *)format, ... {

    DLog(@"format=%@", format);

    va_list args;
    va_start(args, format);

    NSMutableString *result = [format mutableCopy];

    NSRange range = [result rangeOfString:@"%@"];
    while(range.location != NSNotFound) {

        NSObject *obj = va_arg(args, NSObject *);
        NSString *dirty = nil;
        if ([obj isKindOfClass:[NSString class]]) dirty = (NSString *)obj;
        else dirty = [NSString stringWithFormat:@"%@", obj];

        NSString *clean = [dirty stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]];
        DLog(@"dirty=%@, clean=%@", dirty, clean);

        [result replaceCharactersInRange:range withString:clean];

        range = [result rangeOfString:@"%@"];
    }
    va_end(args);

    DLog(@"result=%@", result);

    [self setUrl:result];
}

1 个答案:

答案 0 :(得分:2)

你在这里做了几个假设,但事实并非如此。首先,你没有把NIL作为一个vararg,所以你不能假设会有一个NIL。相反,你必须计算格式占位符的数量,并且只抓住那么多的varargs。

您现在正在做的是在参数列表的末尾运行。有时你很幸运,这个列表后面的随机内存结果是8个零字节,所以它看起来像NIL而你的循环终止了。当它崩溃时,你得到一些其他随机字节,这些字节不是有效的对象指针,或者看起来像是指向不同类对象的指针,这就是你崩溃的原因。

另外,为什么你假设你可以将数组的第一项传递给NSString&#39; s -stringWithFormat :?您的代码恰好适用于此测试用例,但这只是因为您只有一个格式占位符。

-initWithFormat:将匹配数量的参数作为其格式字符串。没有不同的数字(就像你现在传递的那样),而不是数组(你可能认为你正在传递,但是因为C中的数组是指向第一项的指针,而C不能告诉指向一个对象的指针从指向数组的指针,并且不知道数组有多长,你实际传递的只是一个项目。

要完成这项工作,您需要做的就是编写自己的格式字符串解析版本。一个快速和脏的版本将是使用rangeOfString来查找"%@",然后将之前的部分附加到字符串,然后将转义的相应参数附加,并且一旦你的循环结束,剩下的就是字符串。