Hi php internals,
After some long and deep research i finally decided to write my first RFC
about a feature i'd be interested to be improved in the php core: Nested
enclosing returns
The main purpose, as the title say, is to have the possibility to nest
multiple return like we can do currently with break/continue.
I thinks it'll being better with a scheme:
function foo($foo = true)
{
if(!$foo)
{
return false, 3;// Please note the second return argument is the
return nesting level
}
else
{
return true;// Default nested return argument is always equal to
1 (if not specified, current)
}
}
function bar($foo = true)
{
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 2
echo 'Hi jon !';
}
function baz($foo = true)
{
echo 'Hi bertie !';
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 3
echo 'Hi freddy !';
}
baz(true); // Display:
// Hi bertie !
// Hi jon !
// Hi freddy !
baz(false); // Display:
// Hi bertie !
Benefits:
-Wont break compatibility
-May improve code interpretation speed due to part of code skipped by the
nested return.
-Allow "dynamic" return expressions.
Inconveniences:
-May complicate debug/backtrace
Regards,
Georges.L
Hi php internals,
After some long and deep research i finally decided to write my first RFC
about a feature i'd be interested to be improved in the php core: Nested
enclosing returnsThe main purpose, as the title say, is to have the possibility to nest
multiple return like we can do currently with break/continue.I thinks it'll being better with a scheme:
function foo($foo = true)
{
if(!$foo)
{
return false, 3;// Please note the second return argument is the
return nesting level
}
else
{
return true;// Default nested return argument is always equal to
1 (if not specified, current)
}
}function bar($foo = true)
{
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 2
echo 'Hi jon !';
}function baz($foo = true)
{
echo 'Hi bertie !';
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 3
echo 'Hi freddy !';
}baz(true); // Display:
// Hi bertie !
// Hi jon !
// Hi freddy !baz(false); // Display:
// Hi bertie !Benefits:
-Wont break compatibility
-May improve code interpretation speed due to part of code skipped by the
nested return.
-Allow "dynamic" return expressions.Inconveniences:
-May complicate debug/backtrace
Hi Georges,
In my opinion this is a bad idea.
With break/continue/goto the jumps are restricted to only one function body
to explicitly reduce the complexity in understanding them.
With your proposal, as a user of a function I wouldn't know how deep a
function returns. This can easily lead to extremely unexpected behavior.
This is why exceptions are better for jumps up the call-stack, because they
give the caller an explicit way of either accepting or catching the jump.
Regards,
Georges.L
The main purpose of this RFC is not to improve the exception system of
PHP but to improve the code logic/hierarchy.
Using this like loop (for/foreach etc.) you can skip a lot of parent
callers for a desired purpose by example.
I agree with you in the sens were this can complicate significantly the
debug/trace process, but IMO, there's more benefits than inconvenience.
Also I agree with you too, were it can lead in a sort of "spaghetti code"
feature (like Goto operator is...), but which language is not a bit
"spaghetti" today?
But my request is not to have a fully dynamic nested return, but a
"controlled" dynamic nested return. I would not see this to be improved:
return $something, $nesting_level;
If I remember that feature has been removed in continue/break as of php
5.4, so this isn't that hard, right? :)
Georges.L
2015-03-21 14:20 GMT+01:00 Benjamin Eberlei kontakt@beberlei.de:
Hi php internals,
After some long and deep research i finally decided to write my first RFC
about a feature i'd be interested to be improved in the php core: Nested
enclosing returnsThe main purpose, as the title say, is to have the possibility to nest
multiple return like we can do currently with break/continue.I thinks it'll being better with a scheme:
function foo($foo = true)
{
if(!$foo)
{
return false, 3;// Please note the second return argument is the
return nesting level
}
else
{
return true;// Default nested return argument is always equal to
1 (if not specified, current)
}
}function bar($foo = true)
{
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 2
echo 'Hi jon !';
}function baz($foo = true)
{
echo 'Hi bertie !';
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 3
echo 'Hi freddy !';
}baz(true); // Display:
// Hi bertie !
// Hi jon !
// Hi freddy !baz(false); // Display:
// Hi bertie !Benefits:
-Wont break compatibility
-May improve code interpretation speed due to part of code skipped by the
nested return.
-Allow "dynamic" return expressions.Inconveniences:
-May complicate debug/backtraceHi Georges,
In my opinion this is a bad idea.
With break/continue/goto the jumps are restricted to only one function
body to explicitly reduce the complexity in understanding them.With your proposal, as a user of a function I wouldn't know how deep a
function returns. This can easily lead to extremely unexpected behavior.This is why exceptions are better for jumps up the call-stack, because
they give the caller an explicit way of either accepting or catching the
jump.Regards,
Georges.L
The main purpose of this RFC is not to improve the exception system of
PHP but to improve the code logic/hierarchy.Hi php internals,
After some long and deep research i finally decided to write my first RFC
about a feature i'd be interested to be improved in the php core: Nested
enclosing returns
Georges,
This would make simply looking at code and reasoning about what it does impossible.
At present, if I have the following code:
function foo() {
if(doSomething()) {
success();
} else {
failure();
}
return 42;
}
try {
bar(foo());
} catch($ex) {
}
Then I can make the following true statements about this code:
* foo always calls doSomething()
* foo always calls either success() or failure(), based on the result of doSomething()
* foo always returns 42
* bar is always called (with foo’s return value, 42)
* Alternatively to the above, any of the called functions may throw an exception, which will be caught by the catch block
If any of doSomething(), success(), failure(), or bar() can arbitrarily return to some higher calling scope, then the only thing I can say for sure is that doSomething() is called, after which my application could be in some dangerously inconsistent state because I have no idea what will be executed next.
This then provides significant security concerns. For example, if we have this:
function API_Function_With_Callback($callback) {
try {
$callback();
//do more stuff
return true;
} catch($ex) {
//do error stuff
return false;
}
}
function doEvil() {
$sentinel = //some unique value
$result = API_Function_With_Callback(function() use($sentinel) {
$backtrace = `debug_backtrace()`;
$nestingLevel = //determine nesting level from backtrace
if($nestingLevel == 2) return $sentinel, 2;
else if($nestingLevel == 3) return $sentinel, 3;
else if($nestingLevel == 4) return $sentinel, 4;
// etc
}
// Exploit inconsistent state of Call_API_Function here
if($result === $sentinel) { … }
}
Then we can short-circuit code from some other library which isn’t prepared to deal with this kind of hijacking. More seriously, this sort of hijacking can’t be defended against (at least not without a weakening of your original proposal). Any function that takes a callback is potentially vulnerable to this sort of attack.
Can you suggest an actual, practical, example, where this would be such a benefit as to override the inherent difficulty about reasoning about this code, and the potential security concerns? Are there any other languages that make something like this possible?
I suspect that any code that could be “improved” with this functionality is already in significant need of improvement by more conventional means.
-John
To be honest I had not thought about the bad side of this use, i guess it
could be possible to not return upper than a try/catch block (then return a
fatal error if our code is trying to returns upper than a try/catch block).
Now the practical example:
// Code i have the hand on
function main()
{
foo('bar');
echo "I'm an angel !";
}
function bar()
{
echo "I'm not Evil";
}
//Code i don't have the hand on (like a MVC core)
function foo($thing)
{
//
make_love_dont_war();
call_user_func($thing);
//Do some stuff i don't want to execute
echo 'evil';
}
function make_love_dont_war()
{
echo 'Make love, dont war.';
}
You get it ?
Georges.L
2015-03-21 17:07 GMT+01:00 John Bafford jbafford@zort.net:
The main purpose of this RFC is not to improve the exception system of
PHP but to improve the code logic/hierarchy.Hi php internals,
After some long and deep research i finally decided to write my first
RFC
about a feature i'd be interested to be improved in the php core:
Nested
enclosing returnsGeorges,
This would make simply looking at code and reasoning about what it does
impossible.At present, if I have the following code:
function foo() {
if(doSomething()) {
success();
} else {
failure();
}return 42;
}
try {
bar(foo());
} catch($ex) {
}Then I can make the following true statements about this code:
* foo always calls doSomething()
* foo always calls either success() or failure(), based on the
result of doSomething()
* foo always returns 42
* bar is always called (with foo’s return value, 42)
* Alternatively to the above, any of the called functions may
throw an exception, which will be caught by the catch blockIf any of doSomething(), success(), failure(), or bar() can arbitrarily
return to some higher calling scope, then the only thing I can say for sure
is that doSomething() is called, after which my application could be in
some dangerously inconsistent state because I have no idea what will be
executed next.This then provides significant security concerns. For example, if we have
this:function API_Function_With_Callback($callback) {
try {
$callback();//do more stuff return true; } catch($ex) { //do error stuff return false; }
}
function doEvil() {
$sentinel = //some unique value$result = API_Function_With_Callback(function() use($sentinel) { $backtrace = `debug_backtrace()`; $nestingLevel = //determine nesting level from backtrace if($nestingLevel == 2) return $sentinel, 2; else if($nestingLevel == 3) return $sentinel, 3; else if($nestingLevel == 4) return $sentinel, 4; // etc } // Exploit inconsistent state of Call_API_Function here if($result === $sentinel) { … }
}
Then we can short-circuit code from some other library which isn’t
prepared to deal with this kind of hijacking. More seriously, this sort of
hijacking can’t be defended against (at least not without a weakening of
your original proposal). Any function that takes a callback is potentially
vulnerable to this sort of attack.Can you suggest an actual, practical, example, where this would be such a
benefit as to override the inherent difficulty about reasoning about this
code, and the potential security concerns? Are there any other languages
that make something like this possible?I suspect that any code that could be “improved” with this functionality
is already in significant need of improvement by more conventional means.-John
To be honest I had not thought about the bad side of this use, i guess
it
could be possible to not return upper than a try/catch block (then
return a
fatal error if our code is trying to returns upper than a try/catch
block).Now the practical example:
// Code i have the hand on
function main()
{
foo('bar');
echo "I'm an angel !";
}function bar()
{
echo "I'm not Evil";
}//Code i don't have the hand on (like a MVC core)
function foo($thing)
{
//
make_love_dont_war();
call_user_func($thing);//Do some stuff i don't want to execute
echo 'evil';
}function make_love_dont_war()
{
echo 'Make love, dont war.';
}You get it ?
Not really, no. In this example, the code you want to skip happens to be all the code after executing the callback, but what if there was some essential cleanup there, or the "evil" line was before the callback, not after?
In general, a function should be a reusable, self-contained unit of code, and as such shouldn't want to know about where it was called from. But in order to know how many levels to force to return, you need to know how deeply nested you are, and the expected return type of the function you're jumping out of, as well as which code you're bypassing by doing so. If someone reuses the function, or refactors the calling code, some very confusing bugs will result.
Rather than "jump N levels up the call stack", what you maybe want is "jump up to somewhere labelled X". This is sort of what exceptions do - they jump up the stack until they find somewhere expecting to handle that condition. The function throwing the exception doesn't know where it will be caught, so is not fragile to reuse or refactoring. However, using exceptions for anything other than error handling is generally frowned upon, because they can lead to hard to follow logic which would be better off refactored in plain procedural/OO style.
In your example, it's up to the maintainers of the foo() function (e.g. framework) to make the "evil" optional, by passing in some additional setting, or allowing the callback to return a special value, or just splitting the work in such a way that you can access the desired functionality without calling foo() at all. If it's open source, you could submit a patch, or, worst case, maintain a fork.
Regards,
Rowan Collins
[IMSoP]
Hi php internals,
After some long and deep research i finally decided to write my first RFC
about a feature i'd be interested to be improved in the php core: Nested
enclosing returnsThe main purpose, as the title say, is to have the possibility to nest
multiple return like we can do currently with break/continue.I thinks it'll being better with a scheme:
function foo($foo = true)
{
if(!$foo)
{
return false, 3;// Please note the second return argument is the
return nesting level
}
else
{
return true;// Default nested return argument is always equal to
1 (if not specified, current)
}
}function bar($foo = true)
{
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 2
echo 'Hi jon !';
}function baz($foo = true)
{
echo 'Hi bertie !';
foo($foo);
// Some stuff that will never be executed if $foo == false and
nested return argument = 3
echo 'Hi freddy !';
}baz(true); // Display:
// Hi bertie !
// Hi jon !
// Hi freddy !baz(false); // Display:
// Hi bertie !Benefits:
-Wont break compatibility
-May improve code interpretation speed due to part of code skipped by the
nested return.
-Allow "dynamic" return expressions.Inconveniences:
-May complicate debug/backtrace
I think this will lead to a debugging nightmare, sorry.
It allows functions to behave in an incredibly non-obvious way, makes it
really easy to break things, and when something is broken the complexity of
locating and fixing it feels like it will be insane.
I'm imagining functions with branches that have different return levels in
them, called from functions with branches with different return levels in
them.... I feel like crying and it's not even a real feature yet!
Am 21.03.2015 14:15 schrieb "Georges.L" contact@geolim4.com:
The main purpose, as the title say, is to have the possibility to nest
multiple return like we can do currently with break/continue.
I think that is a complete nonstarter. Functions are reusable building
blocks, designed to be called from various places that the function author
should not be concerned with.
break/continue can always be analyzed at the single function level, with
semantics unambiguously given by the local nesting level within the
function.
On the other hand, functions being called from various places means the
nesting distance between a given point in the outer program, and the
function itself, is completely arbitrary for different calls.
In some places in our codebase we have logging functions that try to show
"logically useful" debug_backtraces, by leaving out a number of "lowest"
levels of the stack. Starting down that road soon required us to introduce
"$up = 2" parameters to the inner logging functions, so that different call
pathes could impose how much to leave out (think some convenience
wrappers). This is a very brittle thing to do. Okay for debug backtrace
logging, because at worst it shows some unneccessary level or leaves out
some. But totally unmanageable when it affects really control flow.
I know exactly one language that provides similar stuff - TCL with its
uplevel and upvar constructs - and anybody who had to wrap their heads
around code using that, nifty as it can be, will know the cognitive strain
involved...
Patrick