I have a very peculiar problem here. I'm building a spider to grab hyperlinks from a webpage and put them into a table and I'm using NSRanges to parse the HTML document, but I've run into an issue.
I have the following line of code:
NSLog(#"%lu", [dataString rangeOfString:#"contents.asp?year1" options:0 range:NSMakeRange(index, dataString.length - index)].length);
This echoes 18 to the log, as it should, but if I put that into a boolean statement, seeing if that length is greater than -1:
NSLog(#"%d", ([dataString rangeOfString:#"contents.asp?year1" options:0 range:NSMakeRange(index, dataString.length - index)].length > -1));
This echoes 0, or false. 18 is clearly greater than -1, so what's the problem? If I switch it to < -1, it returns true. Does this have something to do with type-casting the unsigned long?
Here's the definition of NSRange:
typedef struct _NSRange {
NSUInteger location;
NSUInteger length;
} NSRange;
Notice that both fields are of type NSUInteger, an unsigned type. In fact, NSUInteger is unsigned long.
Since there is no wider integer type than unsigned long, the compiler promotes -1 to unsigned. I can't recall whether this is undefined behavior, but on iOS and Mac OS X it has the effect of treating the 2's complement bit pattern of -1 as an unsigned integer. That bit pattern, as an unsigned integer, is the maximum unsigned integer value.
Thus your comparison can never be true.
If you think -1 means "not found", you are mistaken. The correct way to check whether rangeOfString:options:range: failed to find the target is to check whether the location of the returned range is NSNotFound:
NSUInteger location = [dataString rangeOfString:#"contents.asp?year1"
options:0 range:NSMakeRange(index, dataString.length - index)].location
BOOL foundIt = location != NSNotFound;
Related
I have this method which extracts data from NSData at a specific pointer. The method only extracts a certain amount of bytes, in this case it is 4 bytes as I return a uint32.
I pass in a pointer (int start) which is used to create the location for an NSRange, the length of the range is the size of a uint32, which creates the range as 4 bytes long.
This works perfectly fine, until the pointer gets to 2147483648. When it gets to this value, the range is not created with 2147483648 for the location value instead it is created as 18446744071562067968 which is out of bounds for the data, and causes an exception to occur halting my program which stops it from reading the rest of the data.
I have no idea what is causing it do what its doing, the start value is the correct value when it is passed into the method, but it changes when the range is created. It does not happen for any of the previous pointer values.
Have I done something silly in my code? Or is it a different problem? Help will be appreciated.
Thank you.
- (uint32)getUINT32ValueFromData:(NSData *)rawData pointer:(int)start {
uint32 value;
NSRange range;
int length = sizeof(uint32);
NSUInteger dataLength = rawData.length;
NSData *currentData;
NSUInteger remainingBytes = dataLength - start;
if (remainingBytes > length) {
range.location = start;
range.length = length;
//should be 2147483648, location in range is showing 18446744071562067968 which is out of bounds...
currentData = [rawData subdataWithRange:range];
uint32 hostData = CFSwapInt32BigToHost(*(const uint32 *)[currentData bytes]);
value = hostData;
pointer = start + length;
}
else
{
NSLog(#"Data Length Exceeded!");
}
return value;
}
It's seems to be an 32/64 bit and signed/unsigned mismatch issue.
You're using three different types
int is a 32 bit signed type
uint32 is a 32 bit unsigned type
NSUInteger is a 32/64 bit unsigned type depending on the processor architecture.
unit32 for the value is fine, but you should use NSUInteger for the offset into the NSData object.
I'm working through some exercises and have got a warning that states:
Implicit conversion loses integer precision: 'NSUInteger' (aka 'unsigned long') to 'int'
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSArray *myColors;
int i;
int count;
myColors = #[#"Red", #"Green", #"Blue", #"Yellow"];
count = myColors.count; // <<< issue warning here
for (i = 0; i < count; i++)
NSLog (#"Element %i = %#", i, [myColors objectAtIndex: i]);
}
return 0;
}
The count method of NSArray returns an NSUInteger, and on the 64-bit OS X platform
NSUInteger is defined as unsigned long, and
unsigned long is a 64-bit unsigned integer.
int is a 32-bit integer.
So int is a "smaller" datatype than NSUInteger, therefore the compiler warning.
See also NSUInteger in the "Foundation Data Types Reference":
When building 32-bit applications, NSUInteger is a 32-bit unsigned
integer. A 64-bit application treats NSUInteger as a 64-bit unsigned
integer.
To fix that compiler warning, you can either declare the local count variable as
NSUInteger count;
or (if you are sure that your array will never contain more than 2^31-1 elements!),
add an explicit cast:
int count = (int)[myColors count];
Contrary to Martin's answer, casting to int (or ignoring the warning) isn't always safe even if you know your array doesn't have more than 2^31-1 elements. Not when compiling for 64-bit.
For example:
NSArray *array = #[#"a", #"b", #"c"];
int i = (int) [array indexOfObject:#"d"];
// indexOfObject returned NSNotFound, which is NSIntegerMax, which is LONG_MAX in 64 bit.
// We cast this to int and got -1.
// But -1 != NSNotFound. Trouble ahead!
if (i == NSNotFound) {
// thought we'd get here, but we don't
NSLog(#"it's not here");
}
else {
// this is what actually happens
NSLog(#"it's here: %d", i);
// **** crash horribly ****
NSLog(#"the object is %#", array[i]);
}
Change key in Project > Build Setting
"typecheck calls to printf/scanf : NO"
Explanation : [How it works]
Check calls to printf and scanf, etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense.
Hope it work
Other warning
objective c implicit conversion loses integer precision 'NSUInteger' (aka 'unsigned long') to 'int
Change key "implicit conversion to 32Bits Type > Debug > *64 architecture : No"
[caution: It may void other warning of 64 Bits architecture conversion].
Doing the expicit casting to the "int" solves the problem in my case. I had the same issue. So:
int count = (int)[myColors count];
In the comments here -- https://stackoverflow.com/a/9393138/8047 -- I discovered that BOOL has some unexpected behavior when setting its value from an int value. Mostly, if the value is set to 0x1000 it gets evaluated as FALSE (surprisingly).
NSLog(#"All zero? %d %d", (BOOL)0, (bool)0);
NSLog(#"All one? %d %d %d", (BOOL)4095, (BOOL)4096, (BOOL)4097); // 4096=0x1000 or 8-bits
NSLog(#"All one? %d %d %d", (bool)4095, (bool)4096, (bool)4097);
Produces:
All zero? 0 0
All one? -1 0 1
All one? 1 1 1
I think this is odd, but then again, I don't cast from int to BOOL much anyway. However:
Does this imply that bool be preferred to BOOL? Why or why not?
Is it okay to use
if (thatBool) {
or should one prefer
if (thatBool ? YES : NO) {
And why?
Note: This is a more specific version of this question of this -- Objective-C : BOOL vs bool -- but I think it adds to it and is not a duplicate.
I think that (BOOL)4096 being evaluated to 0 is a simple arithmetic overflow, just as (BOOL)256, since BOOL is an unsigned char. And I think that the !! casting trick ("double negation") works fine:
NSLog(#"%i", (BOOL)256); // 0
NSLog(#"%i", !!256); // 1
That means I’d use BOOL to keep the standard Cocoa coding style and simply watch for dangerous type casts. The thatBool ? YES : NO expression hurts my eyes, why would you want to do that? :)
1) bool is the C++ type, BOOL is the Objective-C one.
Casting from int to BOOL doesn't work properly because YES is just (BOOL)1 = (signed char)1 (= 0x001) and that isn't equal to (signed char)4 (= 0x100), for example.
2) Both will work, the second might be unreadable to somebody with little experience in programming. I prefer the good old c-style safe condition check with the constant on the left to prevent accidental omission of one of the equal signs.
if (YES == isEnabled) {
}
When using an int, you can always be explicit when setting a BOOL by checking if the int is equal to a particular value, e.g.
BOOL yesNo = ((int)4096 > 0);
BOOL enableButton = (someInt >= 16);
In other words, don't pass the int to the BOOL directly; turn it into a true/false statement.
When the follow code is run it goes inside the for loop and run NSLog. Why does this happen?
NSString *aString = nil;
for (int i=0; i<([aString length]-2); i++) {
NSLog(#"Inside loop.");
}
As i figure [aString length]-2 results in -2 and that's less then 0?
To be more precise, -[NSString length] returns an unsigned integer, so subtracting two from zero (remember, calling any method on nil gives you zero) doesn't give you -2, it gives you a very, very large number. Cast it to an int (or an NSInteger) to get the results you want.
You're asking the for loop to run if i<-2; as [NSString length] returns an unsigned integer, this value will wrap round to max int - 2.
You're passing a message to a nil object there too: [aString length]. I'm not sure this is defined behaviour, and may return a strange range of values, though I suspect it may be clever enough to return 0. My commentators say that this suspicion is true.
I'm using a 3rd party library for an iOS project I work on, and I'm down to one warning left in the project, namely on this line of code
[NSNumber numberWithUnsignedLongLong:'oaut']
And the warning is
Multi-character character constant
I suck at C, so I don't know how to fix this, but I'm sure the fix is relatively easy. Help?
EDIT: More context.
#implementation MPOAuthCredentialConcreteStore (KeychainAdditions)
- (void)addToKeychainUsingName:(NSString *)inName andValue:(NSString *)inValue {
NSString *serverName = [self.baseURL host];
NSString *securityDomain = [self.authenticationURL host];
// NSString *itemID = [NSString stringWithFormat:#"%#.oauth.%#", [[NSBundle mainBundle] bundleIdentifier], inName];
NSDictionary *searchDictionary = nil;
NSDictionary *keychainItemAttributeDictionary = [NSDictionary dictionaryWithObjectsAndKeys: (id)kSecClassInternetPassword, kSecClass,
securityDomain, kSecAttrSecurityDomain,
serverName, kSecAttrServer,
inName, kSecAttrAccount,
kSecAttrAuthenticationTypeDefault, kSecAttrAuthenticationType,
[NSNumber numberWithUnsignedLongLong:"oaut"], kSecAttrType,
[inValue dataUsingEncoding:NSUTF8StringEncoding], kSecValueData,
nil];
if ([self findValueFromKeychainUsingName:inName returningItem:&searchDictionary]) {
NSMutableDictionary *updateDictionary = [keychainItemAttributeDictionary mutableCopy];
[updateDictionary removeObjectForKey:(id)kSecClass];
SecItemUpdate((CFDictionaryRef)keychainItemAttributeDictionary, (CFDictionaryRef)updateDictionary);
[updateDictionary release];
} else {
OSStatus success = SecItemAdd( (CFDictionaryRef)keychainItemAttributeDictionary, NULL);
if (success == errSecNotAvailable) {
[NSException raise:#"Keychain Not Available" format:#"Keychain Access Not Currently Available"];
} else if (success == errSecDuplicateItem) {
[NSException raise:#"Keychain duplicate item exception" format:#"Item already exists for %#", keychainItemAttributeDictionary];
}
}
}
EDIT 2: They were attempting to meet the requirements of this by creating that NSNumber:
#constant kSecAttrType Specifies a dictionary key whose value is the item's
type attribute. You use this key to set or get a value of type
CFNumberRef that represents the item's type. This number is the
unsigned integer representation of a four-character code (e.g.,
'aTyp').
In C and Obj-C the single-quote ' is used only for single-character constants. You need to use the double-quote: "
Like so:
[NSNumber numberWithUnsignedLongLong:"oaut"]
That covers the warning, but there's also a semantic issue here. Although a single character constant, such as 'o', can be treated as an integer (and can be promoted to an unsigned long long), a "string" (char * or char []) cannot, which means you can't use "oaut" as an argument to numberWithUnsignedLongLong:
Update:
I guess the four-character code is supposed to be treated as an integer, i.e., the 8 bits of each char put in place as if they together were a 32-bit int:
char code[] = "oaut";
uint32_t code_as_int = code[0] | (code[1] << 8) | (code[2] << 16) | (code[3] << 24);
[NSNumber numberWithUnsignedLongLong:code_as_int]
although I'm not sure which endianness would be expected here, nor why this is calling for an unsigned long long, unless just to be certain there are enough bits.
Rudy's comment, now that I think of it, is correct -- multi-character constants are allowed by some compilers for exactly this purpose (it is "implementation-defined" behavior).
'oaut' (single quotes) is a character, so the compiler tries to interpret it as a multi-byte character but can't make any sense of it. That explains the error message.
I guess that if you gave a proper string, like #"oaut", you'd get another error message, since numberWithUnsignedLongLong: expects an unsigned long long, not a string or a character. Are you trying to pass a variable with the name "oaut"? If so, use
[NSNumber numberWithUnsignedLongLong: oaut];
If not, then please explain what "oaut" is.
Edit
'oaut' may actually be the original value. There are/were multi-character character constants in C. Using a (4 byte) char, used as int and promoted to unsigned long long would then be possible. This must be old code. It seems such code was accepted by CodeWarrior.
Assuming that really a multi-char char const was meant, 'oaut' looks like a "magic number" and this value was chosen because it is the beginning of "oauth". I guess it should either be value 0x6F617574 or 0x7475616F.
#Josh Caswell 's answer is partially right, the simplest and "official" solution is:
[NSNumber numberWithUnsignedInt:'oaut']
unsigned int's length is 32-bit in both 32-bit and 64-bit cpu, there's a practical example from Apple: https://developer.apple.com/library/ios/samplecode/CryptoExercise/Listings/Classes_SecKeyWrapper_m.html