From f8355e88d60a02c215551ddfc0952dc6beb3bdf9 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 28 Feb 2024 12:12:45 -0500 Subject: [PATCH] [PRISM] Do not load -r until we check if main script can be read --- iseq.c | 4 ++-- load.c | 2 +- prism_compile.c | 37 ++++++++++++++++++++++++++++++------- prism_compile.h | 2 ++ ruby.c | 43 ++++++++++++++++++++++++++++++++++--------- 5 files changed, 69 insertions(+), 19 deletions(-) diff --git a/iseq.c b/iseq.c index a63516c02b..9e9f609323 100644 --- a/iseq.c +++ b/iseq.c @@ -1245,7 +1245,7 @@ pm_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V VALUE error; if (RB_TYPE_P(src, T_FILE)) { VALUE filepath = rb_io_path(src); - error = pm_parse_file(&result, filepath); + error = pm_load_parse_file(&result, filepath); RB_GC_GUARD(filepath); } else { @@ -1646,7 +1646,7 @@ iseqw_s_compile_file_prism(int argc, VALUE *argv, VALUE self) pm_parse_result_t result = { 0 }; result.options.line = 1; - VALUE error = pm_parse_file(&result, file); + VALUE error = pm_load_parse_file(&result, file); if (error == Qnil) { make_compile_option(&option, opt); diff --git a/load.c b/load.c index d868c06cf4..8963a2e751 100644 --- a/load.c +++ b/load.c @@ -747,7 +747,7 @@ load_iseq_eval(rb_execution_context_t *ec, VALUE fname) pm_parse_result_t result = { 0 }; result.options.line = 1; - VALUE error = pm_parse_file(&result, fname); + VALUE error = pm_load_parse_file(&result, fname); if (error == Qnil) { iseq = pm_iseq_new_top(&result.node, rb_fstring_lit(""), fname, realpath_internal_cached(realpath_map, fname), NULL); diff --git a/prism_compile.c b/prism_compile.c index 06dd2a1f0e..d7bf4845bf 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -8252,15 +8252,11 @@ pm_parse_file_script_lines(const pm_parser_t *parser) } /** - * Parse the given filepath and store the resulting scope node in the given - * parse result struct. It returns a Ruby error if the file cannot be read or - * if it cannot be parsed properly. It is assumed that the parse result object - * is zeroed out. - * - * TODO: This should raise a better error when the file cannot be read. + * Attempt to load the file into memory. Return a Ruby error if the file cannot + * be read. */ VALUE -pm_parse_file(pm_parse_result_t *result, VALUE filepath) +pm_load_file(pm_parse_result_t *result, VALUE filepath) { if (!pm_string_mapped_init(&result->input, RSTRING_PTR(filepath))) { #ifdef _WIN32 @@ -8274,6 +8270,18 @@ pm_parse_file(pm_parse_result_t *result, VALUE filepath) return err; } + return Qnil; +} + +/** + * Parse the given filepath and store the resulting scope node in the given + * parse result struct. It returns a Ruby error if the file cannot be read or + * if it cannot be parsed properly. It is assumed that the parse result object + * is zeroed out. + */ +VALUE +pm_parse_file(pm_parse_result_t *result, VALUE filepath) +{ VALUE error = pm_parse_input(result, filepath); // If we're parsing a filepath, then we need to potentially support the @@ -8292,6 +8300,21 @@ pm_parse_file(pm_parse_result_t *result, VALUE filepath) return error; } +/** + * Load and then parse the given filepath. It returns a Ruby error if the file + * cannot be read or if it cannot be parsed properly. + */ +VALUE +pm_load_parse_file(pm_parse_result_t *result, VALUE filepath) +{ + VALUE error = pm_load_file(result, filepath); + if (NIL_P(error)) { + error = pm_parse_file(result, filepath); + } + + return error; +} + /** * Parse the given source that corresponds to the given filepath and store the * resulting scope node in the given parse result struct. This function could diff --git a/prism_compile.h b/prism_compile.h index 2080db7739..67495e61b3 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -44,7 +44,9 @@ typedef struct { bool parsed; } pm_parse_result_t; +VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath); +VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath); void pm_parse_result_free(pm_parse_result_t *result); diff --git a/ruby.c b/ruby.c index fde179ef7c..a67ad82ead 100644 --- a/ruby.c +++ b/ruby.c @@ -2080,11 +2080,15 @@ process_script(ruby_cmdline_options_t *opt) return ast; } +/** + * Call ruby_opt_init to set up the global state based on the command line + * options, and then warn if prism is enabled and the experimental warning + * category is enabled. + */ static void -prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) +prism_opt_init(ruby_cmdline_options_t *opt) { ruby_opt_init(opt); - memset(result, 0, sizeof(pm_parse_result_t)); if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) { rb_category_warn( @@ -2095,8 +2099,18 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) "issue tracker." ); } +} +/** + * Process the command line options and parse the script into the given result. + * Raise an error if the script cannot be parsed. + */ +static void +prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) +{ + memset(result, 0, sizeof(pm_parse_result_t)); pm_options_t *options = &result->options; + pm_options_line_set(options, 1); pm_options_command_line_p_set(options, opt->do_print); pm_options_command_line_n_set(options, opt->do_loop); @@ -2108,21 +2122,32 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) rb_raise(rb_eRuntimeError, "Prism support for streaming code from stdin is not currently supported"); } else if (opt->e_script) { + prism_opt_init(opt); error = pm_parse_string(result, opt->e_script, rb_str_new2("-e")); } else { - error = pm_parse_file(result, opt->script_name); + error = pm_load_file(result, opt->script_name); - // If we found an __END__ marker, then we're going to define a - // global DATA constant that is a file object that can be read - // to read the contents after the marker. + // If reading the file did not error, at that point we load the command + // line options. We do it in this order so that if the main script fails + // to load, it doesn't require files required by -r. + if (NIL_P(error)) { + prism_opt_init(opt); + error = pm_parse_file(result, opt->script_name); + } + + // If we found an __END__ marker, then we're going to define a global + // DATA constant that is a file object that can be read to read the + // contents after the marker. if (NIL_P(error) && result->parser.data_loc.start != NULL) { int xflag = opt->xflag; VALUE file = open_load_file(opt->script_name, &xflag); - size_t offset = result->parser.data_loc.start - result->parser.start + 7; - if ((result->parser.start + offset < result->parser.end) && result->parser.start[offset] == '\r') offset++; - if ((result->parser.start + offset < result->parser.end) && result->parser.start[offset] == '\n') offset++; + const pm_parser_t *parser = &result->parser; + size_t offset = parser->data_loc.start - parser->start + 7; + + if ((parser->start + offset < parser->end) && parser->start[offset] == '\r') offset++; + if ((parser->start + offset < parser->end) && parser->start[offset] == '\n') offset++; rb_funcall(file, rb_intern_const("seek"), 2, SIZET2NUM(offset), INT2FIX(SEEK_SET)); rb_define_global_const("DATA", file);