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

Context: Fix variable scoping issues for Eval #16

Merged
merged 3 commits into from
Jan 26, 2016
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ func main() {

The above will execute script file `index.php` located in the current folder and will write any output to the `io.Writer` assigned to `Context.Output` (in this case, the standard output).

### Binding and returning variables

The following example demonstrates binding a Go variable to the running PHP context, and returning a PHP variable for use in Go:

```go
package main

import (
"fmt"
php "github.com/deuill/go-php"
)

func main() {
engine, _ := php.New()
context, _ := engine.NewContext()

var str string = "Hello"
context.Bind("var", str)

val, _ := context.Eval("return $var.' World';")
fmt.Printf("%s", val.Interface())
// Prints 'Hello World' back to the user.

engine.Destroy()
}
```

A string value "Hello" is attached using `Context.Bind` under a name `var` (available in PHP as `$var`). A script is executed inline using `Context.Eval`, combinding the attached value with a PHP string and returning it to the user.

Finally, the value is returned as an `interface{}` using `Value.Interface()` (one could also use `Value.String()`, though the both are equivalent in this case).

## License

All code in this repository is covered by the terms of the MIT License, the full text of which can be found in the LICENSE file.
Expand Down
26 changes: 16 additions & 10 deletions engine/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,28 @@ void context_exec(engine_context *context, char *filename) {
}

void *context_eval(engine_context *context, char *script) {
int status;
zval tmp;
zval str;
VALUE_SET_STRING(&str, script);

// Attempt to evaluate inline script.
zend_first_try {
status = zend_eval_string(script, &tmp, "gophp-engine");
} zend_catch {
errno = 1;
return NULL;
} zend_end_try();
// Compile script value.
uint32_t compiler_options = CG(compiler_options);
CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL;
zend_op_array *op = zend_compile_string(&str, "gophp-engine");
CG(compiler_options) = compiler_options;

zval_dtor(&str);

if (status == FAILURE) {
// Return error if script failed to compile.
if (!op) {
errno = 1;
return NULL;
}

// Attempt to execute compiled string.
zval tmp;
CONTEXT_EXECUTE(op, &tmp);

// Allocate result value and copy temporary execution result in.
zval *result = malloc(sizeof(zval));
value_copy(result, &tmp);

Expand Down
14 changes: 6 additions & 8 deletions engine/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ type Context struct {
Header http.Header

context *C.struct__engine_context
values map[string]*Value
values []*Value
}

// NewContext creates a new execution context for the active engine and returns
// an error if the execution context failed to initialize at any point.
func NewContext() (*Context, error) {
ctx := &Context{
Header: make(http.Header),
values: make(map[string]*Value),
values: make([]*Value, 0),
}

ptr, err := C.context_new(unsafe.Pointer(ctx))
Expand All @@ -66,7 +66,7 @@ func (c *Context) Bind(name string, val interface{}) error {
defer C.free(unsafe.Pointer(n))

C.context_bind(c.context, n, v.Ptr())
c.values[name] = v
c.values = append(c.values, v)

return nil
}
Expand All @@ -90,11 +90,7 @@ func (c *Context) Exec(filename string) error {
// containing the PHP value returned by the expression, if any. Any output
// produced is written context's pre-defined io.Writer instance.
func (c *Context) Eval(script string) (*Value, error) {
// When PHP compiles code with a non-NULL return value expected, it simply
// prepends a `return` call to the code, thus breaking simple scripts that
// would otherwise work. Thus, we need to wrap the code in a closure, and
// call it immediately.
s := C.CString("call_user_func(function(){" + script + "});")
s := C.CString(script)
defer C.free(unsafe.Pointer(s))

result, err := C.context_eval(c.context, s)
Expand All @@ -109,6 +105,8 @@ func (c *Context) Eval(script string) (*Value, error) {
return nil, err
}

c.values = append(c.values, val)

return val, nil
}

Expand Down
12 changes: 3 additions & 9 deletions engine/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,14 @@ func TestContextBind(t *testing.T) {
c, _ := NewContext()
c.Output = &w

script, err := NewScript("bind.php", "<?php $i = (isset($i)) ? $i += 1 : 0; echo serialize($$i);")
if err != nil {
t.Fatalf("Could not create temporary file for testing: %s", err)
}

for i, tt := range bindTests {
if err := c.Bind(fmt.Sprintf("%d", i), tt.value); err != nil {
if err := c.Bind(fmt.Sprintf("t%d", i), tt.value); err != nil {
t.Errorf("Context.Bind('%v'): %s", tt.value, err)
continue
}

if err := c.Exec(script.Name()); err != nil {
t.Errorf("Context.Exec(): %s", err)
if _, err := c.Eval(fmt.Sprintf("echo serialize($t%d);", i)); err != nil {
t.Errorf("Context.Eval(): %s", err)
continue
}

Expand All @@ -284,7 +279,6 @@ func TestContextBind(t *testing.T) {
}
}

script.Remove()
c.Destroy()
}

Expand Down
35 changes: 35 additions & 0 deletions engine/include/php5/_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,39 @@
ZEND_SET_SYMBOL(EG(active_symbol_table), n, v); \
} while (0)

#define CONTEXT_EXECUTE(o, v) do { \
zend_op_array *oparr = EG(active_op_array); \
zval *retval=NULL; \
zval **retvalptr = EG(return_value_ptr_ptr); \
zend_op **opline = EG(opline_ptr); \
int interact = CG(interactive); \
EG(return_value_ptr_ptr) = &retval; \
EG(active_op_array) = o; \
EG(no_extensions) = 1; \
if (!EG(active_symbol_table)) { \
zend_rebuild_symbol_table(); \
} \
CG(interactive) = 0; \
zend_try { \
zend_execute(o); \
} zend_catch { \
destroy_op_array(o); \
efree(o); \
zend_bailout(); \
} zend_end_try(); \
destroy_op_array(o); \
efree(o); \
CG(interactive) = interact; \
if (retval) { \
ZVAL_COPY_VALUE(v, retval); \
zval_copy_ctor(v); \
} else { \
ZVAL_NULL(v); \
} \
EG(no_extensions)=0; \
EG(opline_ptr) = opline; \
EG(active_op_array) = oparr; \
EG(return_value_ptr_ptr) = retvalptr; \
} while (0)

#endif
15 changes: 15 additions & 0 deletions engine/include/php7/_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,19 @@
zend_hash_str_update(&EG(symbol_table), n, strlen(n), v); \
} while (0)

#define CONTEXT_EXECUTE(o, v) do { \
EG(no_extensions) = 1; \
zend_try { \
ZVAL_NULL(v); \
zend_execute(o, v); \
} zend_catch { \
destroy_op_array(o); \
efree_size(o, sizeof(zend_op_array)); \
zend_bailout(); \
} zend_end_try(); \
destroy_op_array(o); \
efree_size(o, sizeof(zend_op_array)); \
EG(no_extensions) = 0; \
} while (0)

#endif
13 changes: 7 additions & 6 deletions engine/include/php7/_receiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
#define RECEIVER_FUNC_SET_ARGFLAGS(f) zend_set_function_arg_flags((zend_function *) f);

#define RECEIVER_OBJECT(o) (o)
#define RECEIVER_OBJECT_CREATE(r, t) do { \
r = ecalloc(1, sizeof(engine_receiver) + zend_object_properties_size(t)); \
zend_object_std_init(&r->obj, t); \
object_properties_init(&r->obj, t); \
r->obj.handlers = &receiver_handlers; \
return &r->obj; \
#define RECEIVER_OBJECT_CREATE(r, t) do { \
r = emalloc(sizeof(engine_receiver)); \
memset(r, 0, sizeof(engine_receiver)); \
zend_object_std_init(&r->obj, t); \
object_properties_init(&r->obj, t); \
r->obj.handlers = &receiver_handlers; \
return &r->obj; \
} while (0)

#define RECEIVER_OBJECT_DESTROY(r) do { \
Expand Down