Skip to content

[WIP] Packages #4490

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions Zend/tests/packages/package_declare_errors.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
--TEST--
Error conditions of package_declare()
--FILE--
<?php

try {
package_declare([]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => 42,
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => 42,
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => [
"foo"
],
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => [
"ticks" => "foo",
],
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => [
"ticks" => -1,
],
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => [
"strict_types" => "foo",
],
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
package_declare([
"name" => "foo/bar",
"declares" => [
"strict_types" => 42,
],
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

// Allowed for forward-compatibility
package_declare([
"name" => "foo/bar",
"declares" => [
"unknown" => "foo",
],
]);


try {
package_declare([
"name" => "foo/bar",
]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Package specification must contain "name" key of type string
Package specification must contain "name" key of type string
Package specification key "declares" must be of type array
Declares array in package specification must have string keys
Value of "ticks" must be a non-negative integer
Value of "ticks" must be a non-negative integer
Value of "strict_types" must be 0 or 1
Value of "strict_types" must be 0 or 1
Package "foo/bar" already registered
11 changes: 11 additions & 0 deletions Zend/tests/packages/package_first_statement.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Package declaration must be the first statement (even before declares)
--FILE--
<?php

declare(strict_types=1);
package "foo/bar";

?>
--EXPECTF--
Fatal error: Package declaration must be the first statement in the script in %s on line %d
50 changes: 50 additions & 0 deletions Zend/tests/packages/package_strict_types.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Specifying strict_types through package declaration
--FILE--
<?php

package_declare([
"name" => "with_strict_types",
"declares" => [
"strict_types" => 1,
],
]);

eval(<<<'PHP'
// Package defaults to strict types
package "with_strict_types";

try {
var_dump(strlen(42));
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
PHP);

eval(<<<'PHP'
// Manual override to strict_types=0
package "with_strict_types";
declare(strict_types=0);

try {
var_dump(strlen(42));
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
PHP);

eval(<<<'PHP'
// No package, no strict_types

try {
var_dump(strlen(42));
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
PHP);

?>
--EXPECT--
strlen() expects parameter 1 to be string, int given
int(2)
int(2)
50 changes: 50 additions & 0 deletions Zend/tests/packages/package_ticks.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Specifying ticks through package declaration
--FILE--
<?php

register_tick_function(function() {
echo "Tick\n";
});

package_declare([
"name" => "with_ticks",
"declares" => [
"ticks" => 1,
],
]);

eval(<<<'PHP'
// Package defaults to ticks=1
package "with_ticks";

echo "Foo\n";
echo "Foo\n";
PHP);

eval(<<<'PHP'
// Manual override to ticks=0
package "with_ticks";
declare(ticks=0);

echo "Bar\n";
echo "Bar\n";
PHP);

eval(<<<'PHP'
// No package, no ticks

echo "Baz\n";
echo "Baz\n";
PHP);

?>
--EXPECT--
Foo
Tick
Foo
Tick
Bar
Bar
Baz
Baz
10 changes: 10 additions & 0 deletions Zend/tests/packages/unknown_package.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Package declaration references an unknown package
--FILE--
<?php

package "foo/bar";

?>
--EXPECTF--
Fatal error: Unknown package "foo/bar" in %s on line %d
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ enum _zend_ast_kind {
ZEND_AST_GOTO,
ZEND_AST_BREAK,
ZEND_AST_CONTINUE,
ZEND_AST_PACKAGE,

/* 2 child nodes */
ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down
97 changes: 97 additions & 0 deletions Zend/zend_builtin_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ static ZEND_FUNCTION(gc_enabled);
static ZEND_FUNCTION(gc_enable);
static ZEND_FUNCTION(gc_disable);
static ZEND_FUNCTION(gc_status);
static ZEND_FUNCTION(package_declare);

/* {{{ arginfo */
ZEND_BEGIN_ARG_INFO(arginfo_zend__void, 0)
Expand Down Expand Up @@ -226,6 +227,9 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_extension_loaded, 0, 0, 1)
ZEND_ARG_INFO(0, extension_name)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_package_declare, 0, 0, 1)
ZEND_ARG_INFO(0, info)
ZEND_END_ARG_INFO()
/* }}} */

static const zend_function_entry builtin_functions[] = { /* {{{ */
Expand Down Expand Up @@ -287,6 +291,7 @@ static const zend_function_entry builtin_functions[] = { /* {{{ */
ZEND_FE(gc_enable, arginfo_zend__void)
ZEND_FE(gc_disable, arginfo_zend__void)
ZEND_FE(gc_status, arginfo_zend__void)
ZEND_FE(package_declare, arginfo_package_declare)
ZEND_FE_END
};
/* }}} */
Expand Down Expand Up @@ -2532,3 +2537,95 @@ ZEND_FUNCTION(get_extension_funcs)
}
}
/* }}} */

static int init_package_declares(zend_package_info *info, HashTable *declares) {
zend_string *name;
zval *val;
ZEND_HASH_FOREACH_STR_KEY_VAL(declares, name, val) {
ZVAL_DEREF(val);
if (!name) {
zend_throw_error(NULL, "Declares array in package specification must have string keys");
return FAILURE;
}

if (zend_string_equals_literal(name, "encoding")) {
zend_throw_error(NULL, "Cannot use \"encoding\" in package-level declares");
return FAILURE;
}

if (zend_string_equals_literal_ci(name, "strict_types")) {
if (Z_TYPE_P(val) != IS_LONG || (Z_LVAL_P(val) != 0 && Z_LVAL_P(val) != 1)) {
zend_throw_error(NULL, "Value of \"strict_types\" must be 0 or 1");
return FAILURE;
}

info->declares.strict_types = Z_LVAL_P(val);
continue;
}

if (zend_string_equals_literal_ci(name, "ticks")) {
if (Z_TYPE_P(val) != IS_LONG || Z_LVAL_P(val) < 0) {
zend_throw_error(NULL, "Value of \"ticks\" must be a non-negative integer");
return FAILURE;
}

info->declares.ticks = Z_LVAL_P(val);
continue;
}

/* Unknown declares intentionally ignored for forward-compatibility reasons. */
} ZEND_HASH_FOREACH_END();
return SUCCESS;
}

static void package_info_dtor(zval *zv) {
zend_package_info *info = Z_PTR_P(zv);
zend_string_release(info->name);
efree(info);
}

/* {{{ proto void package_declare(array spec) */
ZEND_FUNCTION(package_declare)
{
HashTable *spec;
zval *name_zv;
zval *declares_zv;
zend_package_info info;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &spec) == FAILURE) {
return;
}

name_zv = zend_hash_str_find_deref(spec, "name", sizeof("name") - 1);
declares_zv = zend_hash_str_find_deref(spec, "declares", sizeof("declares") - 1);

if (!name_zv || Z_TYPE_P(name_zv) != IS_STRING) {
zend_throw_error(NULL, "Package specification must contain \"name\" key of type string");
return;
}

memset(&info, 0, sizeof(zend_package_info));
if (declares_zv) {
if (Z_TYPE_P(declares_zv) != IS_ARRAY) {
zend_throw_error(NULL, "Package specification key \"declares\" must be of type array");
return;
}

if (init_package_declares(&info, Z_ARRVAL_P(declares_zv)) == FAILURE) {
return;
}
}

if (!CG(packages)) {
ALLOC_HASHTABLE(CG(packages));
zend_hash_init(CG(packages), 0, NULL, package_info_dtor, 0);
}

info.name = zend_string_copy(Z_STR_P(name_zv));
if (!zend_hash_add_mem(CG(packages), Z_STR_P(name_zv), &info, sizeof(zend_package_info))) {
zend_string_release(info.name);
zend_throw_error(NULL, "Package \"%s\" already registered", Z_STRVAL_P(name_zv));
return;
}
}
/* }}} */
Loading