From 8c9a453f2d8ac78943df98c61cdbb9e4a4ad0eda Mon Sep 17 00:00:00 2001 From: yugui Date: Thu, 14 Jun 2012 02:22:08 +0000 Subject: [PATCH] Embedding CRuby interpreter without internal headers has been difficult for few years because: * NODE is no longer accessible. * rb_iseq_eval_main crashes without preparing with rb_thread_t. * some existing APIs calls exit(3) without giving the opportunity to finalize or handle errors to the client. * No general-purpose function to compile a source to an iseq are published in the public headers. This commit solves the problems. * include/ruby/ruby.h: Grouped APIs for embedding CRuby interpreter. (ruby_setup, ruby_compile_main_from_file, ruby_compile_main_from_string, ruby_eval_main, ruby_set_script_name): new APIs to embed CRuby. (ruby_opaque_t) Opaque pointer to an internal data, to NODE or iseq in particular. * eval.c (ruby_setup): Similar to ruby_init but returns an error code instead of exit(3) on error. (ruby_eval_main): Similar to ruby_exec_node but returns the evaluation result. (ruby_eval_main_internal): renamed from ruby_exec_internal. * ruby.c (toplevel_context): new helper function. (PREPARE_EVAL_MAIN): moved. (process_options): refactored with new functions. (parse_and_compile_main) new helper funciton. (ruby_compile_main_from_file, ruby_compile_main_from_string) new API (ruby_set_script_name): new API. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36079 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 23 ++++++ eval.c | 93 ++++++++++++++++-------- include/ruby/intern.h | 17 ----- include/ruby/ruby.h | 106 +++++++++++++++++++++------ ruby.c | 164 +++++++++++++++++++++++++++++++++++------- 5 files changed, 306 insertions(+), 97 deletions(-) diff --git a/ChangeLog b/ChangeLog index 30c7c4ddd681d2..6c00c9207061f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +Thu Jun 14 10:44:41 2012 Yuki Yugui Sonoda + + * include/ruby/ruby.h: Grouped APIs for embedding CRuby interpreter. + (ruby_setup, ruby_compile_main_from_file, + ruby_compile_main_from_string, ruby_eval_main, + ruby_set_script_name): new APIs to embed CRuby. + (ruby_opaque_t) Opaque pointer to an internal data, to NODE or iseq + in particular. + + * eval.c (ruby_setup): Similar to ruby_init but returns an error code + instead of exit(3) on error. + (ruby_eval_main): Similar to ruby_exec_node but returns the + evaluation result. + (ruby_eval_main_internal): renamed from ruby_exec_internal. + + * ruby.c (toplevel_context): new helper function. + (PREPARE_EVAL_MAIN): moved. + (process_options): refactored with new functions. + (parse_and_compile_main) new helper funciton. + (ruby_compile_main_from_file, ruby_compile_main_from_string) new API + (ruby_set_script_name): new API. + + Thu Jun 14 10:39:48 2012 Yuki Yugui Sonoda * eval.c: Add doxygen comments. diff --git a/eval.c b/eval.c index a706c23dabb5b8..499edfbbbf82b9 100644 --- a/eval.c +++ b/eval.c @@ -31,16 +31,18 @@ VALUE rb_eSysStackError; #include "eval_error.c" #include "eval_jump.c" -/* initialize ruby */ - -void -ruby_init(void) +/* Initializes the Ruby VM and builtin libraries. + * @retval 0 if succeeded. + * @retval non-zero an error occured. + */ +int +ruby_setup(void) { static int initialized = 0; int state; if (initialized) - return; + return 0; initialized = 1; ruby_init_stack((void *)&state); @@ -54,11 +56,22 @@ ruby_init(void) } POP_TAG(); + if (!state) GET_VM()->running = 1; + return state; +} + +/* Calls ruby_setup() and check error. + * + * Prints errors and calls exit(3) if an error occured. + */ +void +ruby_init(void) +{ + int state = ruby_setup(); if (state) { error_print(); exit(EXIT_FAILURE); } - GET_VM()->running = 1; } /*! Processes command line arguments and compiles the Ruby source to execute. @@ -71,7 +84,7 @@ ruby_init(void) * @return an opaque pointer to the compiled source or an internal special value. * @sa ruby_executable_node(). */ -void * +ruby_opaque_t ruby_options(int argc, char **argv) { int state; @@ -217,26 +230,6 @@ ruby_cleanup(volatile int ex) return ex; } -static int -ruby_exec_internal(void *n) -{ - volatile int state; - VALUE iseq = (VALUE)n; - rb_thread_t *th = GET_THREAD(); - - if (!n) return 0; - - PUSH_TAG(); - if ((state = EXEC_TAG()) == 0) { - SAVE_ROOT_JMPBUF(th, { - th->base_block = 0; - rb_iseq_eval_main(iseq); - }); - } - POP_TAG(); - return state; -} - /*! Calls ruby_cleanup() and exits the process */ void ruby_stop(int ex) @@ -257,7 +250,7 @@ ruby_stop(int ex) * @retval 0 if the given value is such a special value. */ int -ruby_executable_node(void *n, int *status) +ruby_executable_node(ruby_opaque_t n, int *status) { VALUE v = (VALUE)n; int s; @@ -273,12 +266,33 @@ ruby_executable_node(void *n, int *status) return FALSE; } +static int +ruby_eval_main_internal(VALUE iseqval, VALUE* result) +{ + volatile int state; + volatile VALUE retval; + rb_thread_t *th = GET_THREAD(); + + if (!iseqval) return 0; + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, { + th->base_block = 0; + retval = rb_iseq_eval_main(iseqval); + }); + } + POP_TAG(); + *result = state ? th->errinfo : retval; + return state; +} + /*! Runs the given compiled source and exits this process. * @retval 0 if successfully run thhe source * @retval non-zero if an error occurred. */ int -ruby_run_node(void *n) +ruby_run_node(ruby_opaque_t n) { int status; if (!ruby_executable_node(n, &status)) { @@ -290,10 +304,27 @@ ruby_run_node(void *n) /*! Runs the given compiled source */ int -ruby_exec_node(void *n) +ruby_exec_node(ruby_opaque_t n) { + VALUE dummy; ruby_init_stack((void *)&n); - return ruby_exec_internal(n); + return ruby_eval_main_internal((VALUE)n, &dummy); +} + + +/*! + * Evaluates the given iseq in the main (toplevel) context. + * + * @param iseqval a VALUE that wraps an iseq. + * @param result Stores the evaluated value if succeeded, + * or an exception if failed. + * @retval 0 if succeeded + * @retval non-zero if failed. + */ +int +ruby_eval_main(ruby_opaque_t n, VALUE *result) +{ + return !!ruby_eval_main_internal((VALUE)n, result); } /* diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 54b16961c562f7..308a1a94db40d6 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -386,9 +386,6 @@ VALUE rb_protect(VALUE (*)(VALUE), VALUE, int*); void rb_set_end_proc(void (*)(VALUE), VALUE); void rb_mark_end_proc(void); void rb_exec_end_proc(void); -void ruby_finalize(void); -NORETURN(void ruby_stop(int)); -int ruby_cleanup(volatile int); DEPRECATED(void rb_gc_mark_threads(void)); void rb_thread_schedule(void); void rb_thread_wait_fd(int); @@ -432,10 +429,7 @@ VALUE rb_file_directory_p(VALUE,VALUE); VALUE rb_str_encode_ospath(VALUE); int rb_is_absolute_path(const char *); /* gc.c */ -void ruby_set_stack_size(size_t); NORETURN(void rb_memerror(void)); -int ruby_stack_check(void); -size_t ruby_stack_length(VALUE**); int rb_during_gc(void); void rb_gc_mark_locations(VALUE*, VALUE*); void rb_mark_tbl(struct st_table*); @@ -451,7 +445,6 @@ void rb_gc_call_finalizer_at_exit(void); VALUE rb_gc_enable(void); VALUE rb_gc_disable(void); VALUE rb_gc_start(void); -#define Init_stack(addr) ruby_init_stack(addr) void rb_gc_set_params(void); /* hash.c */ void st_foreach_safe(struct st_table *, int (*)(ANYARGS), st_data_t); @@ -662,12 +655,6 @@ int rb_reg_options(VALUE); RUBY_EXTERN VALUE rb_argv0; VALUE rb_get_argv(void); void *rb_load_file(const char*); -void ruby_script(const char*); -void ruby_prog_init(void); -void ruby_set_argv(int, char**); -void *ruby_process_options(int, char**); -void ruby_init_loadpath(void); -void ruby_incpush(const char*); /* signal.c */ VALUE rb_f_kill(int, VALUE*); void rb_gc_mark_trap_list(void); @@ -675,7 +662,6 @@ void rb_gc_mark_trap_list(void); #define posix_signal ruby_posix_signal RETSIGTYPE (*posix_signal(int, RETSIGTYPE (*)(int)))(int); #endif -void ruby_sig_finalize(void); void rb_trap_exit(void); void rb_trap_exec(void); const char *ruby_signal_name(int); @@ -929,9 +915,6 @@ VALUE rb_cv_get(VALUE, const char*); void rb_define_class_variable(VALUE, const char*, VALUE); VALUE rb_mod_class_variables(VALUE); VALUE rb_mod_remove_cvar(VALUE, VALUE); -/* version.c */ -void ruby_show_version(void); -void ruby_show_copyright(void); ID rb_frame_callee(void); VALUE rb_str_succ(VALUE); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 06c2a7fdd44338..39996483d93fc5 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1229,21 +1229,6 @@ NORETURN(void rb_throw_obj(VALUE,VALUE)); VALUE rb_require(const char*); -#ifdef __ia64 -void ruby_init_stack(volatile VALUE*, void*); -#define ruby_init_stack(addr) ruby_init_stack((addr), rb_ia64_bsp()) -#else -void ruby_init_stack(volatile VALUE*); -#endif -#define RUBY_INIT_STACK \ - VALUE variable_in_this_stack_frame; \ - ruby_init_stack(&variable_in_this_stack_frame); -void ruby_init(void); -void *ruby_options(int, char**); -int ruby_run_node(void *); -int ruby_exec_node(void *); -int ruby_executable_node(void *n, int *status); - RUBY_EXTERN VALUE rb_mKernel; RUBY_EXTERN VALUE rb_mComparable; RUBY_EXTERN VALUE rb_mEnumerable; @@ -1400,14 +1385,6 @@ rb_special_const_p(VALUE obj) static char *dln_libs_to_be_linked[] = { EXTLIB, 0 }; #endif -#if (defined(__APPLE__) || defined(__NeXT__)) && defined(__MACH__) -#define RUBY_GLOBAL_SETUP /* use linker option to link startup code with ObjC support */ -#else -#define RUBY_GLOBAL_SETUP -#endif - -void ruby_sysinit(int *, char ***); - #define RUBY_VM 1 /* YARV */ #define HAVE_NATIVETHREAD int ruby_native_thread_p(void); @@ -1495,6 +1472,89 @@ int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap); #include "ruby/subst.h" #endif +/** + * @defgroup embed CRuby Embedding APIs + * CRuby interpreter APIs. These are APIs to embed MRI interpreter into your + * program. + * These functions are not a part of Ruby extention library API. + * Extension libraries of Ruby should not depend on these functions. + * @{ + */ + +/*! Opaque pointer to an inner data structure. + * + * You do not have to know what the actual data type this pointer points. + * It often changes for internal improvements. + */ +typedef void *ruby_opaque_t; + +/*! @deprecated You no longer need to use this macro. */ +#if (defined(__APPLE__) || defined(__NeXT__)) && defined(__MACH__) +#define RUBY_GLOBAL_SETUP /* use linker option to link startup code with ObjC support */ +#else +#define RUBY_GLOBAL_SETUP +#endif + +/** @defgroup ruby1 ruby(1) implementation + * A part of the implementation of ruby(1) command. + * Other programs that embed Ruby interpreter do not always need to use these + * functions. + * @{ + */ + +void ruby_sysinit(int *argc, char ***argv); +void ruby_init(void); +ruby_opaque_t ruby_options(int argc, char** argv); +int ruby_executable_node(ruby_opaque_t n, int *status); +int ruby_run_node(ruby_opaque_t n); + +/* version.c */ +void ruby_show_version(void); +void ruby_show_copyright(void); + + +/*! A convenience macro to call ruby_init_stack(). Must be placed just after + * variable declarations */ +#define RUBY_INIT_STACK \ + VALUE variable_in_this_stack_frame; \ + ruby_init_stack(&variable_in_this_stack_frame); +/*! @} */ + +#ifdef __ia64 +void ruby_init_stack(volatile VALUE*, void*); +#define ruby_init_stack(addr) ruby_init_stack((addr), rb_ia64_bsp()) +#else +void ruby_init_stack(volatile VALUE*); +#endif +#define Init_stack(addr) ruby_init_stack(addr) + +int ruby_setup(void); +int ruby_cleanup(volatile int); + +void ruby_finalize(void); +NORETURN(void ruby_stop(int)); + +void ruby_set_stack_size(size_t); +int ruby_stack_check(void); +size_t ruby_stack_length(VALUE**); + +ruby_opaque_t ruby_compile_main_from_file(VALUE fname, const char* path, VALUE* error); +ruby_opaque_t ruby_compile_main_from_string(VALUE fname, VALUE string, VALUE* error); +int ruby_exec_node(ruby_opaque_t n); +int ruby_eval_main(ruby_opaque_t n, VALUE *result); + +void ruby_script(const char* name); +void ruby_set_script_name(VALUE name); + +void ruby_prog_init(void); +void ruby_set_argv(int, char**); +void *ruby_process_options(int, char**); +void ruby_init_loadpath(void); +void ruby_incpush(const char*); +void ruby_sig_finalize(void); + +/*! @} */ + #if defined(__cplusplus) #if 0 { /* satisfy cc-mode */ diff --git a/ruby.c b/ruby.c index beaf19861249dc..72fddc4e2219e3 100644 --- a/ruby.c +++ b/ruby.c @@ -496,6 +496,26 @@ require_libraries(VALUE *req_list) th->base_block = prev_base_block; } +static rb_env_t* +toplevel_context(void) +{ + rb_env_t *env; + VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")); + rb_binding_t *bind; + + GetBindingPtr(toplevel_binding, bind); + GetEnvPtr(bind->env, env); + return env; +} + +#define PREPARE_PARSE_MAIN(th, env, expr) do { \ + (th)->parse_in_eval--; \ + (th)->base_block = &(env)->block; \ + expr; \ + (th)->parse_in_eval++; \ + (th)->base_block = 0; \ +} while (0) + static void process_sflag(int *sflag) { @@ -1365,22 +1385,7 @@ process_options(int argc, char **argv, struct cmdline_options *opt) ruby_set_argv(argc, argv); process_sflag(&opt->sflag); - { - /* set eval context */ - VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")); - rb_binding_t *bind; - - GetBindingPtr(toplevel_binding, bind); - GetEnvPtr(bind->env, env); - } - -#define PREPARE_PARSE_MAIN(expr) do { \ - th->parse_in_eval--; \ - th->base_block = &env->block; \ - expr; \ - th->parse_in_eval++; \ - th->base_block = 0; \ -} while (0) + env = toplevel_context(); if (opt->e_script) { VALUE progname = rb_progname; @@ -1392,11 +1397,11 @@ process_options(int argc, char **argv, struct cmdline_options *opt) eenc = lenc; } rb_enc_associate(opt->e_script, eenc); - rb_vm_set_progname(rb_progname = opt->script_name); + ruby_set_script_name(opt->script_name); require_libraries(&opt->req_list); - rb_vm_set_progname(rb_progname = progname); + ruby_set_script_name(progname); - PREPARE_PARSE_MAIN({ + PREPARE_PARSE_MAIN(th, env, { tree = rb_parser_compile_string(parser, opt->script, opt->e_script, 1); }); } @@ -1405,12 +1410,11 @@ process_options(int argc, char **argv, struct cmdline_options *opt) forbid_setid("program input from stdin"); } - PREPARE_PARSE_MAIN({ + PREPARE_PARSE_MAIN(th, env, { tree = load_file(parser, opt->script_name, 1, opt); }); } - rb_progname = opt->script_name; - rb_vm_set_progname(rb_progname); + ruby_set_script_name(opt->script_name); if (opt->dump & DUMP_BIT(yydebug)) return Qtrue; if (opt->ext.enc.index >= 0) { @@ -1446,12 +1450,12 @@ process_options(int argc, char **argv, struct cmdline_options *opt) } if (opt->do_print) { - PREPARE_PARSE_MAIN({ + PREPARE_PARSE_MAIN(th, env, { tree = rb_parser_append_print(parser, tree); }); } if (opt->do_loop) { - PREPARE_PARSE_MAIN({ + PREPARE_PARSE_MAIN(th, env, { tree = rb_parser_while_loop(parser, tree, opt->do_line, opt->do_split); }); rb_define_global_function("sub", rb_f_sub, -1); @@ -1466,7 +1470,7 @@ process_options(int argc, char **argv, struct cmdline_options *opt) return Qtrue; } - PREPARE_PARSE_MAIN({ + PREPARE_PARSE_MAIN(th, env, { VALUE path = Qnil; if (!opt->e_script && strcmp(opt->script, "-")) path = rb_realpath_internal(Qnil, opt->script_name, 1); @@ -1622,7 +1626,7 @@ load_file_internal(VALUE arg) if (f != rb_stdin) rb_io_close(f); f = Qnil; } - rb_vm_set_progname(rb_progname = opt->script_name); + ruby_set_script_name(opt->script_name); require_libraries(&opt->req_list); /* Why here? unnatural */ } if (opt->src.enc.index >= 0) { @@ -1689,6 +1693,103 @@ rb_load_file(const char *fname) return load_file(rb_parser_new(), fname_v, 0, cmdline_options_init(&opt)); } +struct ruby_compile_main_arg { + int is_string; + union { + VALUE path; + VALUE string; + } source; +}; + +static ruby_opaque_t +parse_and_compile_main(VALUE fname, const struct ruby_compile_main_arg* arg, VALUE* error) +{ + rb_env_t *const env = toplevel_context(); + rb_thread_t *const th = GET_THREAD(); + NODE* tree; + VALUE iseq; + VALUE path; + int state; + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + PREPARE_PARSE_MAIN(th, env, { + VALUE parser = rb_parser_new(); + th->mild_compile_error++; + if (arg->is_string) { + FilePathValue(fname); + path = fname; + tree = rb_parser_compile_string(parser, RSTRING_PTR(fname), arg->source.string, 1); + } + else { + struct cmdline_options opt; + path = arg->source.path; + tree = load_file(parser, path, 0, cmdline_options_init(&opt)); + } + th->mild_compile_error--; + }); + if (!tree) rb_exc_raise(th->errinfo); + + ruby_set_script_name(fname); + + PREPARE_PARSE_MAIN(th, env, { + iseq = rb_iseq_new_main(tree, fname, path); + }); + } + POP_TAG(); + if (state) { + *error = th->errinfo; + return NULL; + } else { + *error = Qnil; + return iseq; + } +} + +/** + * Compiles a main Ruby script file into the internal a data structure. + * + * This function: + * @li loads the file specified by path. + * @li parses the source and compiles it + * + * @param fname $0 is set to this value. + * If nil, + * uses the given path instead. + * @param path path to the source + * @param error where to store the exception if an error occured. + * @return The compiled source code. Or NULL if an error occured. + */ +ruby_opaque_t +ruby_compile_main_from_file(VALUE fname, const char* path, VALUE* error) +{ + struct ruby_compile_main_arg arg; + arg.is_string = FALSE; + arg.source.path = rb_str_new_cstr(path); + + if (NIL_P(fname)) fname = arg.source.path; + return parse_and_compile_main(fname, &arg, error); +} + +/** + * Compiles a main Ruby script in a string into the internal a data structure. + * + * This function parses the given source and compiles it + * + * @param fname $0 is set to this value. + * @param source Ruby source string + * @param error where to store the exception if an error occured. + * @return The compiled source code. Or NULL if an error occured. + */ +ruby_opaque_t +ruby_compile_main_from_string(VALUE fname, VALUE source, VALUE* error) +{ + struct ruby_compile_main_arg arg; + arg.is_string = TRUE; + arg.source.string = source; + return parse_and_compile_main(fname, &arg, error); +} + static void set_arg0(VALUE val, ID id) { @@ -1720,6 +1821,17 @@ ruby_script(const char *name) } } +/*! Sets the current script name to this value. + * + * Same as ruby_script() but accepts a VALUE. + */ +void +ruby_set_script_name(VALUE name) +{ + rb_progname = rb_str_dup(name); + rb_vm_set_progname(rb_progname); +} + static void init_ids(struct cmdline_options *opt) {