Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/check_sast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ jobs:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: ${{ matrix.language }}
build-mode: none
config-file: .github/codeql/codeql-config.yml

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
category: '/language:${{ matrix.language }}'
upload: False
Expand Down Expand Up @@ -127,7 +127,7 @@ jobs:
continue-on-error: true

- name: Upload SARIF
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
sarif_file: sarif-results/${{ matrix.language }}.sarif
continue-on-error: true
2 changes: 1 addition & 1 deletion .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
sarif_file: results.sarif
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
rustup install ${{ matrix.rust_version }} --profile minimal
rustup default ${{ matrix.rust_version }}
- uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7
- uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
ruby-version: '3.1'
bundler: none

- uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7
- uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,10 @@ lcov*.info
/config.status
/config.status.lineno
/configure
/coverage/simplecov
/coverage/simplecov-html
/coverage/doclie
/coverage/.last_run.json
/coverage/.report_stamp
/coverage/.resultset.json*
/coverage/assets
/coverage/coverage.json
/coverage/index.html
/doc/capi
/enc.mk
Expand Down
104 changes: 56 additions & 48 deletions box.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "internal/hash.h"
#include "internal/io.h"
#include "internal/load.h"
#include "internal/random.h"
#include "internal/st.h"
#include "internal/variable.h"
#include "iseq.h"
Expand Down Expand Up @@ -47,12 +48,16 @@ static rb_box_gem_flags_t box_gem_flags[1];
static char *tmp_dir;
static bool tmp_dir_has_dirsep;

#define BOX_TMP_PREFIX "_ruby_box_"

#ifndef MAXPATHLEN
# define MAXPATHLEN 1024
#endif

/* process-private 0700 directory for box-local copies of extensions */
static char box_ext_tmp_dir[MAXPATHLEN];
static unsigned long box_ext_seq;

#define BOX_TMP_PREFIX "_ruby_box_"

#if defined(_WIN32)
# define DIRSEP "\\"
#else
Expand Down Expand Up @@ -560,13 +565,40 @@ system_tmpdir(void)

/* end of copy */

static int
sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, const char *basename)
/* Box-local copies of extensions are placed in a process-private 0700
* directory with an unpredictable name, so that other local users cannot
* occupy the copy destination in a shared TMPDIR [Bug #22110], and so
* that the file name of each copy stays short regardless of the depth of
* the original path (a full path flattened into a single file name can
* exceed NAME_MAX). */
static void
ensure_box_ext_tmp_dir(void)
{
if (tmp_dir_has_dirsep) {
return snprintf(str, size, "%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, prefix, getpid(), box_id, basename);
if (box_ext_tmp_dir[0]) return;

int last_errno = 0;
for (int retry = 0; retry < 10; retry++) {
char path[MAXPATHLEN];
uint64_t suffix;
if (ruby_fill_random_bytes(&suffix, sizeof(suffix), FALSE) != 0) {
/* no random source; mkdir(0700) below still refuses hijacked names */
suffix = (uint64_t)(uintptr_t)&suffix ^ (uint64_t)retry;
}
int wrote = snprintf(path, sizeof(path), "%s%s%sp%"PRI_PIDT_PREFIX"u_%.16"PRIx64,
tmp_dir, tmp_dir_has_dirsep ? "" : DIRSEP,
BOX_TMP_PREFIX, getpid(), suffix);
if (wrote >= (int)sizeof(path)) {
rb_raise(rb_eLoadError, "TMPDIR for Ruby Box extensions is too long: %s", tmp_dir);
}
if (mkdir(path, 0700) == 0) {
strlcpy(box_ext_tmp_dir, path, sizeof(box_ext_tmp_dir));
return;
}
last_errno = errno;
if (last_errno != EEXIST) break;
}
return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename);
rb_raise(rb_eLoadError, "can't create the temporary directory for Ruby Box extensions under %s: %s",
tmp_dir, strerror(last_errno));
}

enum copy_error_type {
Expand Down Expand Up @@ -738,41 +770,14 @@ copy_ext_file(const char *src_path, const char *dst_path)
#define isdirsep(x) ((x) == '/')
#endif

#define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0)
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)

static void
fname_without_suffix(const char *fname, char *rvalue, size_t rsize)
{
size_t len = strlen(fname);
const char *pos;
for (pos = fname + len; pos > fname; pos--) {
if (IS_SOEXT(pos) || IS_DLEXT(pos)) {
len = pos - fname;
break;
}
if (fname + len - pos > DLEXT_MAXLEN) break;
}
if (len > rsize - 1) len = rsize - 1;
memcpy(rvalue, fname, len);
rvalue[len] = '\0';
}

static void
escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize)
static const char *
ext_basename(const char *path)
{
char *pos;
const char *leaf = path, *found;
// `leaf + 1` looks uncomfortable (when leaf == path), but fname must not be the top-dir itself
while ((found = strstr(leaf + 1, fname)) != NULL) {
leaf = found; // find the last occurrence for the path like /etc/my-crazy-lib-dir/etc.so
}
strlcpy(rvalue, leaf, rsize);
for (pos = rvalue; *pos; pos++) {
if (isdirsep(*pos)) {
*pos = '+';
}
const char *base = path;
for (const char *pos = path; *pos; pos++) {
if (isdirsep(*pos)) base = pos + 1;
}
return base;
}

static void
Expand Down Expand Up @@ -847,22 +852,25 @@ rb_box_unload_local_extensions(void)
ext = next;
}
#endif
if (box_ext_tmp_dir[0]) {
rmdir(box_ext_tmp_dir);
box_ext_tmp_dir[0] = '\0';
}
}

VALUE
rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path, VALUE *cleanup)
rb_box_local_extension(VALUE box_value, VALUE path, VALUE *cleanup)
{
char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN];
char ext_path[MAXPATHLEN];
int wrote;
const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname);
const char *src_path = RSTRING_PTR(path);
rb_box_t *box = rb_get_box_t(box_value);

fname_without_suffix(fname_ptr, fname2, sizeof(fname2));
escaped_basename(src_path, fname2, basename, sizeof(basename));

wrote = sprint_ext_filename(ext_path, sizeof(ext_path), box->box_id, BOX_TMP_PREFIX, basename);
ensure_box_ext_tmp_dir();
wrote = snprintf(ext_path, sizeof(ext_path), "%s%s%ld_%lu_%s",
box_ext_tmp_dir, DIRSEP, box->box_id, box_ext_seq++, ext_basename(src_path));
if (wrote >= (int)sizeof(ext_path)) {
rb_bug("Extension file path in the box was too long");
rb_raise(rb_eLoadError, "extension file path in the box is too long: %"PRIsVALUE, path);
}
VALUE new_path = rb_str_new_cstr(ext_path);
*cleanup = TypedData_Wrap_Struct(0, &box_ext_cleanup_type, NULL);
Expand Down
2 changes: 1 addition & 1 deletion common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ update-config_files: PHONY

update-coverage: main PHONY
$(XRUBY) -C "$(srcdir)" bin/gem install --no-document \
--install-dir .bundle --conservative "simplecov"
--install-dir .bundle --conservative "simplecov" -v "~> 1.1"

refresh-gems: update-bundled_gems prepare-gems
# can't recall exactly, but `make` somewhere (not GNU or nmake)
Expand Down
1 change: 0 additions & 1 deletion coverage/README
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ Limitation

TODO

* more reduce bundled simplecov(additional configuration, formatter, etc.)
* measure rubyspec coverage
52 changes: 40 additions & 12 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -2023,13 +2023,22 @@ rb_file_chardev_p(VALUE obj, VALUE fname)

/*
* call-seq:
* File.exist?(file_name) -> true or false
* File.exist?(object) -> true or false
*
* Return <code>true</code> if the named file exists.
* Return whether the specified +object+, a string path or IO object, exists:
*
* _file_name_ can be an IO object.
* # String paths.
* File.exist?('README.md') # => true
* File.exist?('.') # => true
* filepath = 't.tmp'
* File.exist?(filepath) # => false
* File.write(filepath, 'foo')
* File.exist?(filepath) # => true
* # File (IO object).
* file = File.new(filepath)
* File.exist?(file) # => true
* file.close # Clean up.
*
* "file exists" means that stat() or fstat() system call is successful.
*/

static VALUE
Expand Down Expand Up @@ -2179,17 +2188,36 @@ rb_file_world_writable_p(VALUE obj, VALUE fname)

/*
* call-seq:
* File.executable?(file_name) -> true or false
* File.executable?(path) -> true or false
*
* Returns <code>true</code> if the named file is executable by the effective
* user and group id of this process. See eaccess(3).
* Returns whether the filesystem entry at the given string +path+
* exists and is executable.
*
* Windows does not support execute permissions separately from read
* permissions. On Windows, a file is only considered executable if it ends in
* .bat, .cmd, .com, or .exe.
* On Windows, the entry is executable if its path has file extension
* +.bat+, +.cmd+, +.com+, or +.exe+:
*
* Note that some OS-level security features may cause this to return true
* even though the file is not executable by the effective user/group.
* File.executable?('win32/rtname.cmd') # => true
* File.executable?('win32/rtname') # => false
* File.executable?('win32/nosuch.cmd') # => false
*
* On other systems, the entry is executable if it has the execute/search
* permission for the effective user and group id of the current process;
* see {Permissions}[rdoc-ref:file/filesystem_modes.md@Permissions].
*
* These examples use
* a {helper method}[rdoc-ref:file/filesystem_modes.md@Helper+Method], +mode+,
* that displays a mode both in octal digits and in characters:
*
* File.executable?('.') # => true
* mode('.') # => "040775 drwxrwxr-x"
* File.executable?('bin/gem') # => true
* mode('bin/gem') # => "100775 -rwxrwxr-x"
* File.executable?('/etc/passwd') # => false
* mode('/etc/passwd') # => "100644 -rw-r--r--"
* File.executable?('nosuch') # => false
*
* Note that some filesystem settings may cause this method to return +true+
* even though the entry is not executable by the effective user/group.
*/

static VALUE
Expand Down
3 changes: 2 additions & 1 deletion hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,8 @@ rb_hash_init(rb_execution_context_t *ec, VALUE hash, VALUE capa_value, VALUE ifn

if (capa_value != INT2FIX(0)) {
long capa = NUM2LONG(capa_value);
if (capa > 0 && RHASH_AR_TABLE_P(hash) && RHASH_SIZE(hash) == 0 && capa > RHASH_AR_TABLE_MAX_BOUND(hash)) {
if (capa > 0 && RHASH_AR_TABLE_P(hash) && RHASH_SIZE(hash) == 0 &&
(unsigned long)capa > RHASH_AR_TABLE_MAX_BOUND(hash)) {
hash_st_table_init(hash, capa);
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void rb_box_gc_update_references(void *ptr);
rb_box_t * rb_get_box_t(VALUE ns);
VALUE rb_get_box_object(rb_box_t *ns);

VALUE rb_box_local_extension(VALUE box, VALUE fname, VALUE path, VALUE *cleanup);
VALUE rb_box_local_extension(VALUE box, VALUE path, VALUE *cleanup);
void rb_box_cleanup_local_extension(VALUE cleanup);
void rb_box_defer_unload_local_extension(void *handle);
void rb_box_unload_local_extensions(void);
Expand Down
2 changes: 1 addition & 1 deletion load.c
Original file line number Diff line number Diff line change
Expand Up @@ -1218,7 +1218,7 @@ load_ext(VALUE path, VALUE fname)
const rb_box_t *box = rb_loading_box();
VALUE cleanup = 0;
if (BOX_USER_P(box)) {
loaded = rb_box_local_extension(box->box_object, fname, path, &cleanup);
loaded = rb_box_local_extension(box->box_object, path, &cleanup);
}
rb_scope_visibility_set(METHOD_VISI_PUBLIC);
void *handle = dln_load_feature(RSTRING_PTR(loaded), RSTRING_PTR(fname));
Expand Down
27 changes: 12 additions & 15 deletions spec/bundler/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,27 @@
begin
raise LoadError if File.exist?(File.expand_path("../../lib/bundler/bundler.gemspec", __dir__))

gem "simplecov_json_formatter"
require "simplecov"

SimpleCov.start do
command_name "bundler:#{Process.pid}"
root File.expand_path("../bundler", __dir__)
root File.expand_path("..", __dir__)
coverage_dir File.expand_path("../coverage", __dir__)

add_filter "/spec/"
add_filter "/test/"
add_filter "/lib/rubygems/"
add_filter "/lib/bundler/vendor/"
add_filter "/tool/"
add_filter "/tmp/"
add_filter ".gemspec"
skip "/spec/"
skip "/test/"
skip "/lib/rubygems/"
skip "/lib/bundler/vendor/"
skip "/tool/"
skip "/tmp/"
skip ".gemspec"
end

SimpleCov.print_error_status = false
SimpleCov.at_exit do
$stdout = File.open(File::NULL, "w")
SimpleCov.result.format!
ensure
$stdout = STDOUT
end

# Only merge this process result into the resultset. Parallel workers share a
# coverage directory, so the report is formatted once by `rake coverage:report`.
SimpleCov.at_exit { SimpleCov.result }
rescue LoadError
# SimpleCov is not installed
end
Expand Down
Loading