Skip to content

Commit

Permalink
fix(es/minifier): Preserve unused special properties (#9005)
Browse files Browse the repository at this point in the history
**Related issue:**

 - vercel/next.js#66378
  • Loading branch information
kdy1 committed May 31, 2024
1 parent 74a1fd4 commit e764ff6
Show file tree
Hide file tree
Showing 3 changed files with 624 additions and 0 deletions.
16 changes: 16 additions & 0 deletions crates/swc_ecma_minifier/src/compress/optimize/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,18 +849,30 @@ impl Optimizer<'_> {
match &**prop {
Prop::KeyValue(p) => match &p.key {
PropName::Str(s) => {
if !can_remove_property(&s.value) {
return None;
}

if let Some(v) = unknown_used_props.get_mut(&s.value) {
*v = 0;
}
}
PropName::Ident(i) => {
if !can_remove_property(&i.sym) {
return None;
}

if let Some(v) = unknown_used_props.get_mut(&i.sym) {
*v = 0;
}
}
_ => return None,
},
Prop::Shorthand(p) => {
if !can_remove_property(&p.sym) {
return None;
}

if let Some(v) = unknown_used_props.get_mut(&p.sym) {
*v = 0;
}
Expand Down Expand Up @@ -914,6 +926,10 @@ impl Optimizer<'_> {
}
}

fn can_remove_property(sym: &str) -> bool {
!matches!(sym, "toString" | "valueOf")
}

#[derive(Default)]
struct ThisPropertyVisitor {
properties: FxHashSet<JsWord>,
Expand Down
353 changes: 353 additions & 0 deletions crates/swc_ecma_minifier/tests/fixture/next/66378/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
import { concat, fromString, toString } from "uint8arrays";
import { x25519 } from "@noble/curves/ed25519";
import { varint } from "multiformats";
import { decode, encode } from "multibase";
import { secp256k1 } from "@noble/curves/secp256k1";
import { p256 } from "@noble/curves/p256";
const u8a = {
toString,
fromString,
concat
};
export function bytesToBase64url(b) {
return u8a.toString(b, 'base64url');
}
export function base64ToBytes(s) {
const inputBase64Url = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return u8a.fromString(inputBase64Url, 'base64url');
}
export function bytesToBase64(b) {
return u8a.toString(b, 'base64pad');
}
export function base58ToBytes(s) {
return u8a.fromString(s, 'base58btc');
}
export function bytesToBase58(b) {
return u8a.toString(b, 'base58btc');
}
export const SUPPORTED_PUBLIC_KEY_TYPES = {
ES256: [
'JsonWebKey2020',
'Multikey',
'EcdsaSecp256r1VerificationKey2019'
],
ES256K: [
'EcdsaSecp256k1VerificationKey2019',
/**
* Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress
*/ 'EcdsaSecp256k1RecoveryMethod2020',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'Secp256k1VerificationKey2018',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'Secp256k1SignatureVerificationKey2018',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'EcdsaPublicKeySecp256k1',
/**
* TODO - support R1 key as well
* 'ConditionalProof2022',
*/ 'JsonWebKey2020',
'Multikey'
],
'ES256K-R': [
'EcdsaSecp256k1VerificationKey2019',
/**
* Equivalent to EcdsaSecp256k1VerificationKey2019 when key is an ethereumAddress
*/ 'EcdsaSecp256k1RecoveryMethod2020',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'Secp256k1VerificationKey2018',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'Secp256k1SignatureVerificationKey2018',
/**
* @deprecated, supported for backward compatibility. Equivalent to EcdsaSecp256k1VerificationKey2019 when key is
* not an ethereumAddress
*/ 'EcdsaPublicKeySecp256k1',
'ConditionalProof2022',
'JsonWebKey2020',
'Multikey'
],
Ed25519: [
'ED25519SignatureVerification',
'Ed25519VerificationKey2018',
'Ed25519VerificationKey2020',
'JsonWebKey2020',
'Multikey'
],
EdDSA: [
'ED25519SignatureVerification',
'Ed25519VerificationKey2018',
'Ed25519VerificationKey2020',
'JsonWebKey2020',
'Multikey'
]
};
export const VM_TO_KEY_TYPE = {
Secp256k1SignatureVerificationKey2018: 'Secp256k1',
Secp256k1VerificationKey2018: 'Secp256k1',
EcdsaSecp256k1VerificationKey2019: 'Secp256k1',
EcdsaPublicKeySecp256k1: 'Secp256k1',
EcdsaSecp256k1RecoveryMethod2020: 'Secp256k1',
EcdsaSecp256r1VerificationKey2019: 'P-256',
Ed25519VerificationKey2018: 'Ed25519',
Ed25519VerificationKey2020: 'Ed25519',
ED25519SignatureVerification: 'Ed25519',
X25519KeyAgreementKey2019: 'X25519',
X25519KeyAgreementKey2020: 'X25519',
ConditionalProof2022: undefined,
JsonWebKey2020: undefined,
Multikey: undefined
};
// this is from the multicodec table https://github.com/multiformats/multicodec/blob/master/table.csv
export const supportedCodecs = {
'ed25519-pub': 0xed,
'x25519-pub': 0xec,
'secp256k1-pub': 0xe7,
'bls12_381-g1-pub': 0xea,
'bls12_381-g2-pub': 0xeb,
'p256-pub': 0x1200
};
export const CODEC_TO_KEY_TYPE = {
'bls12_381-g1-pub': 'Bls12381G1',
'bls12_381-g2-pub': 'Bls12381G2',
'ed25519-pub': 'Ed25519',
'p256-pub': 'P-256',
'secp256k1-pub': 'Secp256k1',
'x25519-pub': 'X25519'
};
/**
* Extracts the raw byte representation of a public key from a VerificationMethod along with an inferred key type
* @param pk a VerificationMethod entry from a DIDDocument
* @return an object containing the `keyBytes` of the public key and an inferred `keyType`
*/ export function extractPublicKeyBytes(pk) {
if (pk.publicKeyBase58) {
return {
keyBytes: base58ToBytes(pk.publicKeyBase58),
keyType: VM_TO_KEY_TYPE[pk.type]
};
} else if (pk.publicKeyBase64) {
return {
keyBytes: base64ToBytes(pk.publicKeyBase64),
keyType: VM_TO_KEY_TYPE[pk.type]
};
} else if (pk.publicKeyHex) {
return {
keyBytes: hexToBytes(pk.publicKeyHex),
keyType: VM_TO_KEY_TYPE[pk.type]
};
} else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'secp256k1' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) {
return {
keyBytes: secp256k1.ProjectivePoint.fromAffine({
x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)),
y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y))
}).toRawBytes(false),
keyType: 'Secp256k1'
};
} else if (pk.publicKeyJwk && pk.publicKeyJwk.crv === 'P-256' && pk.publicKeyJwk.x && pk.publicKeyJwk.y) {
return {
keyBytes: p256.ProjectivePoint.fromAffine({
x: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.x)),
y: bytesToBigInt(base64ToBytes(pk.publicKeyJwk.y))
}).toRawBytes(false),
keyType: 'P-256'
};
} else if (pk.publicKeyJwk && pk.publicKeyJwk.kty === 'OKP' && [
'Ed25519',
'X25519'
].includes(pk.publicKeyJwk.crv ?? '') && pk.publicKeyJwk.x) {
return {
keyBytes: base64ToBytes(pk.publicKeyJwk.x),
keyType: pk.publicKeyJwk.crv
};
} else if (pk.publicKeyMultibase) {
const { keyBytes, keyType } = multibaseToBytes(pk.publicKeyMultibase);
return {
keyBytes,
keyType: keyType ?? VM_TO_KEY_TYPE[pk.type]
};
}
return {
keyBytes: new Uint8Array()
};
}
/**
* Encodes the given byte array to a multibase string (defaulting to base58btc).
* If a codec is provided, the corresponding multicodec prefix will be added.
*
* @param b - the Uint8Array to be encoded
* @param base - the base to use for encoding (defaults to base58btc)
* @param codec - the codec to use for encoding (defaults to no codec)
*
* @returns the multibase encoded string
*
* @public
*/ export function bytesToMultibase(b, base = 'base58btc', codec) {
if (!codec) {
return u8a.toString(encode(base, b), 'utf-8');
} else {
const codecCode = typeof codec === 'string' ? supportedCodecs[codec] : codec;
const prefixLength = varint.encodingLength(codecCode);
const multicodecEncoding = new Uint8Array(prefixLength + b.length);
varint.encodeTo(codecCode, multicodecEncoding) // set prefix
;
multicodecEncoding.set(b, prefixLength) // add the original bytes
;
return u8a.toString(encode(base, multicodecEncoding), 'utf-8');
}
}
/**
* Converts a multibase string to the Uint8Array it represents.
* This method will assume the byte array that is multibase encoded is a multicodec and will attempt to decode it.
*
* @param s - the string to be converted
*
* @throws if the string is not formatted correctly.
*
* @public
*/ export function multibaseToBytes(s) {
const bytes = decode(s);
// look for known key lengths first
// Ed25519/X25519, secp256k1/P256 compressed or not, BLS12-381 G1/G2 compressed
if ([
32,
33,
48,
64,
65,
96
].includes(bytes.length)) {
return {
keyBytes: bytes
};
}
// then assume multicodec, otherwise return the bytes
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [codec, length] = varint.decode(bytes);
const possibleCodec = Object.entries(supportedCodecs).filter(([, code]) => code === codec)?.[0][0] ?? '';
return {
keyBytes: bytes.slice(length),
keyType: CODEC_TO_KEY_TYPE[possibleCodec]
};
} catch (e) {
// not a multicodec, return the bytes
return {
keyBytes: bytes
};
}
}
export function hexToBytes(s, minLength) {
let input = s.startsWith('0x') ? s.substring(2) : s;
if (input.length % 2 !== 0) {
input = `0${input}`;
}
if (minLength) {
const paddedLength = Math.max(input.length, minLength * 2);
input = input.padStart(paddedLength, '00');
}
return u8a.fromString(input.toLowerCase(), 'base16');
}
export function encodeBase64url(s) {
return bytesToBase64url(u8a.fromString(s));
}
export function decodeBase64url(s) {
return u8a.toString(base64ToBytes(s));
}
export function bytesToHex(b) {
return u8a.toString(b, 'base16');
}
export function bytesToBigInt(b) {
return BigInt(`0x` + u8a.toString(b, 'base16'));
}
export function bigintToBytes(n, minLength) {
return hexToBytes(n.toString(16), minLength);
}
export function stringToBytes(s) {
return u8a.fromString(s, 'utf-8');
}
export function toJose({ r, s, recoveryParam }, recoverable) {
const jose = new Uint8Array(recoverable ? 65 : 64);
jose.set(u8a.fromString(r, 'base16'), 0);
jose.set(u8a.fromString(s, 'base16'), 32);
if (recoverable) {
if (typeof recoveryParam === 'undefined') {
throw new Error('Signer did not return a recoveryParam');
}
jose[64] = recoveryParam;
}
return bytesToBase64url(jose);
}
export function fromJose(signature) {
const signatureBytes = base64ToBytes(signature);
if (signatureBytes.length < 64 || signatureBytes.length > 65) {
throw new TypeError(`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`);
}
const r = bytesToHex(signatureBytes.slice(0, 32));
const s = bytesToHex(signatureBytes.slice(32, 64));
const recoveryParam = signatureBytes.length === 65 ? signatureBytes[64] : undefined;
return {
r,
s,
recoveryParam
};
}
export function toSealed(ciphertext, tag) {
return u8a.concat([
base64ToBytes(ciphertext),
tag ? base64ToBytes(tag) : new Uint8Array(0)
]);
}
export function leftpad(data, size = 64) {
if (data.length === size) return data;
return '0'.repeat(size - data.length) + data;
}
/**
* Generate random x25519 key pair.
*/ export function generateKeyPair() {
const secretKey = x25519.utils.randomPrivateKey();
const publicKey = x25519.getPublicKey(secretKey);
return {
secretKey: secretKey,
publicKey: publicKey
};
}
/**
* Generate private-public x25519 key pair from `seed`.
*/ export function generateKeyPairFromSeed(seed) {
if (seed.length !== 32) {
throw new Error(`x25519: seed must be ${32} bytes`);
}
return {
publicKey: x25519.getPublicKey(seed),
secretKey: seed
};
}
export function genX25519EphemeralKeyPair() {
const epk = generateKeyPair();
return {
publicKeyJWK: {
kty: 'OKP',
crv: 'X25519',
x: bytesToBase64url(epk.publicKey)
},
secretKey: epk.secretKey
};
}
/**
* Checks if a variable is defined and not null.
* After this check, typescript sees the variable as defined.
*
* @param arg - The input to be verified
*
* @returns true if the input variable is defined.
*/ export function isDefined(arg) {
return arg !== null && typeof arg !== 'undefined';
}
Loading

0 comments on commit e764ff6

Please sign in to comment.