Coverage Report

Created: 2024-11-21 07:03

/src/nss-nspr/nspr/pr/src/io/prscanf.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
/*
7
 * Scan functions for NSPR types
8
 *
9
 * Author: Wan-Teh Chang
10
 *
11
 * Acknowledgment: The implementation is inspired by the source code
12
 * in P.J. Plauger's "The Standard C Library," Prentice-Hall, 1992.
13
 */
14
15
#include <limits.h>
16
#include <ctype.h>
17
#include <string.h>
18
#include <stdlib.h>
19
#include "prprf.h"
20
#include "prdtoa.h"
21
#include "prlog.h"
22
#include "prerror.h"
23
24
/*
25
 * A function that reads a character from 'stream'.
26
 * Returns the character read, or EOF if end of stream is reached.
27
 */
28
typedef int (*_PRGetCharFN)(void* stream);
29
30
/*
31
 * A function that pushes the character 'ch' back to 'stream'.
32
 */
33
typedef void (*_PRUngetCharFN)(void* stream, int ch);
34
35
/*
36
 * The size specifier for the integer and floating point number
37
 * conversions in format control strings.
38
 */
39
typedef enum {
40
  _PR_size_none, /* No size specifier is given */
41
  _PR_size_h,    /* The 'h' specifier, suggesting "short" */
42
  _PR_size_l,    /* The 'l' specifier, suggesting "long" */
43
  _PR_size_L,    /* The 'L' specifier, meaning a 'long double' */
44
  _PR_size_ll    /* The 'll' specifier, suggesting "long long" */
45
} _PRSizeSpec;
46
47
/*
48
 * The collection of data that is passed between the scan function
49
 * and its subordinate functions.  The fields of this structure
50
 * serve as the input or output arguments for these functions.
51
 */
52
typedef struct {
53
  _PRGetCharFN get;     /* get a character from input stream */
54
  _PRUngetCharFN unget; /* unget (push back) a character */
55
  void* stream;         /* argument for get and unget */
56
  va_list ap;           /* the variable argument list */
57
  int nChar;            /* number of characters read from 'stream' */
58
59
  PRBool assign;        /* assign, or suppress assignment? */
60
  int width;            /* field width */
61
  _PRSizeSpec sizeSpec; /* 'h', 'l', 'L', or 'll' */
62
63
  PRBool converted; /* is the value actually converted? */
64
} ScanfState;
65
66
0
#define GET(state) ((state)->nChar++, (state)->get((state)->stream))
67
0
#define UNGET(state, ch) ((state)->nChar--, (state)->unget((state)->stream, ch))
68
69
/*
70
 * The following two macros, GET_IF_WITHIN_WIDTH and WITHIN_WIDTH,
71
 * are always used together.
72
 *
73
 * GET_IF_WITHIN_WIDTH calls the GET macro and assigns its return
74
 * value to 'ch' only if we have not exceeded the field width of
75
 * 'state'.  Therefore, after GET_IF_WITHIN_WIDTH, the value of
76
 * 'ch' is valid only if the macro WITHIN_WIDTH evaluates to true.
77
 */
78
79
#define GET_IF_WITHIN_WIDTH(state, ch) \
80
0
  if (--(state)->width >= 0) {         \
81
0
    (ch) = GET(state);                 \
82
0
  }
83
0
#define WITHIN_WIDTH(state) ((state)->width >= 0)
84
85
/*
86
 * _pr_strtoull:
87
 *     Convert a string to an unsigned 64-bit integer.  The string
88
 *     'str' is assumed to be a representation of the integer in
89
 *     base 'base'.
90
 *
91
 * Warning:
92
 *     - Only handle base 8, 10, and 16.
93
 *     - No overflow checking.
94
 */
95
96
0
static PRUint64 _pr_strtoull(const char* str, char** endptr, int base) {
97
0
  static const int BASE_MAX = 16;
98
0
  static const char digits[] = "0123456789abcdef";
99
0
  char* digitPtr;
100
0
  PRUint64 x; /* return value */
101
0
  PRInt64 base64;
102
0
  const char* cPtr;
103
0
  PRBool negative;
104
0
  const char* digitStart;
105
106
0
  PR_ASSERT(base == 0 || base == 8 || base == 10 || base == 16);
107
0
  if (base < 0 || base == 1 || base > BASE_MAX) {
108
0
    if (endptr) {
109
0
      *endptr = (char*)str;
110
0
      return LL_ZERO;
111
0
    }
112
0
  }
113
114
0
  cPtr = str;
115
0
  while (isspace(*cPtr)) {
116
0
    ++cPtr;
117
0
  }
118
119
0
  negative = PR_FALSE;
120
0
  if (*cPtr == '-') {
121
0
    negative = PR_TRUE;
122
0
    cPtr++;
123
0
  } else if (*cPtr == '+') {
124
0
    cPtr++;
125
0
  }
126
127
0
  if (base == 16) {
128
0
    if (*cPtr == '0' && (cPtr[1] == 'x' || cPtr[1] == 'X')) {
129
0
      cPtr += 2;
130
0
    }
131
0
  } else if (base == 0) {
132
0
    if (*cPtr != '0') {
133
0
      base = 10;
134
0
    } else if (cPtr[1] == 'x' || cPtr[1] == 'X') {
135
0
      base = 16;
136
0
      cPtr += 2;
137
0
    } else {
138
0
      base = 8;
139
0
    }
140
0
  }
141
0
  PR_ASSERT(base != 0);
142
0
  LL_I2L(base64, base);
143
0
  digitStart = cPtr;
144
145
  /* Skip leading zeros */
146
0
  while (*cPtr == '0') {
147
0
    cPtr++;
148
0
  }
149
150
0
  LL_I2L(x, 0);
151
0
  while ((digitPtr = (char*)memchr(digits, tolower(*cPtr), base)) != NULL) {
152
0
    PRUint64 d;
153
154
0
    LL_I2L(d, (digitPtr - digits));
155
0
    LL_MUL(x, x, base64);
156
0
    LL_ADD(x, x, d);
157
0
    cPtr++;
158
0
  }
159
160
0
  if (cPtr == digitStart) {
161
0
    if (endptr) {
162
0
      *endptr = (char*)str;
163
0
    }
164
0
    return LL_ZERO;
165
0
  }
166
167
0
  if (negative) {
168
0
#ifdef HAVE_LONG_LONG
169
    /* The cast to a signed type is to avoid a compiler warning */
170
0
    x = -(PRInt64)x;
171
#else
172
    LL_NEG(x, x);
173
#endif
174
0
  }
175
176
0
  if (endptr) {
177
0
    *endptr = (char*)cPtr;
178
0
  }
179
0
  return x;
180
0
}
181
182
/*
183
 * The maximum field width (in number of characters) that is enough
184
 * (may be more than necessary) to represent a 64-bit integer or
185
 * floating point number.
186
 */
187
0
#define FMAX 31
188
0
#define DECIMAL_POINT '.'
189
190
0
static PRStatus GetInt(ScanfState* state, int code) {
191
0
  char buf[FMAX + 1], *p;
192
0
  int ch = 0;
193
0
  static const char digits[] = "0123456789abcdefABCDEF";
194
0
  PRBool seenDigit = PR_FALSE;
195
0
  int base;
196
0
  int dlen;
197
198
0
  switch (code) {
199
0
    case 'd':
200
0
    case 'u':
201
0
      base = 10;
202
0
      break;
203
0
    case 'i':
204
0
      base = 0;
205
0
      break;
206
0
    case 'x':
207
0
    case 'X':
208
0
    case 'p':
209
0
      base = 16;
210
0
      break;
211
0
    case 'o':
212
0
      base = 8;
213
0
      break;
214
0
    default:
215
0
      return PR_FAILURE;
216
0
  }
217
0
  if (state->width == 0 || state->width > FMAX) {
218
0
    state->width = FMAX;
219
0
  }
220
0
  p = buf;
221
0
  GET_IF_WITHIN_WIDTH(state, ch);
222
0
  if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) {
223
0
    *p++ = ch;
224
0
    GET_IF_WITHIN_WIDTH(state, ch);
225
0
  }
226
0
  if (WITHIN_WIDTH(state) && ch == '0') {
227
0
    seenDigit = PR_TRUE;
228
0
    *p++ = ch;
229
0
    GET_IF_WITHIN_WIDTH(state, ch);
230
0
    if (WITHIN_WIDTH(state) && (ch == 'x' || ch == 'X') &&
231
0
        (base == 0 || base == 16)) {
232
0
      base = 16;
233
0
      *p++ = ch;
234
0
      GET_IF_WITHIN_WIDTH(state, ch);
235
0
    } else if (base == 0) {
236
0
      base = 8;
237
0
    }
238
0
  }
239
0
  if (base == 0 || base == 10) {
240
0
    dlen = 10;
241
0
  } else if (base == 8) {
242
0
    dlen = 8;
243
0
  } else {
244
0
    PR_ASSERT(base == 16);
245
0
    dlen = 16 + 6; /* 16 digits, plus 6 in uppercase */
246
0
  }
247
0
  while (WITHIN_WIDTH(state) && memchr(digits, ch, dlen)) {
248
0
    *p++ = ch;
249
0
    GET_IF_WITHIN_WIDTH(state, ch);
250
0
    seenDigit = PR_TRUE;
251
0
  }
252
0
  if (WITHIN_WIDTH(state)) {
253
0
    UNGET(state, ch);
254
0
  }
255
0
  if (!seenDigit) {
256
0
    return PR_FAILURE;
257
0
  }
258
0
  *p = '\0';
259
0
  if (state->assign) {
260
0
    if (code == 'd' || code == 'i') {
261
0
      if (state->sizeSpec == _PR_size_ll) {
262
0
        PRInt64 llval = _pr_strtoull(buf, NULL, base);
263
0
        *va_arg(state->ap, PRInt64*) = llval;
264
0
      } else {
265
0
        long lval = strtol(buf, NULL, base);
266
267
0
        if (state->sizeSpec == _PR_size_none) {
268
0
          *va_arg(state->ap, PRIntn*) = lval;
269
0
        } else if (state->sizeSpec == _PR_size_h) {
270
0
          *va_arg(state->ap, PRInt16*) = (PRInt16)lval;
271
0
        } else if (state->sizeSpec == _PR_size_l) {
272
0
          *va_arg(state->ap, PRInt32*) = lval;
273
0
        } else {
274
0
          return PR_FAILURE;
275
0
        }
276
0
      }
277
0
    } else {
278
0
      if (state->sizeSpec == _PR_size_ll) {
279
0
        PRUint64 llval = _pr_strtoull(buf, NULL, base);
280
0
        *va_arg(state->ap, PRUint64*) = llval;
281
0
      } else {
282
0
        unsigned long lval = strtoul(buf, NULL, base);
283
284
0
        if (state->sizeSpec == _PR_size_none) {
285
0
          *va_arg(state->ap, PRUintn*) = lval;
286
0
        } else if (state->sizeSpec == _PR_size_h) {
287
0
          *va_arg(state->ap, PRUint16*) = (PRUint16)lval;
288
0
        } else if (state->sizeSpec == _PR_size_l) {
289
0
          *va_arg(state->ap, PRUint32*) = lval;
290
0
        } else {
291
0
          return PR_FAILURE;
292
0
        }
293
0
      }
294
0
    }
295
0
    state->converted = PR_TRUE;
296
0
  }
297
0
  return PR_SUCCESS;
298
0
}
299
300
0
static PRStatus GetFloat(ScanfState* state) {
301
0
  char buf[FMAX + 1], *p;
302
0
  int ch = 0;
303
0
  PRBool seenDigit = PR_FALSE;
304
305
0
  if (state->width == 0 || state->width > FMAX) {
306
0
    state->width = FMAX;
307
0
  }
308
0
  p = buf;
309
0
  GET_IF_WITHIN_WIDTH(state, ch);
310
0
  if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) {
311
0
    *p++ = ch;
312
0
    GET_IF_WITHIN_WIDTH(state, ch);
313
0
  }
314
0
  while (WITHIN_WIDTH(state) && isdigit(ch)) {
315
0
    *p++ = ch;
316
0
    GET_IF_WITHIN_WIDTH(state, ch);
317
0
    seenDigit = PR_TRUE;
318
0
  }
319
0
  if (WITHIN_WIDTH(state) && ch == DECIMAL_POINT) {
320
0
    *p++ = ch;
321
0
    GET_IF_WITHIN_WIDTH(state, ch);
322
0
    while (WITHIN_WIDTH(state) && isdigit(ch)) {
323
0
      *p++ = ch;
324
0
      GET_IF_WITHIN_WIDTH(state, ch);
325
0
      seenDigit = PR_TRUE;
326
0
    }
327
0
  }
328
329
  /*
330
   * This is not robust.  For example, "1.2e+" would confuse
331
   * the code below to read 'e' and '+', only to realize that
332
   * it should have stopped at "1.2".  But we can't push back
333
   * more than one character, so there is nothing I can do.
334
   */
335
336
  /* Parse exponent */
337
0
  if (WITHIN_WIDTH(state) && (ch == 'e' || ch == 'E') && seenDigit) {
338
0
    *p++ = ch;
339
0
    GET_IF_WITHIN_WIDTH(state, ch);
340
0
    if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) {
341
0
      *p++ = ch;
342
0
      GET_IF_WITHIN_WIDTH(state, ch);
343
0
    }
344
0
    while (WITHIN_WIDTH(state) && isdigit(ch)) {
345
0
      *p++ = ch;
346
0
      GET_IF_WITHIN_WIDTH(state, ch);
347
0
    }
348
0
  }
349
0
  if (WITHIN_WIDTH(state)) {
350
0
    UNGET(state, ch);
351
0
  }
352
0
  if (!seenDigit) {
353
0
    return PR_FAILURE;
354
0
  }
355
0
  *p = '\0';
356
0
  if (state->assign) {
357
0
    PRFloat64 dval = PR_strtod(buf, NULL);
358
359
0
    state->converted = PR_TRUE;
360
0
    if (state->sizeSpec == _PR_size_l) {
361
0
      *va_arg(state->ap, PRFloat64*) = dval;
362
0
    } else if (state->sizeSpec == _PR_size_L) {
363
0
      *va_arg(state->ap, long double*) = dval;
364
0
    } else {
365
0
      *va_arg(state->ap, float*) = (float)dval;
366
0
    }
367
0
  }
368
0
  return PR_SUCCESS;
369
0
}
370
371
/*
372
 * Convert, and return the end of the conversion spec.
373
 * Return NULL on error.
374
 */
375
376
0
static const char* Convert(ScanfState* state, const char* fmt) {
377
0
  const char* cPtr;
378
0
  int ch;
379
0
  char* cArg = NULL;
380
381
0
  state->converted = PR_FALSE;
382
0
  cPtr = fmt;
383
0
  if (*cPtr != 'c' && *cPtr != 'n' && *cPtr != '[') {
384
0
    do {
385
0
      ch = GET(state);
386
0
    } while (isspace(ch));
387
0
    UNGET(state, ch);
388
0
  }
389
0
  switch (*cPtr) {
390
0
    case 'c':
391
0
      if (state->assign) {
392
0
        cArg = va_arg(state->ap, char*);
393
0
      }
394
0
      if (state->width == 0) {
395
0
        state->width = 1;
396
0
      }
397
0
      for (; state->width > 0; state->width--) {
398
0
        ch = GET(state);
399
0
        if (ch == EOF) {
400
0
          return NULL;
401
0
        }
402
0
        if (state->assign) {
403
0
          *cArg++ = ch;
404
0
        }
405
0
      }
406
0
      if (state->assign) {
407
0
        state->converted = PR_TRUE;
408
0
      }
409
0
      break;
410
0
    case 'p':
411
0
    case 'd':
412
0
    case 'i':
413
0
    case 'o':
414
0
    case 'u':
415
0
    case 'x':
416
0
    case 'X':
417
0
      if (GetInt(state, *cPtr) == PR_FAILURE) {
418
0
        return NULL;
419
0
      }
420
0
      break;
421
0
    case 'e':
422
0
    case 'E':
423
0
    case 'f':
424
0
    case 'g':
425
0
    case 'G':
426
0
      if (GetFloat(state) == PR_FAILURE) {
427
0
        return NULL;
428
0
      }
429
0
      break;
430
0
    case 'n':
431
      /* do not consume any input */
432
0
      if (state->assign) {
433
0
        switch (state->sizeSpec) {
434
0
          case _PR_size_none:
435
0
            *va_arg(state->ap, PRIntn*) = state->nChar;
436
0
            break;
437
0
          case _PR_size_h:
438
0
            *va_arg(state->ap, PRInt16*) = state->nChar;
439
0
            break;
440
0
          case _PR_size_l:
441
0
            *va_arg(state->ap, PRInt32*) = state->nChar;
442
0
            break;
443
0
          case _PR_size_ll:
444
0
            LL_I2L(*va_arg(state->ap, PRInt64*), state->nChar);
445
0
            break;
446
0
          default:
447
0
            PR_ASSERT(0);
448
0
        }
449
0
      }
450
0
      break;
451
0
    case 's':
452
0
      if (state->width == 0) {
453
0
        state->width = INT_MAX;
454
0
      }
455
0
      if (state->assign) {
456
0
        cArg = va_arg(state->ap, char*);
457
0
      }
458
0
      for (; state->width > 0; state->width--) {
459
0
        ch = GET(state);
460
0
        if ((ch == EOF) || isspace(ch)) {
461
0
          UNGET(state, ch);
462
0
          break;
463
0
        }
464
0
        if (state->assign) {
465
0
          *cArg++ = ch;
466
0
        }
467
0
      }
468
0
      if (state->assign) {
469
0
        *cArg = '\0';
470
0
        state->converted = PR_TRUE;
471
0
      }
472
0
      break;
473
0
    case '%':
474
0
      ch = GET(state);
475
0
      if (ch != '%') {
476
0
        UNGET(state, ch);
477
0
        return NULL;
478
0
      }
479
0
      break;
480
0
    case '[': {
481
0
      PRBool complement = PR_FALSE;
482
0
      const char* closeBracket;
483
0
      size_t n;
484
485
0
      if (*++cPtr == '^') {
486
0
        complement = PR_TRUE;
487
0
        cPtr++;
488
0
      }
489
0
      closeBracket = strchr(*cPtr == ']' ? cPtr + 1 : cPtr, ']');
490
0
      if (closeBracket == NULL) {
491
0
        return NULL;
492
0
      }
493
0
      n = closeBracket - cPtr;
494
0
      if (state->width == 0) {
495
0
        state->width = INT_MAX;
496
0
      }
497
0
      if (state->assign) {
498
0
        cArg = va_arg(state->ap, char*);
499
0
      }
500
0
      for (; state->width > 0; state->width--) {
501
0
        ch = GET(state);
502
0
        if ((ch == EOF) || (!complement && !memchr(cPtr, ch, n)) ||
503
0
            (complement && memchr(cPtr, ch, n))) {
504
0
          UNGET(state, ch);
505
0
          break;
506
0
        }
507
0
        if (state->assign) {
508
0
          *cArg++ = ch;
509
0
        }
510
0
      }
511
0
      if (state->assign) {
512
0
        *cArg = '\0';
513
0
        state->converted = PR_TRUE;
514
0
      }
515
0
      cPtr = closeBracket;
516
0
    } break;
517
0
    default:
518
0
      return NULL;
519
0
  }
520
0
  return cPtr;
521
0
}
522
523
0
static PRInt32 DoScanf(ScanfState* state, const char* fmt) {
524
0
  PRInt32 nConverted = 0;
525
0
  const char* cPtr;
526
0
  int ch;
527
528
0
  state->nChar = 0;
529
0
  cPtr = fmt;
530
0
  while (1) {
531
0
    if (isspace(*cPtr)) {
532
      /* white space: skip */
533
0
      do {
534
0
        cPtr++;
535
0
      } while (isspace(*cPtr));
536
0
      do {
537
0
        ch = GET(state);
538
0
      } while (isspace(ch));
539
0
      UNGET(state, ch);
540
0
    } else if (*cPtr == '%') {
541
      /* format spec: convert */
542
0
      cPtr++;
543
0
      state->assign = PR_TRUE;
544
0
      if (*cPtr == '*') {
545
0
        cPtr++;
546
0
        state->assign = PR_FALSE;
547
0
      }
548
0
      for (state->width = 0; isdigit(*cPtr); cPtr++) {
549
0
        state->width = state->width * 10 + *cPtr - '0';
550
0
      }
551
0
      state->sizeSpec = _PR_size_none;
552
0
      if (*cPtr == 'h') {
553
0
        cPtr++;
554
0
        state->sizeSpec = _PR_size_h;
555
0
      } else if (*cPtr == 'l') {
556
0
        cPtr++;
557
0
        if (*cPtr == 'l') {
558
0
          cPtr++;
559
0
          state->sizeSpec = _PR_size_ll;
560
0
        } else {
561
0
          state->sizeSpec = _PR_size_l;
562
0
        }
563
0
      } else if (*cPtr == 'L') {
564
0
        cPtr++;
565
0
        state->sizeSpec = _PR_size_L;
566
0
      }
567
0
      cPtr = Convert(state, cPtr);
568
0
      if (cPtr == NULL) {
569
0
        return (nConverted > 0 ? nConverted : EOF);
570
0
      }
571
0
      if (state->converted) {
572
0
        nConverted++;
573
0
      }
574
0
      cPtr++;
575
0
    } else {
576
      /* others: must match */
577
0
      if (*cPtr == '\0') {
578
0
        return nConverted;
579
0
      }
580
0
      ch = GET(state);
581
0
      if (ch != *cPtr) {
582
0
        UNGET(state, ch);
583
0
        return nConverted;
584
0
      }
585
0
      cPtr++;
586
0
    }
587
0
  }
588
0
}
589
590
0
static int StringGetChar(void* stream) {
591
0
  char* cPtr = *((char**)stream);
592
593
0
  if (*cPtr == '\0') {
594
0
    return EOF;
595
0
  }
596
0
  *((char**)stream) = cPtr + 1;
597
0
  return (unsigned char)*cPtr;
598
0
}
599
600
0
static void StringUngetChar(void* stream, int ch) {
601
0
  char* cPtr = *((char**)stream);
602
603
0
  if (ch != EOF) {
604
0
    *((char**)stream) = cPtr - 1;
605
0
  }
606
0
}
607
608
PR_IMPLEMENT(PRInt32)
609
0
PR_sscanf(const char* buf, const char* fmt, ...) {
610
0
  PRInt32 rv;
611
0
  ScanfState state;
612
613
0
  state.get = &StringGetChar;
614
0
  state.unget = &StringUngetChar;
615
0
  state.stream = (void*)&buf;
616
0
  va_start(state.ap, fmt);
617
0
  rv = DoScanf(&state, fmt);
618
0
  va_end(state.ap);
619
0
  return rv;
620
0
}