[ruby/prism] IndexTargetNode should always have ATTRIBUTE_WRITE

Because this is a user-facing change, we also need to deal with the
fact that CRuby 3.3.0 was just released.

In order to support workflows that want to parse exactly as CRuby
parses in a specific version, this PR introduces a new option to
the options struct that is "version". This allows you to specify
that you want "3.3.0" parsing.

I'm not sure if this is the correct solution. Another solution is
to just fork and keep around the old branch for security patches.
Or we could keep around a copy of the source files within this
repository as another directory and only update when necessary.
There are a lot of potential solutions here.

Because this change is so small and the check for it is so minimal,
I've decided to go with this enum. If this ends up entirely
cluttering the codebase with version checks, we'll come up with
another solution. But for now this works, so we're going to go in
this direction for a bit until we determine it's no longer working.

https://github.com/ruby/prism/commit/d8c7e6bd10
This commit is contained in:
Kevin Newton 2024-01-02 11:18:29 -05:00 коммит произвёл git
Родитель 04f64608e8
Коммит 23beceedb7
9 изменённых файлов: 106 добавлений и 13 удалений

Просмотреть файл

@ -314,6 +314,9 @@ module Prism
template << "C"
values << (options.fetch(:verbose, true) ? 0 : 1)
template << "C"
values << { nil => 0, "3.3.0" => 1, "latest" => 0 }.fetch(options[:version])
template << "L"
if (scopes = options[:scopes])
values << scopes.length

Просмотреть файл

@ -22,6 +22,7 @@ ID rb_option_id_encoding;
ID rb_option_id_line;
ID rb_option_id_frozen_string_literal;
ID rb_option_id_verbose;
ID rb_option_id_version;
ID rb_option_id_scopes;
/******************************************************************************/
@ -131,6 +132,14 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, value == Qtrue);
} else if (key_id == rb_option_id_verbose) {
pm_options_suppress_warnings_set(options, value != Qtrue);
} else if (key_id == rb_option_id_version) {
if (!NIL_P(value)) {
const char *version = check_string(value);
if (!pm_options_version_set(options, version, RSTRING_LEN(value))) {
rb_raise(rb_eArgError, "invalid version: %"PRIsVALUE, value);
}
}
} else if (key_id == rb_option_id_scopes) {
if (!NIL_P(value)) build_options_scopes(options, value);
} else {
@ -1013,6 +1022,7 @@ Init_prism(void) {
rb_option_id_line = rb_intern_const("line");
rb_option_id_frozen_string_literal = rb_intern_const("frozen_string_literal");
rb_option_id_verbose = rb_intern_const("verbose");
rb_option_id_version = rb_intern_const("version");
rb_option_id_scopes = rb_intern_const("scopes");
/**

Просмотреть файл

@ -40,6 +40,33 @@ pm_options_suppress_warnings_set(pm_options_t *options, bool suppress_warnings)
options->suppress_warnings = suppress_warnings;
}
/**
* Set the version option on the given options struct by parsing the given
* string. If the string contains an invalid option, this returns false.
* Otherwise, it returns true.
*/
PRISM_EXPORTED_FUNCTION bool
pm_options_version_set(pm_options_t *options, const char *version, size_t length) {
if (version == NULL && length == 0) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
if (length == 5) {
if (strncmp(version, "3.3.0", 5) == 0) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_3_0;
return true;
}
if (strncmp(version, "latest", 6) == 0) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
}
return false;
}
/**
* Allocate and zero out the scopes array on the given options struct.
*/
@ -163,6 +190,7 @@ pm_options_read(pm_options_t *options, const char *data) {
options->frozen_string_literal = *data++;
options->suppress_warnings = *data++;
options->version = (pm_options_version_t) *data++;
uint32_t scopes_count = pm_options_read_u32(data);
data += 4;

Просмотреть файл

@ -24,6 +24,19 @@ typedef struct pm_options_scope {
pm_string_t *locals;
} pm_options_scope_t;
/**
* The version of prism that we should be parsing with. This is used to allow
* consumers to specify which behavior they want in case they need to parse
* exactly as a specific version of CRuby.
*/
typedef enum {
/** The current version of prism. */
PM_OPTIONS_VERSION_LATEST = 0,
/** The vendored version of prism in CRuby 3.3.0. */
PM_OPTIONS_VERSION_CRUBY_3_3_0 = 1
} pm_options_version_t;
/**
* The options that can be passed to the parser.
*/
@ -55,6 +68,13 @@ typedef struct {
*/
pm_options_scope_t *scopes;
/**
* The version of prism that we should be parsing with. This is used to
* allow consumers to specify which behavior they want in case they need to
* parse exactly as a specific version of CRuby.
*/
pm_options_version_t version;
/** Whether or not the frozen string literal option has been set. */
bool frozen_string_literal;
@ -106,6 +126,18 @@ PRISM_EXPORTED_FUNCTION void pm_options_frozen_string_literal_set(pm_options_t *
*/
PRISM_EXPORTED_FUNCTION void pm_options_suppress_warnings_set(pm_options_t *options, bool suppress_warnings);
/**
* Set the version option on the given options struct by parsing the given
* string. If the string contains an invalid option, this returns false.
* Otherwise, it returns true.
*
* @param options The options struct to set the version on.
* @param version The version to set.
* @param length The length of the version string.
* @return Whether or not the version was parsed successfully.
*/
PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length);
/**
* Allocate and zero out the scopes array on the given options struct.
*
@ -167,9 +199,17 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
* | ... | the encoding bytes |
* | `1` | frozen string literal |
* | `1` | suppress warnings |
* | `1` | the version |
* | `4` | the number of scopes |
* | ... | the scopes |
*
* The version field is an enum, so it should be one of the following values:
*
* | value | version |
* | ----- | ------------------------- |
* | `0` | use the latest version of prism |
* | `1` | use the version of prism that is vendored in CRuby 3.3.0 |
*
* Each scope is layed out as follows:
*
* | # bytes | field |

Просмотреть файл

@ -9,6 +9,7 @@
#include "prism/ast.h"
#include "prism/defines.h"
#include "prism/encoding.h"
#include "prism/options.h"
#include "prism/util/pm_constant_pool.h"
#include "prism/util/pm_list.h"
#include "prism/util/pm_newline_list.h"
@ -662,6 +663,12 @@ struct pm_parser {
*/
const pm_encoding_t *explicit_encoding;
/** The current parameter name id on parsing its default value. */
pm_constant_id_t current_param_name;
/** The version of prism that we should use to parse. */
pm_options_version_t version;
/** Whether or not we're at the beginning of a command. */
bool command_start;
@ -684,9 +691,6 @@ struct pm_parser {
/** This flag indicates that we are currently parsing a keyword argument. */
bool in_keyword_arg;
/** The current parameter name id on parsing its default value. */
pm_constant_id_t current_param_name;
/**
* Whether or not the parser has seen a token that has semantic meaning
* (i.e., a token that is not a comment or whitespace).

Просмотреть файл

@ -2172,11 +2172,16 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) {
static pm_index_target_node_t *
pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) {
pm_index_target_node_t *node = PM_ALLOC_NODE(parser, pm_index_target_node_t);
pm_node_flags_t flags = target->base.flags;
if (parser->version != PM_OPTIONS_VERSION_CRUBY_3_3_0) {
flags |= PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE;
}
*node = (pm_index_target_node_t) {
{
.type = PM_INDEX_TARGET_NODE,
.flags = target->base.flags,
.flags = flags,
.location = target->base.location
},
.receiver = target->receiver,
@ -17320,6 +17325,9 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
parser->suppress_warnings = true;
}
// version option
parser->version = options->version;
// scopes option
for (size_t scope_index = 0; scope_index < options->scopes_count; scope_index++) {
const pm_options_scope_t *scope = pm_options_scope_get(options, scope_index);

Просмотреть файл

@ -430,7 +430,7 @@
├── @ MultiWriteNode (location: (41,0)-(41,21))
│ ├── lefts: (length: 2)
│ │ ├── @ IndexTargetNode (location: (41,0)-(41,6))
│ │ │ ├── flags:
│ │ │ ├── flags: attribute_write
│ │ │ ├── receiver:
│ │ │ │ @ CallNode (location: (41,0)-(41,3))
│ │ │ │ ├── flags: variable_call
@ -452,7 +452,7 @@
│ │ │ ├── closing_loc: (41,5)-(41,6) = "]"
│ │ │ └── block: ∅
│ │ └── @ IndexTargetNode (location: (41,8)-(41,14))
│ │ ├── flags:
│ │ ├── flags: attribute_write
│ │ ├── receiver:
│ │ │ @ CallNode (location: (41,8)-(41,11))
│ │ │ ├── flags: variable_call
@ -2166,7 +2166,7 @@
│ │ │ ├── operator_loc: (140,17)-(140,19) = "=>"
│ │ │ ├── reference:
│ │ │ │ @ IndexTargetNode (location: (140,20)-(140,24))
│ │ │ │ ├── flags:
│ │ │ │ ├── flags: attribute_write
│ │ │ │ ├── receiver:
│ │ │ │ │ @ CallNode (location: (140,20)-(140,21))
│ │ │ │ │ ├── flags: variable_call
@ -2229,7 +2229,7 @@
│ │ ├── operator_loc: (142,17)-(142,19) = "=>"
│ │ ├── reference:
│ │ │ @ IndexTargetNode (location: (142,20)-(142,27))
│ │ │ ├── flags:
│ │ │ ├── flags: attribute_write
│ │ │ ├── receiver:
│ │ │ │ @ CallNode (location: (142,20)-(142,21))
│ │ │ │ ├── flags: variable_call

Просмотреть файл

@ -319,7 +319,7 @@
├── @ MultiWriteNode (location: (15,0)-(15,24))
│ ├── lefts: (length: 2)
│ │ ├── @ IndexTargetNode (location: (15,1)-(15,8))
│ │ │ ├── flags:
│ │ │ ├── flags: attribute_write
│ │ │ ├── receiver:
│ │ │ │ @ LocalVariableReadNode (location: (15,1)-(15,2))
│ │ │ │ ├── name: :a
@ -338,7 +338,7 @@
│ │ │ ├── closing_loc: (15,7)-(15,8) = "]"
│ │ │ └── block: ∅
│ │ └── @ IndexTargetNode (location: (15,10)-(15,14))
│ │ ├── flags:
│ │ ├── flags: attribute_write
│ │ ├── receiver:
│ │ │ @ LocalVariableReadNode (location: (15,10)-(15,11))
│ │ │ ├── name: :a
@ -370,7 +370,7 @@
├── @ MultiWriteNode (location: (16,0)-(16,21))
│ ├── lefts: (length: 2)
│ │ ├── @ IndexTargetNode (location: (16,1)-(16,5))
│ │ │ ├── flags:
│ │ │ ├── flags: attribute_write
│ │ │ ├── receiver:
│ │ │ │ @ LocalVariableReadNode (location: (16,1)-(16,2))
│ │ │ │ ├── name: :a
@ -385,7 +385,7 @@
│ │ │ ├── closing_loc: (16,4)-(16,5) = "]"
│ │ │ └── block: ∅
│ │ └── @ IndexTargetNode (location: (16,7)-(16,11))
│ │ ├── flags:
│ │ ├── flags: attribute_write
│ │ ├── receiver:
│ │ │ @ LocalVariableReadNode (location: (16,7)-(16,8))
│ │ │ ├── name: :a

Просмотреть файл

@ -34,7 +34,7 @@
│ │ │ ├── name: :a=
│ │ │ └── message_loc: (3,5)-(3,6) = "a"
│ │ └── @ IndexTargetNode (location: (3,8)-(3,18))
│ │ ├── flags:
│ │ ├── flags: attribute_write
│ │ ├── receiver:
│ │ │ @ SelfNode (location: (3,8)-(3,12))
│ │ ├── opening_loc: (3,12)-(3,13) = "["