Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/common/unicode_norm.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 * unicode_norm.c
3
 *    Normalize a Unicode string
4
 *
5
 * This implements Unicode normalization, per the documentation at
6
 * https://www.unicode.org/reports/tr15/.
7
 *
8
 * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
9
 *
10
 * IDENTIFICATION
11
 *    src/common/unicode_norm.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#ifndef FRONTEND
16
#include "postgres.h"
17
#else
18
#include "postgres_fe.h"
19
#endif
20
21
#include "common/unicode_norm.h"
22
#ifndef FRONTEND
23
#include "common/unicode_norm_hashfunc.h"
24
#include "common/unicode_normprops_table.h"
25
#include "port/pg_bswap.h"
26
#include "utils/memutils.h"
27
#else
28
#include "common/unicode_norm_table.h"
29
#endif
30
31
#ifndef FRONTEND
32
0
#define ALLOC(size) palloc(size)
33
0
#define FREE(size) pfree(size)
34
#else
35
#define ALLOC(size) malloc(size)
36
#define FREE(size) free(size)
37
#endif
38
39
/* Constants for calculations with Hangul characters */
40
0
#define SBASE   0xAC00    /* U+AC00 */
41
0
#define LBASE   0x1100    /* U+1100 */
42
0
#define VBASE   0x1161    /* U+1161 */
43
0
#define TBASE   0x11A7    /* U+11A7 */
44
0
#define LCOUNT    19
45
0
#define VCOUNT    21
46
0
#define TCOUNT    28
47
0
#define NCOUNT    VCOUNT * TCOUNT
48
0
#define SCOUNT    LCOUNT * NCOUNT
49
50
#ifdef FRONTEND
51
/* comparison routine for bsearch() of decomposition lookup table. */
52
static int
53
conv_compare(const void *p1, const void *p2)
54
{
55
  uint32    v1,
56
        v2;
57
58
  v1 = *(const uint32 *) p1;
59
  v2 = ((const pg_unicode_decomposition *) p2)->codepoint;
60
  return (v1 > v2) ? 1 : ((v1 == v2) ? 0 : -1);
61
}
62
63
#endif
64
65
/*
66
 * get_code_entry
67
 *
68
 * Get the entry corresponding to code in the decomposition lookup table.
69
 * The backend version of this code uses a perfect hash function for the
70
 * lookup, while the frontend version uses a binary search.
71
 */
72
static const pg_unicode_decomposition *
73
get_code_entry(char32_t code)
74
0
{
75
0
#ifndef FRONTEND
76
0
  int     h;
77
0
  uint32    hashkey;
78
0
  pg_unicode_decompinfo decompinfo = UnicodeDecompInfo;
79
80
  /*
81
   * Compute the hash function. The hash key is the codepoint with the bytes
82
   * in network order.
83
   */
84
0
  hashkey = pg_hton32(code);
85
0
  h = decompinfo.hash(&hashkey);
86
87
  /* An out-of-range result implies no match */
88
0
  if (h < 0 || h >= decompinfo.num_decomps)
89
0
    return NULL;
90
91
  /*
92
   * Since it's a perfect hash, we need only match to the specific codepoint
93
   * it identifies.
94
   */
95
0
  if (code != decompinfo.decomps[h].codepoint)
96
0
    return NULL;
97
98
  /* Success! */
99
0
  return &decompinfo.decomps[h];
100
#else
101
  return bsearch(&(code),
102
           UnicodeDecompMain,
103
           lengthof(UnicodeDecompMain),
104
           sizeof(pg_unicode_decomposition),
105
           conv_compare);
106
#endif
107
0
}
108
109
/*
110
 * Get the combining class of the given codepoint.
111
 */
112
static uint8
113
get_canonical_class(char32_t code)
114
0
{
115
0
  const pg_unicode_decomposition *entry = get_code_entry(code);
116
117
  /*
118
   * If no entries are found, the character used is either a Hangul
119
   * character or a character with a class of 0 and no decompositions.
120
   */
121
0
  if (!entry)
122
0
    return 0;
123
0
  else
124
0
    return entry->comb_class;
125
0
}
126
127
/*
128
 * Given a decomposition entry looked up earlier, get the decomposed
129
 * characters.
130
 *
131
 * Note: the returned pointer can point to statically allocated buffer, and
132
 * is only valid until next call to this function!
133
 */
134
static const char32_t *
135
get_code_decomposition(const pg_unicode_decomposition *entry, int *dec_size)
136
0
{
137
0
  static char32_t x;
138
139
0
  if (DECOMPOSITION_IS_INLINE(entry))
140
0
  {
141
0
    Assert(DECOMPOSITION_SIZE(entry) == 1);
142
0
    x = (char32_t) entry->dec_index;
143
0
    *dec_size = 1;
144
0
    return &x;
145
0
  }
146
0
  else
147
0
  {
148
0
    *dec_size = DECOMPOSITION_SIZE(entry);
149
0
    return &UnicodeDecomp_codepoints[entry->dec_index];
150
0
  }
151
0
}
152
153
/*
154
 * Calculate how many characters a given character will decompose to.
155
 *
156
 * This needs to recurse, if the character decomposes into characters that
157
 * are, in turn, decomposable.
158
 */
159
static int
160
get_decomposed_size(char32_t code, bool compat)
161
0
{
162
0
  const pg_unicode_decomposition *entry;
163
0
  int     size = 0;
164
0
  int     i;
165
0
  const uint32 *decomp;
166
0
  int     dec_size;
167
168
  /*
169
   * Fast path for Hangul characters not stored in tables to save memory as
170
   * decomposition is algorithmic. See
171
   * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details
172
   * on the matter.
173
   */
174
0
  if (code >= SBASE && code < SBASE + SCOUNT)
175
0
  {
176
0
    uint32    tindex,
177
0
          sindex;
178
179
0
    sindex = code - SBASE;
180
0
    tindex = sindex % TCOUNT;
181
182
0
    if (tindex != 0)
183
0
      return 3;
184
0
    return 2;
185
0
  }
186
187
0
  entry = get_code_entry(code);
188
189
  /*
190
   * Just count current code if no other decompositions.  A NULL entry is
191
   * equivalent to a character with class 0 and no decompositions.
192
   */
193
0
  if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
194
0
    (!compat && DECOMPOSITION_IS_COMPAT(entry)))
195
0
    return 1;
196
197
  /*
198
   * If this entry has other decomposition codes look at them as well. First
199
   * get its decomposition in the list of tables available.
200
   */
201
0
  decomp = get_code_decomposition(entry, &dec_size);
202
0
  for (i = 0; i < dec_size; i++)
203
0
  {
204
0
    uint32    lcode = decomp[i];
205
206
0
    size += get_decomposed_size(lcode, compat);
207
0
  }
208
209
0
  return size;
210
0
}
211
212
/*
213
 * Recompose a set of characters. For hangul characters, the calculation
214
 * is algorithmic. For others, an inverse lookup at the decomposition
215
 * table is necessary. Returns true if a recomposition can be done, and
216
 * false otherwise.
217
 */
218
static bool
219
recompose_code(uint32 start, uint32 code, uint32 *result)
220
0
{
221
  /*
222
   * Handle Hangul characters algorithmically, per the Unicode spec.
223
   *
224
   * Check if two current characters are L and V.
225
   */
226
0
  if (start >= LBASE && start < LBASE + LCOUNT &&
227
0
    code >= VBASE && code < VBASE + VCOUNT)
228
0
  {
229
    /* make syllable of form LV */
230
0
    uint32    lindex = start - LBASE;
231
0
    uint32    vindex = code - VBASE;
232
233
0
    *result = SBASE + (lindex * VCOUNT + vindex) * TCOUNT;
234
0
    return true;
235
0
  }
236
  /* Check if two current characters are LV and T */
237
0
  else if (start >= SBASE && start < (SBASE + SCOUNT) &&
238
0
       ((start - SBASE) % TCOUNT) == 0 &&
239
0
       code > TBASE && code < (TBASE + TCOUNT))
240
0
  {
241
    /* make syllable of form LVT */
242
0
    uint32    tindex = code - TBASE;
243
244
0
    *result = start + tindex;
245
0
    return true;
246
0
  }
247
0
  else
248
0
  {
249
0
    const pg_unicode_decomposition *entry;
250
251
    /*
252
     * Do an inverse lookup of the decomposition tables to see if anything
253
     * matches. The comparison just needs to be a perfect match on the
254
     * sub-table of size two, because the start character has already been
255
     * recomposed partially.  This lookup uses a perfect hash function for
256
     * the backend code.
257
     */
258
0
#ifndef FRONTEND
259
260
0
    int     h,
261
0
          inv_lookup_index;
262
0
    uint64    hashkey;
263
0
    pg_unicode_recompinfo recompinfo = UnicodeRecompInfo;
264
265
    /*
266
     * Compute the hash function. The hash key is formed by concatenating
267
     * bytes of the two codepoints in network order. See also
268
     * src/common/unicode/generate-unicode_norm_table.pl.
269
     */
270
0
    hashkey = pg_hton64(((uint64) start << 32) | (uint64) code);
271
0
    h = recompinfo.hash(&hashkey);
272
273
    /* An out-of-range result implies no match */
274
0
    if (h < 0 || h >= recompinfo.num_recomps)
275
0
      return false;
276
277
0
    inv_lookup_index = recompinfo.inverse_lookup[h];
278
0
    entry = &UnicodeDecompMain[inv_lookup_index];
279
280
0
    if (start == UnicodeDecomp_codepoints[entry->dec_index] &&
281
0
      code == UnicodeDecomp_codepoints[entry->dec_index + 1])
282
0
    {
283
0
      *result = entry->codepoint;
284
0
      return true;
285
0
    }
286
287
#else
288
289
    for (size_t i = 0; i < lengthof(UnicodeDecompMain); i++)
290
    {
291
      entry = &UnicodeDecompMain[i];
292
293
      if (DECOMPOSITION_SIZE(entry) != 2)
294
        continue;
295
296
      if (DECOMPOSITION_NO_COMPOSE(entry))
297
        continue;
298
299
      if (start == UnicodeDecomp_codepoints[entry->dec_index] &&
300
        code == UnicodeDecomp_codepoints[entry->dec_index + 1])
301
      {
302
        *result = entry->codepoint;
303
        return true;
304
      }
305
    }
306
#endif              /* !FRONTEND */
307
0
  }
308
309
0
  return false;
310
0
}
311
312
/*
313
 * Decompose the given code into the array given by caller. The
314
 * decomposition begins at the position given by caller, saving one
315
 * lookup on the decomposition table. The current position needs to be
316
 * updated here to let the caller know from where to continue filling
317
 * in the array result.
318
 */
319
static void
320
decompose_code(char32_t code, bool compat, char32_t **result, int *current)
321
0
{
322
0
  const pg_unicode_decomposition *entry;
323
0
  int     i;
324
0
  const uint32 *decomp;
325
0
  int     dec_size;
326
327
  /*
328
   * Fast path for Hangul characters not stored in tables to save memory as
329
   * decomposition is algorithmic. See
330
   * https://www.unicode.org/reports/tr15/tr15-18.html, annex 10 for details
331
   * on the matter.
332
   */
333
0
  if (code >= SBASE && code < SBASE + SCOUNT)
334
0
  {
335
0
    uint32    l,
336
0
          v,
337
0
          tindex,
338
0
          sindex;
339
0
    char32_t   *res = *result;
340
341
0
    sindex = code - SBASE;
342
0
    l = LBASE + sindex / (VCOUNT * TCOUNT);
343
0
    v = VBASE + (sindex % (VCOUNT * TCOUNT)) / TCOUNT;
344
0
    tindex = sindex % TCOUNT;
345
346
0
    res[*current] = l;
347
0
    (*current)++;
348
0
    res[*current] = v;
349
0
    (*current)++;
350
351
0
    if (tindex != 0)
352
0
    {
353
0
      res[*current] = TBASE + tindex;
354
0
      (*current)++;
355
0
    }
356
357
0
    return;
358
0
  }
359
360
0
  entry = get_code_entry(code);
361
362
  /*
363
   * Just fill in with the current decomposition if there are no
364
   * decomposition codes to recurse to.  A NULL entry is equivalent to a
365
   * character with class 0 and no decompositions, so just leave also in
366
   * this case.
367
   */
368
0
  if (entry == NULL || DECOMPOSITION_SIZE(entry) == 0 ||
369
0
    (!compat && DECOMPOSITION_IS_COMPAT(entry)))
370
0
  {
371
0
    char32_t   *res = *result;
372
373
0
    res[*current] = code;
374
0
    (*current)++;
375
0
    return;
376
0
  }
377
378
  /*
379
   * If this entry has other decomposition codes look at them as well.
380
   */
381
0
  decomp = get_code_decomposition(entry, &dec_size);
382
0
  for (i = 0; i < dec_size; i++)
383
0
  {
384
0
    char32_t  lcode = (char32_t) decomp[i];
385
386
    /* Leave if no more decompositions */
387
0
    decompose_code(lcode, compat, result, current);
388
0
  }
389
0
}
390
391
/*
392
 * unicode_normalize - Normalize a Unicode string to the specified form.
393
 *
394
 * The input is a 0-terminated array of codepoints.
395
 *
396
 * In frontend, returns a 0-terminated array of codepoints, allocated with
397
 * malloc. Or NULL if we run out of memory. In backend, the returned
398
 * string is palloc'd instead, and OOM is reported with ereport().
399
 */
400
char32_t *
401
unicode_normalize(UnicodeNormalizationForm form, const char32_t *input)
402
0
{
403
0
  bool    compat = (form == UNICODE_NFKC || form == UNICODE_NFKD);
404
0
  bool    recompose = (form == UNICODE_NFC || form == UNICODE_NFKC);
405
0
  char32_t   *decomp_chars;
406
0
  char32_t   *recomp_chars;
407
0
  int     decomp_size,
408
0
        current_size;
409
0
  int     count;
410
0
  const char32_t *p;
411
412
  /* variables for recomposition */
413
0
  int     last_class;
414
0
  int     starter_pos;
415
0
  int     target_pos;
416
0
  uint32    starter_ch;
417
418
  /* First, do character decomposition */
419
420
  /*
421
   * Calculate how many characters long the decomposed version will be.
422
   *
423
   * Some characters decompose to quite a few code points, so that the
424
   * decomposed version's size could overrun MaxAllocSize, and even 32-bit
425
   * size_t, even though the input string presumably fits in that.  In
426
   * frontend we want to just return NULL in that case, so monitor the sum
427
   * and exit early once we'd need more than MaxAllocSize bytes.
428
   */
429
0
  decomp_size = 0;
430
0
  for (p = input; *p; p++)
431
0
  {
432
0
    decomp_size += get_decomposed_size(*p, compat);
433
0
    if (unlikely(decomp_size > MaxAllocSize / sizeof(char32_t)))
434
0
    {
435
0
#ifndef FRONTEND
436
      /* Exit loop and let palloc() throw error below */
437
0
      break;
438
#else
439
      /* Just return NULL with no explicit error */
440
      return NULL;
441
#endif
442
0
    }
443
0
  }
444
445
0
  decomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
446
0
  if (decomp_chars == NULL)
447
0
    return NULL;
448
449
  /*
450
   * Now fill in each entry recursively. This needs a second pass on the
451
   * decomposition table.
452
   */
453
0
  current_size = 0;
454
0
  for (p = input; *p; p++)
455
0
    decompose_code(*p, compat, &decomp_chars, &current_size);
456
0
  decomp_chars[decomp_size] = '\0';
457
0
  Assert(decomp_size == current_size);
458
459
  /* Leave if there is nothing to decompose */
460
0
  if (decomp_size == 0)
461
0
    return decomp_chars;
462
463
  /*
464
   * Now apply canonical ordering.
465
   */
466
0
  for (count = 1; count < decomp_size; count++)
467
0
  {
468
0
    char32_t  prev = decomp_chars[count - 1];
469
0
    char32_t  next = decomp_chars[count];
470
0
    char32_t  tmp;
471
0
    const uint8 prevClass = get_canonical_class(prev);
472
0
    const uint8 nextClass = get_canonical_class(next);
473
474
    /*
475
     * Per Unicode (https://www.unicode.org/reports/tr15/tr15-18.html)
476
     * annex 4, a sequence of two adjacent characters in a string is an
477
     * exchangeable pair if the combining class (from the Unicode
478
     * Character Database) for the first character is greater than the
479
     * combining class for the second, and the second is not a starter.  A
480
     * character is a starter if its combining class is 0.
481
     */
482
0
    if (prevClass == 0 || nextClass == 0)
483
0
      continue;
484
485
0
    if (prevClass <= nextClass)
486
0
      continue;
487
488
    /* exchange can happen */
489
0
    tmp = decomp_chars[count - 1];
490
0
    decomp_chars[count - 1] = decomp_chars[count];
491
0
    decomp_chars[count] = tmp;
492
493
    /* backtrack to check again */
494
0
    if (count > 1)
495
0
      count -= 2;
496
0
  }
497
498
0
  if (!recompose)
499
0
    return decomp_chars;
500
501
  /*
502
   * The last phase of NFC and NFKC is the recomposition of the reordered
503
   * Unicode string using combining classes. The recomposed string cannot be
504
   * longer than the decomposed one, so make the allocation of the output
505
   * string based on that assumption.
506
   */
507
0
  recomp_chars = (char32_t *) ALLOC((decomp_size + 1) * sizeof(char32_t));
508
0
  if (!recomp_chars)
509
0
  {
510
0
    FREE(decomp_chars);
511
0
    return NULL;
512
0
  }
513
514
0
  last_class = -1;      /* this eliminates a special check */
515
0
  starter_pos = 0;
516
0
  target_pos = 1;
517
0
  starter_ch = recomp_chars[0] = decomp_chars[0];
518
519
0
  for (count = 1; count < decomp_size; count++)
520
0
  {
521
0
    char32_t  ch = decomp_chars[count];
522
0
    int     ch_class = get_canonical_class(ch);
523
0
    char32_t  composite;
524
525
0
    if (last_class < ch_class &&
526
0
      recompose_code(starter_ch, ch, &composite))
527
0
    {
528
0
      recomp_chars[starter_pos] = composite;
529
0
      starter_ch = composite;
530
0
    }
531
0
    else if (ch_class == 0)
532
0
    {
533
0
      starter_pos = target_pos;
534
0
      starter_ch = ch;
535
0
      last_class = -1;
536
0
      recomp_chars[target_pos++] = ch;
537
0
    }
538
0
    else
539
0
    {
540
0
      last_class = ch_class;
541
0
      recomp_chars[target_pos++] = ch;
542
0
    }
543
0
  }
544
0
  recomp_chars[target_pos] = (char32_t) '\0';
545
546
0
  FREE(decomp_chars);
547
548
0
  return recomp_chars;
549
0
}
550
551
/*
552
 * Normalization "quick check" algorithm; see
553
 * <http://www.unicode.org/reports/tr15/#Detecting_Normalization_Forms>
554
 */
555
556
/* We only need this in the backend. */
557
#ifndef FRONTEND
558
559
static const pg_unicode_normprops *
560
qc_hash_lookup(char32_t ch, const pg_unicode_norminfo *norminfo)
561
0
{
562
0
  int     h;
563
0
  uint32    hashkey;
564
565
  /*
566
   * Compute the hash function. The hash key is the codepoint with the bytes
567
   * in network order.
568
   */
569
0
  hashkey = pg_hton32(ch);
570
0
  h = norminfo->hash(&hashkey);
571
572
  /* An out-of-range result implies no match */
573
0
  if (h < 0 || h >= norminfo->num_normprops)
574
0
    return NULL;
575
576
  /*
577
   * Since it's a perfect hash, we need only match to the specific codepoint
578
   * it identifies.
579
   */
580
0
  if (ch != norminfo->normprops[h].codepoint)
581
0
    return NULL;
582
583
  /* Success! */
584
0
  return &norminfo->normprops[h];
585
0
}
586
587
/*
588
 * Look up the normalization quick check character property
589
 */
590
static UnicodeNormalizationQC
591
qc_is_allowed(UnicodeNormalizationForm form, char32_t ch)
592
0
{
593
0
  const pg_unicode_normprops *found = NULL;
594
595
0
  switch (form)
596
0
  {
597
0
    case UNICODE_NFC:
598
0
      found = qc_hash_lookup(ch, &UnicodeNormInfo_NFC_QC);
599
0
      break;
600
0
    case UNICODE_NFKC:
601
0
      found = qc_hash_lookup(ch, &UnicodeNormInfo_NFKC_QC);
602
0
      break;
603
0
    default:
604
0
      Assert(false);
605
0
      break;
606
0
  }
607
608
0
  if (found)
609
0
    return found->quickcheck;
610
0
  else
611
0
    return UNICODE_NORM_QC_YES;
612
0
}
613
614
UnicodeNormalizationQC
615
unicode_is_normalized_quickcheck(UnicodeNormalizationForm form, const char32_t *input)
616
0
{
617
0
  uint8   lastCanonicalClass = 0;
618
0
  UnicodeNormalizationQC result = UNICODE_NORM_QC_YES;
619
620
  /*
621
   * For the "D" forms, we don't run the quickcheck.  We don't include the
622
   * lookup tables for those because they are huge, checking for these
623
   * particular forms is less common, and running the slow path is faster
624
   * for the "D" forms than the "C" forms because you don't need to
625
   * recompose, which is slow.
626
   */
627
0
  if (form == UNICODE_NFD || form == UNICODE_NFKD)
628
0
    return UNICODE_NORM_QC_MAYBE;
629
630
0
  for (const char32_t *p = input; *p; p++)
631
0
  {
632
0
    char32_t  ch = *p;
633
0
    uint8   canonicalClass;
634
0
    UnicodeNormalizationQC check;
635
636
0
    canonicalClass = get_canonical_class(ch);
637
0
    if (lastCanonicalClass > canonicalClass && canonicalClass != 0)
638
0
      return UNICODE_NORM_QC_NO;
639
640
0
    check = qc_is_allowed(form, ch);
641
0
    if (check == UNICODE_NORM_QC_NO)
642
0
      return UNICODE_NORM_QC_NO;
643
0
    else if (check == UNICODE_NORM_QC_MAYBE)
644
0
      result = UNICODE_NORM_QC_MAYBE;
645
646
0
    lastCanonicalClass = canonicalClass;
647
0
  }
648
0
  return result;
649
0
}
650
651
#endif              /* !FRONTEND */