Skip to content

Commit 294ee9f

Browse files
committed
fix(cbor): eliminate dead WithLength decoder family (#159)
The WithLength slice-based decoder family (decodeItemWithLengthSync, decodeBytesWithLengthSync, decodeArrayWithLengthSync, decodeMapWithLengthSync, decodeTagWithLengthSync, decodeTextWithLengthSync, decodeUintSync, decodeNintSync, decodeSimpleOrFloatSync, decodeLengthSync) was never called from any live code path and was never exported. The actual decode path was always: fromCBORBytes / fromCBORHex -> internalDecodeSync -> decodeItemAt (At-family) The WithLength family called data.slice(offset) at every recursive level, allocating a new Uint8Array per nested CBOR item - O(n^2) allocation for deeply nested PlutusData. The At-family threads an integer offset with zero allocation. Deleted ~369 lines of dead code. All 973 tests pass. Closes #159
1 parent 96a36f9 commit 294ee9f

File tree

1 file changed

+0
-369
lines changed

1 file changed

+0
-369
lines changed

packages/evolution/src/CBOR.ts

Lines changed: 0 additions & 369 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,372 +1582,3 @@ const decodeSimpleOrFloatAt = (data: Uint8Array, offset: number): DecodeAtResult
15821582
}
15831583
throw new CBORError({ message: `Unsupported simple/float encoding: ${additionalInfo}` })
15841584
}
1585-
1586-
const decodeUintSync = (data: Uint8Array): CBOR => {
1587-
const firstByte = data[0]
1588-
const additionalInfo = firstByte & 0x1f
1589-
if (additionalInfo < 24) {
1590-
return BigInt(additionalInfo)
1591-
} else if (additionalInfo === 24) {
1592-
if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte unsigned integer" })
1593-
return BigInt(data[1])
1594-
} else if (additionalInfo === 25) {
1595-
if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte unsigned integer" })
1596-
return BigInt(data[1]) * 256n + BigInt(data[2])
1597-
} else if (additionalInfo === 26) {
1598-
if (data.length < 5) throw new CBORError({ message: "Insufficient data for 4-byte unsigned integer" })
1599-
return BigInt(data[1]) * 16777216n + BigInt(data[2]) * 65536n + BigInt(data[3]) * 256n + BigInt(data[4])
1600-
} else if (additionalInfo === 27) {
1601-
if (data.length < 9) throw new CBORError({ message: "Insufficient data for 8-byte unsigned integer" })
1602-
let result = 0n
1603-
for (let i = 1; i <= 8; i++) result = result * 256n + BigInt(data[i])
1604-
return result
1605-
} else {
1606-
throw new CBORError({ message: `Unsupported additional info for unsigned integer: ${additionalInfo}` })
1607-
}
1608-
}
1609-
1610-
const decodeNintSync = (data: Uint8Array): CBOR => {
1611-
const firstByte = data[0]
1612-
const additionalInfo = firstByte & 0x1f
1613-
if (additionalInfo < 24) {
1614-
return -1n - BigInt(additionalInfo)
1615-
} else if (additionalInfo === 24) {
1616-
if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte negative integer" })
1617-
return -1n - BigInt(data[1])
1618-
} else if (additionalInfo === 25) {
1619-
if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte negative integer" })
1620-
return -1n - (BigInt(data[1]) * 256n + BigInt(data[2]))
1621-
} else if (additionalInfo === 26) {
1622-
if (data.length < 5) throw new CBORError({ message: "Insufficient data for 4-byte negative integer" })
1623-
return -1n - (BigInt(data[1]) * 16777216n + BigInt(data[2]) * 65536n + BigInt(data[3]) * 256n + BigInt(data[4]))
1624-
} else if (additionalInfo === 27) {
1625-
if (data.length < 9) throw new CBORError({ message: "Insufficient data for 8-byte negative integer" })
1626-
let result = 0n
1627-
for (let i = 1; i <= 8; i++) result = result * 256n + BigInt(data[i])
1628-
return -1n - result
1629-
} else {
1630-
throw new CBORError({ message: `Unsupported additional info for negative integer: ${additionalInfo}` })
1631-
}
1632-
}
1633-
1634-
const decodeBytesWithLengthSync = (data: Uint8Array): { item: CBOR; bytesConsumed: number } => {
1635-
const firstByte = data[0]
1636-
const additionalInfo = firstByte & 0x1f
1637-
if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) {
1638-
let offset = 1
1639-
const chunks: Array<Uint8Array> = []
1640-
let foundBreak = false
1641-
while (offset < data.length) {
1642-
if (data[offset] === 0xff) {
1643-
offset++
1644-
foundBreak = true
1645-
break
1646-
}
1647-
const chunkFirstByte = data[offset]
1648-
const chunkMajorType = (chunkFirstByte >> 5) & 0x07
1649-
if (chunkMajorType !== CBOR_MAJOR_TYPE.BYTE_STRING) {
1650-
throw new CBORError({ message: `Invalid chunk in indefinite byte string: major type ${chunkMajorType}` })
1651-
}
1652-
const { bytesRead, length: chunkLength } = decodeLengthSync(data, offset)
1653-
offset += bytesRead
1654-
if (data.length < offset + chunkLength)
1655-
throw new CBORError({ message: "Insufficient data for byte string chunk" })
1656-
const chunk = data.slice(offset, offset + chunkLength)
1657-
chunks.push(chunk)
1658-
offset += chunkLength
1659-
}
1660-
if (!foundBreak) throw new CBORError({ message: "Missing break in indefinite-length byte string" })
1661-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
1662-
const bytes = new Uint8Array(totalLength)
1663-
let pos = 0
1664-
for (const chunk of chunks) {
1665-
bytes.set(chunk, pos)
1666-
pos += chunk.length
1667-
}
1668-
return { item: bytes, bytesConsumed: offset }
1669-
} else {
1670-
const { bytesRead, length } = decodeLengthSync(data, 0)
1671-
if (data.length < bytesRead + length) throw new CBORError({ message: "Insufficient data for byte string" })
1672-
const bytes = data.slice(bytesRead, bytesRead + length)
1673-
return { item: bytes, bytesConsumed: bytesRead + length }
1674-
}
1675-
}
1676-
1677-
const decodeTextWithLengthSync = (data: Uint8Array): { item: CBOR; bytesConsumed: number } => {
1678-
const firstByte = data[0]
1679-
const additionalInfo = firstByte & 0x1f
1680-
if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) {
1681-
let offset = 1
1682-
const parts: Array<string> = []
1683-
let foundBreak = false
1684-
while (offset < data.length) {
1685-
if (data[offset] === 0xff) {
1686-
offset++
1687-
foundBreak = true
1688-
break
1689-
}
1690-
const chunkFirstByte = data[offset]
1691-
const chunkMajorType = (chunkFirstByte >> 5) & 0x07
1692-
if (chunkMajorType !== CBOR_MAJOR_TYPE.TEXT_STRING) {
1693-
throw new CBORError({ message: `Invalid chunk in indefinite text string: major type ${chunkMajorType}` })
1694-
}
1695-
const { bytesRead, length: chunkLength } = decodeLengthSync(data, offset)
1696-
offset += bytesRead
1697-
if (data.length < offset + chunkLength)
1698-
throw new CBORError({ message: "Insufficient data for text string chunk" })
1699-
const chunkBytes = data.slice(offset, offset + chunkLength)
1700-
const chunkText = TEXT_DECODER.decode(chunkBytes)
1701-
parts.push(chunkText)
1702-
offset += chunkLength
1703-
}
1704-
if (!foundBreak) throw new CBORError({ message: "Missing break in indefinite-length text string" })
1705-
return { item: parts.join(""), bytesConsumed: offset }
1706-
} else {
1707-
const { bytesRead, length } = decodeLengthSync(data, 0)
1708-
if (data.length < bytesRead + length) throw new CBORError({ message: "Insufficient data for text string" })
1709-
const textBytes = data.slice(bytesRead, bytesRead + length)
1710-
const text = TEXT_DECODER.decode(textBytes)
1711-
return { item: text, bytesConsumed: bytesRead + length }
1712-
}
1713-
}
1714-
1715-
// Decode an item and return both the item and bytes consumed (sync)
1716-
const decodeItemWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => {
1717-
const firstByte = data[0]
1718-
const majorType = (firstByte >> 5) & 0x07
1719-
let item: CBOR
1720-
let bytesConsumed: number
1721-
switch (majorType) {
1722-
case CBOR_MAJOR_TYPE.UNSIGNED_INTEGER: {
1723-
item = decodeUintSync(data)
1724-
const additionalInfo = firstByte & 0x1f
1725-
bytesConsumed =
1726-
additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9
1727-
break
1728-
}
1729-
case CBOR_MAJOR_TYPE.NEGATIVE_INTEGER: {
1730-
item = decodeNintSync(data)
1731-
const additionalInfo = firstByte & 0x1f
1732-
bytesConsumed =
1733-
additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9
1734-
break
1735-
}
1736-
case CBOR_MAJOR_TYPE.BYTE_STRING: {
1737-
const { bytesConsumed: b, item: it } = decodeBytesWithLengthSync(data)
1738-
item = it
1739-
bytesConsumed = b
1740-
break
1741-
}
1742-
case CBOR_MAJOR_TYPE.TEXT_STRING: {
1743-
const { bytesConsumed: b, item: it } = decodeTextWithLengthSync(data)
1744-
item = it
1745-
bytesConsumed = b
1746-
break
1747-
}
1748-
case CBOR_MAJOR_TYPE.ARRAY: {
1749-
const { bytesConsumed: b, item: it } = decodeArrayWithLengthSync(data, options)
1750-
item = it
1751-
bytesConsumed = b
1752-
break
1753-
}
1754-
case CBOR_MAJOR_TYPE.MAP: {
1755-
const { bytesConsumed: b, item: it } = decodeMapWithLengthSync(data, options)
1756-
item = it
1757-
bytesConsumed = b
1758-
break
1759-
}
1760-
case CBOR_MAJOR_TYPE.TAG: {
1761-
const { bytesConsumed: b, item: it } = decodeTagWithLengthSync(data, options)
1762-
item = it
1763-
bytesConsumed = b
1764-
break
1765-
}
1766-
case CBOR_MAJOR_TYPE.SIMPLE_FLOAT: {
1767-
item = decodeSimpleOrFloatSync(data)
1768-
const additionalInfo = firstByte & 0x1f
1769-
bytesConsumed =
1770-
additionalInfo < 24 ? 1 : additionalInfo === 24 ? 2 : additionalInfo === 25 ? 3 : additionalInfo === 26 ? 5 : 9
1771-
break
1772-
}
1773-
default:
1774-
throw new CBORError({ message: `Unsupported major type: ${majorType}` })
1775-
}
1776-
return { item, bytesConsumed }
1777-
}
1778-
1779-
const decodeArrayWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => {
1780-
const firstByte = data[0]
1781-
const additionalInfo = firstByte & 0x1f
1782-
if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) {
1783-
const result: Array<CBOR> = []
1784-
let offset = 1
1785-
while (offset < data.length) {
1786-
if (data[offset] === 0xff) {
1787-
offset++
1788-
break
1789-
}
1790-
const { bytesConsumed, item } = decodeItemWithLengthSync(data.slice(offset), options)
1791-
result.push(item)
1792-
offset += bytesConsumed
1793-
}
1794-
return { item: result, bytesConsumed: offset }
1795-
} else {
1796-
const { bytesRead, length } = decodeLengthSync(data, 0)
1797-
const result: Array<CBOR> = []
1798-
let offset = bytesRead
1799-
for (let i = 0; i < length; i++) {
1800-
const { bytesConsumed, item } = decodeItemWithLengthSync(data.slice(offset), options)
1801-
result.push(item)
1802-
offset += bytesConsumed
1803-
}
1804-
return { item: result, bytesConsumed: offset }
1805-
}
1806-
}
1807-
1808-
const decodeMapWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => {
1809-
const firstByte = data[0]
1810-
const additionalInfo = firstByte & 0x1f
1811-
if (additionalInfo === CBOR_ADDITIONAL_INFO.INDEFINITE) {
1812-
const result =
1813-
options.mode === "custom" && options.mapsAsObjects ? ({} as Record<string, CBOR>) : new Map<CBOR, CBOR>()
1814-
let offset = 1
1815-
while (offset < data.length) {
1816-
if (data[offset] === 0xff) {
1817-
offset++
1818-
break
1819-
}
1820-
const { bytesConsumed: keyBytes, item: key } = decodeItemWithLengthSync(data.slice(offset), options)
1821-
offset += keyBytes
1822-
const { bytesConsumed: valueBytes, item: value } = decodeItemWithLengthSync(data.slice(offset), options)
1823-
offset += valueBytes
1824-
if (result instanceof Map) {
1825-
result.set(key, value)
1826-
} else {
1827-
result[String(key as any)] = value
1828-
}
1829-
}
1830-
return { item: result, bytesConsumed: offset }
1831-
} else {
1832-
const { bytesRead, length } = decodeLengthSync(data, 0)
1833-
const result =
1834-
options.mode === "custom" && options.mapsAsObjects ? ({} as Record<string, CBOR>) : new Map<CBOR, CBOR>()
1835-
let offset = bytesRead
1836-
for (let i = 0; i < length; i++) {
1837-
const { bytesConsumed: keyBytes, item: key } = decodeItemWithLengthSync(data.slice(offset), options)
1838-
offset += keyBytes
1839-
const { bytesConsumed: valueBytes, item: value } = decodeItemWithLengthSync(data.slice(offset), options)
1840-
offset += valueBytes
1841-
if (result instanceof Map) {
1842-
result.set(key, value)
1843-
} else {
1844-
result[String(key as any)] = value
1845-
}
1846-
}
1847-
return { item: result, bytesConsumed: offset }
1848-
}
1849-
}
1850-
1851-
const decodeTagWithLengthSync = (data: Uint8Array, options: CodecOptions): { item: CBOR; bytesConsumed: number } => {
1852-
const firstByte = data[0]
1853-
const additionalInfo = firstByte & 0x1f
1854-
let tag: number
1855-
let dataOffset: number
1856-
if (additionalInfo < 24) {
1857-
tag = additionalInfo
1858-
dataOffset = 1
1859-
} else if (additionalInfo === 24) {
1860-
if (data.length < 2) throw new CBORError({ message: "Insufficient data for 1-byte tag" })
1861-
tag = data[1]
1862-
dataOffset = 2
1863-
} else if (additionalInfo === 25) {
1864-
if (data.length < 3) throw new CBORError({ message: "Insufficient data for 2-byte tag" })
1865-
tag = data[1] * 256 + data[2]
1866-
dataOffset = 3
1867-
} else {
1868-
throw new CBORError({ message: `Unsupported additional info for tag: ${additionalInfo}` })
1869-
}
1870-
const { bytesConsumed, item: innerValue } = decodeItemWithLengthSync(data.slice(dataOffset), options)
1871-
if (tag === 2 || tag === 3) {
1872-
if (!(innerValue instanceof Uint8Array)) throw new CBORError({ message: `Invalid value for bigint tag ${tag}` })
1873-
const bigintValue = (() => {
1874-
let result = 0n
1875-
for (let i = 0; i < innerValue.length; i++) result = (result << 8n) + BigInt(innerValue[i])
1876-
return tag === 2 ? result : -1n - result
1877-
})()
1878-
return { item: bigintValue, bytesConsumed: dataOffset + bytesConsumed }
1879-
}
1880-
return { item: { _tag: "Tag", tag, value: innerValue }, bytesConsumed: dataOffset + bytesConsumed }
1881-
}
1882-
1883-
const decodeSimpleOrFloatSync = (data: Uint8Array): CBOR => {
1884-
const firstByte = data[0]
1885-
const additionalInfo = firstByte & 0x1f
1886-
if (additionalInfo < 20) {
1887-
// Return unassigned simple values as numbers
1888-
return additionalInfo
1889-
} else if (additionalInfo === CBOR_SIMPLE.FALSE) {
1890-
return false
1891-
} else if (additionalInfo === CBOR_SIMPLE.TRUE) {
1892-
return true
1893-
} else if (additionalInfo === CBOR_SIMPLE.NULL) {
1894-
return null
1895-
} else if (additionalInfo === CBOR_SIMPLE.UNDEFINED) {
1896-
return undefined
1897-
} else if (additionalInfo === CBOR_ADDITIONAL_INFO.DIRECT) {
1898-
if (data.length < 2) throw new CBORError({ message: "Insufficient data for simple value (one byte)" })
1899-
const simpleValue = data[1]
1900-
return simpleValue
1901-
} else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT16) {
1902-
if (data.length < 3) throw new CBORError({ message: "Insufficient data for half-precision float" })
1903-
const value = (data[1] << 8) | data[2]
1904-
const float = decodeFloat16(value)
1905-
return float
1906-
} else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT32) {
1907-
if (data.length < 5) throw new CBORError({ message: "Insufficient data for single-precision float" })
1908-
const buffer = data.slice(1, 5)
1909-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)
1910-
return view.getFloat32(0, false)
1911-
} else if (additionalInfo === CBOR_ADDITIONAL_INFO.UINT64) {
1912-
if (data.length < 9) throw new CBORError({ message: "Insufficient data for double-precision float" })
1913-
const buffer = data.slice(1, 9)
1914-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)
1915-
return view.getFloat64(0, false)
1916-
}
1917-
throw new CBORError({ message: `Unsupported additional info for simple/float: ${additionalInfo}` })
1918-
}
1919-
1920-
const decodeLengthSync = (data: Uint8Array, offset: number): { length: number; bytesRead: number } => {
1921-
const firstByte = data[offset]
1922-
const majorType = (firstByte >> 5) & 0x07
1923-
const additionalInfo = firstByte & 0x1f
1924-
let length = 0
1925-
let bytesRead = 0
1926-
if (
1927-
majorType !== CBOR_MAJOR_TYPE.BYTE_STRING &&
1928-
majorType !== CBOR_MAJOR_TYPE.TEXT_STRING &&
1929-
majorType !== CBOR_MAJOR_TYPE.ARRAY &&
1930-
majorType !== CBOR_MAJOR_TYPE.MAP
1931-
) {
1932-
throw new CBORError({ message: `Invalid major type for length decoding: ${majorType}` })
1933-
}
1934-
if (additionalInfo < 24) {
1935-
length = additionalInfo
1936-
bytesRead = 1
1937-
} else if (additionalInfo === 24) {
1938-
if (data.length < offset + 2) throw new CBORError({ message: "Insufficient data for 1-byte length" })
1939-
length = data[offset + 1]
1940-
bytesRead = 2
1941-
} else if (additionalInfo === 25) {
1942-
if (data.length < offset + 3) throw new CBORError({ message: "Insufficient data for 2-byte length" })
1943-
length = data[offset + 1] * 256 + data[offset + 2]
1944-
bytesRead = 3
1945-
} else if (additionalInfo === 26) {
1946-
if (data.length < offset + 5) throw new CBORError({ message: "Insufficient data for 4-byte length" })
1947-
length = (data[offset + 1] << 24) | (data[offset + 2] << 16) | (data[offset + 3] << 8) | data[offset + 4]
1948-
bytesRead = 5
1949-
} else {
1950-
throw new CBORError({ message: `Unsupported additional info for length: ${additionalInfo}` })
1951-
}
1952-
return { length, bytesRead }
1953-
}

0 commit comments

Comments
 (0)