• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Proto internal runtime checks.
3 *
4 * Checks are grouped into different severity, see:
5 * http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers
6 *
7 * Checks are also grouped into different sections:
8 *   - CHECK_BOUNDS:
9 *       Checks that ensure that indexed access is within bounds
10 *       (e.g. an array being accessed past its size).
11 *   - CHECK_STATE
12 *       Checks related to the state of an object
13 *       (e.g. a parser hitting an invalid case).
14 *   - CHECK_TYPE:
15 *       Checks that relate to type errors (e.g. code receives a number instead
16 *       of a string).
17 */
18goog.module('protobuf.internal.checks');
19
20const ByteString = goog.require('protobuf.ByteString');
21const Int64 = goog.require('protobuf.Int64');
22const WireType = goog.require('protobuf.binary.WireType');
23
24//
25// See
26// http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers
27//
28/** @define{string} */
29const CHECK_LEVEL_DEFINE = goog.define('protobuf.defines.CHECK_LEVEL', '');
30
31/** @define{boolean} */
32const POLYFILL_TEXT_ENCODING =
33    goog.define('protobuf.defines.POLYFILL_TEXT_ENCODING', true);
34
35/**
36 * @const {number}
37 */
38const MAX_FIELD_NUMBER = Math.pow(2, 29) - 1;
39
40/**
41 * The largest finite float32 value.
42 * @const {number}
43 */
44const FLOAT32_MAX = 3.4028234663852886e+38;
45
46/** @enum {number} */
47const CheckLevel = {
48  DEBUG: 0,
49  CRITICAL: 1,
50  OFF: 2
51};
52
53
54/** @return {!CheckLevel} */
55function calculateCheckLevel() {
56  const definedLevel = CHECK_LEVEL_DEFINE.toUpperCase();
57  if (definedLevel === '') {
58    // user did not set a value, value now just depends on goog.DEBUG
59    return goog.DEBUG ? CheckLevel.DEBUG : CheckLevel.CRITICAL;
60  }
61
62  if (definedLevel === 'CRITICAL') {
63    return CheckLevel.CRITICAL;
64  }
65
66  if (definedLevel === 'OFF') {
67    return CheckLevel.OFF;
68  }
69
70  if (definedLevel === 'DEBUG') {
71    return CheckLevel.DEBUG;
72  }
73
74  throw new Error(`Unknown value for CHECK_LEVEL: ${CHECK_LEVEL_DEFINE}`);
75}
76
77const /** !CheckLevel */ CHECK_LEVEL = calculateCheckLevel();
78
79const /** boolean */ CHECK_STATE = CHECK_LEVEL === CheckLevel.DEBUG;
80
81const /** boolean */ CHECK_CRITICAL_STATE =
82    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
83
84const /** boolean */ CHECK_BOUNDS = CHECK_LEVEL === CheckLevel.DEBUG;
85
86const /** boolean */ CHECK_CRITICAL_BOUNDS =
87    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
88
89const /** boolean */ CHECK_TYPE = CHECK_LEVEL === CheckLevel.DEBUG;
90
91const /** boolean */ CHECK_CRITICAL_TYPE =
92    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
93
94/**
95 * Ensures the truth of an expression involving the state of the calling
96 * instance, but not involving any parameters to the calling method.
97 *
98 * For cases where failing fast is pretty important and not failing early could
99 * cause bugs that are much harder to debug.
100 * @param {boolean} state
101 * @param {string=} message
102 * @throws {!Error} If the state is false and the check state is critical.
103 */
104function checkCriticalState(state, message = '') {
105  if (!CHECK_CRITICAL_STATE) {
106    return;
107  }
108  if (!state) {
109    throw new Error(message);
110  }
111}
112
113/**
114 * Ensures the truth of an expression involving the state of the calling
115 * instance, but not involving any parameters to the calling method.
116 *
117 * @param {boolean} state
118 * @param {string=} message
119 * @throws {!Error} If the state is false and the check state is debug.
120 */
121function checkState(state, message = '') {
122  if (!CHECK_STATE) {
123    return;
124  }
125  checkCriticalState(state, message);
126}
127
128/**
129 * Ensures that `index` specifies a valid position in an indexable object of
130 * size `size`. A position index may range from zero to size, inclusive.
131 * @param {number} index
132 * @param {number} size
133 * @throws {!Error} If the index is out of range and the check state is debug.
134 */
135function checkPositionIndex(index, size) {
136  if (!CHECK_BOUNDS) {
137    return;
138  }
139  checkCriticalPositionIndex(index, size);
140}
141
142/**
143 * Ensures that `index` specifies a valid position in an indexable object of
144 * size `size`. A position index may range from zero to size, inclusive.
145 * @param {number} index
146 * @param {number} size
147 * @throws {!Error} If the index is out of range and the check state is
148 * critical.
149 */
150function checkCriticalPositionIndex(index, size) {
151  if (!CHECK_CRITICAL_BOUNDS) {
152    return;
153  }
154  if (index < 0 || index > size) {
155    throw new Error(`Index out of bounds: index: ${index} size: ${size}`);
156  }
157}
158
159/**
160 * Ensures that `index` specifies a valid element in an indexable object of
161 * size `size`. A element index may range from zero to size, exclusive.
162 * @param {number} index
163 * @param {number} size
164 * @throws {!Error} If the index is out of range and the check state is
165 * debug.
166 */
167function checkElementIndex(index, size) {
168  if (!CHECK_BOUNDS) {
169    return;
170  }
171  checkCriticalElementIndex(index, size);
172}
173
174/**
175 * Ensures that `index` specifies a valid element in an indexable object of
176 * size `size`. A element index may range from zero to size, exclusive.
177 * @param {number} index
178 * @param {number} size
179 * @throws {!Error} If the index is out of range and the check state is
180 * critical.
181 */
182function checkCriticalElementIndex(index, size) {
183  if (!CHECK_CRITICAL_BOUNDS) {
184    return;
185  }
186  if (index < 0 || index >= size) {
187    throw new Error(`Index out of bounds: index: ${index} size: ${size}`);
188  }
189}
190
191/**
192 * Ensures the range of [start, end) is with the range of [0, size).
193 * @param {number} start
194 * @param {number} end
195 * @param {number} size
196 * @throws {!Error} If start and end are out of range and the check state is
197 * debug.
198 */
199function checkRange(start, end, size) {
200  if (!CHECK_BOUNDS) {
201    return;
202  }
203  checkCriticalRange(start, end, size);
204}
205
206/**
207 * Ensures the range of [start, end) is with the range of [0, size).
208 * @param {number} start
209 * @param {number} end
210 * @param {number} size
211 * @throws {!Error} If start and end are out of range and the check state is
212 * critical.
213 */
214function checkCriticalRange(start, end, size) {
215  if (!CHECK_CRITICAL_BOUNDS) {
216    return;
217  }
218  if (start < 0 || end < 0 || start > size || end > size) {
219    throw new Error(`Range error: start: ${start} end: ${end} size: ${size}`);
220  }
221  if (start > end) {
222    throw new Error(`Start > end: ${start} > ${end}`);
223  }
224}
225
226/**
227 * Ensures that field number is an integer and within the range of
228 * [1, MAX_FIELD_NUMBER].
229 * @param {number} fieldNumber
230 * @throws {!Error} If the field number is out of range and the check state is
231 * debug.
232 */
233function checkFieldNumber(fieldNumber) {
234  if (!CHECK_TYPE) {
235    return;
236  }
237  checkCriticalFieldNumber(fieldNumber);
238}
239
240/**
241 * Ensures that the value is neither null nor undefined.
242 *
243 * @param {T} value
244 * @return {R}
245 *
246 * @template T
247 * @template R :=
248 *     mapunion(T, (V) =>
249 *         cond(eq(V, 'null'),
250 *             none(),
251 *             cond(eq(V, 'undefined'),
252 *                 none(),
253 *                 V)))
254 *  =:
255 */
256function checkDefAndNotNull(value) {
257  if (CHECK_TYPE) {
258    // Note that undefined == null.
259    if (value == null) {
260      throw new Error(`Value can't be null`);
261    }
262  }
263  return value;
264}
265
266/**
267 * Ensures that the value exists and is a function.
268 *
269 * @param {function(?): ?} func
270 */
271function checkFunctionExists(func) {
272  if (CHECK_TYPE) {
273    if (typeof func !== 'function') {
274      throw new Error(`${func} is not a function`);
275    }
276  }
277}
278
279/**
280 * Ensures that field number is an integer and within the range of
281 * [1, MAX_FIELD_NUMBER].
282 * @param {number} fieldNumber
283 * @throws {!Error} If the field number is out of range and the check state is
284 * critical.
285 */
286function checkCriticalFieldNumber(fieldNumber) {
287  if (!CHECK_CRITICAL_TYPE) {
288    return;
289  }
290  if (fieldNumber <= 0 || fieldNumber > MAX_FIELD_NUMBER) {
291    throw new Error(`Field number is out of range: ${fieldNumber}`);
292  }
293}
294
295/**
296 * Ensures that wire type is valid.
297 * @param {!WireType} wireType
298 * @throws {!Error} If the wire type is invalid and the check state is debug.
299 */
300function checkWireType(wireType) {
301  if (!CHECK_TYPE) {
302    return;
303  }
304  checkCriticalWireType(wireType);
305}
306
307/**
308 * Ensures that wire type is valid.
309 * @param {!WireType} wireType
310 * @throws {!Error} If the wire type is invalid and the check state is critical.
311 */
312function checkCriticalWireType(wireType) {
313  if (!CHECK_CRITICAL_TYPE) {
314    return;
315  }
316  if (wireType < WireType.VARINT || wireType > WireType.FIXED32) {
317    throw new Error(`Invalid wire type: ${wireType}`);
318  }
319}
320
321/**
322 * Ensures the given value has the correct type.
323 * @param {boolean} expression
324 * @param {string} errorMsg
325 * @throws {!Error} If the value has the wrong type and the check state is
326 * critical.
327 */
328function checkCriticalType(expression, errorMsg) {
329  if (!CHECK_CRITICAL_TYPE) {
330    return;
331  }
332  if (!expression) {
333    throw new Error(errorMsg);
334  }
335}
336
337/**
338 * Checks whether a given object is an array.
339 * @param {*} value
340 * @return {!Array<*>}
341 */
342function checkCriticalTypeArray(value) {
343  checkCriticalType(
344      Array.isArray(value), `Must be an array, but got: ${value}`);
345  return /** @type {!Array<*>} */ (value);
346}
347
348/**
349 * Checks whether a given object is an iterable.
350 * @param {*} value
351 * @return {!Iterable<*>}
352 */
353function checkCriticalTypeIterable(value) {
354  checkCriticalType(
355      !!value[Symbol.iterator], `Must be an iterable, but got: ${value}`);
356  return /** @type {!Iterable<*>} */ (value);
357}
358
359/**
360 * Checks whether a given object is a boolean.
361 * @param {*} value
362 */
363function checkCriticalTypeBool(value) {
364  checkCriticalType(
365      typeof value === 'boolean', `Must be a boolean, but got: ${value}`);
366}
367
368/**
369 * Checks whether a given object is an array of boolean.
370 * @param {*} values
371 */
372function checkCriticalTypeBoolArray(values) {
373  // TODO(b/134765672)
374  if (!CHECK_CRITICAL_TYPE) {
375    return;
376  }
377  const array = checkCriticalTypeArray(values);
378  for (const value of array) {
379    checkCriticalTypeBool(value);
380  }
381}
382
383/**
384 * Checks whether a given object is a ByteString.
385 * @param {*} value
386 */
387function checkCriticalTypeByteString(value) {
388  checkCriticalType(
389      value instanceof ByteString, `Must be a ByteString, but got: ${value}`);
390}
391
392/**
393 * Checks whether a given object is an array of ByteString.
394 * @param {*} values
395 */
396function checkCriticalTypeByteStringArray(values) {
397  // TODO(b/134765672)
398  if (!CHECK_CRITICAL_TYPE) {
399    return;
400  }
401  const array = checkCriticalTypeArray(values);
402  for (const value of array) {
403    checkCriticalTypeByteString(value);
404  }
405}
406
407/**
408 * Checks whether a given object is a number.
409 * @param {*} value
410 * @throws {!Error} If the value is not float and the check state is debug.
411 */
412function checkTypeDouble(value) {
413  if (!CHECK_TYPE) {
414    return;
415  }
416  checkCriticalTypeDouble(value);
417}
418
419/**
420 * Checks whether a given object is a number.
421 * @param {*} value
422 * @throws {!Error} If the value is not float and the check state is critical.
423 */
424function checkCriticalTypeDouble(value) {
425  checkCriticalType(
426      typeof value === 'number', `Must be a number, but got: ${value}`);
427}
428
429/**
430 * Checks whether a given object is an array of double.
431 * @param {*} values
432 */
433function checkCriticalTypeDoubleArray(values) {
434  // TODO(b/134765672)
435  if (!CHECK_CRITICAL_TYPE) {
436    return;
437  }
438  const array = checkCriticalTypeArray(values);
439  for (const value of array) {
440    checkCriticalTypeDouble(value);
441  }
442}
443
444/**
445 * Checks whether a given object is a number.
446 * @param {*} value
447 * @throws {!Error} If the value is not signed int32 and the check state is
448 *     debug.
449 */
450function checkTypeSignedInt32(value) {
451  if (!CHECK_TYPE) {
452    return;
453  }
454  checkCriticalTypeSignedInt32(value);
455}
456
457/**
458 * Checks whether a given object is a number.
459 * @param {*} value
460 * @throws {!Error} If the value is not signed int32 and the check state is
461 *     critical.
462 */
463function checkCriticalTypeSignedInt32(value) {
464  checkCriticalTypeDouble(value);
465  const valueAsNumber = /** @type {number} */ (value);
466  if (CHECK_CRITICAL_TYPE) {
467    if (valueAsNumber < -Math.pow(2, 31) || valueAsNumber > Math.pow(2, 31) ||
468        !Number.isInteger(valueAsNumber)) {
469      throw new Error(`Must be int32, but got: ${valueAsNumber}`);
470    }
471  }
472}
473
474/**
475 * Checks whether a given object is an array of numbers.
476 * @param {*} values
477 */
478function checkCriticalTypeSignedInt32Array(values) {
479  // TODO(b/134765672)
480  if (!CHECK_CRITICAL_TYPE) {
481    return;
482  }
483  const array = checkCriticalTypeArray(values);
484  for (const value of array) {
485    checkCriticalTypeSignedInt32(value);
486  }
487}
488
489/**
490 * Ensures that value is a long instance.
491 * @param {*} value
492 * @throws {!Error} If the value is not a long instance and check state is
493 *     debug.
494 */
495function checkTypeSignedInt64(value) {
496  if (!CHECK_TYPE) {
497    return;
498  }
499  checkCriticalTypeSignedInt64(value);
500}
501
502/**
503 * Ensures that value is a long instance.
504 * @param {*} value
505 * @throws {!Error} If the value is not a long instance and check state is
506 *     critical.
507 */
508function checkCriticalTypeSignedInt64(value) {
509  if (!CHECK_CRITICAL_TYPE) {
510    return;
511  }
512  if (!(value instanceof Int64)) {
513    throw new Error(`Must be Int64 instance, but got: ${value}`);
514  }
515}
516
517/**
518 * Checks whether a given object is an array of long instances.
519 * @param {*} values
520 * @throws {!Error} If values is not an array of long instances.
521 */
522function checkCriticalTypeSignedInt64Array(values) {
523  // TODO(b/134765672)
524  if (!CHECK_CRITICAL_TYPE) {
525    return;
526  }
527  const array = checkCriticalTypeArray(values);
528  for (const value of array) {
529    checkCriticalTypeSignedInt64(value);
530  }
531}
532
533/**
534 * Checks whether a given object is a number and within float32 precision.
535 * @param {*} value
536 * @throws {!Error} If the value is not float and the check state is debug.
537 */
538function checkTypeFloat(value) {
539  if (!CHECK_TYPE) {
540    return;
541  }
542  checkCriticalTypeFloat(value);
543}
544
545/**
546 * Checks whether a given object is a number and within float32 precision.
547 * @param {*} value
548 * @throws {!Error} If the value is not float and the check state is critical.
549 */
550function checkCriticalTypeFloat(value) {
551  checkCriticalTypeDouble(value);
552  if (CHECK_CRITICAL_TYPE) {
553    const valueAsNumber = /** @type {number} */ (value);
554    if (Number.isFinite(valueAsNumber) &&
555        (valueAsNumber > FLOAT32_MAX || valueAsNumber < -FLOAT32_MAX)) {
556      throw new Error(
557          `Given number does not fit into float precision: ${value}`);
558    }
559  }
560}
561
562/**
563 * Checks whether a given object is an iterable of floats.
564 * @param {*} values
565 */
566function checkCriticalTypeFloatIterable(values) {
567  // TODO(b/134765672)
568  if (!CHECK_CRITICAL_TYPE) {
569    return;
570  }
571  const iterable = checkCriticalTypeIterable(values);
572  for (const value of iterable) {
573    checkCriticalTypeFloat(value);
574  }
575}
576
577/**
578 * Checks whether a given object is a string.
579 * @param {*} value
580 */
581function checkCriticalTypeString(value) {
582  checkCriticalType(
583      typeof value === 'string', `Must be string, but got: ${value}`);
584}
585
586/**
587 * Checks whether a given object is an array of string.
588 * @param {*} values
589 */
590function checkCriticalTypeStringArray(values) {
591  // TODO(b/134765672)
592  if (!CHECK_CRITICAL_TYPE) {
593    return;
594  }
595  const array = checkCriticalTypeArray(values);
596  for (const value of array) {
597    checkCriticalTypeString(value);
598  }
599}
600
601/**
602 * Ensures that value is a valid unsigned int32.
603 * @param {*} value
604 * @throws {!Error} If the value is out of range and the check state is debug.
605 */
606function checkTypeUnsignedInt32(value) {
607  if (!CHECK_TYPE) {
608    return;
609  }
610  checkCriticalTypeUnsignedInt32(value);
611}
612
613/**
614 * Ensures that value is a valid unsigned int32.
615 * @param {*} value
616 * @throws {!Error} If the value is out of range and the check state
617 * is critical.
618 */
619function checkCriticalTypeUnsignedInt32(value) {
620  if (!CHECK_CRITICAL_TYPE) {
621    return;
622  }
623  checkCriticalTypeDouble(value);
624  const valueAsNumber = /** @type {number} */ (value);
625  if (valueAsNumber < 0 || valueAsNumber > Math.pow(2, 32) - 1 ||
626      !Number.isInteger(valueAsNumber)) {
627    throw new Error(`Must be uint32, but got: ${value}`);
628  }
629}
630
631/**
632 * Checks whether a given object is an array of unsigned int32.
633 * @param {*} values
634 */
635function checkCriticalTypeUnsignedInt32Array(values) {
636  // TODO(b/134765672)
637  if (!CHECK_CRITICAL_TYPE) {
638    return;
639  }
640  const array = checkCriticalTypeArray(values);
641  for (const value of array) {
642    checkCriticalTypeUnsignedInt32(value);
643  }
644}
645
646/**
647 * Checks whether a given object is an array of message.
648 * @param {*} values
649 */
650function checkCriticalTypeMessageArray(values) {
651  // TODO(b/134765672)
652  if (!CHECK_CRITICAL_TYPE) {
653    return;
654  }
655  const array = checkCriticalTypeArray(values);
656  for (const value of array) {
657    checkCriticalType(
658        value !== null, 'Given value is not a message instance: null');
659  }
660}
661
662exports = {
663  checkDefAndNotNull,
664  checkCriticalElementIndex,
665  checkCriticalFieldNumber,
666  checkCriticalPositionIndex,
667  checkCriticalRange,
668  checkCriticalState,
669  checkCriticalTypeBool,
670  checkCriticalTypeBoolArray,
671  checkCriticalTypeByteString,
672  checkCriticalTypeByteStringArray,
673  checkCriticalTypeDouble,
674  checkTypeDouble,
675  checkCriticalTypeDoubleArray,
676  checkTypeFloat,
677  checkCriticalTypeFloat,
678  checkCriticalTypeFloatIterable,
679  checkCriticalTypeMessageArray,
680  checkCriticalTypeSignedInt32,
681  checkCriticalTypeSignedInt32Array,
682  checkCriticalTypeSignedInt64,
683  checkTypeSignedInt64,
684  checkCriticalTypeSignedInt64Array,
685  checkCriticalTypeString,
686  checkCriticalTypeStringArray,
687  checkCriticalTypeUnsignedInt32,
688  checkCriticalTypeUnsignedInt32Array,
689  checkCriticalType,
690  checkCriticalWireType,
691  checkElementIndex,
692  checkFieldNumber,
693  checkFunctionExists,
694  checkPositionIndex,
695  checkRange,
696  checkState,
697  checkTypeUnsignedInt32,
698  checkTypeSignedInt32,
699  checkWireType,
700  CHECK_BOUNDS,
701  CHECK_CRITICAL_BOUNDS,
702  CHECK_STATE,
703  CHECK_CRITICAL_STATE,
704  CHECK_TYPE,
705  CHECK_CRITICAL_TYPE,
706  MAX_FIELD_NUMBER,
707  POLYFILL_TEXT_ENCODING,
708};
709