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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ production_flags = $(warning_flags) -O3 -march=native -flto
shared_library_flags = -fPIC

# Default build mode (change this as needed)
flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=c99
flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=gnu99

# Separate test compilation flags
test_flags = $(debug_flags) $(prism_flags) -std=gnu99
Expand Down
10 changes: 10 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,16 @@ errors:
- name: segment
type: token

- name: TimeoutError
message:
template: "Parsing timed out after %zu ms."
arguments:
- timeout_ms

fields:
- name: timeout_ms
type: size_t

warnings:
fields: []
types: []
Expand Down
4 changes: 4 additions & 0 deletions ext/herb/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ static VALUE Herb_parse(int argc, VALUE* argv, VALUE self) {
if (NIL_P(html)) { html = rb_hash_lookup(options, ID2SYM(rb_intern("html"))); }
if (!NIL_P(html) && !RTEST(html)) { parser_options.html = false; }

VALUE timeout = rb_hash_lookup(options, rb_utf8_str_new_cstr("timeout"));
if (NIL_P(timeout)) { timeout = rb_hash_lookup(options, ID2SYM(rb_intern("timeout"))); }
if (!NIL_P(timeout)) { parser_options.timeout_ms = (uint32_t) (NUM2DBL(timeout) * 1000); }

VALUE arena_stats = rb_hash_lookup(options, rb_utf8_str_new_cstr("arena_stats"));
if (NIL_P(arena_stats)) { arena_stats = rb_hash_lookup(options, ID2SYM(rb_intern("arena_stats"))); }
if (!NIL_P(arena_stats) && RTEST(arena_stats)) { print_arena_stats = true; }
Expand Down
1 change: 1 addition & 0 deletions ext/herb/extension_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source, const parser_
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes")), options->prism_nodes ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_nodes_deep")), options->prism_nodes_deep ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("prism_program")), options->prism_program ? Qtrue : Qfalse);
rb_hash_aset(kwargs, ID2SYM(rb_intern("timeout")), DBL2NUM((double) options->timeout_ms / 1000.0));

VALUE parser_options_args[1] = { kwargs };
VALUE parser_options = rb_class_new_instance_kw(1, parser_options_args, cParserOptions, RB_PASS_KEYWORDS);
Expand Down
2 changes: 1 addition & 1 deletion java/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ PRISM_BUILD = $(PRISM_PATH)/build

JAVAC = $(JAVA_HOME)/bin/javac
JAVA_CMD = $(JAVA_HOME)/bin/java
CFLAGS = -std=c99 -Wall -Wextra -fPIC -O2 -DHERB_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_JSON -DPRISM_EXCLUDE_PACK
CFLAGS = -std=gnu99 -Wall -Wextra -fPIC -O2 -DHERB_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_PRETTYPRINT -DPRISM_EXCLUDE_JSON -DPRISM_EXCLUDE_PACK
INCLUDES = -I. -I$(SRC_DIR)/include -I$(PRISM_INCLUDE) $(JNI_INCLUDES)
LDFLAGS = -shared
LIBS = $(PRISM_BUILD)/libprism.a
Expand Down
8 changes: 8 additions & 0 deletions java/herb_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ Java_org_herb_Herb_parse(JNIEnv* env, jclass clazz, jstring source, jobject opti
jboolean html = (*env)->CallBooleanMethod(env, options, getHtml);
parser_options.html = (html == JNI_TRUE);
}

jmethodID getTimeout =
(*env)->GetMethodID(env, optionsClass, "getTimeout", "()I");

if (getTimeout != NULL) {
jint timeout = (*env)->CallIntMethod(env, options, getTimeout);
parser_options.timeout_ms = (uint32_t) timeout;
}
}

hb_allocator_T allocator;
Expand Down
10 changes: 10 additions & 0 deletions java/org/herb/ParserOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class ParserOptions {
private boolean prismProgram = false;
private boolean dotNotationTags = false;
private boolean html = true;
private int timeout = 1000;

public ParserOptions() {}

Expand Down Expand Up @@ -124,6 +125,15 @@ public boolean isHtml() {
return html;
}

public ParserOptions timeout(int value) {
this.timeout = value;
return this;
}

public int getTimeout() {
return timeout;
}

public static ParserOptions create() {
return new ParserOptions();
}
Expand Down
6 changes: 6 additions & 0 deletions javascript/packages/core/src/parser-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ParseOptions {
prism_program?: boolean
dot_notation_tags?: boolean
html?: boolean
timeout?: number
}

export type SerializedParserOptions = Required<ParseOptions>
Expand All @@ -28,6 +29,7 @@ export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = {
prism_program: false,
dot_notation_tags: false,
html: true,
timeout: 1000,
}

/**
Expand Down Expand Up @@ -70,6 +72,9 @@ export class ParserOptions {
/** Whether HTML tag parsing is enabled during parsing. When false, HTML-like content is treated as literal text. */
readonly html: boolean

/** Parse timeout in milliseconds. 0 disables the timeout. */
readonly timeout: number

static from(options: SerializedParserOptions): ParserOptions {
return new ParserOptions(options)
}
Expand All @@ -87,5 +92,6 @@ export class ParserOptions {
this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program
this.dot_notation_tags = options.dot_notation_tags ?? DEFAULT_PARSER_OPTIONS.dot_notation_tags
this.html = options.html ?? DEFAULT_PARSER_OPTIONS.html
this.timeout = options.timeout ?? DEFAULT_PARSER_OPTIONS.timeout
}
}
4 changes: 4 additions & 0 deletions javascript/packages/linter/test/parse-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe("ParseCache", () => {
dot_notation_tags: false,
transform_conditionals: false,
html: true,
timeout: 1000,
})
})

Expand All @@ -98,6 +99,7 @@ describe("ParseCache", () => {
dot_notation_tags: false,
transform_conditionals: false,
html: true,
timeout: 1000,
})
})

Expand All @@ -118,6 +120,7 @@ describe("ParseCache", () => {
dot_notation_tags: false,
transform_conditionals: false,
html: true,
timeout: 1000,
})
})

Expand All @@ -138,6 +141,7 @@ describe("ParseCache", () => {
dot_notation_tags: false,
transform_conditionals: false,
html: true,
timeout: 1000,
})
})
})
Expand Down
13 changes: 9 additions & 4 deletions lib/herb/parser_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ParserOptions
attr_reader :prism_program #: bool
attr_reader :prism_nodes #: bool
attr_reader :prism_nodes_deep #: bool
attr_reader :timeout #: Numeric

DEFAULT_STRICT = true #: bool
DEFAULT_TRACK_WHITESPACE = false #: bool
Expand All @@ -23,9 +24,10 @@ class ParserOptions
DEFAULT_PRISM_PROGRAM = false #: bool
DEFAULT_PRISM_NODES = false #: bool
DEFAULT_PRISM_NODES_DEEP = false #: bool
DEFAULT_TIMEOUT = 1 #: Numeric

#: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool) -> void
def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM)
#: (?strict: bool, ?track_whitespace: bool, ?analyze: bool, ?action_view_helpers: bool, ?transform_conditionals: bool, ?render_nodes: bool, ?strict_locals: bool, ?prism_nodes: bool, ?prism_nodes_deep: bool, ?prism_program: bool, ?timeout: Numeric) -> void
def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPACE, analyze: DEFAULT_ANALYZE, action_view_helpers: DEFAULT_ACTION_VIEW_HELPERS, transform_conditionals: DEFAULT_TRANSFORM_CONDITIONALS, render_nodes: DEFAULT_RENDER_NODES, strict_locals: DEFAULT_STRICT_LOCALS, prism_nodes: DEFAULT_PRISM_NODES, prism_nodes_deep: DEFAULT_PRISM_NODES_DEEP, prism_program: DEFAULT_PRISM_PROGRAM, timeout: DEFAULT_TIMEOUT)
@strict = strict
@track_whitespace = track_whitespace
@analyze = analyze
Expand All @@ -36,9 +38,10 @@ def initialize(strict: DEFAULT_STRICT, track_whitespace: DEFAULT_TRACK_WHITESPAC
@prism_nodes = prism_nodes
@prism_nodes_deep = prism_nodes_deep
@prism_program = prism_program
@timeout = timeout
end

#: () -> Hash[Symbol, bool]
#: () -> Hash[Symbol, (bool | Numeric)]
def to_h
{
strict: @strict,
Expand All @@ -51,6 +54,7 @@ def to_h
prism_nodes: @prism_nodes,
prism_nodes_deep: @prism_nodes_deep,
prism_program: @prism_program,
timeout: @timeout,
}
end

Expand All @@ -66,7 +70,8 @@ def inspect
"strict_locals=#{@strict_locals}\n " \
"prism_nodes=#{@prism_nodes}\n " \
"prism_nodes_deep=#{@prism_nodes_deep}\n " \
"prism_program=#{@prism_program}>"
"prism_program=#{@prism_program}\n " \
"timeout=#{@timeout}>"
end
end
end
2 changes: 1 addition & 1 deletion rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn main() {

let mut build = cc::Build::new();
build
.flag("-std=c99")
.flag("-std=gnu99")
.flag("-Wall")
.flag("-Wextra")
.flag("-Wno-unused-parameter")
Expand Down
6 changes: 6 additions & 0 deletions rust/src/herb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct ParserOptions {
pub prism_program: bool,
pub dot_notation_tags: bool,
pub html: bool,
pub timeout: u32,
}

impl Default for ParserOptions {
Expand All @@ -34,6 +35,7 @@ impl Default for ParserOptions {
prism_program: false,
dot_notation_tags: false,
html: true,
timeout: 1000,
}
}
}
Expand Down Expand Up @@ -120,6 +122,8 @@ pub fn parse_with_options(source: &str, options: &ParserOptions) -> Result<Parse
html: options.html,
start_line: 0,
start_column: 0,
timeout_ms: options.timeout,
deadline_ms: 0,
};

let ast = crate::ffi::herb_parse(c_source.as_ptr(), &c_parser_options, &mut allocator);
Expand Down Expand Up @@ -340,6 +344,8 @@ pub fn diff(old_source: &str, new_source: &str) -> Result<DiffResult, String> {
html: true,
start_line: 0,
start_column: 0,
timeout_ms: 1000,
deadline_ms: 0,
};

let old_root = crate::ffi::herb_parse(old_c_source.as_ptr(), &parser_options, &mut old_allocator);
Expand Down
18 changes: 18 additions & 0 deletions sig/herb/errors.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions sig/herb/parser_options.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/herb.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "include/herb.h"
#include "include/analyze/analyze.h"
#include "include/analyze/prism_annotate.h"
#include "include/errors.h"
#include "include/lexer/lexer.h"
#include "include/lexer/token.h"
#include "include/lib/hb_allocator.h"
Expand Down Expand Up @@ -44,6 +45,8 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(
parser_options_T parser_options = HERB_DEFAULT_PARSER_OPTIONS;
if (options != NULL) { parser_options = *options; }

parser_options_set_deadline(&parser_options);

if (parser_options.start_line > 0) {
lexer.current_line = parser_options.start_line;
lexer.previous_line = parser_options.start_line;
Expand Down Expand Up @@ -73,6 +76,16 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(
);
}

if (parser_options_past_deadline(&parser_options)) {
append_timeout_error(
parser_options.timeout_ms,
document->base.location.start,
document->base.location.end,
allocator,
document->base.errors
);
}

return document;
}

Expand Down
15 changes: 15 additions & 0 deletions src/include/lib/hb_clock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef HERB_CLOCK_H
#define HERB_CLOCK_H

#include <stdint.h>
#include <time.h>

static inline uint64_t hb_monotonic_ms(void) {
struct timespec now;

clock_gettime(CLOCK_MONOTONIC, &now);

return (uint64_t) now.tv_sec * 1000 + (uint64_t) now.tv_nsec / 1000000;
}

#endif
15 changes: 15 additions & 0 deletions src/include/parser/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "../lexer/lexer.h"
#include "../lib/hb_allocator.h"
#include "../lib/hb_array.h"
#include "../lib/hb_clock.h"

#include <stdint.h>

Expand Down Expand Up @@ -33,6 +34,8 @@ typedef struct PARSER_OPTIONS_STRUCT {
bool html;
uint32_t start_line;
uint32_t start_column;
uint32_t timeout_ms;
uint64_t deadline_ms;
} parser_options_T;

typedef struct MATCH_TAGS_CONTEXT_STRUCT {
Expand All @@ -43,6 +46,18 @@ typedef struct MATCH_TAGS_CONTEXT_STRUCT {

extern const parser_options_T HERB_DEFAULT_PARSER_OPTIONS;

static inline bool parser_options_past_deadline(const parser_options_T* options) {
if (options == NULL || options->timeout_ms == 0) { return false; }

return hb_monotonic_ms() >= options->deadline_ms;
}

static inline void parser_options_set_deadline(parser_options_T* options) {
if (options->timeout_ms == 0) { return; }

options->deadline_ms = hb_monotonic_ms() + options->timeout_ms;
}

typedef struct PARSER_STRUCT {
hb_allocator_T* allocator;
lexer_T* lexer;
Expand Down
Loading
Loading