Skip to content
Merged
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
11 changes: 11 additions & 0 deletions include/zphp_extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ enum {
ZPHP_METHOD_STATIC = 1
};

/* class modifiers for zphp_class_set_flags; final and abstract exclude each other */
enum {
ZPHP_CLASS_FINAL = 1,
ZPHP_CLASS_ABSTRACT = 2,
ZPHP_CLASS_READONLY = 4
};

/*
* Lifecycle. module_init runs once per process when the extension loads and
* is the only place registration is allowed. worker_init/worker_shutdown run
Expand Down Expand Up @@ -170,6 +177,9 @@ typedef struct zphp_api {
void (*set_request_data)(zphp_ctx *ctx, void *data);
void *(*worker_data)(zphp_ctx *ctx);
void (*set_worker_data)(zphp_ctx *ctx, void *data);

/* appended after abi 1 shipped; older runtimes end the table above */
int (*class_set_flags)(zphp_class *cls, uint32_t flags);
} zphp_api;

typedef const zphp_extension *(*zphp_entry_fn)(const zphp_api *api);
Expand Down Expand Up @@ -208,6 +218,7 @@ static inline int zphp_class_add_property(zphp_class *cls, const char *name) { r
static inline int zphp_class_add_property_int(zphp_class *cls, const char *name, int64_t value) { return zphp_api_v1->class_add_property_int(cls, name, value); }
static inline int zphp_class_add_property_string(zphp_class *cls, const char *name, const char *value) { return zphp_api_v1->class_add_property_string(cls, name, value); }
static inline int zphp_class_implements(zphp_class *cls, const char *interface_name) { return zphp_api_v1->class_implements(cls, interface_name); }
static inline int zphp_class_set_flags(zphp_class *cls, uint32_t flags) { return zphp_api_v1->class_set_flags(cls, flags); }
static inline int zphp_register_interface(zphp_module *m, const char *name, const char *const *methods, size_t method_count) { return zphp_api_v1->register_interface(m, name, methods, method_count); }
static inline int zphp_register_constant_int(zphp_module *m, const char *name, int64_t value) { return zphp_api_v1->register_constant_int(m, name, value); }
static inline int zphp_register_constant_float(zphp_module *m, const char *name, double value) { return zphp_api_v1->register_constant_float(m, name, value); }
Expand Down
21 changes: 20 additions & 1 deletion src/extension.zig
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ pub const ClassReg = struct {
constants: std.ArrayListUnmanaged(ConstantReg) = .{},
properties: std.ArrayListUnmanaged(PropertyReg) = .{},
resource_type: ?u32 = null,
flags: u32 = 0,
};

const class_flag_final: u32 = 1;
const class_flag_abstract: u32 = 2;
const class_flag_readonly: u32 = 4;

const InterfaceReg = struct { name: []const u8, methods: std.ArrayListUnmanaged([]const u8) = .{} };

// the zphp_module handle: one loaded extension and everything it registered
Expand Down Expand Up @@ -269,9 +274,12 @@ fn registerClass(vm: *VM, cls: *ClassReg) RuntimeError!void {
const a = vm.allocator;
if (vm.classes.contains(cls.name) or vm.interfaces.contains(cls.name)) fail("extension '{s}': class '{s}' is already defined", .{ cls.ext.name, cls.name });
var def = ClassDef{ .name = cls.name, .parent = cls.parent };
def.is_final = cls.flags & class_flag_final != 0;
def.is_abstract = cls.flags & class_flag_abstract != 0;
def.is_readonly = cls.flags & class_flag_readonly != 0;
if (cls.resource_type != null) def.native_cleanup = resourceCleanup;
for (cls.interfaces.items) |iface| try def.interfaces.append(a, iface);
for (cls.properties.items) |p| try def.properties.append(a, .{ .name = p.name, .default = p.default, .has_default = true });
for (cls.properties.items) |p| try def.properties.append(a, .{ .name = p.name, .default = p.default, .has_default = true, .is_readonly = def.is_readonly });
for (cls.constants.items) |c| {
try def.static_props.put(a, c.name, c.value);
try def.constant_names.put(a, c.name, {});
Expand Down Expand Up @@ -478,6 +486,15 @@ fn apiClassAddPropertyString(cls: ?*ClassReg, name: CStr, value: CStr) callconv(
return classProperty(cls, name, .{ .string = Value.String.borrowed(dupe(v)) });
}

fn apiClassSetFlags(cls: ?*ClassReg, flags: u32) callconv(.c) c_int {
const c = cls orelse return -1;
if (!regOnly(c.ext)) return -1;
if (flags & ~(class_flag_final | class_flag_abstract | class_flag_readonly) != 0) return -1;
if (flags & class_flag_final != 0 and flags & class_flag_abstract != 0) return -1;
c.flags = flags;
return 0;
}

fn apiClassImplements(cls: ?*ClassReg, iface: CStr) callconv(.c) c_int {
const c = cls orelse return -1;
if (!regOnly(c.ext)) return -1;
Expand Down Expand Up @@ -922,6 +939,7 @@ pub const Api = extern struct {
set_request_data: *const @TypeOf(apiSetRequestData),
worker_data: *const @TypeOf(apiWorkerData),
set_worker_data: *const @TypeOf(apiSetWorkerData),
class_set_flags: *const @TypeOf(apiClassSetFlags),
};

pub const api_v1 = Api{
Expand Down Expand Up @@ -987,6 +1005,7 @@ pub const api_v1 = Api{
.set_request_data = apiSetRequestData,
.worker_data = apiWorkerData,
.set_worker_data = apiSetWorkerData,
.class_set_flags = apiClassSetFlags,
};

test "the api table matches the header field by field" {
Expand Down
5 changes: 5 additions & 0 deletions tests/extensions/demo.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ static int module_init(zphp_module *m)
zphp_class_add_method(counter, "make", counter_make, 1, ZPHP_METHOD_STATIC);

zphp_register_class(m, "DemoException", "Exception");
zphp_class_set_flags(zphp_register_class(m, "DemoSealed", NULL), ZPHP_CLASS_FINAL);
zphp_class_set_flags(zphp_register_class(m, "DemoShape", NULL), ZPHP_CLASS_ABSTRACT);
zphp_class *point = zphp_register_class(m, "DemoPoint", NULL);
zphp_class_add_property_int(point, "x", 1);
zphp_class_set_flags(point, ZPHP_CLASS_READONLY);

zphp_register_constant_string(m, "DEMO_VERSION", "1.2.3");
zphp_register_constant_int(m, "DEMO_ANSWER", 42);
Expand Down
6 changes: 6 additions & 0 deletions tests/extensions/demo.expected
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,11 @@ int(4)
string(4) "stil"
Trying to clone an uncloneable object of class DemoBuffer
int(4)
bool(true)
bool(true)
bool(true)
Cannot instantiate abstract class DemoShape
int(1)
Cannot modify readonly property DemoPoint::$x
done
shutdown sees 2
4 changes: 4 additions & 0 deletions tests/extensions/demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ function throws() { $x = demo_open(4); throw new RuntimeException("mid"); }
try { $copy = clone $probe; } catch (Error $e) { echo $e->getMessage(), "\n"; }
unset($probe);
var_dump(demo_freed());
$rc = fn(string $c) => new ReflectionClass($c);
var_dump($rc("DemoSealed")->isFinal(), $rc("DemoShape")->isAbstract(), $rc("DemoPoint")->isReadOnly());
try { new DemoShape(); } catch (Error $e) { echo $e->getMessage(), "\n"; }
try { $pt = new DemoPoint(); var_dump($pt->x); $pt->x = 5; } catch (Error $e) { echo $e->getMessage(), "\n"; }
$keep = demo_open(2);
register_shutdown_function(function () { echo "shutdown sees ", demo_add(1, 1), "\n"; });
echo "done\n";
2 changes: 2 additions & 0 deletions tests/extensions/final.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
class DemoSub extends DemoSealed {}
1 change: 1 addition & 0 deletions tests/extensions/run
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ DEMO="$OUT/demo.$LIB"
# the script under a dynamic extension
actual="$("$ZPHP" --extension="$DEMO" run "$SCRIPT_DIR/demo.php" 2>&1)" || true
check "demo.php output" "$(cat "$SCRIPT_DIR/demo.expected")" "$actual"
contains "final extension class cannot be extended" "Class DemoSub cannot extend final class DemoSealed" "$("$ZPHP" --extension="$DEMO" run "$SCRIPT_DIR/final.php" 2>&1)"
actual="$("$ZPHP" --extension "$DEMO" run "$SCRIPT_DIR/demo.php" 2>&1 | head -1)" || true
check "--extension PATH form" 'bool(true)' "$actual"
actual="$(ZPHP_EXTENSION_DIR="$OUT" "$ZPHP" run "$SCRIPT_DIR/demo.php" 2>&1 | tail -1)" || true
Expand Down
Loading