diff options
Diffstat (limited to 'prism/options.c')
| -rw-r--r-- | prism/options.c | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/prism/options.c b/prism/options.c new file mode 100644 index 0000000000..b589865a2a --- /dev/null +++ b/prism/options.c @@ -0,0 +1,420 @@ +#include "prism/internal/options.h" + +#include "prism/compiler/inline.h" + +#include "prism/internal/allocator.h" +#include "prism/internal/char.h" +#include "prism/internal/stringy.h" + +#include <stdlib.h> +#include <string.h> + +/** + * Allocate a new options struct. If the options struct cannot be allocated, + * this function aborts the process. + */ +pm_options_t * +pm_options_new(void) { + pm_options_t *options = xcalloc(1, sizeof(pm_options_t)); + if (options == NULL) abort(); + return options; +} + +/** + * Free the internal memory associated with the options. + */ +void +pm_options_cleanup(pm_options_t *options) { + pm_string_cleanup(&options->filepath); + pm_string_cleanup(&options->encoding); + + for (size_t scope_index = 0; scope_index < options->scopes_count; scope_index++) { + pm_options_scope_t *scope = &options->scopes[scope_index]; + + for (size_t local_index = 0; local_index < scope->locals_count; local_index++) { + pm_string_cleanup(&scope->locals[local_index]); + } + + xfree_sized(scope->locals, scope->locals_count * sizeof(pm_string_t)); + } + + xfree_sized(options->scopes, options->scopes_count * sizeof(pm_options_scope_t)); +} + +/** + * Free both the held memory of the given options struct and the struct itself. + * + * @param options The options struct to free. + */ +void +pm_options_free(pm_options_t *options) { + pm_options_cleanup(options); + xfree_sized(options, sizeof(pm_options_t)); +} + +/** + * Set the shebang callback option on the given options struct. + */ +void +pm_options_shebang_callback_set(pm_options_t *options, pm_options_shebang_callback_t shebang_callback, void *shebang_callback_data) { + options->shebang_callback = shebang_callback; + options->shebang_callback_data = shebang_callback_data; +} + +/** + * Get the filepath option on the given options struct. + */ +const pm_string_t * +pm_options_filepath(const pm_options_t *options) { + return &options->filepath; +} + +/** + * Set the filepath option on the given options struct. + */ +void +pm_options_filepath_set(pm_options_t *options, const char *filepath) { + pm_string_constant_init(&options->filepath, filepath, strlen(filepath)); +} + +/** + * Set the encoding option on the given options struct. + */ +void +pm_options_encoding_set(pm_options_t *options, const char *encoding) { + pm_string_constant_init(&options->encoding, encoding, strlen(encoding)); +} + +/** + * Set the encoding_locked option on the given options struct. + */ +void +pm_options_encoding_locked_set(pm_options_t *options, bool encoding_locked) { + options->encoding_locked = encoding_locked; +} + +/** + * Set the line option on the given options struct. + */ +void +pm_options_line_set(pm_options_t *options, int32_t line) { + options->line = line; +} + +/** + * Set the frozen string literal option on the given options struct. + */ +void +pm_options_frozen_string_literal_set(pm_options_t *options, bool frozen_string_literal) { + options->frozen_string_literal = frozen_string_literal ? PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED : PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED; +} + +/** + * Sets the command line option on the given options struct. + */ +void +pm_options_command_line_set(pm_options_t *options, uint8_t command_line) { + options->command_line = command_line; +} + +/** + * Checks if the given slice represents a number. + */ +static PRISM_INLINE bool +is_number(const char *string, size_t length) { + return pm_strspn_decimal_digit((const uint8_t *) string, (ptrdiff_t) length) == length; +} + +/** + * 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. + */ +bool +pm_options_version_set(pm_options_t *options, const char *version, size_t length) { + if (version == NULL) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + + if (length == 3) { + if (strncmp(version, "3.3", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_3; + return true; + } + + if (strncmp(version, "3.4", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_4; + return true; + } + + if (strncmp(version, "3.5", 3) == 0 || strncmp(version, "4.0", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_0; + return true; + } + + if (strncmp(version, "4.1", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } + + return false; + } + + if (length >= 4 && is_number(version + 4, length - 4)) { + if (strncmp(version, "3.3.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_3; + return true; + } + + if (strncmp(version, "3.4.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_4; + return true; + } + + if (strncmp(version, "3.5.", 4) == 0 || strncmp(version, "4.0.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_0; + return true; + } + + if (strncmp(version, "4.1.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } + } + + if (length == 6) { + if (strncmp(version, "latest", 6) == 0) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + } + + return false; +} + +/** + * Set the version option on the given options struct to the lowest version of + * Ruby that prism supports. + */ +void +pm_options_version_set_lowest(pm_options_t *options) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_3; +} + +/** + * Set the version option on the given options struct to the highest version of + * Ruby that prism supports. + */ +void +pm_options_version_set_highest(pm_options_t *options) { + options->version = PM_OPTIONS_VERSION_LATEST; +} + +/** + * Set the main script option on the given options struct. + */ +void +pm_options_main_script_set(pm_options_t *options, bool main_script) { + options->main_script = main_script; +} + +/** + * Set the partial script option on the given options struct. + */ +void +pm_options_partial_script_set(pm_options_t *options, bool partial_script) { + options->partial_script = partial_script; +} + +/** + * Get the freeze option on the given options struct. + */ +bool +pm_options_freeze(const pm_options_t *options) { + return options->freeze; +} + +/** + * Set the freeze option on the given options struct. + */ +void +pm_options_freeze_set(pm_options_t *options, bool freeze) { + options->freeze = freeze; +} + +// For some reason, GCC analyzer thinks we're leaking allocated scopes and +// locals here, even though we definitely aren't. This is a false positive. +// Ideally we wouldn't need to suppress this. +#if defined(__GNUC__) && (__GNUC__ >= 10) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" +#endif + +/** + * Allocate and zero out the scopes array on the given options struct. + */ +bool +pm_options_scopes_init(pm_options_t *options, size_t scopes_count) { + options->scopes_count = scopes_count; + options->scopes = xcalloc(scopes_count, sizeof(pm_options_scope_t)); + return options->scopes != NULL; +} + +/** + * Return a constant pointer to the scope at the given index within the given + * options. + */ +const pm_options_scope_t * +pm_options_scope(const pm_options_t *options, size_t index) { + return &options->scopes[index]; +} + +/** + * Return a mutable pointer to the scope at the given index within the given + * options. + */ +pm_options_scope_t * +pm_options_scope_mut(pm_options_t *options, size_t index) { + return &options->scopes[index]; +} + +/** + * Create a new options scope struct. This will hold a set of locals that are in + * scope surrounding the code that is being parsed. + */ +void +pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count) { + scope->locals_count = locals_count; + scope->locals = xcalloc(locals_count, sizeof(pm_string_t)); + scope->forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; + if (scope->locals == NULL) abort(); +} + +/** + * Return a constant pointer to the local at the given index within the given + * scope. + */ +const pm_string_t * +pm_options_scope_local(const pm_options_scope_t *scope, size_t index) { + return &scope->locals[index]; +} + +/** + * Return a mutable pointer to the local at the given index within the given + * scope. + */ +pm_string_t * +pm_options_scope_local_mut(pm_options_scope_t *scope, size_t index) { + return &scope->locals[index]; +} + +/** + * Set the forwarding option on the given scope struct. + */ +void +pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding) { + scope->forwarding = forwarding; +} + +/** + * Read a 32-bit unsigned integer from a pointer. This function is used to read + * the options that are passed into the parser from the Ruby implementation. It + * handles aligned and unaligned reads. + */ +static uint32_t +pm_options_read_u32(const char *data) { + if (((uintptr_t) data) % sizeof(uint32_t) == 0) { + return *((uint32_t *) data); + } else { + uint32_t value; + memcpy(&value, data, sizeof(uint32_t)); + return value; + } +} + +/** + * Read a 32-bit signed integer from a pointer. This function is used to read + * the options that are passed into the parser from the Ruby implementation. It + * handles aligned and unaligned reads. + */ +static int32_t +pm_options_read_s32(const char *data) { + if (((uintptr_t) data) % sizeof(int32_t) == 0) { + return *((int32_t *) data); + } else { + int32_t value; + memcpy(&value, data, sizeof(int32_t)); + return value; + } +} + +/** + * Deserialize an options struct from the given binary string. This is used to + * pass options to the parser from an FFI call so that consumers of the library + * from an FFI perspective don't have to worry about the structure of our + * options structs. Since the source of these calls will be from Ruby + * implementation internals we assume it is from a trusted source. + */ +void +pm_options_read(pm_options_t *options, const char *data) { + options->line = 1; // default + if (data == NULL) return; + + uint32_t filepath_length = pm_options_read_u32(data); + data += 4; + + if (filepath_length > 0) { + pm_string_constant_init(&options->filepath, data, filepath_length); + data += filepath_length; + } + + options->line = pm_options_read_s32(data); + data += 4; + + uint32_t encoding_length = pm_options_read_u32(data); + data += 4; + + if (encoding_length > 0) { + pm_string_constant_init(&options->encoding, data, encoding_length); + data += encoding_length; + } + + options->frozen_string_literal = (int8_t) *data++; + options->command_line = (uint8_t) *data++; + options->version = (pm_options_version_t) *data++; + options->encoding_locked = ((uint8_t) *data++) > 0; + options->main_script = ((uint8_t) *data++) > 0; + options->partial_script = ((uint8_t) *data++) > 0; + options->freeze = ((uint8_t) *data++) > 0; + + uint32_t scopes_count = pm_options_read_u32(data); + data += 4; + + if (scopes_count > 0) { + if (!pm_options_scopes_init(options, scopes_count)) return; + + for (size_t scope_index = 0; scope_index < scopes_count; scope_index++) { + uint32_t locals_count = pm_options_read_u32(data); + data += 4; + + pm_options_scope_t *scope = &options->scopes[scope_index]; + pm_options_scope_init(scope, locals_count); + + uint8_t forwarding = (uint8_t) *data++; + pm_options_scope_forwarding_set(&options->scopes[scope_index], forwarding); + + for (size_t local_index = 0; local_index < locals_count; local_index++) { + uint32_t local_length = pm_options_read_u32(data); + data += 4; + + pm_string_constant_init(&scope->locals[local_index], data, local_length); + data += local_length; + } + } + } +} + +#if defined(__GNUC__) && (__GNUC__ >= 10) +#pragma GCC diagnostic pop +#endif |
