/** * @fileoverview Proto internal runtime checks. * * Checks are grouped into different severity, see: * http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers * * Checks are also grouped into different sections: * - CHECK_BOUNDS: * Checks that ensure that indexed access is within bounds * (e.g. an array being accessed past its size). * - CHECK_STATE * Checks related to the state of an object * (e.g. a parser hitting an invalid case). * - CHECK_TYPE: * Checks that relate to type errors (e.g. code receives a number instead * of a string). */ goog.module('protobuf.internal.checks'); const ByteString = goog.require('protobuf.ByteString'); const Int64 = goog.require('protobuf.Int64'); const WireType = goog.require('protobuf.binary.WireType'); // // See // http://g3doc/third_party/protobuf/javascript/README.md#configurable-check-support-in-protocol-buffers // /** @define{string} */ const CHECK_LEVEL_DEFINE = goog.define('protobuf.defines.CHECK_LEVEL', ''); /** @define{boolean} */ const POLYFILL_TEXT_ENCODING = goog.define('protobuf.defines.POLYFILL_TEXT_ENCODING', true); /** * @const {number} */ const MAX_FIELD_NUMBER = Math.pow(2, 29) - 1; /** * The largest finite float32 value. * @const {number} */ const FLOAT32_MAX = 3.4028234663852886e+38; /** @enum {number} */ const CheckLevel = { DEBUG: 0, CRITICAL: 1, OFF: 2 }; /** @return {!CheckLevel} */ function calculateCheckLevel() { const definedLevel = CHECK_LEVEL_DEFINE.toUpperCase(); if (definedLevel === '') { // user did not set a value, value now just depends on goog.DEBUG return goog.DEBUG ? CheckLevel.DEBUG : CheckLevel.CRITICAL; } if (definedLevel === 'CRITICAL') { return CheckLevel.CRITICAL; } if (definedLevel === 'OFF') { return CheckLevel.OFF; } if (definedLevel === 'DEBUG') { return CheckLevel.DEBUG; } throw new Error(`Unknown value for CHECK_LEVEL: ${CHECK_LEVEL_DEFINE}`); } const /** !CheckLevel */ CHECK_LEVEL = calculateCheckLevel(); const /** boolean */ CHECK_STATE = CHECK_LEVEL === CheckLevel.DEBUG; const /** boolean */ CHECK_CRITICAL_STATE = CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; const /** boolean */ CHECK_BOUNDS = CHECK_LEVEL === CheckLevel.DEBUG; const /** boolean */ CHECK_CRITICAL_BOUNDS = CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; const /** boolean */ CHECK_TYPE = CHECK_LEVEL === CheckLevel.DEBUG; const /** boolean */ CHECK_CRITICAL_TYPE = CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG; /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * * For cases where failing fast is pretty important and not failing early could * cause bugs that are much harder to debug. * @param {boolean} state * @param {string=} message * @throws {!Error} If the state is false and the check state is critical. */ function checkCriticalState(state, message = '') { if (!CHECK_CRITICAL_STATE) { return; } if (!state) { throw new Error(message); } } /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * * @param {boolean} state * @param {string=} message * @throws {!Error} If the state is false and the check state is debug. */ function checkState(state, message = '') { if (!CHECK_STATE) { return; } checkCriticalState(state, message); } /** * Ensures that `index` specifies a valid position in an indexable object of * size `size`. A position index may range from zero to size, inclusive. * @param {number} index * @param {number} size * @throws {!Error} If the index is out of range and the check state is debug. */ function checkPositionIndex(index, size) { if (!CHECK_BOUNDS) { return; } checkCriticalPositionIndex(index, size); } /** * Ensures that `index` specifies a valid position in an indexable object of * size `size`. A position index may range from zero to size, inclusive. * @param {number} index * @param {number} size * @throws {!Error} If the index is out of range and the check state is * critical. */ function checkCriticalPositionIndex(index, size) { if (!CHECK_CRITICAL_BOUNDS) { return; } if (index < 0 || index > size) { throw new Error(`Index out of bounds: index: ${index} size: ${size}`); } } /** * Ensures that `index` specifies a valid element in an indexable object of * size `size`. A element index may range from zero to size, exclusive. * @param {number} index * @param {number} size * @throws {!Error} If the index is out of range and the check state is * debug. */ function checkElementIndex(index, size) { if (!CHECK_BOUNDS) { return; } checkCriticalElementIndex(index, size); } /** * Ensures that `index` specifies a valid element in an indexable object of * size `size`. A element index may range from zero to size, exclusive. * @param {number} index * @param {number} size * @throws {!Error} If the index is out of range and the check state is * critical. */ function checkCriticalElementIndex(index, size) { if (!CHECK_CRITICAL_BOUNDS) { return; } if (index < 0 || index >= size) { throw new Error(`Index out of bounds: index: ${index} size: ${size}`); } } /** * Ensures the range of [start, end) is with the range of [0, size). * @param {number} start * @param {number} end * @param {number} size * @throws {!Error} If start and end are out of range and the check state is * debug. */ function checkRange(start, end, size) { if (!CHECK_BOUNDS) { return; } checkCriticalRange(start, end, size); } /** * Ensures the range of [start, end) is with the range of [0, size). * @param {number} start * @param {number} end * @param {number} size * @throws {!Error} If start and end are out of range and the check state is * critical. */ function checkCriticalRange(start, end, size) { if (!CHECK_CRITICAL_BOUNDS) { return; } if (start < 0 || end < 0 || start > size || end > size) { throw new Error(`Range error: start: ${start} end: ${end} size: ${size}`); } if (start > end) { throw new Error(`Start > end: ${start} > ${end}`); } } /** * Ensures that field number is an integer and within the range of * [1, MAX_FIELD_NUMBER]. * @param {number} fieldNumber * @throws {!Error} If the field number is out of range and the check state is * debug. */ function checkFieldNumber(fieldNumber) { if (!CHECK_TYPE) { return; } checkCriticalFieldNumber(fieldNumber); } /** * Ensures that the value is neither null nor undefined. * * @param {T} value * @return {R} * * @template T * @template R := * mapunion(T, (V) => * cond(eq(V, 'null'), * none(), * cond(eq(V, 'undefined'), * none(), * V))) * =: */ function checkDefAndNotNull(value) { if (CHECK_TYPE) { // Note that undefined == null. if (value == null) { throw new Error(`Value can't be null`); } } return value; } /** * Ensures that the value exists and is a function. * * @param {function(?): ?} func */ function checkFunctionExists(func) { if (CHECK_TYPE) { if (typeof func !== 'function') { throw new Error(`${func} is not a function`); } } } /** * Ensures that field number is an integer and within the range of * [1, MAX_FIELD_NUMBER]. * @param {number} fieldNumber * @throws {!Error} If the field number is out of range and the check state is * critical. */ function checkCriticalFieldNumber(fieldNumber) { if (!CHECK_CRITICAL_TYPE) { return; } if (fieldNumber <= 0 || fieldNumber > MAX_FIELD_NUMBER) { throw new Error(`Field number is out of range: ${fieldNumber}`); } } /** * Ensures that wire type is valid. * @param {!WireType} wireType * @throws {!Error} If the wire type is invalid and the check state is debug. */ function checkWireType(wireType) { if (!CHECK_TYPE) { return; } checkCriticalWireType(wireType); } /** * Ensures that wire type is valid. * @param {!WireType} wireType * @throws {!Error} If the wire type is invalid and the check state is critical. */ function checkCriticalWireType(wireType) { if (!CHECK_CRITICAL_TYPE) { return; } if (wireType < WireType.VARINT || wireType > WireType.FIXED32) { throw new Error(`Invalid wire type: ${wireType}`); } } /** * Ensures the given value has the correct type. * @param {boolean} expression * @param {string} errorMsg * @throws {!Error} If the value has the wrong type and the check state is * critical. */ function checkCriticalType(expression, errorMsg) { if (!CHECK_CRITICAL_TYPE) { return; } if (!expression) { throw new Error(errorMsg); } } /** * Checks whether a given object is an array. * @param {*} value * @return {!Array<*>} */ function checkCriticalTypeArray(value) { checkCriticalType( Array.isArray(value), `Must be an array, but got: ${value}`); return /** @type {!Array<*>} */ (value); } /** * Checks whether a given object is an iterable. * @param {*} value * @return {!Iterable<*>} */ function checkCriticalTypeIterable(value) { checkCriticalType( !!value[Symbol.iterator], `Must be an iterable, but got: ${value}`); return /** @type {!Iterable<*>} */ (value); } /** * Checks whether a given object is a boolean. * @param {*} value */ function checkCriticalTypeBool(value) { checkCriticalType( typeof value === 'boolean', `Must be a boolean, but got: ${value}`); } /** * Checks whether a given object is an array of boolean. * @param {*} values */ function checkCriticalTypeBoolArray(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeBool(value); } } /** * Checks whether a given object is a ByteString. * @param {*} value */ function checkCriticalTypeByteString(value) { checkCriticalType( value instanceof ByteString, `Must be a ByteString, but got: ${value}`); } /** * Checks whether a given object is an array of ByteString. * @param {*} values */ function checkCriticalTypeByteStringArray(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeByteString(value); } } /** * Checks whether a given object is a number. * @param {*} value * @throws {!Error} If the value is not float and the check state is debug. */ function checkTypeDouble(value) { if (!CHECK_TYPE) { return; } checkCriticalTypeDouble(value); } /** * Checks whether a given object is a number. * @param {*} value * @throws {!Error} If the value is not float and the check state is critical. */ function checkCriticalTypeDouble(value) { checkCriticalType( typeof value === 'number', `Must be a number, but got: ${value}`); } /** * Checks whether a given object is an array of double. * @param {*} values */ function checkCriticalTypeDoubleArray(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeDouble(value); } } /** * Checks whether a given object is a number. * @param {*} value * @throws {!Error} If the value is not signed int32 and the check state is * debug. */ function checkTypeSignedInt32(value) { if (!CHECK_TYPE) { return; } checkCriticalTypeSignedInt32(value); } /** * Checks whether a given object is a number. * @param {*} value * @throws {!Error} If the value is not signed int32 and the check state is * critical. */ function checkCriticalTypeSignedInt32(value) { checkCriticalTypeDouble(value); const valueAsNumber = /** @type {number} */ (value); if (CHECK_CRITICAL_TYPE) { if (valueAsNumber < -Math.pow(2, 31) || valueAsNumber > Math.pow(2, 31) || !Number.isInteger(valueAsNumber)) { throw new Error(`Must be int32, but got: ${valueAsNumber}`); } } } /** * Checks whether a given object is an array of numbers. * @param {*} values */ function checkCriticalTypeSignedInt32Array(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeSignedInt32(value); } } /** * Ensures that value is a long instance. * @param {*} value * @throws {!Error} If the value is not a long instance and check state is * debug. */ function checkTypeSignedInt64(value) { if (!CHECK_TYPE) { return; } checkCriticalTypeSignedInt64(value); } /** * Ensures that value is a long instance. * @param {*} value * @throws {!Error} If the value is not a long instance and check state is * critical. */ function checkCriticalTypeSignedInt64(value) { if (!CHECK_CRITICAL_TYPE) { return; } if (!(value instanceof Int64)) { throw new Error(`Must be Int64 instance, but got: ${value}`); } } /** * Checks whether a given object is an array of long instances. * @param {*} values * @throws {!Error} If values is not an array of long instances. */ function checkCriticalTypeSignedInt64Array(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeSignedInt64(value); } } /** * Checks whether a given object is a number and within float32 precision. * @param {*} value * @throws {!Error} If the value is not float and the check state is debug. */ function checkTypeFloat(value) { if (!CHECK_TYPE) { return; } checkCriticalTypeFloat(value); } /** * Checks whether a given object is a number and within float32 precision. * @param {*} value * @throws {!Error} If the value is not float and the check state is critical. */ function checkCriticalTypeFloat(value) { checkCriticalTypeDouble(value); if (CHECK_CRITICAL_TYPE) { const valueAsNumber = /** @type {number} */ (value); if (Number.isFinite(valueAsNumber) && (valueAsNumber > FLOAT32_MAX || valueAsNumber < -FLOAT32_MAX)) { throw new Error( `Given number does not fit into float precision: ${value}`); } } } /** * Checks whether a given object is an iterable of floats. * @param {*} values */ function checkCriticalTypeFloatIterable(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const iterable = checkCriticalTypeIterable(values); for (const value of iterable) { checkCriticalTypeFloat(value); } } /** * Checks whether a given object is a string. * @param {*} value */ function checkCriticalTypeString(value) { checkCriticalType( typeof value === 'string', `Must be string, but got: ${value}`); } /** * Checks whether a given object is an array of string. * @param {*} values */ function checkCriticalTypeStringArray(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeString(value); } } /** * Ensures that value is a valid unsigned int32. * @param {*} value * @throws {!Error} If the value is out of range and the check state is debug. */ function checkTypeUnsignedInt32(value) { if (!CHECK_TYPE) { return; } checkCriticalTypeUnsignedInt32(value); } /** * Ensures that value is a valid unsigned int32. * @param {*} value * @throws {!Error} If the value is out of range and the check state * is critical. */ function checkCriticalTypeUnsignedInt32(value) { if (!CHECK_CRITICAL_TYPE) { return; } checkCriticalTypeDouble(value); const valueAsNumber = /** @type {number} */ (value); if (valueAsNumber < 0 || valueAsNumber > Math.pow(2, 32) - 1 || !Number.isInteger(valueAsNumber)) { throw new Error(`Must be uint32, but got: ${value}`); } } /** * Checks whether a given object is an array of unsigned int32. * @param {*} values */ function checkCriticalTypeUnsignedInt32Array(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalTypeUnsignedInt32(value); } } /** * Checks whether a given object is an array of message. * @param {*} values */ function checkCriticalTypeMessageArray(values) { // TODO(b/134765672) if (!CHECK_CRITICAL_TYPE) { return; } const array = checkCriticalTypeArray(values); for (const value of array) { checkCriticalType( value !== null, 'Given value is not a message instance: null'); } } exports = { checkDefAndNotNull, checkCriticalElementIndex, checkCriticalFieldNumber, checkCriticalPositionIndex, checkCriticalRange, checkCriticalState, checkCriticalTypeBool, checkCriticalTypeBoolArray, checkCriticalTypeByteString, checkCriticalTypeByteStringArray, checkCriticalTypeDouble, checkTypeDouble, checkCriticalTypeDoubleArray, checkTypeFloat, checkCriticalTypeFloat, checkCriticalTypeFloatIterable, checkCriticalTypeMessageArray, checkCriticalTypeSignedInt32, checkCriticalTypeSignedInt32Array, checkCriticalTypeSignedInt64, checkTypeSignedInt64, checkCriticalTypeSignedInt64Array, checkCriticalTypeString, checkCriticalTypeStringArray, checkCriticalTypeUnsignedInt32, checkCriticalTypeUnsignedInt32Array, checkCriticalType, checkCriticalWireType, checkElementIndex, checkFieldNumber, checkFunctionExists, checkPositionIndex, checkRange, checkState, checkTypeUnsignedInt32, checkTypeSignedInt32, checkWireType, CHECK_BOUNDS, CHECK_CRITICAL_BOUNDS, CHECK_STATE, CHECK_CRITICAL_STATE, CHECK_TYPE, CHECK_CRITICAL_TYPE, MAX_FIELD_NUMBER, POLYFILL_TEXT_ENCODING, };