ucl 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/ucl.c CHANGED
@@ -2,11 +2,21 @@
2
2
  #include <ruby/io.h>
3
3
  #include <ucl.h>
4
4
  #include <stdio.h>
5
+ #include <stdlib.h>
5
6
  #include <stdbool.h>
6
7
 
7
8
  /* Fake flag */
8
9
  #define UCL_PARSER_KEY_SYMBOL (1 << 12)
9
10
 
11
+ /* Deepest object nesting accepted when converting a parsed tree to Ruby
12
+ * objects. Both the conversion below and libucl's own tree destructor walk
13
+ * children recursively, with nothing but the C stack bounding them: a 1 MiB
14
+ * thread stack is gone at roughly 15000 levels. Real configurations nest a
15
+ * handful of levels (Ruby's own JSON parser stops at 100), so anything
16
+ * deeper is refused with UCL::Error instead of being left to overflow the
17
+ * stack. */
18
+ #define UCL_MAX_NESTING 1000
19
+
10
20
 
11
21
  /**
12
22
  * Document-class: UCL
@@ -17,6 +27,11 @@
17
27
  * Parsed configurations are returned as plain Ruby objects (Hash, Array,
18
28
  * String, Integer, Float, true/false, nil).
19
29
  *
30
+ * Objects nested more than 1000 levels deep are rejected with {UCL::Error};
31
+ * see {UCL.parse}. Input that is both that deeply nested *and* malformed is
32
+ * the one case this cannot cover: libucl releases its half-built tree
33
+ * recursively, out of reach here, and a SystemStackError comes out instead.
34
+ *
20
35
  * @example Parse a string
21
36
  * UCL.parse('name = value') #=> { "name" => "value" }
22
37
  *
@@ -71,89 +86,274 @@ static int ucl_allowed_c_flags = UCL_PARSER_KEY_LOWERCASE |
71
86
  UCL_PARSER_NO_FILEVARS ;
72
87
 
73
88
 
89
+ /* State threaded through a conversion.
90
+ *
91
+ * `iters` points at an array of UCL_MAX_NESTING slots living in the frame
92
+ * that started the conversion: each level parks its iterator there, so an
93
+ * exception unwinding the C stack does not strand them. `max_depth` is the
94
+ * deepest slot ever written; every shallower slot has been written too (a
95
+ * container at depth d sits inside a container at every depth above it),
96
+ * which is what lets the error path sweep exactly iters[0..max_depth]
97
+ * without having to clear the array up front. */
98
+ struct ucl_conv {
99
+ int flags;
100
+ bool failed;
101
+ int max_depth;
102
+ ucl_object_iter_t *iters;
103
+ };
104
+
74
105
 
75
106
  static VALUE
76
- _iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
107
+ _iterate_valid_ucl(struct ucl_conv *cv, ucl_object_t const *root, int depth)
77
108
  {
78
109
  ucl_object_iter_t it = NULL; /* only allocated for objects/arrays */
79
110
  const ucl_object_t *obj = NULL;
80
111
 
81
112
  VALUE val = Qnil;
82
113
 
114
+ /* Bound the recursion (see UCL_MAX_NESTING). Checked before this level
115
+ * allocates its iterator, so the raise cannot strand one. */
116
+ if (depth >= UCL_MAX_NESTING)
117
+ rb_raise(eUCLError, "nesting deeper than %d levels", UCL_MAX_NESTING);
118
+
83
119
  switch (root->type) {
84
120
  case UCL_INT:
85
121
  val = rb_ll2inum((long long)ucl_object_toint(root));
86
122
  break;
87
-
123
+
88
124
  case UCL_FLOAT:
89
125
  val = rb_float_new(ucl_object_todouble(root));
90
126
  break;
91
-
127
+
92
128
  case UCL_STRING: {
93
129
  size_t len;
94
130
  const char *str = ucl_object_tolstring(root, &len);
95
131
  val = rb_str_new(str, len);
96
132
  break;
97
133
  }
98
-
134
+
99
135
  case UCL_BOOLEAN:
100
136
  val = ucl_object_toboolean(root) ? Qtrue : Qfalse;
101
137
  break;
102
-
138
+
103
139
  case UCL_TIME:
104
140
  val = rb_float_new(ucl_object_todouble(root));
105
141
  break;
106
-
142
+
107
143
  case UCL_OBJECT: {
108
144
  bool iterated = false;
109
145
  val = rb_hash_new();
110
146
  it = ucl_object_iterate_new(root);
147
+ if (it == NULL)
148
+ rb_raise(eUCLError, "failed to allocate UCL iterator");
149
+ cv->iters[depth] = it;
150
+ if (depth > cv->max_depth) cv->max_depth = depth;
111
151
  while ((obj = ucl_object_iterate_safe(it, !true))) {
112
152
  iterated = true;
113
153
  size_t keylen;
114
154
  const char *key = ucl_object_keyl(obj, &keylen);
115
155
  VALUE v_key = rb_str_new(key, keylen);
116
- if (flags & UCL_PARSER_KEY_SYMBOL)
156
+ if (cv->flags & UCL_PARSER_KEY_SYMBOL)
117
157
  v_key = rb_to_symbol(v_key);
118
- rb_hash_aset(val, v_key, _iterate_valid_ucl(obj, flags, failed));
158
+ rb_hash_aset(val, v_key, _iterate_valid_ucl(cv, obj, depth + 1));
119
159
  }
120
160
  /* An empty object has a NULL hash that the safe iterator reports as an
121
161
  * exception (EINVAL); ignore that and only flag a genuine error that
122
162
  * occurs while iterating. Accumulate so a nested failure deeper in the
123
163
  * tree is never cleared by a successful parent iteration. */
124
- if (iterated && ucl_object_iter_chk_excpn(it)) *failed = true;
164
+ if (iterated && ucl_object_iter_chk_excpn(it)) cv->failed = true;
125
165
  break;
126
166
  }
127
167
 
128
168
  case UCL_ARRAY: {
129
169
  bool iterated = false;
130
170
  val = rb_ary_new();
131
- it = ucl_object_iterate_new(root);
171
+ it = ucl_object_iterate_new(root);
172
+ if (it == NULL)
173
+ rb_raise(eUCLError, "failed to allocate UCL iterator");
174
+ cv->iters[depth] = it;
175
+ if (depth > cv->max_depth) cv->max_depth = depth;
132
176
  while ((obj = ucl_object_iterate_safe(it, !true))) {
133
177
  iterated = true;
134
- rb_ary_push(val, _iterate_valid_ucl(obj, flags, failed));
178
+ rb_ary_push(val, _iterate_valid_ucl(cv, obj, depth + 1));
135
179
  }
136
- if (iterated && ucl_object_iter_chk_excpn(it)) *failed = true;
180
+ if (iterated && ucl_object_iter_chk_excpn(it)) cv->failed = true;
137
181
  break;
138
182
  }
139
-
183
+
140
184
  case UCL_USERDATA:
141
185
  val = rb_str_new(root->value.sv, root->len);
142
186
  break;
143
-
187
+
144
188
  case UCL_NULL:
145
189
  val = Qnil;
146
190
  break;
147
-
191
+
148
192
  default:
149
193
  rb_raise(eUCLError, "unhandled UCL type (%d)", root->type);
150
194
 
151
195
  }
152
196
 
153
- if (it != NULL) ucl_object_iterate_free(it);
197
+ if (it != NULL) {
198
+ ucl_object_iterate_free(it);
199
+ cv->iters[depth] = NULL;
200
+ }
154
201
  return val;
155
202
  }
156
203
 
204
+
205
+ /* Remove one child from `top` and hand it back with its reference
206
+ * transferred to the caller; NULL once `top` holds no child any more. */
207
+ static ucl_object_t *
208
+ _ucl_detach_child(ucl_object_t *top)
209
+ {
210
+ if (top->type == UCL_ARRAY)
211
+ return ucl_array_pop_last(top);
212
+
213
+ if (top->type == UCL_OBJECT) {
214
+ ucl_object_iter_t it = ucl_object_iterate_new(top);
215
+ const ucl_object_t *first = (it == NULL) ? NULL
216
+ : ucl_object_iterate_safe(it, !true);
217
+ const char *key = NULL;
218
+ size_t keylen = 0;
219
+
220
+ if (first != NULL) key = ucl_object_keyl(first, &keylen);
221
+ if (it != NULL) ucl_object_iterate_free(it);
222
+ if (key == NULL) return NULL;
223
+ /* `key` points into `first`, which `top` still holds a reference to
224
+ * until the pop below hands it over. The iterator is released first
225
+ * because the pop mutates the object it was iterating. */
226
+ return ucl_object_pop_keyl(top, key, keylen);
227
+ }
228
+
229
+ return NULL;
230
+ }
231
+
232
+
233
+ /* Append `obj` to the worklist, growing it as needed; false only when the
234
+ * allocation fails. */
235
+ static bool
236
+ _ucl_worklist_push(ucl_object_t ***work, size_t *len, size_t *cap,
237
+ ucl_object_t *obj)
238
+ {
239
+ if (*len == *cap) {
240
+ size_t ncap = (*cap == 0) ? 64 : *cap * 2;
241
+ ucl_object_t **narr = realloc(*work, ncap * sizeof(*narr));
242
+ if (narr == NULL) return false;
243
+ *work = narr;
244
+ *cap = ncap;
245
+ }
246
+ (*work)[(*len)++] = obj;
247
+ return true;
248
+ }
249
+
250
+
251
+ /* Destroy everything below `root`, leaving `root` itself alive but empty,
252
+ * without recursing.
253
+ *
254
+ * libucl's destructor walks children recursively, so releasing a tree
255
+ * deeper than the C stack allows overflows it -- and that is exactly the
256
+ * tree UCL_MAX_NESTING refuses to convert, which would otherwise turn a
257
+ * clean UCL::Error into a stack overflow inside libucl. Children are
258
+ * detached onto a heap worklist and released once they are childless, so
259
+ * only the worklist grows with the tree. Once this returns, releasing
260
+ * `root` is O(1) and safe. */
261
+ static void
262
+ ucl_tree_dismantle(ucl_object_t *root)
263
+ {
264
+ ucl_object_t **work = NULL;
265
+ size_t len = 0;
266
+ size_t cap = 0;
267
+ ucl_object_t *cur = root;
268
+
269
+ if (root == NULL) return;
270
+
271
+ for (;;) {
272
+ ucl_object_t *child;
273
+
274
+ while ((child = _ucl_detach_child(cur)) != NULL) {
275
+ if (!_ucl_worklist_push(&work, &len, &cap, child)) {
276
+ /* Out of memory: fall back to the recursive release. */
277
+ ucl_object_unref(child);
278
+ }
279
+ }
280
+
281
+ if (cur != root) ucl_object_unref(cur); /* childless now: O(1) */
282
+ if (len == 0) break;
283
+ cur = work[--len];
284
+ }
285
+
286
+ free(work);
287
+ }
288
+
289
+
290
+ struct ucl_conv_args {
291
+ struct ucl_conv *cv;
292
+ const ucl_object_t *root;
293
+ };
294
+
295
+ static VALUE
296
+ _ucl_convert_root(VALUE arg)
297
+ {
298
+ struct ucl_conv_args *a = (struct ucl_conv_args *)arg;
299
+ return _iterate_valid_ucl(a->cv, a->root, 0);
300
+ }
301
+
302
+
303
+ /* Convert the tree held by `parser` into Ruby objects.
304
+ *
305
+ * Takes ownership of `parser`: it is released on every path out, including
306
+ * the ones where the conversion is unwound by an exception -- letting one
307
+ * escape would leak the parser and the whole tree it holds. */
308
+ static VALUE
309
+ ucl_parser_result(struct ucl_parser *parser, int flags)
310
+ {
311
+ ucl_object_iter_t iters[UCL_MAX_NESTING];
312
+ struct ucl_conv cv = { .flags = flags,
313
+ .failed = false,
314
+ .max_depth = -1,
315
+ .iters = iters };
316
+ ucl_object_t *root;
317
+ VALUE res;
318
+ int state = 0;
319
+
320
+ if (ucl_parser_get_error(parser)) {
321
+ /* Copy the message into the exception before freeing the parser:
322
+ * ucl_parser_get_error() points into memory owned by the parser. */
323
+ VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
324
+ ucl_parser_free(parser);
325
+ rb_exc_raise(err);
326
+ }
327
+
328
+ root = ucl_parser_get_object(parser);
329
+ if (root == NULL) {
330
+ ucl_parser_free(parser);
331
+ rb_raise(eUCLError, "parser produced no object");
332
+ }
333
+
334
+ struct ucl_conv_args args = { &cv, root };
335
+ res = rb_protect(_ucl_convert_root, (VALUE)&args, &state);
336
+
337
+ if (state != 0) {
338
+ /* Unwound by an exception: release the iterators the conversion was
339
+ * still holding, then take the tree apart iteratively, since it may
340
+ * be deeper than libucl's recursive destructor can walk. */
341
+ int d;
342
+ for (d = 0; d <= cv.max_depth; d++)
343
+ if (iters[d] != NULL) ucl_object_iterate_free(iters[d]);
344
+ ucl_tree_dismantle(root);
345
+ }
346
+
347
+ ucl_parser_free(parser);
348
+ ucl_object_unref(root);
349
+
350
+ if (state != 0) rb_jump_tag(state);
351
+ if (cv.failed) rb_raise(eUCLError, "failed to iterate over ucl object");
352
+
353
+ return res;
354
+ }
355
+
356
+
157
357
  /**
158
358
  * Default flags applied by {UCL.parse} and {UCL.load_file} when none are
159
359
  * given explicitly.
@@ -163,7 +363,12 @@ _iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
163
363
  static VALUE
164
364
  ucl_s_get_flags(VALUE klass)
165
365
  {
166
- return rb_iv_get(klass, "@flags");
366
+ /* @flags is a plain instance variable and so is not inherited: a
367
+ * subclass that never set its own falls back to UCL's, which is the
368
+ * value its parse/load_file would have used anyway. */
369
+ VALUE flags = rb_attr_get(klass, rb_intern("@flags"));
370
+ if (NIL_P(flags)) flags = rb_attr_get(mUCL, rb_intern("@flags"));
371
+ return flags;
167
372
  }
168
373
 
169
374
 
@@ -187,32 +392,20 @@ ucl_s_set_flags(VALUE klass, VALUE val)
187
392
  }
188
393
 
189
394
 
190
- /**
191
- * Parse a UCL configuration from a string.
192
- *
193
- * @param data [String] the UCL configuration to parse
194
- * @param flags [Integer] parsing flags combined with a bitwise OR;
195
- * defaults to {UCL.flags} when omitted
196
- *
197
- * @example
198
- * UCL.parse('name = value') #=> { "name" => "value" }
199
- * UCL.parse('name = value', UCL::KEY_SYMBOL) #=> { :name => "value" }
200
- *
201
- * @raise [UCL::Error] if the configuration is malformed
202
- *
203
- * @return [Hash, Array, Object] the configuration as Ruby objects
204
- */
395
+ /* Shared body of UCL.parse and UCL.safe_parse; `forced_flags` are the ones
396
+ * the entry point imposes whatever the caller asked for. */
205
397
  static VALUE
206
- ucl_s_parse(int argc, VALUE *argv, VALUE klass)
398
+ ucl_parse_string(int argc, VALUE *argv, VALUE klass, int forced_flags)
207
399
  {
208
400
  VALUE data, flags;
209
401
  rb_scan_args(argc, argv, "11", &data, &flags);
210
- if (NIL_P(flags)) flags = ucl_s_get_flags(mUCL);
402
+ if (NIL_P(flags)) flags = ucl_s_get_flags(klass);
211
403
 
212
404
  rb_check_type(data, T_STRING);
213
405
  rb_check_type(flags, T_FIXNUM);
214
-
215
- int c_flags = FIX2INT(flags) & ucl_allowed_c_flags;
406
+
407
+ int r_flags = FIX2INT(flags) | forced_flags;
408
+ int c_flags = r_flags & ucl_allowed_c_flags;
216
409
 
217
410
  struct ucl_parser *parser =
218
411
  ucl_parser_new(c_flags | UCL_PARSER_NO_IMPLICIT_ARRAYS);
@@ -223,26 +416,65 @@ ucl_s_parse(int argc, VALUE *argv, VALUE klass)
223
416
  (unsigned char *)RSTRING_PTR(data),
224
417
  RSTRING_LEN(data));
225
418
 
226
- if (ucl_parser_get_error(parser)) {
227
- /* Copy the message into the exception before freeing the parser:
228
- * ucl_parser_get_error() points into memory owned by the parser. */
229
- VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
230
- ucl_parser_free(parser);
231
- rb_exc_raise(err);
232
- }
419
+ return ucl_parser_result(parser, r_flags);
420
+ }
233
421
 
234
- bool failed = false;
235
- ucl_object_t *root = ucl_parser_get_object(parser);
236
- VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
237
422
 
238
- ucl_parser_free(parser);
239
- if (root != NULL) { ucl_object_unref(root); }
423
+ /**
424
+ * Parse a UCL configuration from a string.
425
+ *
426
+ * Macros are processed, so a configuration is able to pull in other files
427
+ * through <code>.include</code>. Use {UCL.safe_parse} (or the
428
+ * {UCL::DISABLE_MACRO} flag) for input that is not trusted.
429
+ *
430
+ * Objects nested more than 1000 levels deep are refused: both this
431
+ * conversion and libucl's own tree handling recurse per level, so a deeper
432
+ * tree would exhaust the C stack.
433
+ *
434
+ * @overload parse(data, flags = UCL.flags)
435
+ * @param data [String] the UCL configuration to parse
436
+ * @param flags [Integer] parsing flags combined with a bitwise OR;
437
+ * defaults to {UCL.flags} when omitted
438
+ *
439
+ * @example
440
+ * UCL.parse('name = value') #=> { "name" => "value" }
441
+ * UCL.parse('name = value', UCL::KEY_SYMBOL) #=> { :name => "value" }
442
+ *
443
+ * @raise [UCL::Error] if the configuration is malformed, or nested too deeply
444
+ *
445
+ * @return [Hash, Array, Object] the configuration as Ruby objects
446
+ */
447
+ static VALUE
448
+ ucl_s_parse(int argc, VALUE *argv, VALUE klass)
449
+ {
450
+ return ucl_parse_string(argc, argv, klass, 0);
451
+ }
240
452
 
241
- if (failed) {
242
- rb_raise(eUCLError, "failed to iterate over ucl object");
243
- }
244
453
 
245
- return res;
454
+ /**
455
+ * Parse a UCL configuration from a string with macros disabled.
456
+ *
457
+ * Identical to {UCL.parse} but always adds {UCL::DISABLE_MACRO}, so the
458
+ * configuration cannot reach outside itself through <code>.include</code>.
459
+ * This is the entry point to use for input from an untrusted source.
460
+ *
461
+ * @overload safe_parse(data, flags = UCL.flags)
462
+ * @param data [String] the UCL configuration to parse
463
+ * @param flags [Integer] parsing flags combined with a bitwise OR;
464
+ * defaults to {UCL.flags} when omitted; {UCL::DISABLE_MACRO} is added
465
+ * to whatever is given
466
+ *
467
+ * @example
468
+ * UCL.safe_parse('.include "/etc/passwd"') #=> raises UCL::Error
469
+ *
470
+ * @raise [UCL::Error] if the configuration is malformed, or nested too deeply
471
+ *
472
+ * @return [Hash, Array, Object] the configuration as Ruby objects
473
+ */
474
+ static VALUE
475
+ ucl_s_safe_parse(int argc, VALUE *argv, VALUE klass)
476
+ {
477
+ return ucl_parse_string(argc, argv, klass, UCL_PARSER_DISABLE_MACRO);
246
478
  }
247
479
 
248
480
 
@@ -253,14 +485,19 @@ ucl_s_parse(int argc, VALUE *argv, VALUE klass)
253
485
  * $CURDIR) from the loaded file, so they can be referenced from within
254
486
  * the configuration.
255
487
  *
256
- * @param file [String] path to the configuration file
257
- * @param flags [Integer] parsing flags combined with a bitwise OR;
258
- * defaults to {UCL.flags} when omitted
488
+ * Macros are processed, and the nesting limit of {UCL.parse} applies here
489
+ * too.
490
+ *
491
+ * @overload load_file(file, flags = UCL.flags)
492
+ * @param file [String] path to the configuration file
493
+ * @param flags [Integer] parsing flags combined with a bitwise OR;
494
+ * defaults to {UCL.flags} when omitted
259
495
  *
260
496
  * @example
261
497
  * UCL.load_file('foo.conf', UCL::KEY_SYMBOL)
262
498
  *
263
- * @raise [UCL::Error] if the file cannot be read or is malformed
499
+ * @raise [UCL::Error] if the file cannot be read, is malformed, or is
500
+ * nested too deeply
264
501
  *
265
502
  * @return [Hash, Array, Object] the configuration as Ruby objects
266
503
  */
@@ -269,12 +506,13 @@ ucl_s_load_file(int argc, VALUE *argv, VALUE klass)
269
506
  {
270
507
  VALUE file, flags;
271
508
  rb_scan_args(argc, argv, "11", &file, &flags);
272
- if (NIL_P(flags)) flags = ucl_s_get_flags(mUCL);
509
+ if (NIL_P(flags)) flags = ucl_s_get_flags(klass);
273
510
 
274
511
  rb_check_type(file, T_STRING);
275
512
  rb_check_type(flags, T_FIXNUM);
276
-
277
- int c_flags = FIX2INT(flags) & ucl_allowed_c_flags;
513
+
514
+ int r_flags = FIX2INT(flags);
515
+ int c_flags = r_flags & ucl_allowed_c_flags;
278
516
  char *c_file = StringValueCStr(file);
279
517
 
280
518
  struct ucl_parser *parser =
@@ -282,36 +520,18 @@ ucl_s_load_file(int argc, VALUE *argv, VALUE klass)
282
520
  if (parser == NULL)
283
521
  rb_raise(eUCLError, "failed to allocate UCL parser");
284
522
 
285
- ucl_parser_add_file(parser, c_file);
523
+ /* Before the file is read, not after: the variables are substituted
524
+ * while parsing, so setting them afterwards would have no effect at
525
+ * all. (libucl sets them from the file as well.) */
286
526
  ucl_parser_set_filevars(parser, c_file, false);
527
+ ucl_parser_add_file(parser, c_file);
287
528
 
288
- if (ucl_parser_get_error(parser)) {
289
- /* Copy the message into the exception before freeing the parser:
290
- * ucl_parser_get_error() points into memory owned by the parser. */
291
- VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
292
- ucl_parser_free(parser);
293
- rb_exc_raise(err);
294
- }
295
-
296
- bool failed = false;
297
- ucl_object_t *root = ucl_parser_get_object(parser);
298
- VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
299
-
300
- ucl_parser_free(parser);
301
- if (root != NULL) { ucl_object_unref(root); }
302
-
303
- if (failed) {
304
- rb_raise(eUCLError, "failed to iterate over ucl object");
305
- }
306
-
307
- return res;
529
+ return ucl_parser_result(parser, r_flags);
308
530
  }
309
531
 
310
532
 
311
533
 
312
534
 
313
-
314
-
315
535
  void Init_ucl(void) {
316
536
  /* Main classes */
317
537
  mUCL = rb_define_class("UCL", rb_cObject);
@@ -326,12 +546,11 @@ void Init_ucl(void) {
326
546
 
327
547
  /* Variables */
328
548
  ucl_s_set_flags(mUCL, INT2FIX(0));
329
-
549
+
330
550
  /* Definitions */
331
- rb_define_singleton_method(mUCL, "load_file", ucl_s_load_file, -1);
332
- rb_define_singleton_method(mUCL, "parse", ucl_s_parse, -1);
333
- rb_define_singleton_method(mUCL, "flags", ucl_s_get_flags, 0);
334
- rb_define_singleton_method(mUCL, "flags=", ucl_s_set_flags, 1);
551
+ rb_define_singleton_method(mUCL, "load_file", ucl_s_load_file, -1);
552
+ rb_define_singleton_method(mUCL, "parse", ucl_s_parse, -1);
553
+ rb_define_singleton_method(mUCL, "safe_parse", ucl_s_safe_parse, -1);
554
+ rb_define_singleton_method(mUCL, "flags", ucl_s_get_flags, 0);
555
+ rb_define_singleton_method(mUCL, "flags=", ucl_s_set_flags, 1);
335
556
  }
336
-
337
-