ox 2.14.28 → 2.14.29

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/ox/parse.c CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include <errno.h>
7
7
  #include <stdbool.h>
8
+ #include <stdint.h>
8
9
  #include <stdio.h>
9
10
  #include <stdlib.h>
10
11
  #include <string.h>
@@ -19,9 +20,6 @@
19
20
  #include "special.h"
20
21
 
21
22
  #define MAX_ELEMENT_DEPTH 1000
22
- // Anything even close to the max prolog length is most likely someone trying
23
- // to break something.
24
- #define MAX_PROLOG 32767
25
23
 
26
24
  static void mark_pi_cb(void *ptr);
27
25
  static void read_instruction(PInfo pi);
@@ -140,115 +138,155 @@ static void mark_pi_cb(void *ptr) {
140
138
  }
141
139
  }
142
140
 
143
- VALUE
144
- ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options, Err err) {
145
- struct _pInfo pi;
146
- int body_read = 0;
147
- int block_given = rb_block_given_p();
148
- volatile VALUE wrap;
141
+ // State shared between the parse body and its ensure block. Bundled so the
142
+ // whole parse can run under rb_ensure and clean up even when a callback raises.
143
+ typedef struct _parseCtx {
144
+ PInfo pi;
145
+ char **endp;
146
+ Err err;
147
+ VALUE wrap;
148
+ int block_given;
149
+ } *ParseCtx;
150
+
151
+ static VALUE ox_parse_body(VALUE ctxv) {
152
+ ParseCtx ctx = (ParseCtx)ctxv;
153
+ PInfo pi = ctx->pi;
154
+ char **endp = ctx->endp;
155
+ Err err = ctx->err;
156
+ int block_given = ctx->block_given;
157
+ int body_read = 0;
149
158
 
150
- if (0 == xml) {
151
- set_error(err, "Invalid arg, xml string can not be null", xml, 0);
152
- return Qnil;
153
- }
154
- if (DEBUG <= options->trace) {
155
- printf("Parsing xml:\n%s\n", xml);
156
- }
157
- // initialize parse info
158
- helper_stack_init(&pi.helpers);
159
- // Protect against GC
160
- wrap = TypedData_Wrap_Struct(rb_cObject, &ox_wrap_type, &pi);
161
-
162
- err_init(&pi.err);
163
- pi.str = xml;
164
- pi.end = pi.str + len;
165
- pi.s = xml;
166
- pi.pcb = pcb;
167
- pi.obj = Qnil;
168
- pi.circ_array = 0;
169
- pi.options = options;
170
- pi.marked = NULL;
171
- pi.mark_size = 0;
172
- pi.mark_cnt = 0;
173
159
  while (1) {
174
- next_non_white(&pi); // skip white space
175
- if ('\0' == *pi.s) {
160
+ next_non_white(pi); // skip white space
161
+ if ('\0' == *pi->s) {
176
162
  break;
177
163
  }
178
164
  if (body_read && 0 != endp) {
179
- *endp = pi.s;
165
+ *endp = pi->s;
180
166
  break;
181
167
  }
182
- if ('<' != *pi.s) { // all top level entities start with <
183
- set_error(err, "invalid format, expected <", pi.str, pi.s);
184
- helper_stack_cleanup(&pi.helpers);
168
+ if ('<' != *pi->s) { // all top level entities start with <
169
+ set_error(err, "invalid format, expected <", pi->str, pi->s);
185
170
  return Qnil;
186
171
  }
187
- pi.s++; // past <
188
- switch (*pi.s) {
172
+ pi->s++; // past <
173
+ switch (*pi->s) {
189
174
  case '?': // processing instruction
190
- pi.s++;
191
- read_instruction(&pi);
175
+ pi->s++;
176
+ read_instruction(pi);
192
177
  break;
193
178
  case '!': // comment or doctype
194
- pi.s++;
195
- if ('\0' == *pi.s) {
196
- set_error(err, "invalid format, DOCTYPE or comment not terminated", pi.str, pi.s);
197
- helper_stack_cleanup(&pi.helpers);
179
+ pi->s++;
180
+ if ('\0' == *pi->s) {
181
+ set_error(err, "invalid format, DOCTYPE or comment not terminated", pi->str, pi->s);
198
182
  return Qnil;
199
- } else if ('-' == *pi.s) {
200
- pi.s++; // skip -
201
- if ('-' != *pi.s) {
202
- set_error(err, "invalid format, bad comment format", pi.str, pi.s);
203
- helper_stack_cleanup(&pi.helpers);
183
+ } else if ('-' == *pi->s) {
184
+ pi->s++; // skip -
185
+ if ('-' != *pi->s) {
186
+ set_error(err, "invalid format, bad comment format", pi->str, pi->s);
204
187
  return Qnil;
205
188
  } else {
206
- pi.s++; // skip second -
207
- read_comment(&pi);
189
+ pi->s++; // skip second -
190
+ read_comment(pi);
208
191
  }
209
- } else if ((TolerantEffort == options->effort) ? 0 == strncasecmp("DOCTYPE", pi.s, 7)
210
- : 0 == strncmp("DOCTYPE", pi.s, 7)) {
211
- pi.s += 7;
212
- read_doctype(&pi);
192
+ } else if ((TolerantEffort == pi->options->effort) ? 0 == strncasecmp("DOCTYPE", pi->s, 7)
193
+ : 0 == strncmp("DOCTYPE", pi->s, 7)) {
194
+ pi->s += 7;
195
+ read_doctype(pi);
213
196
  } else {
214
- set_error(err, "invalid format, DOCTYPE or comment expected", pi.str, pi.s);
215
- helper_stack_cleanup(&pi.helpers);
197
+ set_error(err, "invalid format, DOCTYPE or comment expected", pi->str, pi->s);
216
198
  return Qnil;
217
199
  }
218
200
  break;
219
- case '\0':
220
- set_error(err, "invalid format, document not terminated", pi.str, pi.s);
221
- helper_stack_cleanup(&pi.helpers);
222
- return Qnil;
201
+ case '\0': set_error(err, "invalid format, document not terminated", pi->str, pi->s); return Qnil;
223
202
  default:
224
- read_element(&pi, 0);
203
+ read_element(pi, 0);
225
204
  body_read = 1;
226
205
  break;
227
206
  }
228
- if (err_has(&pi.err)) {
229
- *err = pi.err;
230
- helper_stack_cleanup(&pi.helpers);
207
+ if (err_has(&pi->err)) {
208
+ *err = pi->err;
231
209
  return Qnil;
232
210
  }
233
- if (block_given && Qnil != pi.obj && Qundef != pi.obj) {
234
- if (NULL != pcb->finish) {
235
- pcb->finish(&pi);
211
+ if (block_given && Qnil != pi->obj && Qundef != pi->obj) {
212
+ if (NULL != pi->pcb->finish) {
213
+ pi->pcb->finish(pi);
236
214
  }
237
- rb_yield(pi.obj);
215
+ rb_yield(pi->obj);
238
216
  }
239
217
  }
240
- DATA_PTR(wrap) = NULL;
241
- helper_stack_cleanup(&pi.helpers);
242
- if (NULL != pcb->finish) {
243
- pcb->finish(&pi);
218
+ if (NULL != pi->pcb->finish) {
219
+ pi->pcb->finish(pi);
244
220
  }
245
- return pi.obj;
221
+ return pi->obj;
222
+ }
223
+
224
+ static VALUE ox_parse_ensure(VALUE ctxv) {
225
+ ParseCtx ctx = (ParseCtx)ctxv;
226
+
227
+ // pi is a stack value wrapped for GC marking. Detach the wrapper before
228
+ // this frame is gone so a later conservative GC can not mark a dead helper
229
+ // stack, and free the helper stack whether the parse returned normally or a
230
+ // callback raised out of it.
231
+ if (Qnil != ctx->wrap) {
232
+ DATA_PTR(ctx->wrap) = NULL;
233
+ }
234
+ helper_stack_cleanup(&ctx->pi->helpers);
235
+ // end_element() only frees the circular reference table on a complete
236
+ // parse, so release it here for an error or a callback raising out.
237
+ if (0 != ctx->pi->circ_array) {
238
+ ox_circ_array_free(ctx->pi->circ_array);
239
+ ctx->pi->circ_array = 0;
240
+ }
241
+ // Same for hash mode's mark list, freed by finish() only if the loop ends.
242
+ xfree(ctx->pi->marked);
243
+ ctx->pi->marked = NULL;
244
+ ctx->pi->mark_size = 0;
245
+ ctx->pi->mark_cnt = 0;
246
+ return Qnil;
247
+ }
248
+
249
+ VALUE
250
+ ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options, Err err) {
251
+ struct _pInfo pi;
252
+ struct _parseCtx ctx;
253
+
254
+ if (0 == xml) {
255
+ set_error(err, "Invalid arg, xml string can not be null", xml, 0);
256
+ return Qnil;
257
+ }
258
+ if (DEBUG <= options->trace) {
259
+ printf("Parsing xml:\n%s\n", xml);
260
+ }
261
+ // initialize parse info
262
+ helper_stack_init(&pi.helpers);
263
+ err_init(&pi.err);
264
+ pi.str = xml;
265
+ pi.end = pi.str + len;
266
+ pi.s = xml;
267
+ pi.pcb = pcb;
268
+ pi.obj = Qnil;
269
+ pi.circ_array = 0;
270
+ pi.options = options;
271
+ pi.marked = NULL;
272
+ pi.mark_size = 0;
273
+ pi.mark_cnt = 0;
274
+
275
+ ctx.pi = &pi;
276
+ ctx.endp = endp;
277
+ ctx.err = err;
278
+ ctx.block_given = rb_block_given_p();
279
+ // Protect against GC. The wrapper marks the helper stack while parsing;
280
+ // ox_parse_ensure detaches it on every exit, including a callback raise.
281
+ ctx.wrap = TypedData_Wrap_Struct(rb_cObject, &ox_wrap_type, &pi);
282
+
283
+ return rb_ensure(ox_parse_body, (VALUE)&ctx, ox_parse_ensure, (VALUE)&ctx);
246
284
  }
247
285
 
248
286
  // Entered after the "<?" sequence. Ready to read the rest.
249
287
  static void read_instruction(PInfo pi) {
250
288
  char content[256];
251
- char *content_ptr;
289
+ char *content_ptr = content;
252
290
  struct _attrStack attrs;
253
291
  char *attr_name;
254
292
  char *attr_value;
@@ -262,7 +300,7 @@ static void read_instruction(PInfo pi) {
262
300
  *content = '\0';
263
301
  attr_stack_init(&attrs);
264
302
  if (0 == (target = read_name_token(pi))) {
265
- return;
303
+ goto CLEANUP;
266
304
  }
267
305
  end = pi->s;
268
306
  for (; true; pi->s++) {
@@ -273,7 +311,7 @@ static void read_instruction(PInfo pi) {
273
311
  goto DONE;
274
312
  }
275
313
  break;
276
- case '\0': set_error(&pi->err, "processing instruction not terminated", pi->str, pi->s); return;
314
+ case '\0': set_error(&pi->err, "processing instruction not terminated", pi->str, pi->s); goto CLEANUP;
277
315
  default: break;
278
316
  }
279
317
  }
@@ -296,21 +334,18 @@ DONE:
296
334
  while ('?' != c) {
297
335
  pi->last = 0;
298
336
  if ('\0' == *pi->s) {
299
- attr_stack_cleanup(&attrs);
300
337
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
301
- return;
338
+ goto CLEANUP;
302
339
  }
303
340
  next_non_white(pi);
304
341
  if (0 == (attr_name = read_name_token(pi))) {
305
- attr_stack_cleanup(&attrs);
306
- return;
342
+ goto CLEANUP;
307
343
  }
308
344
  end = pi->s;
309
345
  next_non_white(pi);
310
346
  if ('\0' == *pi->s) {
311
- attr_stack_cleanup(&attrs);
312
347
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
313
- return;
348
+ goto CLEANUP;
314
349
  }
315
350
  if ('=' != *pi->s++) {
316
351
  attrs_ok = false;
@@ -320,8 +355,7 @@ DONE:
320
355
  // read value
321
356
  next_non_white(pi);
322
357
  if (0 == (attr_value = read_quoted_value(pi))) {
323
- attr_stack_cleanup(&attrs);
324
- return;
358
+ goto CLEANUP;
325
359
  }
326
360
  attr_stack_push(&attrs, attr_name, attr_value);
327
361
  next_non_white(pi);
@@ -339,9 +373,8 @@ DONE:
339
373
  }
340
374
  if (attrs_ok) {
341
375
  if ('>' != *pi->s++) {
342
- attr_stack_cleanup(&attrs);
343
376
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
344
- return;
377
+ goto CLEANUP;
345
378
  }
346
379
  } else {
347
380
  pi->s = cend + 1;
@@ -362,6 +395,9 @@ DONE:
362
395
  }
363
396
  }
364
397
  }
398
+ // Single exit, so no error return can leak the heap content buffer or the
399
+ // heap-grown attribute stack.
400
+ CLEANUP:
365
401
  attr_stack_cleanup(&attrs);
366
402
  if (content_ptr != content) {
367
403
  xfree(content_ptr);
@@ -567,6 +603,7 @@ static char *read_element(PInfo pi, int depth) {
567
603
  /* read value */
568
604
  next_non_white(pi);
569
605
  if (0 == (attr_value = read_quoted_value(pi))) {
606
+ attr_stack_cleanup(&attrs);
570
607
  return 0;
571
608
  }
572
609
  if (pi->options->convert_special && 0 != strchr(attr_value, '&')) {
@@ -597,7 +634,7 @@ static char *read_element(PInfo pi, int depth) {
597
634
  if (OffSkip == pi->options->skip && start < pi->s && '<' == *pi->s) {
598
635
  c = *pi->s;
599
636
  *pi->s = '\0';
600
- pi->pcb->add_text(pi, start, 1);
637
+ pi->pcb->add_text(pi, start, (size_t)(pi->s - start), 1);
601
638
  *pi->s = c;
602
639
  }
603
640
  c = *pi->s++;
@@ -679,7 +716,9 @@ static char *read_element(PInfo pi, int depth) {
679
716
  default: break;
680
717
  }
681
718
  if ('\0' != *start) {
682
- pi->pcb->add_text(pi, start, 1);
719
+ // The skip handling above moves the terminator, so
720
+ // the length is whatever survived it.
721
+ pi->pcb->add_text(pi, start, strlen(start), 1);
683
722
  }
684
723
  }
685
724
  pi->s++;
@@ -711,6 +750,7 @@ static char *read_element(PInfo pi, int depth) {
711
750
  return name;
712
751
  }
713
752
  } else if (err_has(&pi->err)) {
753
+ attr_stack_cleanup(&attrs);
714
754
  return 0;
715
755
  }
716
756
  break;
@@ -748,15 +788,109 @@ static char *read_element(PInfo pi, int depth) {
748
788
  return 0;
749
789
  }
750
790
 
791
+ #define TEXT_ONES 0x0101010101010101ULL
792
+ #define TEXT_HIGH 0x8080808080808080ULL
793
+
794
+ /* Sets the high bit of every byte of a word that read_text can not copy
795
+ * through as is, that is a byte less than or equal to 0x20, a '&', or a '<'.
796
+ * Bytes with the high bit set are never flagged because ~v clears them, which
797
+ * matches the byte loop where a signed char with the high bit set is negative
798
+ * and so falls to the plain copy.
799
+ *
800
+ * A borrow out of one byte can also flag the byte above it, but a borrow is
801
+ * only produced by a byte that matched, so the lowest flagged byte is always a
802
+ * real match and there are never false negatives.
803
+ */
804
+ inline static uint64_t text_bytes_of_interest(uint64_t v) {
805
+ uint64_t a = v ^ (TEXT_ONES * (uint64_t)'&');
806
+ uint64_t l = v ^ (TEXT_ONES * (uint64_t)'<');
807
+
808
+ return (((v - TEXT_ONES * 0x21) & ~v) | ((a - TEXT_ONES) & ~a) | ((l - TEXT_ONES) & ~l)) & TEXT_HIGH;
809
+ }
810
+
811
+ /* Offset of the lowest flagged byte, which is the first byte of the word that
812
+ * has to go through the switch below. Only ever called with a non-zero mask.
813
+ */
814
+ inline static int text_first_of_interest(const char *s, uint64_t mask) {
815
+ #if defined(__GNUC__) || defined(__clang__)
816
+ #if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
817
+ (void)s;
818
+ return (int)(__builtin_clzll(mask) >> 3);
819
+ #else
820
+ (void)s;
821
+ return (int)(__builtin_ctzll(mask) >> 3);
822
+ #endif
823
+ #else
824
+ int i;
825
+
826
+ (void)mask;
827
+ for (i = 0; i < 8; i++) {
828
+ unsigned char u = (unsigned char)s[i];
829
+
830
+ if (u <= 0x20 || '&' == u || '<' == u) {
831
+ break;
832
+ }
833
+ }
834
+ return i;
835
+ #endif
836
+ }
837
+
751
838
  static void read_text(PInfo pi) {
752
- char buf[MAX_TEXT_LEN];
753
- char *b = buf;
754
- char *alloc_buf = 0;
755
- char *end = b + sizeof(buf) - 2;
756
- char c;
757
- int done = 0;
839
+ char buf[MAX_TEXT_LEN];
840
+ char *b = buf;
841
+ char *alloc_buf = 0;
842
+ char *end = b + sizeof(buf) - 2;
843
+ char *text;
844
+ size_t len;
845
+ char c;
846
+ int done = 0;
847
+ /* fix_newlines only rewrites '\r'. SpcSkip folds every '\r' to a space, so
848
+ * the only way one reaches buf under it is an entity like &#13;. Any other
849
+ * skip mode can keep a '\r', so assume one might be present there and run
850
+ * fix_newlines as before. SpcSkip text with no such entity, the common
851
+ * case, then skips the fix_newlines scan and second pass entirely.
852
+ */
853
+ int maybe_cr = (SpcSkip != pi->options->skip);
758
854
 
759
855
  while (!done) {
856
+ /* A byte above 0x20 that is neither '&' nor '<' is copied through
857
+ * unchanged for every skip mode and every effort, so runs of them can
858
+ * be moved a word at a time instead of one switch dispatch per byte.
859
+ * pi->end addresses the terminating '\0' so the loads never leave the
860
+ * document, which keeps the loop clean under ASan.
861
+ */
862
+ while (pi->s + 8 <= pi->end && b + 8 <= end) {
863
+ uint64_t v;
864
+ uint64_t mask;
865
+
866
+ memcpy(&v, pi->s, 8);
867
+ mask = text_bytes_of_interest(v);
868
+ /* b + 8 <= end so the whole word can be stored even when only part
869
+ * of it is kept. The bytes past the kept prefix are overwritten by
870
+ * the next copy or cut off by the terminating '\0'.
871
+ */
872
+ memcpy(b, pi->s, 8);
873
+ if (0 != mask) {
874
+ int n = text_first_of_interest(pi->s, mask);
875
+
876
+ b += n;
877
+ pi->s += n;
878
+ break;
879
+ }
880
+ b += 8;
881
+ pi->s += 8;
882
+ }
883
+ /* Only reached when a bound stopped the word loop, or for the byte the
884
+ * word loop stopped on.
885
+ */
886
+ while (pi->s < pi->end && b < end) {
887
+ unsigned char u = (unsigned char)*pi->s;
888
+
889
+ if (u <= 0x20 || '&' == u || '<' == u) {
890
+ break;
891
+ }
892
+ *b++ = *pi->s++;
893
+ }
760
894
  c = *pi->s++;
761
895
  switch (c) {
762
896
  case '<':
@@ -766,7 +900,7 @@ static void read_text(PInfo pi) {
766
900
  case '\0':
767
901
  pi->s--;
768
902
  set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s);
769
- return;
903
+ goto CLEANUP;
770
904
  default:
771
905
  if (end <= (b + (('&' == c) ? 7 : 0))) { /* extra 8 for special just in case it is sequence of bytes */
772
906
  unsigned long size;
@@ -787,13 +921,15 @@ static void read_text(PInfo pi) {
787
921
  }
788
922
  if ('&' == c) {
789
923
  if (0 == (b = read_coded_chars(pi, b))) {
790
- return;
924
+ goto CLEANUP;
791
925
  }
926
+ /* An entity such as &#13; can decode to '\r'; be conservative. */
927
+ maybe_cr = 1;
792
928
  } else {
793
929
  if (0 <= c && c <= 0x20) {
794
930
  if (StrictEffort == pi->options->effort && 'x' == xml_valid_lower_chars[(unsigned char)c]) {
795
931
  set_error(&pi->err, "invalid character", pi->str, pi->s);
796
- return;
932
+ goto CLEANUP;
797
933
  }
798
934
  switch (pi->options->skip) {
799
935
  case CrSkip:
@@ -823,14 +959,21 @@ static void read_text(PInfo pi) {
823
959
  break;
824
960
  }
825
961
  }
826
- *b = '\0';
962
+ *b = '\0';
963
+ text = (0 != alloc_buf) ? alloc_buf : buf;
964
+ len = (size_t)(b - text);
965
+ if (maybe_cr) {
966
+ // fix_newlines can fold "\r\n" to "\n" and shorten the text, so the
967
+ // length is only known after it runs. When it was skipped the text is
968
+ // exactly b - text, so add_text avoids a strlen on the common path.
969
+ fix_newlines(text);
970
+ len = strlen(text);
971
+ }
972
+ pi->pcb->add_text(pi, text, len, ('/' == *(pi->s + 1)));
973
+ // Single exit, so no error return can leak the heap-grown text buffer.
974
+ CLEANUP:
827
975
  if (0 != alloc_buf) {
828
- fix_newlines(alloc_buf);
829
- pi->pcb->add_text(pi, alloc_buf, ('/' == *(pi->s + 1)));
830
976
  xfree(alloc_buf);
831
- } else {
832
- fix_newlines(buf);
833
- pi->pcb->add_text(pi, buf, ('/' == *(pi->s + 1)));
834
977
  }
835
978
  }
836
979
 
data/ext/ox/sax.c CHANGED
@@ -197,7 +197,7 @@ static void comment(SaxDrive dr, long pos, long line, long col) {
197
197
  Hint h = ox_hint_find(dr->options.hints, "!--");
198
198
 
199
199
  if (NULL == parent || NULL == parent->hint || OffOverlay != parent->hint->overlay ||
200
- (NULL != h && (ActiveOverlay == h->overlay || ActiveOverlay == h->overlay))) {
200
+ (NULL != h && (ActiveOverlay == h->overlay || NestOverlay == h->overlay))) {
201
201
  VALUE arg = rb_str_new2(dr->buf.str);
202
202
 
203
203
  if (0 != dr->encoding) {
@@ -581,7 +581,7 @@ static char read_instruction(SaxDrive dr) {
581
581
  return c;
582
582
  }
583
583
 
584
- static char read_delimited(SaxDrive dr, char end) {
584
+ static char read_delimited(SaxDrive dr, char end, int depth) {
585
585
  char c;
586
586
 
587
587
  if ('"' == end || '\'' == end) {
@@ -591,6 +591,9 @@ static char read_delimited(SaxDrive dr, char end) {
591
591
  return c;
592
592
  }
593
593
  }
594
+ } else if (MAX_PROLOG <= depth) {
595
+ rb_raise(ox_parse_error_class, "prolog (doctype) too long.\n");
596
+ return '\0';
594
597
  } else {
595
598
  while (1) {
596
599
  c = buf_get(&dr->buf);
@@ -599,10 +602,10 @@ static char read_delimited(SaxDrive dr, char end) {
599
602
  }
600
603
  switch (c) {
601
604
  case '\0': ox_sax_drive_error(dr, NO_TERM "doctype not terminated"); return c;
602
- case '"': c = read_delimited(dr, c); break;
603
- case '\'': c = read_delimited(dr, c); break;
604
- case '[': c = read_delimited(dr, ']'); break;
605
- case '<': c = read_delimited(dr, '>'); break;
605
+ case '"': c = read_delimited(dr, c, depth + 1); break;
606
+ case '\'': c = read_delimited(dr, c, depth + 1); break;
607
+ case '[': c = read_delimited(dr, ']', depth + 1); break;
608
+ case '<': c = read_delimited(dr, '>', depth + 1); break;
606
609
  default: break;
607
610
  }
608
611
  }
@@ -621,7 +624,7 @@ static char read_doctype(SaxDrive dr) {
621
624
 
622
625
  buf_backup(&dr->buf); /* back up to the start in case the doctype is empty */
623
626
  buf_protect(&dr->buf);
624
- read_delimited(dr, '>');
627
+ read_delimited(dr, '>', 0);
625
628
  if (dr->options.smart && 0 == dr->options.hints) {
626
629
  for (s = dr->buf.str; is_white(*s); s++) {
627
630
  }
@@ -876,16 +879,17 @@ static char read_element_start(SaxDrive dr) {
876
879
  }
877
880
  }
878
881
  }
879
- name = str2sym(dr, dr->buf.str, nlen, &ename);
880
- if (NULL == ename) {
881
- if (sizeof(ebuf) <= nlen) {
882
- ename = ox_strndup(dr->buf.str, nlen);
883
- efree = true;
884
- } else {
885
- memcpy(ebuf, dr->buf.str, nlen);
886
- ebuf[nlen] = '\0';
887
- ename = ebuf;
888
- }
882
+ name = str2sym(dr, dr->buf.str, nlen, NULL);
883
+ // ename lives until stack_push() copies it, across the start_element
884
+ // callback and read_attrs. The cache's key is inside a Slot, which a GC can
885
+ // retire and a later intern free, so copy it instead.
886
+ if (sizeof(ebuf) <= nlen) {
887
+ ename = ox_strndup(dr->buf.str, nlen);
888
+ efree = true;
889
+ } else {
890
+ memcpy(ebuf, dr->buf.str, nlen);
891
+ ebuf[nlen] = '\0';
892
+ ename = ebuf;
889
893
  }
890
894
  if (dr->has_start_element && 0 >= dr->blocked &&
891
895
  (NULL == h || ActiveOverlay == h->overlay || NestOverlay == h->overlay)) {
@@ -930,7 +934,7 @@ static char read_element_start(SaxDrive dr) {
930
934
  stack_push(&dr->stack, ename, nlen, name, h);
931
935
  }
932
936
  if (efree) {
933
- free((char *)ename);
937
+ xfree((char *)ename);
934
938
  }
935
939
  if ('>' != c) {
936
940
  ox_sax_drive_error(dr, WRONG_CHAR "element not closed");