summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <[email protected]>2024-09-11 16:49:08 -0400
committerKevin Newton <[email protected]>2024-09-12 13:43:04 -0400
commit38ba15beed5eb9f3a4923f9a215473965f31a7bc (patch)
treed08a137031db3343d37d733c57347d3ce8569f59
parentca61729fa7713f650c7e4144daa08932f8b66454 (diff)
[ruby/prism] Check errno for parsing directory
https://github.com/ruby/prism/commit/d68ea29d04
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/11497
-rw-r--r--lib/prism/ffi.rb18
-rw-r--r--prism/extension.c26
-rw-r--r--prism/util/pm_string.c78
-rw-r--r--prism/util/pm_string.h28
-rw-r--r--test/prism/api/parse_test.rb14
5 files changed, 118 insertions, 46 deletions
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index 46c4a1a755..b972520be1 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -72,6 +72,7 @@ module Prism
end
callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer
+ enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY]
load_exported_functions_from(
"prism.h",
@@ -176,13 +177,26 @@ module Prism
def self.with_file(filepath)
raise TypeError unless filepath.is_a?(String)
+ # On Windows and Mac, it's expected that filepaths will be encoded in
+ # UTF-8. If they are not, we need to convert them to UTF-8 before
+ # passing them into pm_string_mapped_init.
+ if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) &&
+ (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8
+ filepath = filepath.encode(Encoding::UTF_8)
+ end
+
FFI::MemoryPointer.new(SIZEOF) do |pm_string|
- if LibRubyParser.pm_string_mapped_init(pm_string, filepath)
+ case (result = LibRubyParser.pm_string_mapped_init(pm_string, filepath))
+ when :PM_STRING_INIT_SUCCESS
pointer = LibRubyParser.pm_string_source(pm_string)
length = LibRubyParser.pm_string_length(pm_string)
return yield new(pointer, length, false)
- else
+ when :PM_STRING_INIT_ERROR_GENERIC
raise SystemCallError.new(filepath, FFI.errno)
+ when :PM_STRING_INIT_ERROR_DIRECTORY
+ raise Errno::EISDIR.new(filepath)
+ else
+ raise "Unknown error initializing pm_string_t: #{result.inspect}"
end
ensure
LibRubyParser.pm_string_free(pm_string)
diff --git a/prism/extension.c b/prism/extension.c
index 6a8fedcb05..79761770f6 100644
--- a/prism/extension.c
+++ b/prism/extension.c
@@ -263,18 +263,32 @@ file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options, V
*encoded_filepath = rb_str_encode_ospath(filepath);
extract_options(options, *encoded_filepath, keywords);
- const char * string_source = (const char *) pm_string_source(&options->filepath);
+ const char *source = (const char *) pm_string_source(&options->filepath);
+ pm_string_init_result_t result;
- if (!pm_string_file_init(input, string_source)) {
- pm_options_free(options);
+ switch (result = pm_string_file_init(input, source)) {
+ case PM_STRING_INIT_SUCCESS:
+ break;
+ case PM_STRING_INIT_ERROR_GENERIC: {
+ pm_options_free(options);
#ifdef _WIN32
- int e = rb_w32_map_errno(GetLastError());
+ int e = rb_w32_map_errno(GetLastError());
#else
- int e = errno;
+ int e = errno;
#endif
- rb_syserr_fail(e, string_source);
+ rb_syserr_fail(e, source);
+ break;
+ }
+ case PM_STRING_INIT_ERROR_DIRECTORY:
+ pm_options_free(options);
+ rb_syserr_fail(EISDIR, source);
+ break;
+ default:
+ pm_options_free(options);
+ rb_raise(rb_eRuntimeError, "Unknown error (%d) initializing file: %s", result, source);
+ break;
}
}
diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c
index 3e1e22e34f..99a27ede4d 100644
--- a/prism/util/pm_string.c
+++ b/prism/util/pm_string.c
@@ -64,24 +64,33 @@ typedef struct {
* Open the file indicated by the filepath parameter for reading on Windows.
* Perform any kind of normalization that needs to happen on the filepath.
*/
-static bool
+static pm_string_init_result_t
pm_string_file_handle_open(pm_string_file_handle_t *handle, const char *filepath) {
int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0);
- if (length == 0) return false;
+ if (length == 0) return PM_STRING_INIT_ERROR_GENERIC;
handle->path = xmalloc(sizeof(WCHAR) * ((size_t) length));
if ((handle->path == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, handle->path, length) == 0)) {
xfree(handle->path);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
handle->file = CreateFileW(handle->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (handle->file == INVALID_HANDLE_VALUE) {
+ pm_string_init_result_t result = PM_STRING_INIT_ERROR_GENERIC;
+
+ if (GetLastError() == ERROR_ACCESS_DENIED) {
+ DWORD attributes = GetFileAttributesW(handle->path);
+ if ((attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ result = PM_STRING_INIT_ERROR_DIRECTORY;
+ }
+ }
+
xfree(handle->path);
- return false;
+ return result;
}
- return true;
+ return PM_STRING_INIT_SUCCESS;
}
/**
@@ -105,18 +114,19 @@ pm_string_file_handle_close(pm_string_file_handle_t *handle) {
* `MapViewOfFile`, on POSIX systems that have access to `mmap` we'll use
* `mmap`, and on other POSIX systems we'll use `read`.
*/
-PRISM_EXPORTED_FUNCTION bool
+PRISM_EXPORTED_FUNCTION pm_string_init_result_t
pm_string_mapped_init(pm_string_t *string, const char *filepath) {
#ifdef _WIN32
// Open the file for reading.
pm_string_file_handle_t handle;
- if (!pm_string_file_handle_open(&handle, filepath)) return false;
+ pm_string_init_result_t result = pm_string_file_handle_open(&handle, filepath);
+ if (result != PM_STRING_INIT_SUCCESS) return result;
// Get the file size.
DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// If the file is empty, then we don't need to do anything else, we'll set
@@ -125,14 +135,14 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
- return true;
+ return PM_STRING_INIT_SUCCESS;
}
// Create a mapping of the file.
HANDLE mapping = CreateFileMapping(handle.file, NULL, PAGE_READONLY, 0, 0, NULL);
if (mapping == NULL) {
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// Map the file into memory.
@@ -141,30 +151,29 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
pm_string_file_handle_close(&handle);
if (source == NULL) {
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
*string = (pm_string_t) { .type = PM_STRING_MAPPED, .source = source, .length = (size_t) file_size };
- return true;
+ return PM_STRING_INIT_SUCCESS;
#elif defined(_POSIX_MAPPED_FILES)
// Open the file for reading
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// Stat the file to get the file size
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// Ensure it is a file and not a directory
if (S_ISDIR(sb.st_mode)) {
- errno = EISDIR;
close(fd);
- return false;
+ return PM_STRING_INIT_ERROR_DIRECTORY;
}
// mmap the file descriptor to virtually get the contents
@@ -175,17 +184,17 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
close(fd);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
- return true;
+ return PM_STRING_INIT_SUCCESS;
}
source = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (source == MAP_FAILED) {
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
close(fd);
*string = (pm_string_t) { .type = PM_STRING_MAPPED, .source = source, .length = size };
- return true;
+ return PM_STRING_INIT_SUCCESS;
#else
return pm_string_file_init(string, filepath);
#endif
@@ -196,18 +205,19 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
*/
-PRISM_EXPORTED_FUNCTION bool
+PRISM_EXPORTED_FUNCTION pm_string_init_result_t
pm_string_file_init(pm_string_t *string, const char *filepath) {
#ifdef _WIN32
// Open the file for reading.
pm_string_file_handle_t handle;
- if (!pm_string_file_handle_open(&handle, filepath)) return false;
+ pm_string_init_result_t result = pm_string_file_handle_open(&handle, filepath);
+ if (result != PM_STRING_INIT_SUCCESS) return result;
// Get the file size.
DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// If the file is empty, then we don't need to do anything else, we'll set
@@ -216,37 +226,37 @@ pm_string_file_init(pm_string_t *string, const char *filepath) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
- return true;
+ return PM_STRING_INIT_SUCCESS;
}
// Create a buffer to read the file into.
uint8_t *source = xmalloc(file_size);
if (source == NULL) {
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// Read the contents of the file
DWORD bytes_read;
if (!ReadFile(handle.file, source, file_size, &bytes_read, NULL)) {
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
// Check the number of bytes read
if (bytes_read != file_size) {
xfree(source);
pm_string_file_handle_close(&handle);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
pm_string_file_handle_close(&handle);
*string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = (size_t) file_size };
- return true;
+ return PM_STRING_INIT_SUCCESS;
#elif defined(PRISM_HAS_FILESYSTEM)
FILE *file = fopen(filepath, "rb");
if (file == NULL) {
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
fseek(file, 0, SEEK_END);
@@ -254,21 +264,21 @@ pm_string_file_init(pm_string_t *string, const char *filepath) {
if (file_size == -1) {
fclose(file);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
if (file_size == 0) {
fclose(file);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
- return true;
+ return PM_STRING_INIT_SUCCESS;
}
size_t length = (size_t) file_size;
uint8_t *source = xmalloc(length);
if (source == NULL) {
fclose(file);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
fseek(file, 0, SEEK_SET);
@@ -277,16 +287,16 @@ pm_string_file_init(pm_string_t *string, const char *filepath) {
if (bytes_read != 1) {
xfree(source);
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
}
*string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = length };
- return true;
+ return PM_STRING_INIT_SUCCESS;
#else
(void) string;
(void) filepath;
perror("pm_string_file_init is not implemented for this platform");
- return false;
+ return PM_STRING_INIT_ERROR_GENERIC;
#endif
}
diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h
index e4a20558d3..6ba327b247 100644
--- a/prism/util/pm_string.h
+++ b/prism/util/pm_string.h
@@ -94,6 +94,26 @@ void pm_string_owned_init(pm_string_t *string, uint8_t *source, size_t length);
void pm_string_constant_init(pm_string_t *string, const char *source, size_t length);
/**
+ * Represents the result of calling pm_string_mapped_init or
+ * pm_string_file_init. We need this additional information because there is
+ * not a platform-agnostic way to indicate that the file that was attempted to
+ * be opened was a directory.
+ */
+typedef enum {
+ /** Indicates that the string was successfully initialized. */
+ PM_STRING_INIT_SUCCESS = 0,
+ /**
+ * Indicates a generic error from a string_*_init function, where the type
+ * of error should be read from `errno` or `GetLastError()`.
+ */
+ PM_STRING_INIT_ERROR_GENERIC = 1,
+ /**
+ * Indicates that the file that was attempted to be opened was a directory.
+ */
+ PM_STRING_INIT_ERROR_DIRECTORY = 2
+} pm_string_init_result_t;
+
+/**
* Read the file indicated by the filepath parameter into source and load its
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
@@ -106,9 +126,9 @@ void pm_string_constant_init(pm_string_t *string, const char *source, size_t len
*
* @param string The string to initialize.
* @param filepath The filepath to read.
- * @return Whether or not the file was successfully mapped.
+ * @return The success of the read, indicated by the value of the enum.
*/
-PRISM_EXPORTED_FUNCTION bool pm_string_mapped_init(pm_string_t *string, const char *filepath);
+PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_mapped_init(pm_string_t *string, const char *filepath);
/**
* Read the file indicated by the filepath parameter into source and load its
@@ -117,9 +137,9 @@ PRISM_EXPORTED_FUNCTION bool pm_string_mapped_init(pm_string_t *string, const ch
*
* @param string The string to initialize.
* @param filepath The filepath to read.
- * @return Whether or not the file was successfully read.
+ * @return The success of the read, indicated by the value of the enum.
*/
-PRISM_EXPORTED_FUNCTION bool pm_string_file_init(pm_string_t *string, const char *filepath);
+PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_file_init(pm_string_t *string, const char *filepath);
/**
* Ensure the string is owned. If it is not, then reinitialize it as owned and
diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb
index 6ad3829de0..58962aab6a 100644
--- a/test/prism/api/parse_test.rb
+++ b/test/prism/api/parse_test.rb
@@ -79,6 +79,20 @@ module Prism
end
end
+ def test_parse_directory
+ error = nil
+
+ begin
+ Prism.parse_file(__dir__)
+ rescue SystemCallError => error
+ end
+
+ refute_nil error
+ return if error.is_a?(Errno::ENOMEM)
+
+ assert_kind_of Errno::EISDIR, error
+ end
+
private
def find_source_file_node(program)