Initial Release

This commit is contained in:
Sara Golemon
2004-05-05 22:37:32 +00:00
parent 078c33b556
commit 3996427d02
8 changed files with 1163 additions and 0 deletions

53
README Normal file
View File

@@ -0,0 +1,53 @@
parsekit: PHP Opcode Analyser
Provides a userspace interpretation of the opcodes
generated by the Zend engine compiler built into PHP.
This extension is meant for development and debug
purposes only and contains some code which is
potentially non-threadsafe.
This extension exports two functions
(parsekit_compile_string(string phpcode[, array &errors])
and parsekit_compile_file(string filename[, array &errors]))
which will attempt to compile one or more blocks of PHP
code into Zend opcodes.
The output of these functions is an n-depth hash containing
the main block of code's oparray at its root, with
function_table and class_table elements to hold any functions
or classes declared within the compiled code.
In order to accomplish these compilation steps within
the context of an active execution, some potentially unsafe
methods are used.
1) Compilation modifies EG(function_table) and EG(class_table),
after compilation completes, parsekit pops these functions
and classes off the end of these stacks.
2) To avoid parse errors in supplied code causing a zend_bailout(),
parsekit replaces zend_error_cb with a wrapper which passes E_CORE_ERROR
or any error which occurs outside of parsekit_compile_*() onto
the original zend_error_cb. If another module which replaced
zend_error_cb were to be loaded prior to parsekit, then unloaded
after parsekit's startup, but prior to parsekit's shutdown, then
the global value zend_error_cb could become populated with an
invalid function pointer.
The moral of the story: Use this at your own risk.
This extension also exports constant entries for all class types,
function types, node types, and opcodes. While a name collision
would be unexpected, all constants are prefixed with PARSEKIT_
just to be safe.
Example:
#define ZEND_NOP 0
Exported as:
REGISTER_LONG_CONSTANT("PARSEKIT_ZEND_NOP", 0, CONST_CS | CONST_PERSISTENT);
For examples on usage, refer to the examples subdirectory in this package.

10
config.m4 Normal file
View File

@@ -0,0 +1,10 @@
dnl $Id$
dnl config.m4 for extension parsekit
PHP_ARG_ENABLE(parsekit, whether to enable parsekit support,
[ --enable-parsekit Enable parsekit support])
if test "$PHP_PARSEKIT" != "no"; then
AC_DEFINE(HAVE_PARSEKITLIB, 1, [ Parser Toolkit ])
PHP_NEW_EXTENSION(parsekit, parsekit.c, $ext_shared)
fi

View File

@@ -0,0 +1,6 @@
<?php
/* Compile ourself */
$oparray = parsekit_compile_file($_SERVER['PHP_SELF']);
var_dump($oparray);

View File

@@ -0,0 +1,14 @@
<?php
/* Compile a simple Hello World type program */
$oparray = parsekit_compile_string('
function foo($bar = "Hello World") {
return $bar;
}
echo foo();
');
var_dump($oparray);

View File

@@ -0,0 +1,14 @@
<?php
/* Compile a buggy Hello World program */
$oparray = parsekit_compile_string('
function foo($bar = "Hello World) {
return $bar;
}
echo foo();
', $errors);
var_dump($errors);

40
package.xml Normal file
View File

@@ -0,0 +1,40 @@
<package version="1.0">
<name>parsekit</name>
<summary>PHP Opcode Analyser</summary>
<description>
Provides a userspace interpretation of the opcodes generated by the Zend engine compiler built into PHP.
This extension is meant for development and debug purposes only and contains some code which is potentially non-threadsafe.
</description>
<license>PHP</license>
<maintainers>
<maintainer>
<user>pollita</user>
<name>Sara Golemon</name>
<email>pollita@php.net</email>
<role>lead</role>
</maintainer>
</maintainers>
<release>
<version>0.1</version>
<state>alpha</state>
<date>2004-05-06</date>
<notes>Initial Release</notes>
<filelist>
<file role="src" name="config.m4"/>
<file role="src" name="parsekit.c"/>
<file role="src" name="php_parsekit.h"/>
<file role="doc" name="README"/>
<dir name="examples">
<file role="script" name="compile_string.php"/>
<file role="script" name="compile_file.php"/>
<file role="script" name="compile_string_show_errors.php"/>
</dir>
</filelist>
<deps>
<dep type="php" rel="ge" version="5.0.0" />
</deps>
</release>
</package>

791
parsekit.c Normal file
View File

@@ -0,0 +1,791 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2004 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.0 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_0.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_parsekit.h"
ZEND_DECLARE_MODULE_GLOBALS(parsekit)
/* Potentially thread-unsafe, see MINIT_FUNCTION */
void (*original_error_function)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 4, 0);
/* Parsekit Workhorse */
/* {{{ php_parsekit_define_name */
static char* php_parsekit_define_name(long val, php_parsekit_define_list *lookup_list, char *unknown_default)
{
php_parsekit_define_list *names;
for(names = lookup_list; names->str; names++) {
if (names->val == val) {
return names->str;
}
}
return unknown_default ? unknown_default : PHP_PARSEKIT_UNKNOWN;
}
/* }}} */
/* {{{ php_parsekit_parse_node */
static void php_parsekit_parse_node(zval *return_value, znode *node TSRMLS_DC)
{
array_init(return_value);
add_assoc_long(return_value, "type", node->op_type);
add_assoc_string(return_value, "type_name", php_parsekit_define_name(node->op_type, php_parsekit_nodetype_names, PHP_PARSEKIT_NODETYPE_UNKNOWN), 1);
if (node->op_type == IS_CONST) {
zval *tmpzval;
MAKE_STD_ZVAL(tmpzval);
*tmpzval = node->u.constant;
zval_copy_ctor(tmpzval);
add_assoc_zval(return_value, "constant", tmpzval);
} else {
/* IS_VAR || IS_TMP_VAR || IS_UNUSED */
char sop[(sizeof(void *) * 2) + 1];
/* Which of these is relevant depends on the opcode
Just offer them all up and let the user decide */
/* Probably best to only do the snprintf once,
but leave in the extra calls for readability.
This is just a debug extension for pete's sake. */
add_assoc_long(return_value, "var", node->u.var);
snprintf(sop, (sizeof(void *) * 2) + 1, "%X", (unsigned int)node->u.opline_num);
add_assoc_string(return_value, "opline_num", sop, 1);
snprintf(sop, (sizeof(void *) * 2) + 1, "%X", (unsigned int)node->u.op_array);
add_assoc_string(return_value, "op_array", sop, 1);
snprintf(sop, (sizeof(void *) * 2) + 1, "%X", (unsigned int)node->u.jmp_addr);
add_assoc_string(return_value, "jmp_addr", sop, 1);
add_assoc_long(return_value, "EA.type", node->u.EA.type);
}
}
/* }}} */
/* {{{ php_parsekit_parse_op */
static void php_parsekit_parse_op(zval *return_value, zend_op *op TSRMLS_DC)
{
zval *result, *op1, *op2;
array_init(return_value);
/* op->handler */
add_assoc_long(return_value, "opcode", op->opcode);
add_assoc_string(return_value, "opcode_name", php_parsekit_define_name(op->opcode, php_parsekit_opcode_names, PHP_PARSEKIT_OPCODE_UNKNOWN) , 1);
/* args: result, op1, op2 */
MAKE_STD_ZVAL(result);
MAKE_STD_ZVAL(op1);
MAKE_STD_ZVAL(op2);
php_parsekit_parse_node(result, &(op->result) TSRMLS_CC);
php_parsekit_parse_node(op1, &(op->op1) TSRMLS_CC);
php_parsekit_parse_node(op2, &(op->op2) TSRMLS_CC);
add_assoc_zval(return_value, "result", result);
add_assoc_zval(return_value, "op1", op1);
add_assoc_zval(return_value, "op2", op2);
add_assoc_long(return_value, "extended_value", op->extended_value);
add_assoc_long(return_value, "lineno", op->lineno);
}
/* }}} */
/* {{{ php_parsekit_parse_arginfo */
static void php_parsekit_parse_arginfo(zval *return_value, zend_uint num_args, zend_arg_info *arginfo TSRMLS_DC)
{
zend_uint i;
array_init(return_value);
for(i = 0; i < num_args; i++) {
zval *tmpzval;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
add_assoc_stringl(tmpzval, "name", arginfo[i].name, arginfo[i].name_len, 1);
if (arginfo[i].class_name_len) {
add_assoc_stringl(tmpzval, "class_name", arginfo[i].class_name, arginfo[i].class_name_len, 1);
} else {
add_assoc_null(tmpzval, "class_name");
}
add_assoc_bool(tmpzval, "allow_null", arginfo[i].allow_null);
add_assoc_bool(tmpzval, "pass_by_reference", arginfo[i].pass_by_reference);
add_next_index_zval(return_value, tmpzval);
}
}
/* }}} */
/* {{{ php_parsekit_parse_op_array */
static void php_parsekit_parse_op_array(zval *return_value, zend_op_array *ops TSRMLS_DC)
{
zend_op *op;
zval *tmpzval;
int i = 0;
array_init(return_value);
/* "Common" members */
add_assoc_long(return_value, "type", (long)(ops->type));
add_assoc_string(return_value, "type_name", php_parsekit_define_name(ops->type, php_parsekit_function_types, PHP_PARSEKIT_FUNCTYPE_UNKNOWN), 1);
if (ops->function_name) {
add_assoc_string(return_value, "function_name", ops->function_name, 1);
} else {
add_assoc_null(return_value, "function_name");
}
if (ops->scope && ops->scope->name) {
add_assoc_stringl(return_value, "scope", ops->scope->name, ops->scope->name_length, 1);
} else {
add_assoc_null(return_value, "scope");
}
add_assoc_long(return_value, "fn_flags", ops->fn_flags);
if (ops->prototype) {
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
add_assoc_long(tmpzval, "type", ops->prototype->type);
add_assoc_string(return_value, "type_name", php_parsekit_define_name(ops->prototype->type, php_parsekit_function_types, PHP_PARSEKIT_FUNCTYPE_UNKNOWN), 1);
if (ops->prototype->common.function_name) {
add_assoc_string(tmpzval, "function_name", ops->prototype->common.function_name, 1);
} else {
add_assoc_null(tmpzval, "function_name");
}
if (ops->prototype->common.scope && ops->prototype->common.scope->name) {
add_assoc_stringl(tmpzval, "scope", ops->prototype->common.scope->name, ops->prototype->common.scope->name_length, 1);
} else {
add_assoc_null(tmpzval, "scope");
}
add_assoc_zval(return_value, "prototype", tmpzval);
} else {
add_assoc_null(return_value, "prototype");
}
add_assoc_long(return_value, "num_args", ops->num_args);
add_assoc_long(return_value, "required_num_args", ops->required_num_args);
add_assoc_bool(return_value, "pass_rest_by_reference", ops->pass_rest_by_reference);
add_assoc_bool(return_value, "return_reference", ops->return_reference);
if (ops->num_args && ops->arg_info) {
MAKE_STD_ZVAL(tmpzval);
php_parsekit_parse_arginfo(tmpzval, ops->num_args, ops->arg_info TSRMLS_CC);
add_assoc_zval(return_value, "arg_info", tmpzval);
} else {
add_assoc_null(return_value, "arg_info");
}
/* End "Common" */
add_assoc_long(return_value, "refcount", *(ops->refcount));
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
for(op = ops->opcodes, i = 0; op && i < ops->size; op++, i++) {
zval *zop;
char sop[(sizeof(void *) * 2) + 1];
/* Use the memory address as a convenient reference point
This lets us find target ops when we JMP */
snprintf(sop, (sizeof(void *) * 2) + 1, "%X", (unsigned int)op);
MAKE_STD_ZVAL(zop);
php_parsekit_parse_op(zop, op TSRMLS_CC);
add_assoc_zval(tmpzval, sop, zop);
}
add_assoc_zval(return_value, "opcodes", tmpzval);
add_assoc_long(return_value, "last", ops->last);
add_assoc_long(return_value, "size", ops->size);
add_assoc_long(return_value, "T", ops->T);
if (ops->last_brk_cont > 0) {
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
for(i = 0; i < ops->last_brk_cont; i++) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmp_zval);
array_init(tmp_zval);
add_assoc_long(tmp_zval, "cont", ops->brk_cont_array[i].cont);
add_assoc_long(tmp_zval, "brk", ops->brk_cont_array[i].brk);
add_assoc_long(tmp_zval, "parent", ops->brk_cont_array[i].parent);
add_index_zval(tmpzval, i, tmp_zval);
}
add_assoc_zval(return_value, "brk_cont_array", tmpzval);
} else {
add_assoc_null(return_value, "brk_cont_array");
}
add_assoc_long(return_value, "last_brk_cont", ops->last_brk_cont);
add_assoc_long(return_value, "current_brk_cont", ops->current_brk_cont);
if (ops->last_try_catch > 0) {
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
for(i = 0; i < ops->last_try_catch; i++) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmp_zval);
array_init(tmp_zval);
add_assoc_long(tmp_zval, "try_op", ops->try_catch_array[i].try_op);
add_assoc_long(tmp_zval, "catch_op", ops->try_catch_array[i].catch_op);
add_index_zval(tmpzval, i, tmp_zval);
}
add_assoc_zval(return_value, "try_catch_array", tmpzval);
} else {
add_assoc_null(return_value, "try_catch_array");
}
if (ops->static_variables) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
zend_hash_copy(HASH_OF(tmpzval), ops->static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *));
add_assoc_zval(return_value, "static_variables", tmpzval);
} else {
add_assoc_null(return_value, "static_variables");
}
if (ops->start_op) {
char sop[(sizeof(void *) * 2) + 1];
snprintf(sop, sizeof(sop), "%X", (unsigned int)ops->start_op);
add_assoc_string(return_value, "start_op", sop, 1);
} else {
add_assoc_null(return_value, "start_op");
}
add_assoc_long(return_value, "backpatch_count", ops->backpatch_count);
add_assoc_bool(return_value, "done_pass_two", ops->done_pass_two);
add_assoc_bool(return_value, "uses_this", ops->uses_this);
if (ops->filename) {
add_assoc_string(return_value, "filename", ops->filename, 1);
} else {
add_assoc_null(return_value, "filename");
}
add_assoc_long(return_value, "line_start", ops->line_start);
add_assoc_long(return_value, "line_end", ops->line_end);
if (ops->doc_comment && ops->doc_comment_len) {
add_assoc_stringl(return_value, "doc_comment", ops->doc_comment, ops->doc_comment_len, 1);
} else {
add_assoc_null(return_value, "doc_comment");
}
}
/* }}} */
/* {{{ php_parsekit_pop_functions */
static int php_parsekit_pop_functions(zval *return_value, HashTable *function_table, int target_count TSRMLS_DC)
{
array_init(return_value);
while (target_count < zend_hash_num_elements(function_table)) {
long func_index;
unsigned int func_name_len;
char *func_name;
zend_function *function;
zval *function_ops;
zend_hash_internal_pointer_end(function_table);
if (zend_hash_get_current_data(function_table, (void **)&function) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from function table: Illegal function entry found.");
return FAILURE;
}
if (function->type != ZEND_USER_FUNCTION) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from function table: "
"Found %s where ZEND_USER_FUNCTION was expected.",
php_parsekit_define_name(function->type, php_parsekit_function_types, PHP_PARSEKIT_FUNCTYPE_UNKNOWN));
return FAILURE;
}
MAKE_STD_ZVAL(function_ops);
php_parsekit_parse_op_array(function_ops, &(function->op_array) TSRMLS_CC);
add_assoc_zval(return_value, function->common.function_name, function_ops);
if (zend_hash_get_current_key_ex(function_table, &func_name, &func_name_len, &func_index, 0, NULL) == HASH_KEY_IS_STRING) {
/* TODO: dispose of the function properly */
if (zend_hash_del(function_table, func_name, func_name_len) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from function table: Unknown hash_del failure.");
return FAILURE;
}
} else {
/* Absolutely no reason this should ever occur */
zend_hash_index_del(function_table, func_index);
}
}
return SUCCESS;
}
/* }}} */
/* {{{ php_parsekit_parse_class_entry */
static int php_parsekit_parse_class_entry(zval *return_value, zend_class_entry *ce TSRMLS_DC)
{
zval *tmpzval;
int i;
array_init(return_value);
add_assoc_long(return_value, "type", ce->type);
add_assoc_string(return_value, "type_name", php_parsekit_define_name(ce->type, php_parsekit_class_types, PHP_PARSEKIT_CLASSTYPE_UNKNOWN), 1);
add_assoc_stringl(return_value, "name", ce->name, ce->name_length, 1);
if (ce->parent) {
add_assoc_stringl(return_value, "parent", ce->parent->name, ce->parent->name_length, 1);
} else {
add_assoc_null(return_value, "parent");
}
add_assoc_long(return_value, "refcount", ce->refcount);
add_assoc_bool(return_value, "constnats_updated", ce->constants_updated);
add_assoc_long(return_value, "ce_flags", ce->ce_flags);
/* function table pop destorys entries! */
if (ce->constructor) {
add_assoc_string(return_value, "constructor", ce->constructor->common.function_name, 1);
} else {
add_assoc_null(return_value, "constructor");
}
if (ce->clone) {
add_assoc_string(return_value, "clone", ce->clone->common.function_name, 1);
} else {
add_assoc_null(return_value, "clone");
}
if (ce->__get) {
add_assoc_string(return_value, "__get", ce->__get->common.function_name, 1);
} else {
add_assoc_null(return_value, "__get");
}
if (ce->__set) {
add_assoc_string(return_value, "__set", ce->__set->common.function_name, 1);
} else {
add_assoc_null(return_value, "__set");
}
if (ce->__call) {
add_assoc_string(return_value, "__call", ce->__call->common.function_name, 1);
} else {
add_assoc_null(return_value, "__call");
}
if (zend_hash_num_elements(&(ce->function_table)) > 0) {
MAKE_STD_ZVAL(tmpzval);
if (php_parsekit_pop_functions(tmpzval, &(ce->function_table), 0 TSRMLS_CC) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to cleanup class %s: Error scrubbing function_table", ce->name);
return FAILURE;
}
add_assoc_zval(return_value, "function_table", tmpzval);
} else {
add_assoc_null(return_value, "function_table");
}
if (zend_hash_num_elements(&(ce->default_properties)) > 0) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
zend_hash_copy(HASH_OF(tmpzval), &(ce->default_properties), (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *));
add_assoc_zval(return_value, "default_properties", tmpzval);
} else {
add_assoc_null(return_value, "default_properties");
}
if (zend_hash_num_elements(&(ce->properties_info)) > 0) {
zend_property_info *property_info;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
for(zend_hash_internal_pointer_reset(&(ce->properties_info));
zend_hash_get_current_data(&(ce->properties_info), (void **)&property_info) == SUCCESS;
zend_hash_move_forward(&(ce->properties_info))) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmp_zval);
array_init(tmp_zval);
add_assoc_long(tmp_zval, "flags", property_info->flags);
add_assoc_stringl(tmp_zval, "name", property_info->name, property_info->name_length, 1);
add_assoc_long(tmp_zval, "h", property_info->h);
add_next_index_zval(tmpzval, tmp_zval);
}
add_assoc_zval(return_value, "properties_info", tmpzval);
} else {
add_assoc_null(return_value, "properties_info");
}
if (ce->static_members && zend_hash_num_elements(ce->static_members) > 0) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
zend_hash_copy(HASH_OF(tmpzval), ce->static_members, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *));
add_assoc_zval(return_value, "static_members", tmpzval);
} else {
add_assoc_null(return_value, "static_members");
}
if (zend_hash_num_elements(&(ce->constants_table)) > 0) {
zval *tmp_zval;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
zend_hash_copy(HASH_OF(tmpzval), &(ce->constants_table), (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *));
add_assoc_zval(return_value, "constants_table", tmpzval);
} else {
add_assoc_null(return_value, "constants_table");
}
if (ce->num_interfaces > 0) {
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
for(i = 0; i < ce->num_interfaces; i++) {
add_next_index_stringl(tmpzval, ce->interfaces[i]->name, ce->interfaces[i]->name_length, 1);
}
add_assoc_zval(return_value, "interfaces", tmpzval);
} else {
add_assoc_null(return_value, "interfaces");
}
add_assoc_string(return_value, "filename", ce->filename, 1);
add_assoc_long(return_value, "line_start", ce->line_start);
add_assoc_long(return_value, "line_end", ce->line_end);
if (ce->doc_comment) {
add_assoc_stringl(return_value, "doc_comment", ce->doc_comment, ce->doc_comment_len, 1);
} else {
add_assoc_null(return_value, "doc_comment");
}
return SUCCESS;
}
/* }}} */
/* {{{ php_parsekit_pop_classes */
static int php_parsekit_pop_classes(zval *return_value, HashTable *class_table, int target_count TSRMLS_DC)
{
array_init(return_value);
while (target_count < zend_hash_num_elements(class_table)) {
long class_index;
unsigned int class_name_len;
char *class_name;
zend_class_entry *class_entry, **pce;
zval *class_data;
zend_hash_internal_pointer_end(class_table);
if (zend_hash_get_current_data(class_table, (void **)&pce) == FAILURE || !pce || !(*pce)) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from class table: Illegal class entry found.");
return FAILURE;
}
class_entry = *pce;
if (class_entry->type != ZEND_USER_CLASS) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from class table: "
"Found %s where ZEND_USER_CLASS was expected.",
php_parsekit_define_name(class_entry->type, php_parsekit_class_types, PHP_PARSEKIT_CLASSTYPE_UNKNOWN));
return FAILURE;
}
MAKE_STD_ZVAL(class_data);
if (php_parsekit_parse_class_entry(class_data, class_entry TSRMLS_CC) == FAILURE) {
return FAILURE; /* Exit gracefully even though the E_ERROR condition will clean up after us */
}
add_assoc_zval(return_value, class_entry->name, class_data);
if (zend_hash_get_current_key_ex(class_table, &class_name, &class_name_len, &class_index, 0, NULL) == HASH_KEY_IS_STRING) {
/* TODO: dispose of the class properly */
if (zend_hash_del(class_table, class_name, class_name_len) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to remove pollution from class table: Unknown hash_del failure.");
return FAILURE;
}
} else {
/* Absolutely no reason this should ever occur */
zend_hash_index_del(class_table, class_index);
}
}
return SUCCESS;
}
/* }}} */
/* {{{ php_parsekit_common */
static void php_parsekit_common(zval *return_value, int original_num_functions, int original_num_classes, zend_op_array *ops TSRMLS_DC)
{
zval *declared_functions, *declared_classes;
/* main() */
php_parsekit_parse_op_array(return_value, ops TSRMLS_CC);
MAKE_STD_ZVAL(declared_functions);
ZVAL_NULL(declared_functions);
MAKE_STD_ZVAL(declared_classes);
ZVAL_NULL(declared_classes);
if (original_num_functions < zend_hash_num_elements(EG(function_table))) {
/* The compiled code introduced new functions, get them out of there! */
php_parsekit_pop_functions(declared_functions, EG(function_table), original_num_functions TSRMLS_CC);
}
if (original_num_classes < zend_hash_num_elements(EG(class_table))) {
/* The compiled code introduced new classes, get them out of here */
php_parsekit_pop_classes(declared_classes, EG(class_table), original_num_classes TSRMLS_CC);
}
add_assoc_zval(return_value, "function_table", declared_functions);
add_assoc_zval(return_value, "class_table", declared_classes);
}
/* }}} */
/* ****************************************** */
/* Module Housekeeping and Userland Functions */
/* ****************************************** */
/* {{{ proto array parsekit_compile_string(string phpcode)
Return array of opcodes compiled from phpcode */
PHP_FUNCTION(parsekit_compile_string)
{
int original_num_functions = zend_hash_num_elements(EG(function_table));
int original_num_classes = zend_hash_num_elements(EG(class_table));
zend_uchar original_handle_op_arrays;
zend_op_array *ops;
zval *zcode, *zerrors = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &zcode, &zerrors) == FAILURE) {
RETURN_FALSE;
}
if (zerrors) {
zval_dtor(zerrors);
ZVAL_NULL(zerrors);
PARSEKIT_G(compile_errors) = zerrors;
}
convert_to_string(zcode);
original_handle_op_arrays = CG(handle_op_arrays);
CG(handle_op_arrays) = 0;
PARSEKIT_G(in_parsekit_compile) = 1;
ops = compile_string(zcode, "Parsekit Compiler" TSRMLS_CC);
PARSEKIT_G(in_parsekit_compile) = 0;
PARSEKIT_G(compile_errors) = NULL;
CG(handle_op_arrays) = original_handle_op_arrays;
if (ops) {
php_parsekit_common(return_value, original_num_functions, original_num_classes, ops TSRMLS_CC);
destroy_op_array(ops TSRMLS_CC);
efree(ops);
} else {
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto array parsekit_compile_file(string filename)
Return array of opcodes compiled from phpfile */
PHP_FUNCTION(parsekit_compile_file)
{
int original_num_functions = zend_hash_num_elements(EG(function_table));
int original_num_classes = zend_hash_num_elements(EG(class_table));
zend_uchar original_handle_op_arrays;
zend_op_array *ops;
zval *zfilename, *zerrors = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &zfilename, &zerrors) == FAILURE) {
RETURN_FALSE;
}
if (zerrors) {
zval_dtor(zerrors);
ZVAL_NULL(zerrors);
PARSEKIT_G(compile_errors) = zerrors;
}
convert_to_string(zfilename);
original_handle_op_arrays = CG(handle_op_arrays);
CG(handle_op_arrays) = 0;
PARSEKIT_G(in_parsekit_compile) = 1;
ops = compile_filename(ZEND_INCLUDE, zfilename TSRMLS_CC);
PARSEKIT_G(in_parsekit_compile) = 0;
PARSEKIT_G(compile_errors) = NULL;
CG(handle_op_arrays) = original_handle_op_arrays;
if (ops) {
php_parsekit_common(return_value, original_num_functions, original_num_classes, ops TSRMLS_CC);
destroy_op_array(ops TSRMLS_CC);
efree(ops);
} else {
RETURN_FALSE;
}
}
/* }}} */
static
ZEND_BEGIN_ARG_INFO(second_arg_force_ref, 0)
ZEND_ARG_PASS_INFO(0)
ZEND_ARG_PASS_INFO(1)
ZEND_END_ARG_INFO()
/* {{{ function_entry */
function_entry parsekit_functions[] = {
PHP_FE(parsekit_compile_string, second_arg_force_ref)
PHP_FE(parsekit_compile_file, second_arg_force_ref)
{NULL, NULL, NULL}
};
/* }}} */
/* {{{ parsekit_module_entry
*/
zend_module_entry parsekit_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"parsekit",
parsekit_functions,
PHP_MINIT(parsekit),
PHP_MSHUTDOWN(parsekit),
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
PHP_MINFO(parsekit),
#if ZEND_MODULE_API_NO >= 20010901
"0.1", /* Replace with version number for your extension */
#endif
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_PARSEKIT
ZEND_GET_MODULE(parsekit)
#endif
/* {{{ php_parsekit_error_cb
Capture error messages and locations while suppressing otherwise fatal (non-core) errors */
static void php_parsekit_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
{
char *buffer;
int buffer_len;
zval *tmpzval;
TSRMLS_FETCH();
if (!PARSEKIT_G(in_parsekit_compile) || type == E_CORE_ERROR) {
/* Some normal (or massively abnormal) event triggered this error. */
original_error_function(type, (char *)error_filename, error_lineno, format, args);
return;
}
if (!PARSEKIT_G(compile_errors)) {
/* All errors ignored */
return;
}
/* If an error gets triggered in here, revert to normal handling to avoid potential loop */
PARSEKIT_G(in_parsekit_compile) = 0;
MAKE_STD_ZVAL(tmpzval);
array_init(tmpzval);
add_assoc_long(tmpzval, "errno", type);
add_assoc_string(tmpzval, "filename", (char *)error_filename, 1);
add_assoc_long(tmpzval, "lineno", error_lineno);
buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, args);
add_assoc_stringl(tmpzval, "errstr", buffer, buffer_len, 1);
if (Z_TYPE_P(PARSEKIT_G(compile_errors)) == IS_NULL) {
array_init(PARSEKIT_G(compile_errors));
}
add_next_index_zval(PARSEKIT_G(compile_errors), tmpzval);
/* Restore compiler state */
PARSEKIT_G(in_parsekit_compile) = 1;
}
/* }}} */
#define REGISTER_PARSEKIT_CONSTANTS(define_list) \
{ \
char const_name[96]; \
int const_name_len; \
php_parsekit_define_list *defines = (define_list); \
while (defines->str) { \
/* the macros don't like variable constant names */ \
const_name_len = snprintf(const_name, sizeof(const_name), "PARSEKIT_%s", defines->str); \
zend_register_long_constant(const_name, const_name_len+1, defines->val, CONST_CS | CONST_PERSISTENT, module_number TSRMLS_CC); \
defines++; \
} \
}
/* {{{ php_parsekit_init_globals
*/
static void php_parsekit_init_globals(zend_parsekit_globals *parsekit_globals)
{
parsekit_globals->in_parsekit_compile = 0;
parsekit_globals->compile_errors = NULL;
}
/* }}} */
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(parsekit)
{
REGISTER_PARSEKIT_CONSTANTS(php_parsekit_class_types);
REGISTER_PARSEKIT_CONSTANTS(php_parsekit_function_types);
REGISTER_PARSEKIT_CONSTANTS(php_parsekit_nodetype_names);
REGISTER_PARSEKIT_CONSTANTS(php_parsekit_opcode_names);
ZEND_INIT_MODULE_GLOBALS(parsekit, php_parsekit_init_globals, NULL);
/* Changing zend_error_cb isn't threadsafe,
so we'll have to just change it for everybody
and track whether or not we're in parsekit_compile()
on a perthread basis and go from there.
DANGER!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
This could tank if another module does this same hack
before us then unloads. */
original_error_function = zend_error_cb;
zend_error_cb = php_parsekit_error_cb;
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(parsekit)
{
zend_error_cb = original_error_function;
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(parsekit)
{
php_info_print_table_start();
php_info_print_table_header(2, "parsekit support", "enabled");
php_info_print_table_end();
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/

235
php_parsekit.h Normal file
View File

@@ -0,0 +1,235 @@
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2004 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.0 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_0.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#ifndef PHP_PARSEKIT_H
#define PHP_PARSEKIT_H
#ifdef ZTS
#include "TSRM.h"
#endif
extern zend_module_entry parsekit_module_entry;
#define phpext_parsekit_ptr &parsekit_module_entry
PHP_MINIT_FUNCTION(parsekit);
PHP_MSHUTDOWN_FUNCTION(parsekit);
PHP_MINFO_FUNCTION(parsekit);
PHP_FUNCTION(parsekit_compile_string);
PHP_FUNCTION(parsekit_compile_file);
ZEND_BEGIN_MODULE_GLOBALS(parsekit)
int in_parsekit_compile;
zval *compile_errors;
ZEND_END_MODULE_GLOBALS(parsekit)
#ifdef ZTS
#define PARSEKIT_G(v) TSRMG(parsekit_globals_id, zend_parsekit_globals *, v)
#else
#define PARSEKIT_G(v) (parsekit_globals.v)
#endif
typedef struct _php_parsekit_define_list {
long val;
char *str;
} php_parsekit_define_list;
#define PHP_PARSEKIT_UNKNOWN "UNKNOWN"
#define PHP_PARSEKIT_OPCODE_UNKNOWN "Unknown Opcode"
#define PHP_PARSEKIT_NODETYPE_UNKNOWN "Unknown Nodetype"
#define PHP_PARSEKIT_FUNCTYPE_UNKNOWN "Unknown Functiontype"
#define PHP_PARSEKIT_CLASSTYPE_UNKNOWN "Unknown Classtype"
static php_parsekit_define_list php_parsekit_class_types[] = {
{ ZEND_INTERNAL_CLASS, "ZEND_INTERNAL_CLASS" },
{ ZEND_USER_CLASS, "ZEND_USER_CLASS" },
{ 0, NULL }
};
static php_parsekit_define_list php_parsekit_function_types[] = {
{ ZEND_INTERNAL_FUNCTION, "ZEND_INTERNAL_FUNCTION" },
{ ZEND_USER_FUNCTION, "ZEND_USER_FUNCTION" },
{ ZEND_OVERLOADED_FUNCTION, "ZEND_OVERLOADED_FUNCTION" },
{ ZEND_EVAL_CODE, "ZEND_EVAL_CODE" },
{ ZEND_OVERLOADED_FUNCTION_TEMPORARY, "ZEND_OVERLOADED_FUNCTION_TEMPORARY" },
{ 0, NULL }
};
static php_parsekit_define_list php_parsekit_nodetype_names[] = {
{ IS_CONST, "IS_CONST" },
{ IS_TMP_VAR, "IS_TMP_VAR" },
{ IS_VAR, "IS_VAR" },
{ IS_UNUSED, "IS_UNUSED" },
{ 0, NULL }
};
static php_parsekit_define_list php_parsekit_opcode_names[] = {
{ ZEND_NOP, "ZEND_NOP" },
{ ZEND_ADD, "ZEND_ADD" },
{ ZEND_SUB, "ZEND_SUB" },
{ ZEND_MUL, "ZEND_MUL" },
{ ZEND_DIV, "ZEND_DIV" },
{ ZEND_MOD, "ZEND_MOD" },
{ ZEND_SL, "ZEND_SL" },
{ ZEND_SR, "ZEND_SR" },
{ ZEND_CONCAT, "ZEND_CONCAT" },
{ ZEND_BW_OR, "ZEND_BW_OR" },
{ ZEND_BW_AND, "ZEND_BW_AND" },
{ ZEND_BW_XOR, "ZEND_BW_XOR" },
{ ZEND_BW_NOT, "ZEND_BW_NOT" },
{ ZEND_BOOL_NOT, "ZEND_BOOL_NOT" },
{ ZEND_BOOL_XOR, "ZEND_BOOL_XOR" },
{ ZEND_IS_IDENTICAL, "ZEND_IS_IDENTICAL" },
{ ZEND_IS_NOT_IDENTICAL, "ZEND_IS_NOT_IDENTICAL" },
{ ZEND_IS_EQUAL, "ZEND_IS_EQUAL" },
{ ZEND_IS_NOT_EQUAL, "ZEND_IS_NOT_EQUAL" },
{ ZEND_IS_SMALLER, "ZEND_IS_SMALLER" },
{ ZEND_IS_SMALLER_OR_EQUAL, "ZEND_IS_SMALLER_OR_EQUAL" },
{ ZEND_CAST, "ZEND_CAST" },
{ ZEND_QM_ASSIGN, "ZEND_QM_ASSIGN" },
{ ZEND_ASSIGN_ADD, "ZEND_ASSIGN_ADD" },
{ ZEND_ASSIGN_SUB, "ZEND_ASSIGN_SUB" },
{ ZEND_ASSIGN_MUL, "ZEND_ASSIGN_MUL" },
{ ZEND_ASSIGN_DIV, "ZEND_ASSIGN_DIV" },
{ ZEND_ASSIGN_MOD, "ZEND_ASSIGN_MOD" },
{ ZEND_ASSIGN_SL, "ZEND_ASSIGN_SL" },
{ ZEND_ASSIGN_SR, "ZEND_ASSIGN_SR" },
{ ZEND_ASSIGN_CONCAT, "ZEND_ASSIGN_CONCAT" },
{ ZEND_ASSIGN_BW_OR, "ZEND_ASSIGN_BW_OR" },
{ ZEND_ASSIGN_BW_AND, "ZEND_ASSIGN_BW_AND" },
{ ZEND_ASSIGN_BW_XOR, "ZEND_ASSIGN_BW_XOR" },
{ ZEND_PRE_INC, "ZEND_PRE_INC" },
{ ZEND_PRE_DEC, "ZEND_PRE_DEC" },
{ ZEND_POST_INC, "ZEND_POST_INC" },
{ ZEND_POST_DEC, "ZEND_POST_DEC" },
{ ZEND_ASSIGN, "ZEND_ASSIGN" },
{ ZEND_ASSIGN_REF, "ZEND_ASSIGN_REF" },
{ ZEND_ECHO, "ZEND_ECHO" },
{ ZEND_PRINT, "ZEND_PRINT" },
{ ZEND_JMP, "ZEND_JMP" },
{ ZEND_JMPZ, "ZEND_JMPZ" },
{ ZEND_JMPNZ, "ZEND_JMPNZ" },
{ ZEND_JMPZNZ, "ZEND_JMPZNZ" },
{ ZEND_JMPZ_EX, "ZEND_JMPZ_EX" },
{ ZEND_JMPNZ_EX, "ZEND_JMPNZ_EX" },
{ ZEND_CASE, "ZEND_CASE" },
{ ZEND_SWITCH_FREE, "ZEND_SWITCH_FREE" },
{ ZEND_BRK, "ZEND_BRK" },
{ ZEND_CONT, "ZEND_CONT" },
{ ZEND_BOOL, "ZEND_BOOL" },
{ ZEND_INIT_STRING, "ZEND_INIT_STRING" },
{ ZEND_ADD_CHAR, "ZEND_ADD_CHAR" },
{ ZEND_ADD_STRING, "ZEND_ADD_STRING" },
{ ZEND_ADD_VAR, "ZEND_ADD_VAR" },
{ ZEND_BEGIN_SILENCE, "ZEND_BEGIN_SILENCE" },
{ ZEND_END_SILENCE, "ZEND_END_SILENCE" },
{ ZEND_INIT_FCALL_BY_NAME, "ZEND_INIT_FCALL_BY_NAME" },
{ ZEND_DO_FCALL, "ZEND_DO_FCALL" },
{ ZEND_DO_FCALL_BY_NAME, "ZEND_DO_FCALL_BY_NAME" },
{ ZEND_RETURN, "ZEND_RETURN" },
{ ZEND_RECV, "ZEND_RECV" },
{ ZEND_RECV_INIT, "ZEND_RECV_INIT" },
{ ZEND_SEND_VAL, "ZEND_SEND_VAL" },
{ ZEND_SEND_VAR, "ZEND_SEND_VAR" },
{ ZEND_SEND_REF, "ZEND_SEND_REF" },
{ ZEND_NEW, "ZEND_NEW" },
{ ZEND_JMP_NO_CTOR, "ZEND_JMP_NO_CTOR" },
{ ZEND_FREE, "ZEND_FREE" },
{ ZEND_INIT_ARRAY, "ZEND_INIT_ARRAY" },
{ ZEND_ADD_ARRAY_ELEMENT, "ZEND_ADD_ARRAY_ELEMENT" },
{ ZEND_INCLUDE_OR_EVAL, "ZEND_INCLUDE_OR_EVAL" },
{ ZEND_UNSET_VAR, "ZEND_UNSET_VAR" },
{ ZEND_UNSET_DIM_OBJ, "ZEND_UNSET_DIM_OBJ" },
{ ZEND_FE_RESET, "ZEND_FE_RESET" },
{ ZEND_FE_FETCH, "ZEND_FE_FETCH" },
{ ZEND_EXIT, "ZEND_EXIT" },
{ ZEND_FETCH_R, "ZEND_FETCH_R" },
{ ZEND_FETCH_DIM_R, "ZEND_FETCH_DIM_R" },
{ ZEND_FETCH_OBJ_R, "ZEND_FETCH_OBJ_R" },
{ ZEND_FETCH_W, "ZEND_FETCH_W" },
{ ZEND_FETCH_DIM_W, "ZEND_FETCH_DIM_W" },
{ ZEND_FETCH_OBJ_W, "ZEND_FETCH_OBJ_W" },
{ ZEND_FETCH_RW, "ZEND_FETCH_RW" },
{ ZEND_FETCH_DIM_RW, "ZEND_FETCH_DIM_RW" },
{ ZEND_FETCH_OBJ_RW, "ZEND_FETCH_OBJ_RW" },
{ ZEND_FETCH_IS, "ZEND_FETCH_IS" },
{ ZEND_FETCH_DIM_IS, "ZEND_FETCH_DIM_IS" },
{ ZEND_FETCH_OBJ_IS, "ZEND_FETCH_OBJ_IS" },
{ ZEND_FETCH_FUNC_ARG, "ZEND_FETCH_FUNC_ARG" },
{ ZEND_FETCH_DIM_FUNC_ARG, "ZEND_FETCH_DIM_FUNC_ARG" },
{ ZEND_FETCH_OBJ_FUNC_ARG, "ZEND_FETCH_OBJ_FUNC_ARG" },
{ ZEND_FETCH_UNSET, "ZEND_FETCH_UNSET" },
{ ZEND_FETCH_DIM_UNSET, "ZEND_FETCH_DIM_UNSET" },
{ ZEND_FETCH_OBJ_UNSET, "ZEND_FETCH_OBJ_UNSET" },
{ ZEND_FETCH_DIM_TMP_VAR, "ZEND_FETCH_DIM_TMP_VAR" },
{ ZEND_FETCH_CONSTANT, "ZEND_FETCH_CONSTANT" },
{ ZEND_EXT_STMT, "ZEND_EXT_STMT" },
{ ZEND_EXT_FCALL_BEGIN, "ZEND_EXT_FCALL_BEGIN" },
{ ZEND_EXT_FCALL_END, "ZEND_EXT_FCALL_END" },
{ ZEND_EXT_NOP, "ZEND_EXT_NOP" },
{ ZEND_TICKS, "ZEND_TICKS" },
{ ZEND_SEND_VAR_NO_REF, "ZEND_SEND_VAR_NO_REF" },
{ ZEND_CATCH, "ZEND_CATCH" },
{ ZEND_THROW, "ZEND_THROW" },
{ ZEND_FETCH_CLASS, "ZEND_FETCH_CLASS" },
{ ZEND_CLONE, "ZEND_CLONE" },
{ ZEND_INIT_CTOR_CALL, "ZEND_INIT_CTOR_CALL" },
{ ZEND_INIT_METHOD_CALL, "ZEND_INIT_METHOD_CALL" },
{ ZEND_INIT_STATIC_METHOD_CALL, "ZEND_INIT_STATIC_METHOD_CALL" },
{ ZEND_ISSET_ISEMPTY_VAR, "ZEND_ISSET_ISEMPTY_VAR" },
{ ZEND_ISSET_ISEMPTY_DIM_OBJ, "ZEND_ISSET_ISEMPTY_DIM_OBJ" },
{ ZEND_IMPORT_FUNCTION, "ZEND_IMPORT_FUNCTION" },
{ ZEND_IMPORT_CLASS, "ZEND_IMPORT_CLASS" },
{ ZEND_IMPORT_CONST, "ZEND_IMPORT_CONST" },
{ ZEND_PRE_INC_OBJ, "ZEND_PRE_INC_OBJ" },
{ ZEND_PRE_DEC_OBJ, "ZEND_PRE_DEC_OBJ" },
{ ZEND_POST_INC_OBJ, "ZEND_POST_INC_OBJ" },
{ ZEND_POST_DEC_OBJ, "ZEND_POST_DEC_OBJ" },
{ ZEND_ASSIGN_OBJ, "ZEND_ASSIGN_OBJ" },
{ ZEND_OP_DATA, "ZEND_OP_DATA" },
{ ZEND_INSTANCEOF, "ZEND_INSTANCEOF" },
{ ZEND_DECLARE_CLASS, "ZEND_DECLARE_CLASS" },
{ ZEND_DECLARE_INHERITED_CLASS, "ZEND_DECLARE_INHERITED_CLASS" },
{ ZEND_DECLARE_FUNCTION, "ZEND_DECLARE_FUNCTION" },
{ ZEND_RAISE_ABSTRACT_ERROR, "ZEND_RAISE_ABSTRACT_ERROR" },
{ ZEND_ADD_INTERFACE, "ZEND_ADD_INTERFACE" },
{ ZEND_VERIFY_ABSTRACT_CLASS, "ZEND_VERIFY_ABSTRACT_CLASS" },
{ ZEND_ASSIGN_DIM, "ZEND_ASSIGN_DIM" },
{ ZEND_ISSET_ISEMPTY_PROP_OBJ, "ZEND_ISSET_ISEMPTY_PROP_OBJ" },
{ ZEND_HANDLE_EXCEPTION, "ZEND_HANDLE_EXCEPTION" },
{ 0, NULL }
};
#else
#define phpext_parsekit_ptr &parsekit_module_entry
#endif /* PHP_PARSEKIT_H */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/