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

Potential infinite loop on re-stubbing undefined global function without any return value #31

Closed
mehamasum opened this issue Feb 9, 2022 · 0 comments · Fixed by #30
Closed

Comments

@mehamasum
Copy link
Contributor

If we stub a previously undefined global function, a new global function is generated.
And then if we re-stub it again without any return value, it gets redefined and an entry is created in GlobalSpies::$redefined_functions (because of changes in this pr). Previously, the re-stub wouldn't work and there would be no entry in GlobalSpies::$redefined_functions.

But a new problem arises when the re-stub is invoked: \Spies\GlobalSpies::handle_call_for is called and in turn \Spies\GlobalSpies::call_original_global_function gets called (since stubbed without a return value).
Since there is an entry for the invoked function in GlobalSpies::$redefined_functions, the previous definition is restored (which happens to be the generated function) and call_user_func_array is called on that. This invokes \Spies\GlobalSpies::handle_call_for again and this keeps happening in a cycle.

Previously (before this pr), the re-stub wouldn't work and thus the redefinition would never trigger. Only the first generated function or the original global function (had it existed) would be invoked over and over again. In our case, the first generated function calls \Spies\GlobalSpies::handle_call_for like above, but it returns early from \Spies\GlobalSpies::call_original_global_function due to lack of redefinition and thus loop is not created.

Example code:

<?php

class Test_Foo extends PHPUnit\Framework\TestCase {
	function tearDown() {
		\Spies\finish_spying();
	}

	public function test_foo_1() {
		$bar_spy = \Spies\get_spy_for( 'bar' ); // stubbing previously undefined global function
		$ret = bar();
		$this->assertTrue(
			$bar_spy->was_called_times( 1 )
		);
		$this->assertEquals( $this::$ret, $ret );
	}

	public function test_foo_2() {
		$bar_spy = \Spies\get_spy_for( 'bar' );  // re-stubbing
		$ret = bar();
		$this->assertTrue(
			$bar_spy->was_called_times( 1 )
		);
		$this->assertEquals( $this::$ret, $ret );
	}
}

Invocation of the second bar above will create an infinite loop.

Note: this case doesn't generate if the global function exists beforehand or if the re-stub has a return value. In those cases, \Spies\GlobalSpies::call_original_global_function is not called and there is no loop.

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

Successfully merging a pull request may close this issue.

1 participant