What happens when non-void method reaches end in Objective-C? - ios

Question I'd like to ask what happens when a non-void method reaches end without return?
Environment This question is related to iOS and Objective-C. I'm not sure how things described in this question behave elsewhere.
Introduction and background
Normally, when you declare a non-void method in Objective-C and you don't specify a return, Xcode shows a compile error
Control reaches end of non-void function
and forbids you from compiling unless you fix this issue.
The system can be very smart in detecting if your code can or cannot reach an end of non-void function - recognizing if your if/else blocks satisfy all options, going through switches etc.
However, I've found one (and there may be others, really) options of brain-f**king the compiler into thinking "Hey mate, yeah, this code looks perfectly fine, let's compile it and have fun!". It was by accident and caused some head-aches. Here's the code!
The Code
typedef enum eSections { eSectionRecent, eSectionAround, eSectionAll } eSections;
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger localSection = section;
if (numberOfSections == 2) localSection++;
switch ((eSections)#(localSection))
{
case eSectionRecent:
return 1;
break;
case eSectionAround:
return 1;
break;
case eSectionAll:
return 3;
break;
}
}
The Result
With this code, the compiler thinks that it's ok to let this method compile, because hey, there's the switch object which is an enum, and there is a case for each of the possible enum values, so, yeah, that sounds legit...
So I'm really interested, what happens now that the compiler was confused and let me run this faulty code to the end?
The Error
For anyone interested, my error was in (eSections)#(localSection), where instead of typing localSection into eSections I typed #(localSection), which compiles into a NSNumber and instead of the value uses the pointer to the object, thus failing to match with any of the the case, since pointers can be quite long (and very probably not 0, 1 or 2). What happened next was that the project tried to allocate space for a really huge number of cells (I think that not specifying a return was something like let's just throw some memory value there) and ultimately crashed the app for memory pressure.
Thanks for all answers!

The result is undefined behavior.
Usually, the caller gets unspecified junk as the return value. Whatever was left in the register or, in some cases, some stack location used by the caller for a local variable is what the caller gets.

Related

Where does a segmentation fault occur if the debugger points to a function definition?

To elaborate, I am currently writing a program that requires a function that is provided by the professor. When I run the program, I get a segmentation fault, and the debugger I use (gdb) says that the segmentation fault occurred at the definition of the function that, like I said, was provided by the professor.
So my question here is, is the definition itself causing the fault, or is it somewhere else in the program that called the function causing the fault?
I attempted to find a spot in the program that might have been leading to it, such as areas that might have incorrect parameters. I have not changed the function itself, as it is not supposed to be modified (as per instructions). This is my first time posting a question, so if there is any other information needed, please let me know.
The error thrown is as follows:
Program received signal SIGSEGV, Segmentation fault. .0x00401450 in Parser::GetNextToken (in=..., line=#0x63fef0: 1) at PA2.cpp:20 20 return GetNextToken(in, line);
The code itself that this is happening at is this:
static LexItem GetNextToken(istream& in, int& line) {
if( pushed_back ) {
pushed_back = false;
return pushed_token;
}
return GetNextToken(in, line);
}
Making many assumptions here, but maybe the lesson is to understand how the stack is affected by a function call and parameters. Create a main() function, that call the professor's provided function and trace the code using dbg, looking at the stack.

Crashlytics iOS - Crash at line 0 - Swift sources

I'm currently facing a problem with some Swift source files when a crash occurs. Indeed, on Crashlytics I have a weird info about the line and the reason of the crash. It tells me the source has crashed at the line 0 and it gives me a SIGTRAP error. I read that this error occurs when a Thread hits a BreakPoint. But the problem is that this error occurs while I'm not debugging (application test from TestFlight).
Here is an example when Crashlytics tells me there's a SIGTRAP Error at line 0 :
// Method that crashs
private func extractSubDataFrom(writeBuffer: inout Data, chunkSize: Int) -> Data? {
guard chunkSize > 0 else { // Prevent from having a 0 division
return nil
}
// Get nb of chunks to write (then the number of bytes from it)
let nbOfChunksToWrite: Int = Int(floor(Double(writeBuffer.count) / Double(chunkSize)))
let dataCountToWrite = max(0, nbOfChunksToWrite * chunkSize)
guard dataCountToWrite > 0 else {
return nil // Not enough data to write for now
}
// Extract data
let subData = writeBuffer.extractSubDataWith(range: 0..<dataCountToWrite)
return subData
}
Another Swift file to explain what happens at the line "writeBuffer.extractSubDataWith(range: 0..
public extension Data {
//MARK: - Public
public mutating func extractSubDataWith(range: Range) -> Data? {
guard range.lowerBound >= 0 && range.upperBound <= self.count else {
return nil
}
// Get a copy of data and remove them from self
let subData = self.subdata(in: range)
self.removeSubrange(range)
return subData
}
}
Could you tell me what I'm doing wrong ? Or what can occurs this weird SIGTRAP error ?
Thank you
Crashing with a line of zero is indeed weird. But, common in Swift code.
The Swift compiler can do code generation on your behalf. This can happen quite a bit with generic functions, but may also happen for other reasons. When the compiler generates code, it also produces debug information for the code it generates. This debug information typically references the file that caused the code to be generated. But, the compiler tags it all with a line of 0 to distinguish it from code that was actually written by the developer.
These generic functions also do not have to be written by you - I've seen this happen with standard library functions too.
(Aside: I believe that the DWARF standard can, in fact, describe this situation more precisely. But, unfortunately Apple doesn't seem to use it in that way.)
Apple verified this line zero behavior via a Radar I filed about it a number of years ago. You can also poke around in your app's own debug data (via, for example dwarfdump) if you want to confirm.
One reason you might want to try to do this, is if you really don't trust that Crashlytics is labelling the lines correctly. There's a lot of stuff between their UI and the raw crash data. It is conceivable something's gone wrong. The only way you can confirm this is to grab the crashing address + binary, and do the lookup yourself. If dwarfdump tells you this happened at line zero, then that confirms this is just an artifact of compile-time code generation.
However, I would tend to believe there's nothing wrong with the Crashlytics UI. I just wanted to point it out as a possibility.
As for SIGTRAP - there's nothing weird about that at all. This is just an indication that the code being run has decided to terminate the process. This is different, for example, from a SIGBUS, where the OS does the terminating. This could be caused by Swift integer and/or range bounds checking. Your code does have some of that kind of thing in both places. And, since that would be so performance-critical - would be a prime candidate for inline code generation.
Update
It now seems like, at least in some situations, the compiler also now uses a file name of <compiler-generated>. I'm sure they did this to make this case clearer. So, it could be that with more recent versions of Swift, you'll instead see <compiler-generated>:0. This might not help tracking down a crash, but will least make things more obvious.

(iOS) Jump bypasses initialization of retaining variable when using goto

I got this compiler error when using goto in my ARC iOS project.
Cannot jump from this goto statement to its label. Jump bypasses
initialization of retaining variable
I know goto is bad in general but ... please just tell me how to fix it. The codes are like following,
//some process
NSArray *current = ... ;
if (current.count ==0) goto cleanup;
//proceed to next
if (processed failed) goto cleanup;
//further process
cleanup:
//clean up codes
I finally figured it out! Actually the warning said it clearly, "Jump bypasses initialization of retaining variable" so in the section next
//In proceed to next section I declare & init some object!
My codes/problem is basically the same as c99 goto past initialization
The solution is simple, just added a {} block to it, as here mentioned Why can't variables be declared in a switch statement?
For those who are wondering why I still need goto, I think this explained it Is it ever advantageous to use 'goto' in a language that supports loops and functions? If so, why? , especially "Cleanly exiting a function", check here for an example
http://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c
Without goto mainline code is deep inside the nested conditions (Of course we can also introduce a helper function to deal with it).

isKindOfClass returning different values for Test Cases

I have a method someMethod. This method, at some point has the following if-else condition.
- (void) someMethod {
// ... some more code ...
if ([userArray[0] isKindOfClass:[Me class]]) {
// some code
}
else {
// some other code
}
}
Now this if-condition is always met when I execute the code normally. But when I call it from one of my test-cases, the else-part gets executed instead. I am calling this method exactly the same way (it has no side-effects, etc).
When I debugged the thing in both normal run, and testing run. I saw something different.
While running in Test, the userArray had 1 object, (Me_Me_2 *)0x00007fa61d39dbf0.
And while running it normally, the userArray had the same object, but there was one difference. It said (Me_Me_ *)0x00007fce71459ae0.
When I print the value of NSStringFromClass([userArray[0] class]), they both print "Me".
"Me" is a NSManagedObject.
Another interesting thing is, if I add an expression in the debugger and evaluate it, it always evaluates to true - ([((NSObject*)userArray[0]) isKindOfClass:[Me class]]) returns (bool)true. This is totally bizarre! If the condition is true, why does it ever go into the else block?
Now some questions -
What is happening over here? Are Core Data objects treated different when running in tests?
Why is the type of the object "Me_Me_2" while testing and "Me_Me_" otherwise? Why is it not just "Me"?
This sounds similar to the following issue: isKindOfClass doesn't work as expected
In short, is the class being compared a target member of the test target? It should only be a target member of the application.

Passing Data through the Stack

I wanted to see if you could pass struct through the stack and I manage to get a local var from a void function in another void function.
Do you guys thinks there is any use to that and is there any chance you can get corrupted data between the two function call ?
Here's the Code in C (I know it's dirty)
#include <stdio.h>
typedef struct pouet
{
int a,b,c;
char d;
char * e;
}Pouet;
void test1()
{
Pouet p1;
p1.a = 1;
p1.b = 2;
p1.c = 3;
p1.d = 'a';
p1.e = "1234567890";
printf("Declared struct : %d %d %d %c \'%s\'\n", p1.a, p1.b, p1.c, p1.d, p1.e);
}
void test2()
{
Pouet p2;
printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e);
p2.a++;
}
int main()
{
test1();
test2();
test2();
return 0;
}
Output is :
Declared struct : 1 2 3 a '1234567890'
Element of struct undeclared : 1 2 3 a '1234567890'
Element of struct undeclared : 2 2 3 a '1234567890'
Contrary to the opinion of the majority, I think it can work out in most of the cases (not that you should rely on it, though).
Let's check it out. First you call test1, and it gets a new stack frame: the stack pointer which signifies the top of the stack goes up. On that stack frame, besides other things, memory for your struct (exactly the size of sizeof(struct pouet)) is reserved and then initialized. What happens when test1 returns? Does its stack frame, along with your memory, get destroyed?
Quite the opposite. It stays on the stack. However, the stack pointer drops below it, back into the calling function. You see, this is quite a simple operation, it's just a matter of changing the stack pointer's value. I doubt there is any technology that clears a stack frame when it is disposed. It's just too costy a thing to do!
What happens then? Well, you call test2. All it stores on the stack is just another instance of struct pouet, which means that its stack frame will most probably be exactly the same size as that of test1. This also means that test2 will reserve the memory that previously contained your initialized struct pouet for its own variable Pouet p2, since both variables should most probably have the same positions relative to the beginning of the stack frame. Which in turn means that it will be initialized to the same value.
However, this setup is not something to be relied upon. Even with concerns about non-standartized behaviour aside, it's bound to be broken by something as simple as a call to a different function between the calls to test1 and test2, or test1 and test2 having stack frames of different sizes.
Also, you should take compiler optimizations into account, which could break things too. However, the more similar your functions are, the less chances there are that they will receive different optimization treatment.
Of course there's a chance you can get corrupted data; you're using undefined behavior.
What you have is undefined behavior.
printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e);
The scope of the variable p2 is local to function test2() and as soon as you exit the function the variable is no more valid.
You are accessing uninitialized variables which will lead to undefined behavior.
The output what you see is not guaranteed at all times and on all platforms. So you need to get rid of the undefined behavior in your code.
The data may or may not appear in test2. It depends on exactly how the program was compiled. It's more likely to work in a toy example like yours than in a real program, and it's more likely to work if you turn off compiler optimizations.
The language definition says that the local variable ceases to exist at the end of the function. Attempting to read the address where you think it was stored may or may produce a result; it could even crash the program, or make it execute some completely unexpected code. It's undefined behavior.
For example, the compiler might decide to put a variable in registers in one function but not in the other, breaking the alignment of variables on the stack. It can even do that with a big struct, splitting it into several registers and some stack — as long as you don't take the address of the struct it doesn't need to exist as an addressable chunk of memory. The compiler might write a stack canary on top of one of the variables. These are just possibilities at the top of my head.
C lets you see a lot behind the scenes. A lot of what you see behind the scenes can completely change from one production compilation or run to the next.
Understanding what's going on here is useful as a debugging skill, to understand where values that you see in a debugger might be coming from. As a programming technique, this is useless since you aren't making the computer accomplish any particular result.
Just because this works for one compiler doesn't mean that it will for all. How uninitialized variables are handled is undefined and one computer could very well init pointers to null etc without breaking any rules.
So don't do this or rely on it. I have actually seen code that depended on functionality in mysql that was a bug. When that was fixed in later versions the program stopped working. My thoughts about the designer of that system I'll keep to myself.
In short, never rely on functionality that is not defined. If you knowingly use it for a specific function and you are prepared that an update to the compiler etc can break it and you keep an eye out for this at all times it might be something you could explain and live with. But most of the time this is far from a good idea.

Resources