From 7d563c7fe62e64ba0162c4d68a1fd256bd294b83 Mon Sep 17 00:00:00 2001 From: Alex Palaistras Date: Sun, 21 Feb 2016 19:11:50 +0000 Subject: [PATCH 1/4] Fetch `Context` reference from calling `Engine`, not a stored pointer This fixes compatibility with Go 1.6, which specifies stricter rules for passing Go pointers to C and vice versa. Exported methods now use the C context's pointer hexadecimal representation to find the parent Go Context. --- engine/context.c | 3 +- engine/engine.c | 6 ++-- engine/engine.go | 71 +++++++++++++++++++++++++++++----------- engine/include/context.h | 3 +- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/engine/context.c b/engine/context.c index c5b73bf..86406e1 100644 --- a/engine/context.c +++ b/engine/context.c @@ -11,7 +11,7 @@ #include "value.h" #include "context.h" -engine_context *context_new(void *ctx) { +engine_context *context_new() { engine_context *context; // Initialize context. @@ -21,7 +21,6 @@ engine_context *context_new(void *ctx) { return NULL; } - context->ctx = ctx; SG(server_context) = context; // Initialize request lifecycle. diff --git a/engine/engine.c b/engine/engine.c index 9130a45..31e352f 100644 --- a/engine/engine.c +++ b/engine/engine.c @@ -33,7 +33,7 @@ const char engine_ini_defaults[] = { static ENGINE_UB_WRITE(str, len) { engine_context *context = SG(server_context); - int written = engineWriteOut(context->ctx, (void *) str, len); + int written = engineWriteOut(context, (void *) str, len); if (written != len) { php_handle_aborted_connection(); } @@ -48,7 +48,7 @@ static int engine_header_handler(sapi_header_struct *sapi_header, sapi_header_op case SAPI_HEADER_REPLACE: case SAPI_HEADER_ADD: case SAPI_HEADER_DELETE: - engineSetHeader(context->ctx, op, (void *) sapi_header->header, sapi_header->header_len); + engineSetHeader(context, op, (void *) sapi_header->header, sapi_header->header_len); break; } @@ -70,7 +70,7 @@ static void engine_register_variables(zval *track_vars_array) { static void engine_log_message(char *str) { engine_context *context = SG(server_context); - engineWriteLog(context->ctx, (void *) str, strlen(str)); + engineWriteLog(context, (void *) str, strlen(str)); } static sapi_module_struct engine_module = { diff --git a/engine/engine.go b/engine/engine.go index 91b4ad3..5ce1b3f 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -26,42 +26,50 @@ import ( // Engine represents the core PHP engine bindings. type Engine struct { engine *C.struct__php_engine - contexts []*Context + contexts map[string]*Context } +// This contains a reference to the active engine, if any. +var engine *Engine + // New initializes a PHP engine instance on which contexts can be executed. It // corresponds to PHP's MINIT (module init) phase. func New() (*Engine, error) { + if engine != nil { + return nil, fmt.Errorf("Cannot activate multiple engine instances") + } + ptr, err := C.engine_init() if err != nil { return nil, fmt.Errorf("PHP engine failed to initialize") } - e := &Engine{ + engine = &Engine{ engine: ptr, - contexts: make([]*Context, 0), + contexts: make(map[string]*Context), } - return e, nil + return engine, nil } // NewContext creates a new execution context for the active engine and returns // an error if the execution context failed to initialize at any point. This // corresponds to PHP's RINIT (request init) phase. func (e *Engine) NewContext() (*Context, error) { + ptr, err := C.context_new() + if err != nil { + return nil, fmt.Errorf("Failed to initialize context for PHP engine") + } + ctx := &Context{ Header: make(http.Header), + context: ptr, values: make([]*Value, 0), receivers: make(map[string]*Receiver), } - ptr, err := C.context_new(unsafe.Pointer(ctx)) - if err != nil { - return nil, fmt.Errorf("Failed to initialize context for PHP engine") - } - - ctx.context = ptr - e.contexts = append(e.contexts, ctx) + // Store reference to context, using pointer as key. + e.contexts[contextPtrKey(ptr)] = ctx return ctx, nil } @@ -80,6 +88,22 @@ func (e *Engine) Destroy() { C.engine_shutdown(e.engine) e.engine = nil + + engine = nil +} + +func contextPtrKey(ctxptr interface{}) string { + return fmt.Sprintf("%p", ctxptr) +} + +func contextFromPtr(ctxptr unsafe.Pointer) *Context { + key := contextPtrKey(ctxptr) + + if engine == nil || engine.contexts[key] == nil { + return nil + } + + return engine.contexts[key] } func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { @@ -98,21 +122,30 @@ func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { //export engineWriteOut func engineWriteOut(ctxptr, buffer unsafe.Pointer, length C.uint) C.int { - c := (*Context)(ctxptr) + ctx := contextFromPtr(ctxptr) + if ctx == nil { + return -1 + } - return write(c.Output, buffer, length) + return write(ctx.Output, buffer, length) } //export engineWriteLog func engineWriteLog(ctxptr unsafe.Pointer, buffer unsafe.Pointer, length C.uint) C.int { - c := (*Context)(ctxptr) + ctx := contextFromPtr(ctxptr) + if ctx == nil { + return -1 + } - return write(c.Log, buffer, length) + return write(ctx.Log, buffer, length) } //export engineSetHeader func engineSetHeader(ctxptr unsafe.Pointer, operation C.uint, buffer unsafe.Pointer, length C.uint) { - c := (*Context)(ctxptr) + ctx := contextFromPtr(ctxptr) + if ctx == nil { + return + } header := (string)(C.GoBytes(buffer, C.int(length))) split := strings.SplitN(header, ":", 2) @@ -124,15 +157,15 @@ func engineSetHeader(ctxptr unsafe.Pointer, operation C.uint, buffer unsafe.Poin switch operation { case 0: // Replace header. if len(split) == 2 && split[1] != "" { - c.Header.Set(split[0], split[1]) + ctx.Header.Set(split[0], split[1]) } case 1: // Append header. if len(split) == 2 && split[1] != "" { - c.Header.Add(split[0], split[1]) + ctx.Header.Add(split[0], split[1]) } case 2: // Delete header. if split[0] != "" { - c.Header.Del(split[0]) + ctx.Header.Del(split[0]) } } } diff --git a/engine/include/context.h b/engine/include/context.h index 0fbda76..4cff173 100644 --- a/engine/include/context.h +++ b/engine/include/context.h @@ -8,10 +8,9 @@ #include "_context.h" typedef struct _engine_context { - void *ctx; } engine_context; -engine_context *context_new(void *ctx); +engine_context *context_new(); void context_exec(engine_context *context, char *filename); void *context_eval(engine_context *context, char *script); void context_bind(engine_context *context, char *name, void *value); From 886afb7ac9ea963d6918ebd11508cd6aa4f58ff0 Mon Sep 17 00:00:00 2001 From: Alex Palaistras Date: Fri, 26 Feb 2016 17:58:33 +0000 Subject: [PATCH 2/4] Make relationship between stored Contexts and C context explicit --- engine/engine.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 5ce1b3f..f2d9435 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -26,7 +26,7 @@ import ( // Engine represents the core PHP engine bindings. type Engine struct { engine *C.struct__php_engine - contexts map[string]*Context + contexts map[*C.struct__engine_context]*Context } // This contains a reference to the active engine, if any. @@ -46,7 +46,7 @@ func New() (*Engine, error) { engine = &Engine{ engine: ptr, - contexts: make(map[string]*Context), + contexts: make(map[*C.struct__engine_context]*Context), } return engine, nil @@ -69,7 +69,7 @@ func (e *Engine) NewContext() (*Context, error) { } // Store reference to context, using pointer as key. - e.contexts[contextPtrKey(ptr)] = ctx + e.contexts[ptr] = ctx return ctx, nil } @@ -92,18 +92,12 @@ func (e *Engine) Destroy() { engine = nil } -func contextPtrKey(ctxptr interface{}) string { - return fmt.Sprintf("%p", ctxptr) -} - -func contextFromPtr(ctxptr unsafe.Pointer) *Context { - key := contextPtrKey(ctxptr) - - if engine == nil || engine.contexts[key] == nil { +func contextFromPtr(ctxptr *C.struct__engine_context) *Context { + if engine == nil || engine.contexts[ctxptr] == nil { return nil } - return engine.contexts[key] + return engine.contexts[ctxptr] } func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { @@ -121,7 +115,7 @@ func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { } //export engineWriteOut -func engineWriteOut(ctxptr, buffer unsafe.Pointer, length C.uint) C.int { +func engineWriteOut(ctxptr *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { ctx := contextFromPtr(ctxptr) if ctx == nil { return -1 @@ -131,7 +125,7 @@ func engineWriteOut(ctxptr, buffer unsafe.Pointer, length C.uint) C.int { } //export engineWriteLog -func engineWriteLog(ctxptr unsafe.Pointer, buffer unsafe.Pointer, length C.uint) C.int { +func engineWriteLog(ctxptr *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { ctx := contextFromPtr(ctxptr) if ctx == nil { return -1 @@ -141,7 +135,7 @@ func engineWriteLog(ctxptr unsafe.Pointer, buffer unsafe.Pointer, length C.uint) } //export engineSetHeader -func engineSetHeader(ctxptr unsafe.Pointer, operation C.uint, buffer unsafe.Pointer, length C.uint) { +func engineSetHeader(ctxptr *C.struct__engine_context, operation C.uint, buffer unsafe.Pointer, length C.uint) { ctx := contextFromPtr(ctxptr) if ctx == nil { return From 2a1e45785bcb77603c565df4135c1709051fac6d Mon Sep 17 00:00:00 2001 From: Alex Palaistras Date: Sat, 27 Feb 2016 15:16:48 +0000 Subject: [PATCH 3/4] engine.go: Simplify write, header calls for context pointers --- engine/engine.go | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index f2d9435..e0506de 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -92,14 +92,6 @@ func (e *Engine) Destroy() { engine = nil } -func contextFromPtr(ctxptr *C.struct__engine_context) *Context { - if engine == nil || engine.contexts[ctxptr] == nil { - return nil - } - - return engine.contexts[ctxptr] -} - func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { // Do not return error if writer is unavailable. if w == nil { @@ -115,29 +107,26 @@ func write(w io.Writer, buffer unsafe.Pointer, length C.uint) C.int { } //export engineWriteOut -func engineWriteOut(ctxptr *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { - ctx := contextFromPtr(ctxptr) - if ctx == nil { +func engineWriteOut(ctx *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { + if engine == nil || engine.contexts[ctx] == nil { return -1 } - return write(ctx.Output, buffer, length) + return write(engine.contexts[ctx].Output, buffer, length) } //export engineWriteLog -func engineWriteLog(ctxptr *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { - ctx := contextFromPtr(ctxptr) - if ctx == nil { +func engineWriteLog(ctx *C.struct__engine_context, buffer unsafe.Pointer, length C.uint) C.int { + if engine == nil || engine.contexts[ctx] == nil { return -1 } - return write(ctx.Log, buffer, length) + return write(engine.contexts[ctx].Log, buffer, length) } //export engineSetHeader -func engineSetHeader(ctxptr *C.struct__engine_context, operation C.uint, buffer unsafe.Pointer, length C.uint) { - ctx := contextFromPtr(ctxptr) - if ctx == nil { +func engineSetHeader(ctx *C.struct__engine_context, operation C.uint, buffer unsafe.Pointer, length C.uint) { + if engine == nil || engine.contexts[ctx] == nil { return } @@ -151,15 +140,15 @@ func engineSetHeader(ctxptr *C.struct__engine_context, operation C.uint, buffer switch operation { case 0: // Replace header. if len(split) == 2 && split[1] != "" { - ctx.Header.Set(split[0], split[1]) + engine.contexts[ctx].Header.Set(split[0], split[1]) } case 1: // Append header. if len(split) == 2 && split[1] != "" { - ctx.Header.Add(split[0], split[1]) + engine.contexts[ctx].Header.Add(split[0], split[1]) } case 2: // Delete header. if split[0] != "" { - ctx.Header.Del(split[0]) + engine.contexts[ctx].Header.Del(split[0]) } } } From 50c36f8a31af076c3f7edbd947e21f1feaed703a Mon Sep 17 00:00:00 2001 From: Alex Palaistras Date: Sat, 27 Feb 2016 22:26:25 +0000 Subject: [PATCH 4/4] Fix Receiver bindings for Go 1.6 Several API-breaking changes: * Define method and related functionality moved back to Engine package * Exported functions for receivers moved to the engine package * Receiver API cleaned up and made public Receivers no longer store Go pointers in C contexts, and instead use the C engine_receiver pointer as reference in the Go context (similar to what we do for Contexts). Some changes have been made to the C code to accomodate the above. --- engine/context.go | 40 +-------- engine/context_test.go | 22 ----- engine/engine.go | 143 ++++++++++++++++++++++++++++-- engine/engine_test.go | 22 ++++- engine/include/context.h | 4 +- engine/include/engine.h | 4 +- engine/include/php5/_receiver.h | 5 ++ engine/include/php5/_value.h | 10 ++- engine/include/php7/_receiver.h | 5 ++ engine/include/php7/_value.h | 12 +-- engine/include/receiver.h | 14 +-- engine/include/value.h | 10 +-- engine/receiver.c | 25 +++--- engine/receiver.go | 150 +++++++++++++------------------- engine/receiver_test.go | 10 +-- 15 files changed, 262 insertions(+), 214 deletions(-) diff --git a/engine/context.go b/engine/context.go index ed6c978..590b58c 100644 --- a/engine/context.go +++ b/engine/context.go @@ -9,7 +9,6 @@ package engine // // #include // #include
-// #include "receiver.h" // #include "context.h" import "C" @@ -31,9 +30,8 @@ type Context struct { // Header represents the HTTP headers set by current PHP context. Header http.Header - context *C.struct__engine_context - values []*Value - receivers map[string]*Receiver + context *C.struct__engine_context + values []*Value } // Bind allows for binding Go values into the current execution context under @@ -55,34 +53,6 @@ func (c *Context) Bind(name string, val interface{}) error { return nil } -// Define registers a PHP class for the name passed, using function fn as -// constructor for individual object instances as needed by the PHP context. -// -// The class name registered is assumed to be unique for the active engine. -// -// The constructor function accepts a slice of arguments, as passed by the PHP -// context, and should return a method receiver instance, or nil on error (in -// which case, an exception is thrown on the PHP object constructor). -func (c *Context) Define(name string, fn func(args []interface{}) interface{}) error { - if _, exists := c.receivers[name]; exists { - return fmt.Errorf("Failed to define duplicate receiver '%s'", name) - } - - rcvr := &Receiver{ - name: name, - create: fn, - objects: make([]*ReceiverObject, 0), - } - - n := C.CString(name) - defer C.free(unsafe.Pointer(n)) - - C.receiver_define(n, unsafe.Pointer(rcvr)) - c.receivers[name] = rcvr - - return nil -} - // Exec executes a PHP script pointed to by filename in the current execution // context, and returns an error, if any. Output produced by the script is // written to the context's pre-defined io.Writer instance. @@ -129,12 +99,6 @@ func (c *Context) Destroy() { return } - for _, r := range c.receivers { - r.Destroy() - } - - c.receivers = nil - for _, v := range c.values { v.Destroy() } diff --git a/engine/context_test.go b/engine/context_test.go index 5894712..72a3e6c 100644 --- a/engine/context_test.go +++ b/engine/context_test.go @@ -31,28 +31,6 @@ func TestContextNew(t *testing.T) { c.Destroy() } -func TestContextDefine(t *testing.T) { - ctor := func(args []interface{}) interface{} { - return nil - } - - c, _ := e.NewContext() - - if err := c.Define("TestDefine", ctor); err != nil { - t.Errorf("Context.Define(): %s", err) - } - - if len(c.receivers) != 1 { - t.Errorf("Context.Define(): `Context.receivers` length is %d, should be 1", len(c.receivers)) - } - - if err := c.Define("TestDefine", ctor); err == nil { - t.Errorf("Context.Define(): Incorrectly defined duplicate receiver") - } - - c.Destroy() -} - var execTests = []struct { name string script string diff --git a/engine/engine.go b/engine/engine.go index e0506de..fd10bdf 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -11,6 +11,7 @@ package engine // // #include // #include
+// #include "receiver.h" // #include "context.h" // #include "engine.h" import "C" @@ -25,8 +26,9 @@ import ( // Engine represents the core PHP engine bindings. type Engine struct { - engine *C.struct__php_engine - contexts map[*C.struct__engine_context]*Context + engine *C.struct__php_engine + contexts map[*C.struct__engine_context]*Context + receivers map[string]*Receiver } // This contains a reference to the active engine, if any. @@ -45,8 +47,9 @@ func New() (*Engine, error) { } engine = &Engine{ - engine: ptr, - contexts: make(map[*C.struct__engine_context]*Context), + engine: ptr, + contexts: make(map[*C.struct__engine_context]*Context), + receivers: make(map[string]*Receiver), } return engine, nil @@ -62,10 +65,9 @@ func (e *Engine) NewContext() (*Context, error) { } ctx := &Context{ - Header: make(http.Header), - context: ptr, - values: make([]*Value, 0), - receivers: make(map[string]*Receiver), + Header: make(http.Header), + context: ptr, + values: make([]*Value, 0), } // Store reference to context, using pointer as key. @@ -74,12 +76,46 @@ func (e *Engine) NewContext() (*Context, error) { return ctx, nil } +// Define registers a PHP class for the name passed, using function fn as +// constructor for individual object instances as needed by the PHP context. +// +// The class name registered is assumed to be unique for the active engine. +// +// The constructor function accepts a slice of arguments, as passed by the PHP +// context, and should return a method receiver instance, or nil on error (in +// which case, an exception is thrown on the PHP object constructor). +func (e *Engine) Define(name string, fn func(args []interface{}) interface{}) error { + if _, exists := e.receivers[name]; exists { + return fmt.Errorf("Failed to define duplicate receiver '%s'", name) + } + + rcvr := &Receiver{ + name: name, + create: fn, + objects: make(map[*C.struct__engine_receiver]*ReceiverObject), + } + + n := C.CString(name) + defer C.free(unsafe.Pointer(n)) + + C.receiver_define(n) + e.receivers[name] = rcvr + + return nil +} + // Destroy shuts down and frees any resources related to the PHP engine bindings. func (e *Engine) Destroy() { if e.engine == nil { return } + for _, r := range e.receivers { + r.Destroy() + } + + e.receivers = nil + for _, c := range e.contexts { c.Destroy() } @@ -152,3 +188,94 @@ func engineSetHeader(ctx *C.struct__engine_context, operation C.uint, buffer uns } } } + +//export engineReceiverNew +func engineReceiverNew(rcvr *C.struct__engine_receiver, args unsafe.Pointer) C.int { + n := C.GoString(C.receiver_get_name(rcvr)) + if engine == nil || engine.receivers[n] == nil { + return 1 + } + + va, err := NewValueFromPtr(args) + if err != nil { + return 1 + } + + defer va.Destroy() + + obj, err := engine.receivers[n].NewObject(va.Slice()) + if err != nil { + return 1 + } + + engine.receivers[n].objects[rcvr] = obj + + return 0 +} + +//export engineReceiverGet +func engineReceiverGet(rcvr *C.struct__engine_receiver, name *C.char) unsafe.Pointer { + n := C.GoString(C.receiver_get_name(rcvr)) + if engine == nil || engine.receivers[n].objects[rcvr] == nil { + return nil + } + + val, err := engine.receivers[n].objects[rcvr].Get(C.GoString(name)) + if err != nil { + return nil + } + + return val.Ptr() +} + +//export engineReceiverSet +func engineReceiverSet(rcvr *C.struct__engine_receiver, name *C.char, val unsafe.Pointer) { + n := C.GoString(C.receiver_get_name(rcvr)) + if engine == nil || engine.receivers[n].objects[rcvr] == nil { + return + } + + v, err := NewValueFromPtr(val) + if err != nil { + return + } + + engine.receivers[n].objects[rcvr].Set(C.GoString(name), v.Interface()) +} + +//export engineReceiverExists +func engineReceiverExists(rcvr *C.struct__engine_receiver, name *C.char) C.int { + n := C.GoString(C.receiver_get_name(rcvr)) + if engine == nil || engine.receivers[n].objects[rcvr] == nil { + return 0 + } + + if engine.receivers[n].objects[rcvr].Exists(C.GoString(name)) { + return 1 + } + + return 0 +} + +//export engineReceiverCall +func engineReceiverCall(rcvr *C.struct__engine_receiver, name *C.char, args unsafe.Pointer) unsafe.Pointer { + n := C.GoString(C.receiver_get_name(rcvr)) + if engine == nil || engine.receivers[n].objects[rcvr] == nil { + return nil + } + + // Process input arguments. + va, err := NewValueFromPtr(args) + if err != nil { + return nil + } + + defer va.Destroy() + + val := engine.receivers[n].objects[rcvr].Call(C.GoString(name), va.Slice()) + if val == nil { + return nil + } + + return val.Ptr() +} diff --git a/engine/engine_test.go b/engine/engine_test.go index fcf5c59..4af9ec3 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -44,7 +44,7 @@ func TestEngineNew(t *testing.T) { t.Fatalf("New(): %s", err) } - if e.engine == nil || e.contexts == nil { + if e.engine == nil || e.contexts == nil || e.receivers == nil { t.Fatalf("New(): Struct fields are `nil` but no error returned") } } @@ -60,10 +60,28 @@ func TestEngineNewContext(t *testing.T) { } } +func TestEngineDefine(t *testing.T) { + ctor := func(args []interface{}) interface{} { + return nil + } + + if err := e.Define("TestDefine", ctor); err != nil { + t.Errorf("Engine.Define(): %s", err) + } + + if len(e.receivers) != 1 { + t.Errorf("Engine.Define(): `Engine.receivers` length is %d, should be 1", len(e.receivers)) + } + + if err := e.Define("TestDefine", ctor); err == nil { + t.Errorf("Engine.Define(): Incorrectly defined duplicate receiver") + } +} + func TestEngineDestroy(t *testing.T) { e.Destroy() - if e.engine != nil || e.contexts != nil { + if e.engine != nil || e.contexts != nil || e.receivers != nil { t.Errorf("Engine.Destroy(): Did not set internal fields to `nil`") } diff --git a/engine/include/context.h b/engine/include/context.h index 4cff173..ee030c7 100644 --- a/engine/include/context.h +++ b/engine/include/context.h @@ -5,8 +5,6 @@ #ifndef __CONTEXT_H__ #define __CONTEXT_H__ -#include "_context.h" - typedef struct _engine_context { } engine_context; @@ -16,4 +14,6 @@ void *context_eval(engine_context *context, char *script); void context_bind(engine_context *context, char *name, void *value); void context_destroy(engine_context *context); +#include "_context.h" + #endif \ No newline at end of file diff --git a/engine/include/engine.h b/engine/include/engine.h index a026dd7..7a600f1 100644 --- a/engine/include/engine.h +++ b/engine/include/engine.h @@ -5,12 +5,12 @@ #ifndef __ENGINE_H__ #define __ENGINE_H__ -#include "_engine.h" - typedef struct _php_engine { } php_engine; php_engine *engine_init(void); void engine_shutdown(php_engine *engine); +#include "_engine.h" + #endif \ No newline at end of file diff --git a/engine/include/php5/_receiver.h b/engine/include/php5/_receiver.h index 60dcda4..62db83c 100644 --- a/engine/include/php5/_receiver.h +++ b/engine/include/php5/_receiver.h @@ -59,4 +59,9 @@ static inline zval *RECEIVER_RETVAL() { h.get_class_entry = std->get_class_entry; \ } while (0) +// Return class name for method receiver. +static inline char *receiver_get_name(engine_receiver *rcvr) { + return rcvr->obj.ce->name; +} + #endif \ No newline at end of file diff --git a/engine/include/php5/_value.h b/engine/include/php5/_value.h index cae6e7b..c1f8f3b 100644 --- a/engine/include/php5/_value.h +++ b/engine/include/php5/_value.h @@ -15,10 +15,6 @@ ZVAL_NULL(v); \ } while (0) -#define VALUE_FREE(v) do { \ - zval_dtor(v); \ -} while (0) - #define HASH_GET_CURRENT_KEY(h, k, i) zend_hash_get_current_key(h, k, i, 0) #define HASH_SET_CURRENT_KEY(h, v) do { \ zval *t; \ @@ -52,4 +48,10 @@ return v; \ } while (0) +// Destroy and free engine value. +static inline void value_destroy(engine_value *val) { + zval_dtor(val->internal); + free(val); +} + #endif \ No newline at end of file diff --git a/engine/include/php7/_receiver.h b/engine/include/php7/_receiver.h index 0c17587..015f7f6 100644 --- a/engine/include/php7/_receiver.h +++ b/engine/include/php7/_receiver.h @@ -50,4 +50,9 @@ h.free_obj = receiver_free; \ } while (0) +// Return class name for method receiver. +static inline char *receiver_get_name(engine_receiver *rcvr) { + return rcvr->obj.ce->name->val; +} + #endif \ No newline at end of file diff --git a/engine/include/php7/_value.h b/engine/include/php7/_value.h index dbd42e7..a9e70aa 100644 --- a/engine/include/php7/_value.h +++ b/engine/include/php7/_value.h @@ -15,11 +15,6 @@ ZVAL_NULL(v); \ } while (0) -#define VALUE_FREE(v) do { \ - zval_dtor(v); \ - free(v); \ -} while (0) - #define HASH_GET_CURRENT_KEY(h, k, i) zend_hash_get_current_key(h, k, i) #define HASH_SET_CURRENT_KEY(h, v) do { \ zval t; \ @@ -54,4 +49,11 @@ return v; \ } while (0) +// Destroy and free engine value. +static inline void value_destroy(engine_value *val) { + zval_dtor(val->internal); + free(val->internal); + free(val); +} + #endif \ No newline at end of file diff --git a/engine/include/receiver.h b/engine/include/receiver.h index 7dbc7c6..add9345 100644 --- a/engine/include/receiver.h +++ b/engine/include/receiver.h @@ -5,21 +5,13 @@ #ifndef __RECEIVER_H__ #define __RECEIVER_H__ -#include "_receiver.h" - -#define RECEIVER_POINTER(ce, name) \ - (void *) Z_LVAL_P(zend_read_static_property(ce, name, sizeof(name) - 1, 1)) - -#define RECEIVER_POINTER_SET(ce, name, ptr) \ - zend_declare_property_long(ce, name, sizeof(name) - 1, (long int) ptr, \ - ZEND_ACC_STATIC | ZEND_ACC_PRIVATE) - typedef struct _engine_receiver { zend_object obj; - void *rcvr; } engine_receiver; -void receiver_define(char *name, void *rcvr); +void receiver_define(char *name); void receiver_destroy(char *name); +#include "_receiver.h" + #endif \ No newline at end of file diff --git a/engine/include/value.h b/engine/include/value.h index ba7bd25..9a1671a 100644 --- a/engine/include/value.h +++ b/engine/include/value.h @@ -5,8 +5,6 @@ #ifndef __VALUE_H__ #define __VALUE_H__ -#include "_value.h" - typedef struct _engine_value { zval *internal; int kind; @@ -28,14 +26,8 @@ static inline void value_copy(zval *dst, zval *src) { zval_copy_ctor(dst); } -static inline void value_destroy(engine_value *val) { - VALUE_FREE(val->internal); - free(val); -} - engine_value *value_new(); int value_kind(engine_value *val); -void value_destroy(engine_value *val); void value_set_null(engine_value *val); void value_set_long(engine_value *val, long int num); @@ -63,4 +55,6 @@ engine_value *value_array_next_get(engine_value *arr); engine_value *value_array_index_get(engine_value *arr, unsigned long idx); engine_value *value_array_key_get(engine_value *arr, char *key); +#include "_value.h" + #endif \ No newline at end of file diff --git a/engine/receiver.c b/engine/receiver.c index 00228aa..eeffd5f 100644 --- a/engine/receiver.c +++ b/engine/receiver.c @@ -18,7 +18,7 @@ static zval *RECEIVER_GET(zval *object, zval *member) { engine_receiver *this = RECEIVER_THIS(object); zval *val = RECEIVER_RETVAL(); - engine_value *result = receiverGet(this->rcvr, Z_STRVAL_P(member)); + engine_value *result = engineReceiverGet(this, Z_STRVAL_P(member)); if (result == NULL) { ZVAL_NULL(val); return val; @@ -33,14 +33,14 @@ static zval *RECEIVER_GET(zval *object, zval *member) { // Set field for method receiver. static void RECEIVER_SET(zval *object, zval *member, zval *value) { engine_receiver *this = RECEIVER_THIS(object); - receiverSet(this->rcvr, Z_STRVAL_P(member), (void *) value); + engineReceiverSet(this, Z_STRVAL_P(member), (void *) value); } // Check if field exists for method receiver. static int RECEIVER_EXISTS(zval *object, zval *member, int check) { engine_receiver *this = RECEIVER_THIS(object); - if (!receiverExists(this->rcvr, Z_STRVAL_P(member))) { + if (!engineReceiverExists(this, Z_STRVAL_P(member))) { // Value does not exist. return 0; } else if (check == 2) { @@ -49,7 +49,7 @@ static int RECEIVER_EXISTS(zval *object, zval *member, int check) { } int result = 0; - engine_value *val = receiverGet(this->rcvr, Z_STRVAL_P(member)); + engine_value *val = engineReceiverGet(this, Z_STRVAL_P(member)); if (check == 1) { // Value exists and is "truthy". @@ -78,7 +78,7 @@ static int RECEIVER_METHOD_CALL(method) { if (zend_copy_parameters_array(ZEND_NUM_ARGS(), &args) == FAILURE) { RETVAL_NULL(); } else { - engine_value *result = receiverCall(this->rcvr, name, (void *) &args); + engine_value *result = engineReceiverCall(this, name, (void *) &args); if (result == NULL) { RETVAL_NULL(); } else { @@ -102,10 +102,8 @@ static void receiver_new(INTERNAL_FUNCTION_PARAMETERS) { zend_throw_exception(NULL, "Could not parse parameters for method receiver", 0); } else { // Create receiver instance. Throws an exception if creation fails. - void *ctor = RECEIVER_POINTER(this->obj.ce, "__rcvr__"); - this->rcvr = receiverNew(ctor, (void *) &args); - - if (this->rcvr == NULL) { + int result = engineReceiverNew(this, (void *) &args); + if (result != 0) { zend_throw_exception(NULL, "Failed to instantiate method receiver", 0); } } @@ -191,8 +189,8 @@ static RECEIVER_INIT(zend_class_entry *class_type) { RECEIVER_OBJECT_CREATE(this, class_type); } -// Define class with unique name, using `rcvr` as the method receiver prototype. -void receiver_define(char *name, void *rcvr) { +// Define class with unique name. +void receiver_define(char *name) { zend_class_entry tmp; INIT_CLASS_ENTRY_EX(tmp, name, strlen(name), NULL); @@ -203,12 +201,9 @@ void receiver_define(char *name, void *rcvr) { // Set standard handlers for receiver. RECEIVER_HANDLERS_SET(receiver_handlers); - - // Method receiver is stored as internal class property. - RECEIVER_POINTER_SET(this, "__rcvr__", rcvr); } void receiver_destroy(char *name) { name = php_strtolower(name, strlen(name)); RECEIVER_DESTROY(name); -} \ No newline at end of file +} diff --git a/engine/receiver.go b/engine/receiver.go index f6f3a69..26aec9a 100644 --- a/engine/receiver.go +++ b/engine/receiver.go @@ -13,63 +13,31 @@ package engine import "C" import ( + "fmt" "reflect" "unsafe" ) -// ReceiverObject represents an object instance of a pre-defined method receiver. -type ReceiverObject struct { - instance interface{} - values map[string]reflect.Value - methods map[string]reflect.Value -} - // Receiver represents a method receiver. type Receiver struct { name string create func(args []interface{}) interface{} - objects []*ReceiverObject -} - -// Destroy removes references to the generated PHP class for this receiver and -// frees any memory used by object instances. -func (r *Receiver) Destroy() { - if r.create == nil { - return - } - - n := C.CString(r.name) - defer C.free(unsafe.Pointer(n)) - - C.receiver_destroy(n) - - r.create = nil - r.objects = nil + objects map[*C.struct__engine_receiver]*ReceiverObject } -//export receiverNew -func receiverNew(rcvr unsafe.Pointer, args unsafe.Pointer) unsafe.Pointer { - r := (*Receiver)(rcvr) - - va, err := NewValueFromPtr(args) - if err != nil { - return nil - } - - defer va.Destroy() - +// NewObject instantiates a new method receiver object, using the Receiver's +// create function and passing in a slice of values as a parameter. +func (r *Receiver) NewObject(args []interface{}) (*ReceiverObject, error) { obj := &ReceiverObject{ - instance: r.create(va.Slice()), + instance: r.create(args), values: make(map[string]reflect.Value), methods: make(map[string]reflect.Value), } if obj.instance == nil { - return nil + return nil, fmt.Errorf("Failed to instantiate method receiver") } - r.objects = append(r.objects, obj) - v := reflect.ValueOf(obj.instance) vi := reflect.Indirect(v) @@ -93,93 +61,95 @@ func receiverNew(rcvr unsafe.Pointer, args unsafe.Pointer) unsafe.Pointer { } } - return unsafe.Pointer(obj) + return obj, nil } -//export receiverGet -func receiverGet(obj unsafe.Pointer, name *C.char) unsafe.Pointer { - o := (*ReceiverObject)(obj) - n := C.GoString(name) - - if _, exists := o.values[n]; !exists || !o.values[n].CanInterface() { - return nil +// Destroy removes references to the generated PHP class for this receiver and +// frees any memory used by object instances. +func (r *Receiver) Destroy() { + if r.create == nil { + return } - result, err := NewValue(o.values[n].Interface()) - if err != nil { - return nil - } + n := C.CString(r.name) + defer C.free(unsafe.Pointer(n)) - return result.Ptr() + C.receiver_destroy(n) + r.create = nil + r.objects = nil } -//export receiverSet -func receiverSet(obj unsafe.Pointer, name *C.char, val unsafe.Pointer) { - o := (*ReceiverObject)(obj) - n := C.GoString(name) +// ReceiverObject represents an object instance of a pre-defined method receiver. +type ReceiverObject struct { + instance interface{} + values map[string]reflect.Value + methods map[string]reflect.Value +} - // Do not attempt to set non-existing or unset-able field. - if _, exists := o.values[n]; !exists || !o.values[n].CanSet() { - return +// Get returns a named internal property of the receiver object instance, or an +// error if the property does not exist or is not addressable. +func (o *ReceiverObject) Get(name string) (*Value, error) { + if _, exists := o.values[name]; !exists || !o.values[name].CanInterface() { + return nil, fmt.Errorf("Value '%s' does not exist or is not addressable", name) } - v, err := NewValueFromPtr(val) + val, err := NewValue(o.values[name].Interface()) if err != nil { - return + return nil, err } - o.values[n].Set(reflect.ValueOf(v.Interface())) + return val, nil } -//export receiverExists -func receiverExists(obj unsafe.Pointer, name *C.char) C.int { - o := (*ReceiverObject)(obj) - n := C.GoString(name) - - if _, exists := o.values[n]; !exists { - return 0 +// Set assigns value to named internal property. If the named property does not +// exist or cannot be set, the method does nothing. +func (o *ReceiverObject) Set(name string, val interface{}) { + // Do not attempt to set non-existing or unset-able field. + if _, exists := o.values[name]; !exists || !o.values[name].CanSet() { + return } - return 1 + o.values[name].Set(reflect.ValueOf(val)) } -//export receiverCall -func receiverCall(obj unsafe.Pointer, name *C.char, args unsafe.Pointer) unsafe.Pointer { - o := (*ReceiverObject)(obj) - n := C.GoString(name) - - if _, exists := o.methods[n]; !exists { - return nil +// Exists checks if named internal property exists and returns true, or false if +// property does not exist. +func (o *ReceiverObject) Exists(name string) bool { + if _, exists := o.values[name]; !exists { + return false } - // Process input arguments. - va, err := NewValueFromPtr(args) - if err != nil { + return true +} + +// Call executes a method receiver's named internal method, passing a slice of +// values as arguments to the method. If the method fails to execute or returns +// no value, nil is returned, otherwise a Value instance is returned. +func (o *ReceiverObject) Call(name string, args []interface{}) *Value { + if _, exists := o.methods[name]; !exists { return nil } in := make([]reflect.Value, 0) - for _, v := range va.Slice() { + for _, v := range args { in = append(in, reflect.ValueOf(v)) } - va.Destroy() - // Call receiver method. var result interface{} - ret := o.methods[n].Call(in) + val := o.methods[name].Call(in) // Process results, returning a single value if result slice contains a single // element, otherwise returns a slice of values. - if len(ret) > 1 { - t := make([]interface{}, len(ret)) - for i, v := range ret { + if len(val) > 1 { + t := make([]interface{}, len(val)) + for i, v := range val { t[i] = v.Interface() } result = t - } else if len(ret) == 1 { - result = ret[0].Interface() + } else if len(val) == 1 { + result = val[0].Interface() } else { return nil } @@ -189,5 +159,5 @@ func receiverCall(obj unsafe.Pointer, name *C.char, args unsafe.Pointer) unsafe. return nil } - return v.Ptr() + return v } diff --git a/engine/receiver_test.go b/engine/receiver_test.go index 111872c..f018484 100644 --- a/engine/receiver_test.go +++ b/engine/receiver_test.go @@ -125,12 +125,12 @@ func TestReceiverDefine(t *testing.T) { c, _ := e.NewContext() c.Output = &w - if err := c.Define("TestReceiver", newTestReceiver); err != nil { + if err := e.Define("TestReceiver", newTestReceiver); err != nil { t.Fatalf("Engine.Define(): Failed to define method receiver: %s", err) } // Attempting to define a receiver twice should fail. - if err := c.Define("TestReceiver", newTestReceiver); err == nil { + if err := e.Define("TestReceiver", newTestReceiver); err == nil { t.Fatalf("Engine.Define(): Defining duplicate receiver should fail") } @@ -156,11 +156,7 @@ func TestReceiverDestroy(t *testing.T) { c, _ := e.NewContext() defer c.Destroy() - if err := c.Define("TestReceiver", newTestReceiver); err != nil { - t.Fatalf("Engine.Define(): Failed to define method receiver: %s", err) - } - - r := c.receivers["TestReceiver"] + r := e.receivers["TestReceiver"] if r == nil { t.Fatalf("Receiver.Destroy(): Could not find defined receiver") }