diff --git a/scripts/arcanist.php b/scripts/arcanist.php --- a/scripts/arcanist.php +++ b/scripts/arcanist.php @@ -461,11 +461,7 @@ while ($ex) { fwrite(STDERR, $ex->getMessage()."\n"); - if ($ex instanceof PhutilProxyException) { - $ex = $ex->getPreviousException(); - } else { - $ex = null; - } + $ex = $ex->getPrevious(); } fwrite(STDERR, phutil_console_format( diff --git a/src/channel/PhutilJSONProtocolChannel.php b/src/channel/PhutilJSONProtocolChannel.php --- a/src/channel/PhutilJSONProtocolChannel.php +++ b/src/channel/PhutilJSONProtocolChannel.php @@ -101,8 +101,9 @@ try { $objects[] = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Failed to decode JSON object.'), + 0, $ex); } diff --git a/src/conduit/ConduitFuture.php b/src/conduit/ConduitFuture.php --- a/src/conduit/ConduitFuture.php +++ b/src/conduit/ConduitFuture.php @@ -54,10 +54,11 @@ try { $data = phutil_json_decode($raw); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Host returned HTTP/200, but invalid JSON data in response to '. 'a Conduit method call.'), + 0, $ex); } diff --git a/src/config/ArcanistConfigurationSourceList.php b/src/config/ArcanistConfigurationSourceList.php --- a/src/config/ArcanistConfigurationSourceList.php +++ b/src/config/ArcanistConfigurationSourceList.php @@ -177,12 +177,13 @@ $source, $raw_value); } catch (Exception $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Configuration value ("%s") defined in source "%s" is not '. 'valid.', $key, $source->getSourceDisplayName()), + 0, $ex); } } diff --git a/src/configuration/ArcanistConfigurationManager.php b/src/configuration/ArcanistConfigurationManager.php --- a/src/configuration/ArcanistConfigurationManager.php +++ b/src/configuration/ArcanistConfigurationManager.php @@ -212,8 +212,9 @@ try { $user_config = phutil_json_decode($user_config_data); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht("Your '%s' file is not a valid JSON file.", '~/.arcrc'), + 0, $ex); } } else { @@ -313,10 +314,11 @@ try { $system_config = phutil_json_decode($file); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Your '%s' file is not a valid JSON file.", $system_config_path), + 0, $ex); } } else { diff --git a/src/error/PhutilErrorHandler.php b/src/error/PhutilErrorHandler.php --- a/src/error/PhutilErrorHandler.php +++ b/src/error/PhutilErrorHandler.php @@ -88,18 +88,14 @@ * can use @{class:PhutilProxyException} to nest exceptions; after PHP 5.3 * all exceptions are nestable. * + * @deprecated Call Exception::getPrevious directly. + * * @param Exception|Throwable $ex Exception to unnest. * @return Exception|Throwable|null Previous exception, if one exists. * @task exutil */ public static function getPreviousException($ex) { - if (method_exists($ex, 'getPrevious')) { - return $ex->getPrevious(); - } - if (method_exists($ex, 'getPreviousException')) { - return $ex->getPreviousException(); - } - return null; + return $ex->getPrevious(); } @@ -112,8 +108,8 @@ */ public static function getRootException($ex) { $root = $ex; - while (self::getPreviousException($root)) { - $root = self::getPreviousException($root); + while ($root->getPrevious()) { + $root = $root->getPrevious(); } return $root; } @@ -411,7 +407,7 @@ $current = $value; do { $messages[] = '('.get_class($current).') '.$current->getMessage(); - } while ($current = self::getPreviousException($current)); + } while ($current = $current->getPrevious()); $messages = implode(' {>} ', $messages); if (strlen($messages) > 4096) { @@ -608,7 +604,7 @@ } // If this is a proxy exception, add the proxied exception. - $prev = self::getPreviousException($ex); + $prev = $ex->getPrevious(); if ($prev) { $stack[] = array(++$id, $prev); } diff --git a/src/error/PhutilProxyException.php b/src/error/PhutilProxyException.php --- a/src/error/PhutilProxyException.php +++ b/src/error/PhutilProxyException.php @@ -6,32 +6,17 @@ * @{class:PhutilErrorHandler} to unnest exceptions in a forward-compatible way. * * @concrete-extensible + * + * @deprecated Use the exception constructor argument $previous directly. */ class PhutilProxyException extends Exception { - private $previousException; - public function __construct($message, $previous, $code = 0) { - $this->previousException = $previous; - - // This may be an "Exception" or a "Throwable". The "__construct()" method - // for the Exception is documented as taking an Exception, not a Throwable. - // Although passing a Throwable appears to work in PHP 7.3, don't risk it. - $is_exception = ($previous instanceof Exception); - - if (version_compare(PHP_VERSION, '5.3.0', '>=') && $is_exception) { - parent::__construct($message, $code, $previous); - } else { - parent::__construct($message, $code); - } + parent::__construct($message, $code, $previous); } public function getPreviousException() { - // NOTE: This can not be named "getPrevious()" because that method is final - // after PHP 5.3. Similarly, the property can not be named "previous" - // because HPHP declares a property with the same name and "protected" - // visibility. - return $this->previousException; + return $this->getPrevious(); } } diff --git a/src/error/__tests__/PhutilErrorHandlerTestCase.php b/src/error/__tests__/PhutilErrorHandlerTestCase.php --- a/src/error/__tests__/PhutilErrorHandlerTestCase.php +++ b/src/error/__tests__/PhutilErrorHandlerTestCase.php @@ -2,19 +2,6 @@ final class PhutilErrorHandlerTestCase extends PhutilTestCase { - public function testProxyException() { - $a = new Exception('a'); - $b = new PhutilProxyException('b', $a); - $c = new PhutilProxyException('c', $b); - - $this->assertEqual($a, $b->getPrevious()); - $this->assertEqual($a, PhutilErrorHandler::getRootException($b)); - $this->assertEqual($a, PhutilErrorHandler::getPreviousException($b)); - - $this->assertEqual($a, PhutilErrorHandler::getRootException($c)); - $this->assertEqual($b, PhutilErrorHandler::getPreviousException($c)); - } - public function testSilenceHandler() { // Errors should normally be logged. $this->assertTrue(strlen($this->emitError()) > 0); diff --git a/src/error/phlog.php b/src/error/phlog.php --- a/src/error/phlog.php +++ b/src/error/phlog.php @@ -26,7 +26,7 @@ // If this is an exception, proxy it and generate a composite trace which // shows both where the phlog() was called and where the exception was // originally thrown from. - $proxy = new PhutilProxyException('', $event); + $proxy = new Exception('', 0, $event); $trace = PhutilErrorHandler::getExceptionTrace($proxy); $data['trace'] = $trace; } else { diff --git a/src/filesystem/PhutilErrorLog.php b/src/filesystem/PhutilErrorLog.php --- a/src/filesystem/PhutilErrorLog.php +++ b/src/filesystem/PhutilErrorLog.php @@ -44,7 +44,7 @@ try { Filesystem::createDirectory($log_dir, 0755, true); } catch (FilesystemException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Unable to write log "%s" to path "%s". The containing '. 'directory ("%s") does not exist or is not readable, and '. @@ -52,6 +52,7 @@ $this->getLogName(), $log_path, $log_dir), + 0, $ex); } } @@ -68,12 +69,13 @@ // If we ran into a write exception and couldn't resolve it, fail. if ($write_exception) { - throw new PhutilProxyException( + throw new Exception( pht( 'Unable to write log "%s" to path "%s" because the path is not '. 'writable.', $this->getLogName(), $log_path), + 0, $write_exception); } } diff --git a/src/future/asana/PhutilAsanaFuture.php b/src/future/asana/PhutilAsanaFuture.php --- a/src/future/asana/PhutilAsanaFuture.php +++ b/src/future/asana/PhutilAsanaFuture.php @@ -86,8 +86,9 @@ try { $data = phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from Asana.'), + 0, $ex); } diff --git a/src/future/exec/PhutilExecutableFuture.php b/src/future/exec/PhutilExecutableFuture.php --- a/src/future/exec/PhutilExecutableFuture.php +++ b/src/future/exec/PhutilExecutableFuture.php @@ -170,10 +170,11 @@ try { Filesystem::assertExists($cwd); } catch (FilesystemException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Unable to run a command in directory "%s".', $cwd), + 0, $ex); } diff --git a/src/future/github/PhutilGitHubFuture.php b/src/future/github/PhutilGitHubFuture.php --- a/src/future/github/PhutilGitHubFuture.php +++ b/src/future/github/PhutilGitHubFuture.php @@ -95,8 +95,9 @@ $data = array(); } } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from GitHub.'), + 0, $ex); } diff --git a/src/future/http/HTTPSFuture.php b/src/future/http/HTTPSFuture.php --- a/src/future/http/HTTPSFuture.php +++ b/src/future/http/HTTPSFuture.php @@ -439,10 +439,11 @@ 'Call to "curl_setopt(...)" returned "false".')); } } catch (Exception $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Call to "curl_setopt(...) failed for option key "%s".', $curl_key), + 0, $ex); } } diff --git a/src/future/oauth/PhutilOAuth1Future.php b/src/future/oauth/PhutilOAuth1Future.php --- a/src/future/oauth/PhutilOAuth1Future.php +++ b/src/future/oauth/PhutilOAuth1Future.php @@ -303,7 +303,7 @@ try { return phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException(pht('Expected JSON.'), $ex); + throw new Exception(pht('Expected JSON.'), 0, $ex); } } diff --git a/src/future/postmark/PhutilPostmarkFuture.php b/src/future/postmark/PhutilPostmarkFuture.php --- a/src/future/postmark/PhutilPostmarkFuture.php +++ b/src/future/postmark/PhutilPostmarkFuture.php @@ -81,8 +81,9 @@ try { $data = phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from Postmark.'), + 0, $ex); } diff --git a/src/future/slack/PhutilSlackFuture.php b/src/future/slack/PhutilSlackFuture.php --- a/src/future/slack/PhutilSlackFuture.php +++ b/src/future/slack/PhutilSlackFuture.php @@ -71,8 +71,9 @@ try { $data = phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from Slack.'), + 0, $ex); } diff --git a/src/future/twitch/PhutilTwitchFuture.php b/src/future/twitch/PhutilTwitchFuture.php --- a/src/future/twitch/PhutilTwitchFuture.php +++ b/src/future/twitch/PhutilTwitchFuture.php @@ -77,8 +77,9 @@ try { $data = phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from Twitch.'), + 0, $ex); } diff --git a/src/future/wordpress/PhutilWordPressFuture.php b/src/future/wordpress/PhutilWordPressFuture.php --- a/src/future/wordpress/PhutilWordPressFuture.php +++ b/src/future/wordpress/PhutilWordPressFuture.php @@ -72,8 +72,9 @@ try { $data = phutil_json_decode($body); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('Expected JSON response from WordPress.com.'), + 0, $ex); } diff --git a/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php b/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php --- a/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php +++ b/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php @@ -20,12 +20,13 @@ try { $config = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Expected '%s' file to be a valid JSON file, but ". "failed to decode '%s'.", '.arclint', $config_path), + 0, $ex); } @@ -39,8 +40,9 @@ 'linters' => 'map>', )); } catch (PhutilTypeCheckException $ex) { - throw new PhutilProxyException( + throw new Exception( pht("Error in parsing '%s' file.", $config_path), + 0, $ex); } @@ -89,11 +91,12 @@ 'exclude' => 'optional regex | list', ) + $more); } catch (PhutilTypeCheckException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Error in parsing '%s' file, for linter '%s'.", '.arclint', $name), + 0, $ex); } @@ -102,12 +105,13 @@ try { $linter->setLinterConfigurationValue($key, $spec[$key]); } catch (Exception $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Error in parsing '%s' file, in key '%s' for linter '%s'.", '.arclint', $key, $name), + 0, $ex); } } diff --git a/src/lint/linter/ArcanistJSHintLinter.php b/src/lint/linter/ArcanistJSHintLinter.php --- a/src/lint/linter/ArcanistJSHintLinter.php +++ b/src/lint/linter/ArcanistJSHintLinter.php @@ -119,8 +119,9 @@ $errors = phutil_json_decode($stdout); } catch (PhutilJSONParserException $ex) { // Something went wrong and we can't decode the output. Exit abnormally. - throw new PhutilProxyException( + throw new Exception( pht('JSHint returned unparseable output.'), + 0, $ex); } diff --git a/src/parser/xhpast/__tests__/PHPASTParserTestCase.php b/src/parser/xhpast/__tests__/PHPASTParserTestCase.php --- a/src/parser/xhpast/__tests__/PHPASTParserTestCase.php +++ b/src/parser/xhpast/__tests__/PHPASTParserTestCase.php @@ -90,10 +90,11 @@ try { $stdout = phutil_json_decode($stdout); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'Output for test file "%s" is not valid JSON.', $name), + 0, $ex); } diff --git a/src/parser/xhpast/api/XHPASTTree.php b/src/parser/xhpast/api/XHPASTTree.php --- a/src/parser/xhpast/api/XHPASTTree.php +++ b/src/parser/xhpast/api/XHPASTTree.php @@ -67,8 +67,9 @@ try { $data = phutil_json_decode($stdout); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('%s: failed to decode tree.', 'XHPAST'), + 0, $ex); } diff --git a/src/parser/xhpast/bin/PhutilXHPASTBinary.php b/src/parser/xhpast/bin/PhutilXHPASTBinary.php --- a/src/parser/xhpast/bin/PhutilXHPASTBinary.php +++ b/src/parser/xhpast/bin/PhutilXHPASTBinary.php @@ -81,7 +81,7 @@ // user to build it themselves. self::build(); } catch (CommandException $ex) { - throw new PhutilProxyException(self::getBuildInstructions(), $ex); + throw new Exception(self::getBuildInstructions(), 0, $ex); } } $future = new ExecFuture('%s', self::getPath()); diff --git a/src/toolset/ArcanistAlias.php b/src/toolset/ArcanistAlias.php --- a/src/toolset/ArcanistAlias.php +++ b/src/toolset/ArcanistAlias.php @@ -61,10 +61,11 @@ $alias->toolset = idx($value, 'toolset'); $alias->command = idx($value, 'command'); } catch (PhutilTypeCheckException $ex) { - $alias->exception = new PhutilProxyException( + $alias->exception = new Exception( pht( 'Found invalid alias definition (with key "%s").', $key), + 0, $ex); } } else { diff --git a/src/unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php b/src/unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php --- a/src/unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php +++ b/src/unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php @@ -33,12 +33,13 @@ try { $config = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Expected '%s' file to be a valid JSON file, but ". "failed to decode '%s'.", '.arcunit', $config_path), + 0, $ex); } @@ -51,8 +52,9 @@ 'engines' => 'map>', )); } catch (PhutilTypeCheckException $ex) { - throw new PhutilProxyException( + throw new Exception( pht("Error in parsing '%s' file.", $config_path), + 0, $ex); } @@ -89,11 +91,12 @@ 'exclude' => 'optional regex | list', )); } catch (PhutilTypeCheckException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( "Error in parsing '%s' file, for test engine '%s'.", '.arcunit', $name), + 0, $ex); } diff --git a/src/utils/utils.php b/src/utils/utils.php --- a/src/utils/utils.php +++ b/src/utils/utils.php @@ -1732,8 +1732,9 @@ try { assert_stringlike($key); } catch (InvalidArgumentException $ex) { - throw new PhutilProxyException( + throw new Exception( pht('HTTP query parameter key must be a scalar.'), + 0, $ex); } @@ -1742,10 +1743,11 @@ try { assert_stringlike($value); } catch (InvalidArgumentException $ex) { - throw new PhutilProxyException( + throw new Exception( pht( 'HTTP query parameter value (for key "%s") must be a scalar.', $key), + 0, $ex); } diff --git a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php --- a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php +++ b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php @@ -206,8 +206,9 @@ try { return phutil_json_decode($raw_config); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht("Unable to parse '%s' file '%s'.", '.arcconfig', $from_where), + 0, $ex); } } @@ -306,8 +307,9 @@ $json = Filesystem::readFile($local_path); return phutil_json_decode($json); } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( + throw new Exception( pht("Failed to parse '%s' as JSON.", $local_path), + 0, $ex); } } else {