Process Hacker
format_i.h
Go to the documentation of this file.
1 /*
2  * This file contains the actual formatting code used by various public interface
3  * functions.
4  *
5  * There are three macros defined by the parent function which control how this code
6  * writes the formatted string:
7  * * ENSURE_BUFFER - This macro is passed the number of bytes required whenever
8  * characters need to be written to the buffer. The macro can resize the buffer
9  * if needed.
10  * * OK_BUFFER - This macro returns TRUE if it is OK to write to the buffer, otherwise
11  * FALSE when the buffer is too large, is not specified, or some other error has
12  * occurred.
13  * * ADVANCE_BUFFER - This macro is passed the number of bytes written to the buffer
14  * and should increment the "buffer" pointer and "usedLength" counter.
15  * In addition to these macros, the "buffer" and "usedLength" variables are assumed to
16  * be present.
17  *
18  * The below code defines many macros; this is so that composite formatting types can
19  * be constructed (e.g. the "size" type).
20  */
21 
22 {
23  if (PhBeginInitOnce(&PhpFormatInitOnce))
24  {
25  WCHAR localeBuffer[4];
26 
27  if (
28  GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, localeBuffer, 4) &&
29  (localeBuffer[0] != 0 && localeBuffer[1] == 0)
30  )
31  {
32  PhpFormatDecimalSeparator = localeBuffer[0];
33  }
34 
35  if (
36  GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, localeBuffer, 4) &&
37  (localeBuffer[0] != 0 && localeBuffer[1] == 0)
38  )
39  {
40  PhpFormatThousandSeparator = localeBuffer[0];
41  }
42 
43  if (PhpFormatDecimalSeparator != '.')
44  PhpFormatUserLocale = _create_locale(LC_ALL, "");
45 
46  PhEndInitOnce(&PhpFormatInitOnce);
47  }
48 
49  while (Count--)
50  {
51  PPH_FORMAT format;
52  SIZE_T partLength;
53  WCHAR tempBuffer[BUFFER_SIZE];
54  ULONG flags;
55  ULONG int32;
56  ULONG64 int64;
57 
58  format = Format++;
59 
60  // Save the currently used length so we can compute the
61  // part length later.
62  partLength = usedLength;
63 
64  flags = 0;
65 
66  switch (format->Type & FormatTypeMask)
67  {
68 
69  // Characters and Strings
70 
71  case CharFormatType:
72  ENSURE_BUFFER(sizeof(WCHAR));
73  if (OK_BUFFER)
74  *buffer = format->u.Char;
75  ADVANCE_BUFFER(sizeof(WCHAR));
76  break;
77  case StringFormatType:
78  ENSURE_BUFFER(format->u.String.Length);
79  if (OK_BUFFER)
80  memcpy(buffer, format->u.String.Buffer, format->u.String.Length);
81  ADVANCE_BUFFER(format->u.String.Length);
82  break;
83  case StringZFormatType:
84  {
85  SIZE_T count;
86 
87  count = PhCountStringZ(format->u.StringZ);
88  ENSURE_BUFFER(count * sizeof(WCHAR));
89  if (OK_BUFFER)
90  memcpy(buffer, format->u.StringZ, count * sizeof(WCHAR));
91  ADVANCE_BUFFER(count * sizeof(WCHAR));
92  }
93  break;
96  {
97  ULONG bytesInUnicodeString;
98  PSTR multiByteBuffer;
99  SIZE_T multiByteLength;
100 
101  if (format->Type == MultiByteStringFormatType)
102  {
103  multiByteBuffer = format->u.MultiByteString.Buffer;
104  multiByteLength = format->u.MultiByteString.Length;
105  }
106  else
107  {
108  multiByteBuffer = format->u.MultiByteStringZ;
109  multiByteLength = strlen(multiByteBuffer);
110  }
111 
113  &bytesInUnicodeString,
114  multiByteBuffer,
115  (ULONG)multiByteLength
116  )))
117  {
118  ENSURE_BUFFER(bytesInUnicodeString);
119 
120  if (!OK_BUFFER || NT_SUCCESS(RtlMultiByteToUnicodeN(
121  buffer,
122  bytesInUnicodeString,
123  NULL,
124  multiByteBuffer,
125  (ULONG)multiByteLength
126  )))
127  {
128  ADVANCE_BUFFER(bytesInUnicodeString);
129  }
130  }
131  }
132  break;
133 
134  // Integers
135 
136 #define PROCESS_DIGIT(Input) \
137  do { \
138  r = (ULONG)(Input % radix); \
139  Input /= radix; \
140  *temp-- = integerToChar[r]; \
141  tempCount++; \
142  } while (0)
143 
144 #define COMMON_INTEGER_FORMAT(Input, Format) \
145  do { \
146  ULONG radix; \
147  PCHAR integerToChar; \
148  PWSTR temp; \
149  ULONG tempCount; \
150  ULONG r; \
151  ULONG preCount; \
152  ULONG padCount; \
153  \
154  radix = 10; \
155  if (((Format)->Type & FormatUseRadix) && (Format)->Radix >= 2 && (Format)->Radix <= 69) \
156  radix = (Format)->Radix; \
157  integerToChar = PhIntegerToChar; \
158  if ((Format)->Type & FormatUpperCase) \
159  integerToChar = PhIntegerToCharUpper; \
160  temp = tempBuffer + BUFFER_SIZE - 1; \
161  tempCount = 0; \
162  \
163  if (Input != 0) \
164  { \
165  if ((Format)->Type & FormatGroupDigits) \
166  { \
167  ULONG needsSep = 0; \
168  \
169  do \
170  { \
171  PROCESS_DIGIT(Input); \
172  \
173  if (++needsSep == 3 && Input != 0) /* get rid of trailing separator */ \
174  { \
175  *temp-- = PhpFormatThousandSeparator; \
176  tempCount++; \
177  needsSep = 0; \
178  } \
179  } while (Input != 0); \
180  } \
181  else \
182  { \
183  do \
184  { \
185  PROCESS_DIGIT(Input); \
186  } while (Input != 0); \
187  } \
188  } \
189  else \
190  { \
191  *temp-- = '0'; \
192  tempCount++; \
193  } \
194  \
195  preCount = 0; \
196  \
197  if (flags & PHP_FORMAT_NEGATIVE) \
198  preCount++; \
199  else if ((Format)->Type & FormatPrefixSign) \
200  preCount++; \
201  \
202  if (((Format)->Type & FormatPadZeros) && !((Format)->Type & FormatGroupDigits)) \
203  { \
204  if (preCount + tempCount < (Format)->Width) \
205  { \
206  flags |= PHP_FORMAT_PAD; \
207  padCount = (Format)->Width - (preCount + tempCount); \
208  preCount += padCount; \
209  } \
210  } \
211  \
212  temp++; \
213  ENSURE_BUFFER((preCount + tempCount) * sizeof(WCHAR)); \
214  if (OK_BUFFER) \
215  { \
216  if (flags & PHP_FORMAT_NEGATIVE) \
217  *buffer++ = '-'; \
218  else if ((Format)->Type & FormatPrefixSign) \
219  *buffer++ = '+'; \
220  \
221  if (flags & PHP_FORMAT_PAD) \
222  { \
223  wmemset(buffer, '0', padCount); \
224  buffer += padCount; \
225  } \
226  \
227  memcpy(buffer, temp, tempCount * sizeof(WCHAR)); \
228  buffer += tempCount; \
229  } \
230  usedLength += (preCount + tempCount) * sizeof(WCHAR); \
231  } while (0)
232 
233 #ifndef _WIN64
234  case IntPtrFormatType:
235  int32 = format->u.IntPtr;
236  goto CommonMaybeNegativeInt32Format;
237 #endif
238  case Int32FormatType:
239  int32 = format->u.Int32;
240 
241 #ifndef _WIN64
242 CommonMaybeNegativeInt32Format:
243 #endif
244  if ((LONG)int32 < 0)
245  {
246  int32 = -(LONG)int32;
247  flags |= PHP_FORMAT_NEGATIVE;
248  }
249 
250  goto CommonInt32Format;
251 #ifndef _WIN64
252  case UIntPtrFormatType:
253  int32 = format->u.UIntPtr;
254  goto CommonInt32Format;
255 #endif
256  case UInt32FormatType:
257  int32 = format->u.UInt32;
258 CommonInt32Format:
259  COMMON_INTEGER_FORMAT(int32, format);
260  break;
261 #ifdef _WIN64
262  case IntPtrFormatType:
263  int64 = format->u.IntPtr;
264  goto CommonMaybeNegativeInt64Format;
265 #endif
266  case Int64FormatType:
267  int64 = format->u.Int64;
268 
269 #ifdef _WIN64
270 CommonMaybeNegativeInt64Format:
271 #endif
272  if ((LONG64)int64 < 0)
273  {
274  int64 = -(LONG64)int64;
275  flags |= PHP_FORMAT_NEGATIVE;
276  }
277 
278  goto CommonInt64Format;
279 #ifdef _WIN64
280  case UIntPtrFormatType:
281  int64 = format->u.UIntPtr;
282  goto CommonInt64Format;
283 #endif
284  case UInt64FormatType:
285  int64 = format->u.UInt64;
286 CommonInt64Format:
287  COMMON_INTEGER_FORMAT(int64, format);
288  break;
289 
290  // Floating point numbers
291 
292 #define COMMON_DOUBLE_FORMAT(Format) \
293  do { \
294  ULONG precision; \
295  DOUBLE value; \
296  CHAR c; \
297  PSTR temp; \
298  ULONG length; \
299  \
300  if ((Format)->Type & FormatUsePrecision) \
301  { \
302  precision = (Format)->Precision; \
303  \
304  if (precision > BUFFER_SIZE - 1 - _CVTBUFSIZE) \
305  precision = BUFFER_SIZE - 1 - _CVTBUFSIZE; \
306  } \
307  else \
308  { \
309  precision = 6; \
310  } \
311  \
312  c = 'f'; \
313  \
314  if ((Format)->Type & FormatStandardForm) \
315  c = 'e'; \
316  else if ((Format)->Type & FormatHexadecimalForm) \
317  c = 'a'; \
318  \
319  if ((Format)->Type & FormatUpperCase) \
320  c -= 32; /* uppercase the format type */ \
321  \
322  /* Use MS CRT routines to do the work. */ \
323  \
324  value = (Format)->u.Double; \
325  temp = (PSTR)tempBuffer + 1; /* leave one character so we can insert a prefix if needed */ \
326  _cfltcvt_l( \
327  &value, \
328  temp, \
329  sizeof(tempBuffer) - 1, \
330  c, \
331  precision, \
332  !!((Format)->Type & FormatUpperCase), \
333  PhpFormatUserLocale \
334  ); \
335  \
336  /* if (((Format)->Type & FormatForceDecimalPoint) && precision == 0) */ \
337  /* _forcdecpt_l(tempBufferAnsi, PhpFormatUserLocale); */ \
338  if ((Format)->Type & FormatCropZeros) \
339  _cropzeros_l(temp, PhpFormatUserLocale); \
340  \
341  length = (ULONG)strlen(temp); \
342  \
343  if (temp[0] == '-') \
344  { \
345  flags |= PHP_FORMAT_NEGATIVE; \
346  temp++; \
347  length--; \
348  } \
349  else if ((Format)->Type & FormatPrefixSign) \
350  { \
351  flags |= PHP_FORMAT_POSITIVE; \
352  } \
353  \
354  if (((Format)->Type & FormatGroupDigits) && !((Format)->Type & (FormatStandardForm | FormatHexadecimalForm))) \
355  { \
356  PSTR whole; \
357  PSTR decimalPoint; \
358  ULONG wholeCount; \
359  ULONG sepsCount; \
360  ULONG ensureLength; \
361  ULONG copyCount; \
362  ULONG needsSep; \
363  \
364  /* Find the first non-digit character and assume that is the */ \
365  /* decimal point (or the end of the string). */ \
366  \
367  whole = temp; \
368  decimalPoint = temp; \
369  \
370  while ((UCHAR)(*decimalPoint - '0') < 10) \
371  decimalPoint++; \
372  \
373  /* Copy the characters to the output buffer, and at the same time */ \
374  /* insert the separators. */ \
375  \
376  wholeCount = (ULONG)(decimalPoint - temp); \
377  \
378  if (wholeCount != 0) \
379  sepsCount = (wholeCount + 2) / 3 - 1; \
380  else \
381  sepsCount = 0; \
382  \
383  ensureLength = (length + sepsCount) * sizeof(WCHAR); \
384  if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
385  ensureLength += sizeof(WCHAR); \
386  ENSURE_BUFFER(ensureLength); \
387  \
388  copyCount = wholeCount; \
389  needsSep = (wholeCount + 2) % 3; \
390  \
391  if (OK_BUFFER) \
392  { \
393  if (flags & PHP_FORMAT_NEGATIVE) \
394  *buffer++ = '-'; \
395  else if (flags & PHP_FORMAT_POSITIVE) \
396  *buffer++ = '+'; \
397  \
398  while (copyCount--) \
399  { \
400  *buffer++ = *whole++; \
401  \
402  if (needsSep-- == 0 && copyCount != 0) /* get rid of trailing separator */ \
403  { \
404  *buffer++ = PhpFormatThousandSeparator; \
405  needsSep = 2; \
406  } \
407  } \
408  } \
409  \
410  if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
411  usedLength += sizeof(WCHAR); \
412  usedLength += (wholeCount + sepsCount) * sizeof(WCHAR); \
413  \
414  /* Copy the rest. */ \
415  \
416  copyCount = length - wholeCount; \
417  \
418  if (OK_BUFFER) \
419  { \
420  PhZeroExtendToUtf16Buffer(decimalPoint, copyCount, buffer); \
421  ADVANCE_BUFFER(copyCount * sizeof(WCHAR)); \
422  } \
423  } \
424  else \
425  { \
426  SIZE_T preLength; \
427  SIZE_T padLength; \
428  \
429  /* Take care of the sign and zero padding. */ \
430  preLength = 0; \
431  \
432  if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
433  preLength++; \
434  \
435  if ((Format)->Type & FormatPadZeros) \
436  { \
437  if (preLength + length < (Format)->Width) \
438  { \
439  flags |= PHP_FORMAT_PAD; \
440  padLength = (Format)->Width - (preLength + length); \
441  preLength += padLength; \
442  } \
443  } \
444  /* We don't need to group digits, so directly copy the characters */ \
445  /* to the output buffer. */ \
446  \
447  ENSURE_BUFFER((preLength + length) * sizeof(WCHAR)); \
448  \
449  if (OK_BUFFER) \
450  { \
451  if (flags & PHP_FORMAT_NEGATIVE) \
452  *buffer++ = '-'; \
453  else if (flags & PHP_FORMAT_POSITIVE) \
454  *buffer++ = '+'; \
455  \
456  if (flags & PHP_FORMAT_PAD) \
457  { \
458  wmemset(buffer, '0', padLength); \
459  buffer += padLength; \
460  } \
461  } \
462  \
463  usedLength += preLength * sizeof(WCHAR); \
464  \
465  if (OK_BUFFER) \
466  { \
467  PhZeroExtendToUtf16Buffer((PSTR)temp, length, buffer); \
468  ADVANCE_BUFFER(length * sizeof(WCHAR)); \
469  } \
470  } \
471  } while (0)
472 
473  case DoubleFormatType:
474  flags = 0;
475  COMMON_DOUBLE_FORMAT(format);
476  break;
477 
478  // Additional types
479 
480  case SizeFormatType:
481  {
482  ULONG i = 0;
483  ULONG maxSizeUnit;
484  DOUBLE s;
485  PH_FORMAT doubleFormat;
486 
487  s = (DOUBLE)format->u.Size;
488 
489  if (format->u.Size == 0)
490  {
491  ENSURE_BUFFER(sizeof(WCHAR));
492  if (OK_BUFFER)
493  *buffer = '0';
494  ADVANCE_BUFFER(sizeof(WCHAR));
495  goto ContinueLoop;
496  }
497 
498  if (format->Type & FormatUseRadix)
499  maxSizeUnit = format->Radix;
500  else
501  maxSizeUnit = PhMaxSizeUnit;
502 
503  while (
504  s >= 1024 &&
505  i < sizeof(PhpSizeUnitNamesCounted) / sizeof(PH_STRINGREF) &&
506  i < maxSizeUnit
507  )
508  {
509  s /= 1024;
510  i++;
511  }
512 
513  // Format the number, then append the unit name.
514 
516  doubleFormat.Precision = (format->Type & FormatUsePrecision) ? format->Precision : 2;
517  doubleFormat.Width = 0; // stupid compiler
518  doubleFormat.u.Double = s;
519  flags = 0;
520  COMMON_DOUBLE_FORMAT(&doubleFormat);
521 
522  ENSURE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length);
523  if (OK_BUFFER)
524  {
525  *buffer = ' ';
526  memcpy(buffer + 1, PhpSizeUnitNamesCounted[i].Buffer, PhpSizeUnitNamesCounted[i].Length);
527  }
528  ADVANCE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length);
529  }
530  break;
531  }
532 
533 ContinueLoop:
534  partLength = usedLength - partLength;
535 
536  if (format->Type & (FormatLeftAlign | FormatRightAlign))
537  {
538  SIZE_T newLength;
539  SIZE_T addLength;
540 
541  newLength = format->Width * sizeof(WCHAR);
542 
543  // We only pad and never truncate.
544  if (partLength < newLength)
545  {
546  addLength = newLength - partLength;
547  ENSURE_BUFFER(addLength);
548 
549  if (OK_BUFFER)
550  {
551  WCHAR pad;
552 
553  if (format->Type & FormatUsePad)
554  pad = format->Pad;
555  else
556  pad = ' ';
557 
558  if (format->Type & FormatLeftAlign)
559  {
560  // Left alignment is easy; we just fill the remaining space
561  // with the pad character.
562  wmemset(buffer, pad, addLength / sizeof(WCHAR));
563  }
564  else
565  {
566  PWSTR start;
567 
568  // Right alignment is much slower and involves moving the
569  // text forward, then filling in the space before it.
570  start = buffer - partLength / sizeof(WCHAR);
571  memmove(start + addLength / sizeof(WCHAR), start, partLength);
572  wmemset(start, pad, addLength / sizeof(WCHAR));
573  }
574  }
575 
576  ADVANCE_BUFFER(addLength);
577  }
578  }
579  }
580 }