liquid-c 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/liquid.yml +24 -2
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +14 -5
  6. data/README.md +29 -5
  7. data/Rakefile +13 -62
  8. data/ext/liquid_c/block.c +488 -60
  9. data/ext/liquid_c/block.h +28 -2
  10. data/ext/liquid_c/c_buffer.c +42 -0
  11. data/ext/liquid_c/c_buffer.h +76 -0
  12. data/ext/liquid_c/context.c +233 -0
  13. data/ext/liquid_c/context.h +70 -0
  14. data/ext/liquid_c/document_body.c +89 -0
  15. data/ext/liquid_c/document_body.h +59 -0
  16. data/ext/liquid_c/expression.c +116 -0
  17. data/ext/liquid_c/expression.h +24 -0
  18. data/ext/liquid_c/extconf.rb +19 -9
  19. data/ext/liquid_c/intutil.h +22 -0
  20. data/ext/liquid_c/lexer.c +6 -2
  21. data/ext/liquid_c/lexer.h +18 -3
  22. data/ext/liquid_c/liquid.c +76 -6
  23. data/ext/liquid_c/liquid.h +24 -1
  24. data/ext/liquid_c/parse_context.c +76 -0
  25. data/ext/liquid_c/parse_context.h +13 -0
  26. data/ext/liquid_c/parser.c +141 -65
  27. data/ext/liquid_c/parser.h +4 -2
  28. data/ext/liquid_c/raw.c +110 -0
  29. data/ext/liquid_c/raw.h +6 -0
  30. data/ext/liquid_c/resource_limits.c +279 -0
  31. data/ext/liquid_c/resource_limits.h +23 -0
  32. data/ext/liquid_c/stringutil.h +44 -0
  33. data/ext/liquid_c/tokenizer.c +149 -35
  34. data/ext/liquid_c/tokenizer.h +20 -9
  35. data/ext/liquid_c/usage.c +18 -0
  36. data/ext/liquid_c/usage.h +9 -0
  37. data/ext/liquid_c/variable.c +196 -20
  38. data/ext/liquid_c/variable.h +18 -1
  39. data/ext/liquid_c/variable_lookup.c +44 -0
  40. data/ext/liquid_c/variable_lookup.h +8 -0
  41. data/ext/liquid_c/vm.c +588 -0
  42. data/ext/liquid_c/vm.h +25 -0
  43. data/ext/liquid_c/vm_assembler.c +491 -0
  44. data/ext/liquid_c/vm_assembler.h +240 -0
  45. data/ext/liquid_c/vm_assembler_pool.c +97 -0
  46. data/ext/liquid_c/vm_assembler_pool.h +27 -0
  47. data/lib/liquid/c/compile_ext.rb +44 -0
  48. data/lib/liquid/c/version.rb +3 -1
  49. data/lib/liquid/c.rb +225 -46
  50. data/liquid-c.gemspec +16 -10
  51. data/performance/c_profile.rb +23 -0
  52. data/performance.rb +6 -4
  53. data/rakelib/compile.rake +15 -0
  54. data/rakelib/integration_test.rake +43 -0
  55. data/rakelib/performance.rake +43 -0
  56. data/rakelib/rubocop.rake +6 -0
  57. data/rakelib/unit_test.rake +14 -0
  58. data/test/integration_test.rb +11 -0
  59. data/test/liquid_test_helper.rb +21 -0
  60. data/test/test_helper.rb +14 -2
  61. data/test/unit/block_test.rb +130 -0
  62. data/test/unit/context_test.rb +83 -0
  63. data/test/unit/expression_test.rb +186 -0
  64. data/test/unit/gc_stress_test.rb +28 -0
  65. data/test/unit/raw_test.rb +19 -0
  66. data/test/unit/resource_limits_test.rb +50 -0
  67. data/test/unit/tokenizer_test.rb +90 -20
  68. data/test/unit/variable_test.rb +212 -60
  69. metadata +59 -11
  70. data/test/liquid_test.rb +0 -11
@@ -0,0 +1,110 @@
1
+ #include "liquid.h"
2
+ #include "raw.h"
3
+ #include "stringutil.h"
4
+ #include "tokenizer.h"
5
+
6
+ static VALUE id_block_name, id_raise_tag_never_closed, id_block_delimiter, id_ivar_body;
7
+ static VALUE cLiquidRaw;
8
+
9
+ struct full_token_possibly_invalid_t {
10
+ long body_len;
11
+ const char *delimiter_start;
12
+ long delimiter_len;
13
+ };
14
+
15
+ static bool match_full_token_possibly_invalid(token_t *token, struct full_token_possibly_invalid_t *match)
16
+ {
17
+ const char *str = token->str_full;
18
+ long len = token->len_full;
19
+
20
+ match->body_len = 0;
21
+ match->delimiter_start = NULL;
22
+ match->delimiter_len = 0;
23
+
24
+ if (len < 5) return false; // Must be at least 5 characters: \{%\w%\}
25
+ if (str[len - 1] != '}' || str[len - 2] != '%') return false;
26
+
27
+ const char *curr_delimiter_start;
28
+ long curr_delimiter_len = 0;
29
+
30
+ for (long i = len - 3; i >= 0; i--) {
31
+ char c = str[i];
32
+
33
+ if (is_word_char(c)) {
34
+ curr_delimiter_start = str + i;
35
+ curr_delimiter_len++;
36
+ } else {
37
+ if (curr_delimiter_len > 0) {
38
+ match->delimiter_start = curr_delimiter_start;
39
+ match->delimiter_len = curr_delimiter_len;
40
+ }
41
+ curr_delimiter_start = NULL;
42
+ curr_delimiter_len = 0;
43
+ }
44
+
45
+ if (c == '%' && match->delimiter_len > 0 &&
46
+ i - 1 >= 0 && str[i - 1] == '{') {
47
+ match->body_len = i - 1;
48
+ return true;
49
+ }
50
+ }
51
+
52
+ return false;
53
+ }
54
+
55
+ static VALUE raw_parse_method(VALUE self, VALUE tokens)
56
+ {
57
+ tokenizer_t *tokenizer;
58
+ Tokenizer_Get_Struct(tokens, tokenizer);
59
+
60
+ token_t token;
61
+ struct full_token_possibly_invalid_t match;
62
+
63
+ VALUE block_delimiter = rb_funcall(self, id_block_delimiter, 0);
64
+ Check_Type(block_delimiter, T_STRING);
65
+ char *block_delimiter_str = RSTRING_PTR(block_delimiter);
66
+ long block_delimiter_len = RSTRING_LEN(block_delimiter);
67
+
68
+ const char *body = NULL;
69
+ long body_len = 0;
70
+
71
+ while (true) {
72
+ tokenizer_next(tokenizer, &token);
73
+
74
+ if (!token.type) break;
75
+
76
+ if (body == NULL) {
77
+ body = token.str_full;
78
+ }
79
+
80
+ if (match_full_token_possibly_invalid(&token, &match)
81
+ && match.delimiter_len == block_delimiter_len
82
+ && memcmp(match.delimiter_start, block_delimiter_str, block_delimiter_len) == 0) {
83
+ body_len += match.body_len;
84
+ VALUE body_str = rb_enc_str_new(body, body_len, utf8_encoding);
85
+ rb_ivar_set(self, id_ivar_body, body_str);
86
+ if (RBASIC_CLASS(self) == cLiquidRaw) {
87
+ tokenizer->raw_tag_body = RSTRING_PTR(body_str);
88
+ tokenizer->raw_tag_body_len = (unsigned int)body_len;
89
+ }
90
+ return Qnil;
91
+ }
92
+
93
+ body_len += token.len_full;
94
+ }
95
+
96
+ rb_funcall(self, id_raise_tag_never_closed, 1, rb_funcall(self, id_block_name, 0));
97
+ return Qnil;
98
+ }
99
+
100
+ void liquid_define_raw(void)
101
+ {
102
+ id_block_name = rb_intern("block_name");
103
+ id_raise_tag_never_closed = rb_intern("raise_tag_never_closed");
104
+ id_block_delimiter = rb_intern("block_delimiter");
105
+ id_ivar_body = rb_intern("@body");
106
+
107
+ cLiquidRaw = rb_const_get(mLiquid, rb_intern("Raw"));
108
+
109
+ rb_define_method(cLiquidRaw, "c_parse", raw_parse_method, 1);
110
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef LIQUID_RAW_H
2
+ #define LIQUID_RAW_H
3
+
4
+ void liquid_define_raw(void);
5
+
6
+ #endif
@@ -0,0 +1,279 @@
1
+ #include "liquid.h"
2
+ #include "resource_limits.h"
3
+
4
+ VALUE cLiquidResourceLimits;
5
+
6
+ static void resource_limits_free(void *ptr)
7
+ {
8
+ resource_limits_t *resource_limits = ptr;
9
+ xfree(resource_limits);
10
+ }
11
+
12
+ static size_t resource_limits_memsize(const void *ptr)
13
+ {
14
+ return sizeof(resource_limits_t);
15
+ }
16
+
17
+ const rb_data_type_t resource_limits_data_type = {
18
+ "liquid_resource_limits",
19
+ { NULL, resource_limits_free, resource_limits_memsize },
20
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
21
+ };
22
+
23
+ static void resource_limits_reset(resource_limits_t *resource_limit)
24
+ {
25
+ resource_limit->reached_limit = true;
26
+ resource_limit->last_capture_length = -1;
27
+ resource_limit->render_score = 0;
28
+ resource_limit->assign_score = 0;
29
+ }
30
+
31
+ static VALUE resource_limits_allocate(VALUE klass)
32
+ {
33
+ resource_limits_t *resource_limits;
34
+
35
+ VALUE obj = TypedData_Make_Struct(klass, resource_limits_t, &resource_limits_data_type, resource_limits);
36
+
37
+ resource_limits_reset(resource_limits);
38
+
39
+ return obj;
40
+ }
41
+
42
+ static VALUE resource_limits_render_length_limit_method(VALUE self)
43
+ {
44
+ resource_limits_t *resource_limits;
45
+ ResourceLimits_Get_Struct(self, resource_limits);
46
+
47
+ return LONG2NUM(resource_limits->render_length_limit);
48
+ }
49
+
50
+ static VALUE resource_limits_set_render_length_limit_method(VALUE self, VALUE render_length_limit)
51
+ {
52
+ resource_limits_t *resource_limits;
53
+ ResourceLimits_Get_Struct(self, resource_limits);
54
+
55
+ if (render_length_limit == Qnil) {
56
+ resource_limits->render_length_limit = LONG_MAX;
57
+ } else {
58
+ resource_limits->render_length_limit = NUM2LONG(render_length_limit);
59
+ }
60
+
61
+ return Qnil;
62
+ }
63
+
64
+ static VALUE resource_limits_render_score_limit_method(VALUE self)
65
+ {
66
+ resource_limits_t *resource_limits;
67
+ ResourceLimits_Get_Struct(self, resource_limits);
68
+
69
+ return LONG2NUM(resource_limits->render_score_limit);
70
+ }
71
+
72
+ static VALUE resource_limits_set_render_score_limit_method(VALUE self, VALUE render_score_limit)
73
+ {
74
+ resource_limits_t *resource_limits;
75
+ ResourceLimits_Get_Struct(self, resource_limits);
76
+
77
+ if (render_score_limit == Qnil) {
78
+ resource_limits->render_score_limit = LONG_MAX;
79
+ } else {
80
+ resource_limits->render_score_limit = NUM2LONG(render_score_limit);
81
+ }
82
+
83
+ return Qnil;
84
+ }
85
+
86
+ static VALUE resource_limits_assign_score_limit_method(VALUE self)
87
+ {
88
+ resource_limits_t *resource_limits;
89
+ ResourceLimits_Get_Struct(self, resource_limits);
90
+
91
+ return LONG2NUM(resource_limits->assign_score_limit);
92
+ }
93
+
94
+ static VALUE resource_limits_set_assign_score_limit_method(VALUE self, VALUE assign_score_limit)
95
+ {
96
+ resource_limits_t *resource_limits;
97
+ ResourceLimits_Get_Struct(self, resource_limits);
98
+
99
+ if (assign_score_limit == Qnil) {
100
+ resource_limits->assign_score_limit = LONG_MAX;
101
+ } else {
102
+ resource_limits->assign_score_limit = NUM2LONG(assign_score_limit);
103
+ }
104
+
105
+ return Qnil;
106
+ }
107
+
108
+ static VALUE resource_limits_render_score_method(VALUE self)
109
+ {
110
+ resource_limits_t *resource_limits;
111
+ ResourceLimits_Get_Struct(self, resource_limits);
112
+
113
+ return LONG2NUM(resource_limits->render_score);
114
+ }
115
+
116
+ static VALUE resource_limits_assign_score_method(VALUE self)
117
+ {
118
+ resource_limits_t *resource_limits;
119
+ ResourceLimits_Get_Struct(self, resource_limits);
120
+
121
+ return LONG2NUM(resource_limits->assign_score);
122
+ }
123
+
124
+ static VALUE resource_limits_initialize_method(VALUE self, VALUE render_length_limit,
125
+ VALUE render_score_limit, VALUE assign_score_limit)
126
+ {
127
+ resource_limits_set_render_length_limit_method(self, render_length_limit);
128
+ resource_limits_set_render_score_limit_method(self, render_score_limit);
129
+ resource_limits_set_assign_score_limit_method(self, assign_score_limit);
130
+
131
+ return Qnil;
132
+ }
133
+
134
+ __attribute__((noreturn))
135
+ void resource_limits_raise_limits_reached(resource_limits_t *resource_limit)
136
+ {
137
+ resource_limit->reached_limit = true;
138
+ rb_raise(cMemoryError, "Memory limits exceeded");
139
+ }
140
+
141
+ void resource_limits_increment_render_score(resource_limits_t *resource_limits, long amount)
142
+ {
143
+ resource_limits->render_score = resource_limits->render_score + amount;
144
+
145
+ if (resource_limits->render_score > resource_limits->render_score_limit) {
146
+ resource_limits_raise_limits_reached(resource_limits);
147
+ }
148
+ }
149
+
150
+ static VALUE resource_limits_increment_render_score_method(VALUE self, VALUE amount)
151
+ {
152
+ resource_limits_t *resource_limits;
153
+ ResourceLimits_Get_Struct(self, resource_limits);
154
+
155
+ resource_limits_increment_render_score(resource_limits, NUM2LONG(amount));
156
+
157
+ return Qnil;
158
+ }
159
+
160
+ static void resource_limits_increment_assign_score(resource_limits_t *resource_limits, long amount)
161
+ {
162
+ resource_limits->assign_score = resource_limits->assign_score + amount;
163
+
164
+ if (resource_limits->assign_score > resource_limits->assign_score_limit) {
165
+ resource_limits_raise_limits_reached(resource_limits);
166
+ }
167
+ }
168
+
169
+ static VALUE resource_limits_increment_assign_score_method(VALUE self, VALUE amount)
170
+ {
171
+ resource_limits_t *resource_limits;
172
+ ResourceLimits_Get_Struct(self, resource_limits);
173
+
174
+ resource_limits_increment_assign_score(resource_limits, NUM2LONG(amount));
175
+
176
+ return Qnil;
177
+ }
178
+
179
+ void resource_limits_increment_write_score(resource_limits_t *resource_limits, VALUE output)
180
+ {
181
+ long captured = RSTRING_LEN(output);
182
+
183
+ if (resource_limits->last_capture_length >= 0) {
184
+ long increment = captured - resource_limits->last_capture_length;
185
+ resource_limits->last_capture_length = captured;
186
+ resource_limits_increment_assign_score(resource_limits, increment);
187
+ } else if (captured > resource_limits->render_length_limit) {
188
+ resource_limits_raise_limits_reached(resource_limits);
189
+ }
190
+ }
191
+
192
+ static VALUE resource_limits_increment_write_score_method(VALUE self, VALUE output)
193
+ {
194
+ Check_Type(output, T_STRING);
195
+
196
+ resource_limits_t *resource_limits;
197
+ ResourceLimits_Get_Struct(self, resource_limits);
198
+
199
+ resource_limits_increment_write_score(resource_limits, output);
200
+
201
+ return Qnil;
202
+ }
203
+
204
+ static VALUE resource_limits_raise_limits_reached_method(VALUE self)
205
+ {
206
+ resource_limits_t *resource_limits;
207
+ ResourceLimits_Get_Struct(self, resource_limits);
208
+
209
+ resource_limits_raise_limits_reached(resource_limits);
210
+ }
211
+
212
+ static VALUE resource_limits_reached_method(VALUE self)
213
+ {
214
+ resource_limits_t *resource_limits;
215
+ ResourceLimits_Get_Struct(self, resource_limits);
216
+
217
+ return resource_limits->reached_limit ? Qtrue : Qfalse;
218
+ }
219
+
220
+ struct capture_ensure_t {
221
+ resource_limits_t *resource_limits;
222
+ long old_capture_length;
223
+ };
224
+
225
+ static VALUE capture_ensure(VALUE data)
226
+ {
227
+ struct capture_ensure_t *ensure_data = (struct capture_ensure_t *)data;
228
+ ensure_data->resource_limits->last_capture_length = ensure_data->old_capture_length;
229
+
230
+ return Qnil;
231
+ }
232
+
233
+ static VALUE resource_limits_with_capture_method(VALUE self)
234
+ {
235
+ resource_limits_t *resource_limits;
236
+ ResourceLimits_Get_Struct(self, resource_limits);
237
+
238
+ struct capture_ensure_t ensure_data = {
239
+ .resource_limits = resource_limits,
240
+ .old_capture_length = resource_limits->last_capture_length
241
+ };
242
+
243
+ resource_limits->last_capture_length = 0;
244
+
245
+ return rb_ensure(rb_yield, Qundef, capture_ensure, (VALUE)&ensure_data);
246
+ }
247
+
248
+
249
+ static VALUE resource_limits_reset_method(VALUE self)
250
+ {
251
+ resource_limits_t *resource_limits;
252
+ ResourceLimits_Get_Struct(self, resource_limits);
253
+ resource_limits_reset(resource_limits);
254
+ return Qnil;
255
+ }
256
+
257
+ void liquid_define_resource_limits(void)
258
+ {
259
+ cLiquidResourceLimits = rb_define_class_under(mLiquidC, "ResourceLimits", rb_cObject);
260
+ rb_global_variable(&cLiquidResourceLimits);
261
+
262
+ rb_define_alloc_func(cLiquidResourceLimits, resource_limits_allocate);
263
+ rb_define_method(cLiquidResourceLimits, "initialize", resource_limits_initialize_method, 3);
264
+ rb_define_method(cLiquidResourceLimits, "render_length_limit", resource_limits_render_length_limit_method, 0);
265
+ rb_define_method(cLiquidResourceLimits, "render_length_limit=", resource_limits_set_render_length_limit_method, 1);
266
+ rb_define_method(cLiquidResourceLimits, "render_score_limit", resource_limits_render_score_limit_method, 0);
267
+ rb_define_method(cLiquidResourceLimits, "render_score_limit=", resource_limits_set_render_score_limit_method, 1);
268
+ rb_define_method(cLiquidResourceLimits, "assign_score_limit", resource_limits_assign_score_limit_method, 0);
269
+ rb_define_method(cLiquidResourceLimits, "assign_score_limit=", resource_limits_set_assign_score_limit_method, 1);
270
+ rb_define_method(cLiquidResourceLimits, "render_score", resource_limits_render_score_method, 0);
271
+ rb_define_method(cLiquidResourceLimits, "assign_score", resource_limits_assign_score_method, 0);
272
+ rb_define_method(cLiquidResourceLimits, "increment_render_score", resource_limits_increment_render_score_method, 1);
273
+ rb_define_method(cLiquidResourceLimits, "increment_assign_score", resource_limits_increment_assign_score_method, 1);
274
+ rb_define_method(cLiquidResourceLimits, "increment_write_score", resource_limits_increment_write_score_method, 1);
275
+ rb_define_method(cLiquidResourceLimits, "raise_limits_reached", resource_limits_raise_limits_reached_method, 0);
276
+ rb_define_method(cLiquidResourceLimits, "reached?", resource_limits_reached_method, 0);
277
+ rb_define_method(cLiquidResourceLimits, "reset", resource_limits_reset_method, 0);
278
+ rb_define_method(cLiquidResourceLimits, "with_capture", resource_limits_with_capture_method, 0);
279
+ }
@@ -0,0 +1,23 @@
1
+ #ifndef LIQUID_RESOURCE_LIMITS
2
+ #define LIQUID_RESOURCE_LIMITS
3
+
4
+ typedef struct resource_limits {
5
+ long render_length_limit;
6
+ long render_score_limit;
7
+ long assign_score_limit;
8
+ bool reached_limit;
9
+ long last_capture_length;
10
+ long render_score;
11
+ long assign_score;
12
+ } resource_limits_t;
13
+
14
+ extern VALUE cLiquidResourceLimits;
15
+ extern const rb_data_type_t resource_limits_data_type;
16
+ #define ResourceLimits_Get_Struct(obj, sval) TypedData_Get_Struct(obj, resource_limits_t, &resource_limits_data_type, sval)
17
+
18
+ void liquid_define_resource_limits(void);
19
+ void resource_limits_raise_limits_reached(resource_limits_t *resource_limit);
20
+ void resource_limits_increment_render_score(resource_limits_t *resource_limits, long amount);
21
+ void resource_limits_increment_write_score(resource_limits_t *resource_limits, VALUE output);
22
+
23
+ #endif
@@ -0,0 +1,44 @@
1
+ #if !defined(LIQUID_UTIL_H)
2
+ #define LIQUID_UTIL_H
3
+
4
+ inline static const char *read_while(const char *start, const char *end, int (func)(int))
5
+ {
6
+ while (start < end && func((unsigned char) *start)) start++;
7
+ return start;
8
+ }
9
+
10
+ inline static const char *read_while_reverse(const char *start, const char *end, int (func)(int))
11
+ {
12
+ end--;
13
+ while (start <= end && func((unsigned char) *end)) end--;
14
+ end++;
15
+ return end;
16
+ }
17
+
18
+ inline static int count_newlines(const char *start, const char *end)
19
+ {
20
+ int count = 0;
21
+ while (start < end) {
22
+ if (*start == '\n') count++;
23
+ start++;
24
+ }
25
+ return count;
26
+ }
27
+
28
+ inline static int is_non_newline_space(int c)
29
+ {
30
+ return rb_isspace(c) && c != '\n';
31
+ }
32
+
33
+ inline static int not_newline(int c)
34
+ {
35
+ return c != '\n';
36
+ }
37
+
38
+ inline static bool is_word_char(char c)
39
+ {
40
+ return ISALNUM(c) || c == '_';
41
+ }
42
+
43
+ #endif
44
+