src/vs/base/common/hash.ts
324 LOC · 249 covered · 75 uncovered · 53 ranges · 11566 concepts · 8 introducers · 5913 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
hash.ts ×17
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { encodeHex, VSBuffer } from './buffer.js';
import * as strings from './strings.js';
type NotSyncHashable = ArrayBufferLike | ArrayBufferView;
/**
* Return a hash value for an object.
*
* Note that this should not be used for binary data types. Instead,
* prefer {@link hashAsync}.
*/
export function hash<T>(obj: T extends NotSyncHashable ? never : T): number {
}
export function doHash(obj: unknown, hashVal: number): number {
case 'object':
return numberHash(349, hashVal);
return arrayHash(obj, hashVal);
}
return objectHash(obj, hashVal);
return stringHash(obj, hashVal);
case 'boolean':
return numberHash(617, hashVal);
}
export function numberHash(val: number, initialHashVal: number): number {
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
hash.ts ×2
}
return numberHash(b ? 433 : 863, initialHashVal);
}
export function stringHash(s: string, hashVal: number) {
for (let i = 0, length = s.length; i < length; i++) {
hashVal = numberHash(s.charCodeAt(i), hashVal);
}
return hashVal;
}
initialHashVal = numberHash(104579, initialHashVal);
return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);
}
initialHashVal = numberHash(181387, initialHashVal);
return Object.keys(obj).sort().reduce((hashVal, key) => {
hashVal = stringHash(key, hashVal);
return doHash((obj as Record<string, unknown>)[key], hashVal);
}, initialHashVal);
}
/** Hashes the input as SHA-1, returning a hex-encoded string. */
export const hashAsync = (input: string | ArrayBufferView | VSBuffer) => {
// Note: I would very much like to expose a streaming interface for hashing
// generally, but this is not available in web crypto yet, see
// https://github.com/w3c/webcrypto/issues/73
// StringSHA1 is faster for small string input, use it since we have it:
if (typeof input === 'string' && input.length < 250) {
const sha = new StringSHA1();
sha.update(input);
return Promise.resolve(sha.digest());
}
let buff: ArrayBufferView;
if (typeof input === 'string') {
buff = new TextEncoder().encode(input);
} else if (input instanceof VSBuffer) {
buff = input.buffer;
} else {
buff = input;
}
return crypto.subtle.digest('sha-1', buff as ArrayBufferView<ArrayBuffer>).then(toHexString); // CodeQL [SM04514] we use sha1 here for validating old stored client state, not for security
};
const enum SHA1Constant {
BLOCK_SIZE = 64, // 512 / 8
UNICODE_REPLACEMENT = 0xFFFD,
}
function leftRotate(value: number, bits: number, totalBits: number = 32): number {
hash.ts ×18
// delta + bits = totalBits
const delta = totalBits - bits;
// All ones, expect `delta` zeros aligned to the right
const mask = ~((1 << delta) - 1);
// Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits)
return ((value << bits) | ((mask & value) >>> delta)) >>> 0;
}
function toHexString(buffer: ArrayBuffer): string;
function toHexString(value: number, bitsize?: number): string;
function toHexString(bufferOrValue: ArrayBuffer | number, bitsize: number = 32): string {
hash.ts ×18
if (bufferOrValue instanceof ArrayBuffer) {
return encodeHex(VSBuffer.wrap(new Uint8Array(bufferOrValue)));
}
return (bufferOrValue >>> 0).toString(16).padStart(bitsize / 4, '0');
}
/**
* A SHA1 implementation that works with strings and does not allocate.
*
* Prefer to use {@link hashAsync} in async contexts
*/
export class StringSHA1 {
private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320
private _h0 = 0x67452301;
private _h1 = 0xEFCDAB89;
private _h2 = 0x98BADCFE;
private _h3 = 0x10325476;
private _h4 = 0xC3D2E1F0;
private readonly _buff: Uint8Array;
private readonly _buffDV: DataView;
private _buffLen: number;
private _totalLen: number;
private _leftoverHighSurrogate: number;
private _finished: boolean;
constructor() {
this._buffDV = new DataView(this._buff.buffer);
this._buffLen = 0;
this._totalLen = 0;
this._leftoverHighSurrogate = 0;
this._finished = false;
}
public update(str: string): void {
if (strLen === 0) {
return;
}
const buff = this._buff;
let buffLen = this._buffLen;
let leftoverHighSurrogate = this._leftoverHighSurrogate;
let charCode: number;
let offset: number;
if (leftoverHighSurrogate !== 0) {
charCode = leftoverHighSurrogate;
offset = -1;
leftoverHighSurrogate = 0;
charCode = str.charCodeAt(0);
offset = 0;
}
while (true) {
let codePoint = charCode;
if (strings.isHighSurrogate(charCode)) {
if (offset + 1 < strLen) {
const nextCharCode = str.charCodeAt(offset + 1);
if (strings.isLowSurrogate(nextCharCode)) {
offset++;
codePoint = strings.computeCodePoint(charCode, nextCharCode);
} else {
// illegal => unicode replacement character
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
}
} else {
// last character is a surrogate pair
leftoverHighSurrogate = charCode;
break;
}
// illegal => unicode replacement character
codePoint = SHA1Constant.UNICODE_REPLACEMENT;
}
buffLen = this._push(buff, buffLen, codePoint);
offset++;
if (offset < strLen) {
break;
}
}
this._buffLen = buffLen;
this._leftoverHighSurrogate = leftoverHighSurrogate;
}
private _push(buff: Uint8Array, buffLen: number, codePoint: number): number {
buff[buffLen++] = codePoint;
} else if (codePoint < 0x0800) {
buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
} else if (codePoint < 0x10000) {
buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
} else {
buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
if (buffLen >= SHA1Constant.BLOCK_SIZE) {
this._step();
buffLen -= SHA1Constant.BLOCK_SIZE;
this._totalLen += SHA1Constant.BLOCK_SIZE;
// take last 3 in case of UTF8 overflow
buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0];
buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1];
buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2];
}
return buffLen;
}
public digest(): string {
this._finished = true;
if (this._leftoverHighSurrogate) {
// illegal => unicode replacement character
this._leftoverHighSurrogate = 0;
this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT);
}
this._wrapUp();
}
return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4);
}
private _wrapUp(): void {
this._buff.subarray(this._buffLen).fill(0);
if (this._buffLen > 56) {
this._step();
this._buff.fill(0);
}
// this will fit because the mantissa can cover up to 52 bits
const ml = 8 * this._totalLen;
this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false);
this._buffDV.setUint32(60, ml % 4294967296, false);
this._step();
}
private _step(): void {
const data = this._buffDV;
for (let j = 0; j < 64 /* 16*4 */; j += 4) {
bigBlock32.setUint32(j, data.getUint32(j, false), false);
}
for (let j = 64; j < 320 /* 80*4 */; j += 4) {
bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false);
}
let a = this._h0;
let b = this._h1;
let c = this._h2;
let d = this._h3;
let e = this._h4;
let f: number, k: number;
let temp: number;
for (let j = 0; j < 80; j++) {
if (j < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (j < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (j < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff;
e = d;
d = c;
c = leftRotate(b, 30);
b = a;
a = temp;
}
this._h0 = (this._h0 + a) & 0xffffffff;
this._h1 = (this._h1 + b) & 0xffffffff;
this._h2 = (this._h2 + c) & 0xffffffff;
this._h3 = (this._h3 + d) & 0xffffffff;
this._h4 = (this._h4 + e) & 0xffffffff;
}