Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix returning null from functions with output parameters in Python #2907

Closed
wants to merge 1 commit into from

Conversation

vadz
Copy link
Member

@vadz vadz commented May 14, 2024

Null pointers returned from functions also using output parameters or C strings typemaps were lost because the generated code didn't distinguish between Py_None corresponding to them and Py_None indicating the return value of a "void" function.

This doesn't seem to be easy to solve properly because the function return type and output parameters typemaps are independent and there is no way to customize the behaviour of the latter depending on the former.

So we do it using a hack involving a global variable which is always 0 and is passed to SWIG_Python_AppendOutput() from non-void functions, but is shadowed by a local variable with the same name set to 1 in the void ones. This allows us to only discard Py_None if it corresponds to a void function return value.

This is obviously ugly, but requires only minimal changes, doesn't impose much extra run-time overhead and is MT-safe (unlike any solution involving global variables).

Also add a unit test checking for this for Python and for JS, where the same function already worked correctly (unlike in some other languages, including at least Ruby and PHP).

Closes #2905.

@vadz vadz added the Python label May 14, 2024
@ojwb
Copy link
Member

ojwb commented May 14, 2024

Won't this result in -Wshadow warnings?

Null pointers returned from functions also using output parameters or C
strings typemaps were lost because the generated code didn't distinguish
between Py_None corresponding to them and Py_None indicating the return
value of a "void" function.

This doesn't seem to be easy to solve properly because the function
return type and output parameters typemaps are independent and there is
no way to customize the behaviour of the latter depending on the former.

So we do it using a hack involving a global variable which is always 0
and is passed to SWIG_Python_AppendOutput() from non-void functions, but
is shadowed by a local variable with the same name set to 1 in the void
ones. This allows us to only discard Py_None if it corresponds to a void
function return value.

This is obviously ugly, but requires only minimal changes, doesn't
impose much extra run-time overhead and is MT-safe (unlike any solution
involving changing global variables).

Also add a unit test checking for this for Python and for JS, where the
same function already worked correctly (unlike in some other languages,
including at least Ruby and PHP).

Closes swig#2905.
@vadz vadz force-pushed the py-nullptr-with-outparam branch from 355b8a2 to 6a49871 Compare May 14, 2024 21:16
@vadz
Copy link
Member Author

vadz commented May 14, 2024

Won't this result in -Wshadow warnings?

It will (as well as -Wunused-variable for the void functions without output params), if the warning is enabled for SWIG-generated code but personally I always disable all warnings in it as there are just too many of them otherwise (especially if you enable -Wall -Wextra and a few more globally for your projects, as I do).

And this was the only way to make this work that I found. I don't know if I should feel proud or horrified, but I think this solution is nice, in spite of being so ugly, because it's pretty minimal, both at SWIG and at the generated code level.

But I'll obviously need to wait to see what @wsfulton thinks of it. I hope it can be accepted because it solves a real problem, but if not, it won't be the first patch I'll just maintain in my own fork.

@ojwb
Copy link
Member

ojwb commented May 14, 2024

Warnings in generated code do seem of somewhat limited use, though SWIG generated files can contain significant amounts of non-generated code from the user interface file and they can be useful there. Also it's often much simpler to enable warnings across a whole project.

For whatever reasons, enough people expect SWIG-generated code not to trigger warnings that knowingly introducing such warnings is just setting up for future bug reports we then have to deal with. Better to address this now while it's fresh in our minds, and we already have SWIGUNUSED to add suitable attributes for at least GCC and compilers with compatible attribute syntax (and this could presumably be adjusted to use C++11 attribute syntax with modern compilers).

@vadz
Copy link
Member Author

vadz commented May 14, 2024

Sorry, I don't know how to avoid -Wshadow here, as shadowing is what allows this hack to work, other than by disabling it in the entire file which is hardly going to be very helpful -- but if you really think we should do this, it's trivial to add. I'd like to repeat that IME SWIG-generated code always gives warnings with a high enough warning level/sufficiently many warnings enabled of MSVC/gcc/clang so I always end up using -w or equivalent with it anyhow.

@ojwb
Copy link
Member

ojwb commented May 14, 2024

Oh true SWIGUNUSED doesn't help with -Wshadow, but it should avoid the -Wunused-variable warnings you mention.

Pretty much any code will generate warnings if you turn on obscure enough warning options, but it seems reasonable to aim for being clean with -Wall -Wextra.

@vadz
Copy link
Member Author

vadz commented May 15, 2024

I couldn't find a way to use SWIGUNUSED with a typemap-declared variable, but I can push this:

diff --git a/Lib/python/pytypemaps.swg b/Lib/python/pytypemaps.swg
index e1f649885..3b14fb232 100644
--- a/Lib/python/pytypemaps.swg
+++ b/Lib/python/pytypemaps.swg
@@ -81,7 +81,8 @@
    than the return value of a void function. By intentionally shadowing the
    global swig_is_void which is always 0 by this local variable set to 1 here,
    we can tell it to do this only for void functions. */
-%typemap(out,noblock=1) void (int swig_is_void = 1) {$result = VOID_Object;}
+%typemap(out,noblock=1) void (int swig_is_void = 1) {(void)swig_is_void;
+  $result = VOID_Object;}
 
 /* Consttab, needed for callbacks, it should be removed later */
 

which fixes the warnings given by -Wall -Wextra (but not -Wshadow if you enable it explicitly, of course).

To avoid rerunning the CI uselessly, I'll wait for William's review before doing it.

@vadz
Copy link
Member Author

vadz commented Jun 12, 2024

@wsfulton This is yet another (relatively) simple bug fix correcting a real problem in SWIG and yet there has been no reaction since almost a month. I'd really appreciate if it could be applied or if I could get at least some feedback about it. TIA!

@wsfulton
Copy link
Member

Hi @vadz, sure, I can look at this one today for you.

@wsfulton
Copy link
Member

wsfulton commented Jun 13, 2024

Clever hack, but I can put in a proper fix. I've added 790f7d6 to your py-nullptr-with-outparam branch rebased on master with a quick implementation of it. It's quite similar to the $null special variable used for handling void return type differently in the statically typed languages. See https://rawgit.com/swig/swig/master/Doc/Manual/Java.html#Java_special_variables. There's a fair amount of tidying up to do and I'll extend to all relevant languages next.

@vadz
Copy link
Member Author

vadz commented Jun 13, 2024

Thanks, this is indeed a much better solution (but I didn't think about it as I wanted to do it entirely at typemaps level, for some reason)!

If you'd like, I can try to clean it up and generalize to the other languages now — but if you'd prefer to do it yourself, I wouldn't mind at all, of course, just please let me know.

Thanks again!

@wsfulton
Copy link
Member

Thanks, but I can clean it up. I spotted some deprecated stuff and a few quirks in this area while looking at it that I'd like to deal with.

wsfulton added a commit that referenced this pull request Jun 15, 2024
The $isvoid special variable expands to 1 if the
wrapped function has a void return, otherwise expands to 0.

In the implementation, use a consistent variable name, returntype,
for a node's "type" attribute.

Issue #2907
@wsfulton
Copy link
Member

See commits f3d4698...d940190

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

Successfully merging this pull request may close these issues.

Don't discard null return value in function with output parameters
3 participants