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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,6 @@ group :development do
gem 'rake-compiler', ">= 0.4.1"
gem 'ruby-maven', :platforms => :jruby
gem 'test-unit'
gem 'test-unit-ruby-core', ">= 1.0.7"
gem 'test-unit-ruby-core', ">= 1.0.16"
gem 'power_assert', '~> 2.0' if RUBY_VERSION < '3.0' # https://github.com/ruby/power_assert/pull/61
end
91 changes: 75 additions & 16 deletions ext/psych/psych_parser.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,19 +52,28 @@ static int io_reader(void * data, unsigned char *buf, size_t size, size_t *read)
return 1;
}

/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() reinitialises the
* parser it is handed, which would pull the input out from under the loop
* still driving it, so keep a flag to reject a reentrant call. */
typedef struct {
yaml_parser_t yaml_parser;
int parsing;
} psych_parser_t;

static void dealloc(void * ptr)
{
yaml_parser_t * parser;
psych_parser_t * parser;

parser = (yaml_parser_t *)ptr;
yaml_parser_delete(parser);
parser = (psych_parser_t *)ptr;
yaml_parser_delete(&parser->yaml_parser);
xfree(parser);
}

#if 0
static size_t memsize(const void *ptr)
{
const yaml_parser_t *parser = ptr;
const psych_parser_t *parser = ptr;
/* TODO: calculate parser's size */
return 0;
}
Expand All@@ -81,10 +90,10 @@ static const rb_data_type_t psych_parser_type = {

static VALUE allocate(VALUE klass)
{
yaml_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, yaml_parser_t, &psych_parser_type, parser);
psych_parser_t * parser;
VALUE obj = TypedData_Make_Struct(klass, psych_parser_t, &psych_parser_type, parser);

yaml_parser_initialize(parser);
yaml_parser_initialize(&parser->yaml_parser);

return obj;
Comment on lines 91 to 98
}
Expand DownExpand Up@@ -257,18 +266,29 @@ static VALUE protected_event_location(VALUE pointer)
return rb_funcall3(args[0], id_event_location, 4, args + 1);
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
struct parse_args {
psych_parser_t * psych_parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
yaml_parser_t * parser;
struct parse_args * pargs = (struct parse_args *)ptr;
yaml_parser_t * parser = &pargs->psych_parser->yaml_parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
yaml_event_t event;
int done = 0;
int state = 0;
int parser_encoding = YAML_ANY_ENCODING;
int encoding = rb_utf8_encindex();
rb_encoding * internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);

yaml_parser_delete(parser);
yaml_parser_initialize(parser);

Expand DownExpand Up@@ -312,6 +332,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = end_line;
event_args[4] = end_column;
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
yaml_event_delete(&event);
rb_jump_tag(state);
}

switch(event.type) {
case YAML_STREAM_START_EVENT:
Expand DownExpand Up@@ -496,7 +520,11 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case YAML_NO_EVENT:
/* Once libyaml has produced the stream end, every later call
* succeeds with a zeroed event and YAML_STREAM_END_EVENT can no
* longer be reached. Stop rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case YAML_STREAM_END_EVENT:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -510,6 +538,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_parser_t * parser = (psych_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_parser_t * parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.psych_parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand All@@ -521,13 +580,13 @@ static VALUE mark(VALUE self)
{
VALUE mark_klass;
VALUE args[3];
yaml_parser_t * parser;
psych_parser_t * parser;

TypedData_Get_Struct(self, yaml_parser_t, &psych_parser_type, parser);
TypedData_Get_Struct(self, psych_parser_t, &psych_parser_type, parser);
mark_klass = rb_const_get_at(cPsychParser, rb_intern("Mark"));
args[0] = SIZET2NUM(parser->mark.index);
args[1] = SIZET2NUM(parser->mark.line);
args[2] = SIZET2NUM(parser->mark.column);
args[0] = SIZET2NUM(parser->yaml_parser.mark.index);
args[1] = SIZET2NUM(parser->yaml_parser.mark.line);
args[2] = SIZET2NUM(parser->yaml_parser.mark.column);

return rb_class_new_instance(3, args, mark_klass);
}
Expand Down
62 changes: 58 additions & 4 deletions ext/psych/psych_parser_fy.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,11 @@ typedef struct {
size_t mark_line;
size_t mark_column;
size_t mark_index;
/* The parser calls back into Ruby for every event, so a handler can call
* Psych::Parser#parse again on the same object. parse() destroys and
* recreates fyp, which would pull the parser out from under the loop still
* driving it, so keep a flag to reject a reentrant call. */
int parsing;
} psych_fy_parser_t;

static const struct fy_parse_cfg psych_parse_cfg = {
Expand DownExpand Up@@ -256,17 +261,28 @@ static VALUE token_to_str(struct fy_token *tok, int encoding, rb_encoding *inter
return str;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
struct parse_args {
psych_fy_parser_t *parser;
VALUE self;
VALUE handler;
VALUE yaml;
VALUE path;
};

static VALUE parse_body(VALUE ptr)
{
struct parse_args *pargs = (struct parse_args *)ptr;
psych_fy_parser_t *parser = pargs->parser;
VALUE self = pargs->self;
VALUE handler = pargs->handler;
VALUE yaml = pargs->yaml;
VALUE path = pargs->path;
struct fy_event *event;
int done = 0;
int state = 0;
int encoding = rb_utf8_encindex();
rb_encoding *internal_enc = rb_default_internal_encoding();

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

/* Use a pristine parser for each parse, like fy-tool does. Reusing a
* parser across documents via fy_parser_reset() left the default tag
* handles unset for bare (no "---") tag-led documents. */
Expand DownExpand Up@@ -348,6 +364,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
event_args[3] = SIZET2NUM(em ? (size_t)em->line : 0);
event_args[4] = SIZET2NUM(em ? (size_t)em->column : 0);
rb_protect(protected_event_location, (VALUE)event_args, &state);
if (state) {
fy_parser_event_free(parser->fyp, event);
rb_jump_tag(state);
}

switch (event->type) {
case FYET_STREAM_START:
Expand DownExpand Up@@ -473,7 +493,10 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
rb_protect(protected_end_mapping, handler, &state);
break;
case FYET_NONE:
/* An event with no type cannot advance the stream, so stop
* rather than loop forever. */
rb_protect(protected_empty, handler, &state);
done = 1;
break;
case FYET_STREAM_END:
rb_protect(protected_end_stream, handler, &state);
Expand All@@ -489,6 +512,37 @@ static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
return self;
}

static VALUE parse_ensure(VALUE ptr)
{
psych_fy_parser_t *parser = (psych_fy_parser_t *)ptr;

parser->parsing = 0;

return Qnil;
}

static VALUE parse(VALUE self, VALUE handler, VALUE yaml, VALUE path)
{
psych_fy_parser_t *parser;
struct parse_args pargs;

TypedData_Get_Struct(self, psych_fy_parser_t, &psych_parser_type, parser);

if (parser->parsing) {
rb_raise(rb_const_get(mPsych, rb_intern("Exception")),
"parser is already parsing, it cannot be reused from a handler callback");
}
parser->parsing = 1;

pargs.parser = parser;
pargs.self = self;
pargs.handler = handler;
pargs.yaml = yaml;
pargs.path = path;

return rb_ensure(parse_body, (VALUE)&pargs, parse_ensure, (VALUE)parser);
}

/*
* call-seq:
* parser.mark # => #<Psych::Parser::Mark>
Expand Down
83 changes: 83 additions & 0 deletions test/psych/test_parser.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,39 @@ def #{m} *args
end
end

# Calls Parser#parse again, once, from inside a callback of the parse it is
# already handling.
class ReentrantHandler < Handler
attr_accessor :parser, :inner_yaml
attr_reader :inner_error, :scalars, :empty_calls

def initialize
@parser = nil
@inner_yaml = nil
@inner_error = nil
@scalars = []
@empty_calls = 0
end

def empty
@empty_calls += 1
raise "handler#empty keeps being called, the parse loop is not terminating" if @empty_calls > 1000
end

def scalar value, anchor, tag, plain, quoted, style
@scalars << value

inner, @inner_yaml = @inner_yaml, nil
return unless inner

begin
@parser.parse inner
rescue => e
@inner_error = e
end
end
end

def setup
super
@handler = EventCatcher.new
Expand DownExpand Up@@ -70,6 +103,56 @@ def test_exception_memory_leak
end
end

def test_event_location_exception_is_propagated
klass = Class.new(Psych::Handler) do
def event_location start_line, start_column, end_line, end_column
raise "from event_location"
end
end

parser = Psych::Parser.new klass.new
2.times do
ex = assert_raise(RuntimeError) { parser.parse "--- hello\n" }
assert_equal "from event_location", ex.message
end
end

def test_parse_is_not_reentrant
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- inner\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls

# The in-use flag is cleared when the parse finishes, so the same parser
# can be used again afterwards.
handler.scalars.clear
parser.parse "--- second\n"
assert_equal ['second'], handler.scalars
end

def test_parse_is_not_reentrant_with_invalid_inner_document
pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/

handler = ReentrantHandler.new
handler.inner_yaml = "--- \x00bad\n"
parser = Psych::Parser.new handler
handler.parser = parser

parser.parse "--- outer\n"

assert_kind_of Psych::Exception, handler.inner_error
assert_equal ['outer'], handler.scalars
assert_equal 0, handler.empty_calls
end

def test_multiparse
3.times do
@parser.parse '--- foo'
Expand Down
Loading