Skip to content

Commit 5ac5ac4

Browse files
authored
Fix issues surrounding type checking for Uint8Arrays and Buffers (#346)
1 parent 7291685 commit 5ac5ac4

File tree

2 files changed

+200
-37
lines changed

2 files changed

+200
-37
lines changed

index.js

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ function fromArrayBuffer (array, byteOffset, length) {
317317

318318
function fromObject (obj) {
319319
if (Buffer.isBuffer(obj)) {
320+
// Note: Probably not necessary anymore.
320321
const len = checked(obj.length) | 0
321322
const buf = createBuffer(len)
322323

@@ -363,9 +364,7 @@ Buffer.isBuffer = function isBuffer (b) {
363364
}
364365

365366
Buffer.compare = function compare (a, b) {
366-
if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)
367-
if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)
368-
if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
367+
if (!isInstance(a, Uint8Array) || !isInstance(b, Uint8Array)) {
369368
throw new TypeError(
370369
'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array'
371370
)
@@ -428,37 +427,28 @@ Buffer.concat = function concat (list, length) {
428427
const buffer = Buffer.allocUnsafe(length)
429428
let pos = 0
430429
for (i = 0; i < list.length; ++i) {
431-
let buf = list[i]
432-
if (isInstance(buf, Uint8Array)) {
433-
if (pos + buf.length > buffer.length) {
434-
if (!Buffer.isBuffer(buf)) {
435-
buf = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)
436-
}
437-
buf.copy(buffer, pos)
438-
} else {
439-
Uint8Array.prototype.set.call(
440-
buffer,
441-
buf,
442-
pos
443-
)
444-
}
445-
} else if (!Buffer.isBuffer(buf)) {
430+
const buf = list[i]
431+
if (!isInstance(buf, Uint8Array)) {
446432
throw new TypeError('"list" argument must be an Array of Buffers')
447-
} else {
448-
buf.copy(buffer, pos)
449433
}
434+
if (pos + buf.length > buffer.length) {
435+
buffer.set(buf.subarray(0, buffer.length - pos), pos)
436+
break
437+
}
438+
buffer.set(buf, pos)
450439
pos += buf.length
451440
}
452441
return buffer
453442
}
454443

455444
function byteLength (string, encoding) {
456-
if (Buffer.isBuffer(string)) {
457-
return string.length
458-
}
459445
if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {
460446
return string.byteLength
461447
}
448+
if (typeof SharedArrayBuffer !== 'undefined' &&
449+
isInstance(string, SharedArrayBuffer)) {
450+
return string.byteLength
451+
}
462452
if (typeof string !== 'string') {
463453
throw new TypeError(
464454
'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' +
@@ -632,7 +622,6 @@ Buffer.prototype.toString = function toString () {
632622
Buffer.prototype.toLocaleString = Buffer.prototype.toString
633623

634624
Buffer.prototype.equals = function equals (b) {
635-
if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
636625
if (this === b) return true
637626
return Buffer.compare(this, b) === 0
638627
}
@@ -649,10 +638,7 @@ if (customInspectSymbol) {
649638
}
650639

651640
Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
652-
if (isInstance(target, Uint8Array)) {
653-
target = Buffer.from(target, target.offset, target.byteLength)
654-
}
655-
if (!Buffer.isBuffer(target)) {
641+
if (!isInstance(target, Uint8Array)) {
656642
throw new TypeError(
657643
'The "target" argument must be one of type Buffer or Uint8Array. ' +
658644
'Received type ' + (typeof target)
@@ -697,13 +683,10 @@ Buffer.prototype.compare = function compare (target, start, end, thisStart, this
697683
let y = end - start
698684
const len = Math.min(x, y)
699685

700-
const thisCopy = this.slice(thisStart, thisEnd)
701-
const targetCopy = target.slice(start, end)
702-
703686
for (let i = 0; i < len; ++i) {
704-
if (thisCopy[i] !== targetCopy[i]) {
705-
x = thisCopy[i]
706-
y = targetCopy[i]
687+
if (this[thisStart + i] !== target[start + i]) {
688+
x = this[thisStart + i]
689+
y = target[start + i]
707690
break
708691
}
709692
}
@@ -1719,7 +1702,7 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert
17191702

17201703
// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
17211704
Buffer.prototype.copy = function copy (target, targetStart, start, end) {
1722-
if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')
1705+
if (!isInstance(target, Uint8Array)) throw new TypeError('argument should be a Buffer')
17231706
if (!start) start = 0
17241707
if (!end && end !== 0) end = this.length
17251708
if (targetStart >= target.length) targetStart = target.length
@@ -1814,7 +1797,7 @@ Buffer.prototype.fill = function fill (val, start, end, encoding) {
18141797
this[i] = val
18151798
}
18161799
} else {
1817-
const bytes = Buffer.isBuffer(val)
1800+
const bytes = isInstance(val, Uint8Array)
18181801
? val
18191802
: Buffer.from(val, encoding)
18201803
const len = bytes.length
@@ -2100,7 +2083,8 @@ function blitBuffer (src, dst, offset, length) {
21002083
function isInstance (obj, type) {
21012084
return obj instanceof type ||
21022085
(obj != null && obj.constructor != null && obj.constructor.name != null &&
2103-
obj.constructor.name === type.name)
2086+
obj.constructor.name === type.name) ||
2087+
(type === Uint8Array && Buffer.isBuffer(obj))
21042088
}
21052089
function numberIsNaN (obj) {
21062090
// For IE11 support

test/typing.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
'use strict'
2+
3+
const Buffer = require('../').Buffer
4+
const test = require('tape')
5+
const vm = require('vm')
6+
7+
// Get a Uint8Array and Buffer constructor from another context.
8+
const code = `
9+
'use strict'
10+
function Buffer (...args) {
11+
const buf = new Uint8Array(...args)
12+
Object.setPrototypeOf(buf, Buffer.prototype)
13+
return buf
14+
}
15+
Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype)
16+
Object.setPrototypeOf(Buffer, Uint8Array)
17+
Buffer.prototype._isBuffer = true
18+
exports.Uint8Array = Uint8Array
19+
exports.Buffer = Buffer
20+
`
21+
22+
const context = {}
23+
24+
// Should work in browserify.
25+
vm.runInNewContext(code, { exports: context })
26+
27+
const arrays = [context.Uint8Array, context.Buffer]
28+
29+
// Extracted from the index.js code for testing purposes.
30+
function isInstance (obj, type) {
31+
return (obj instanceof type) ||
32+
(obj != null &&
33+
obj.constructor != null &&
34+
obj.constructor.name != null &&
35+
obj.constructor.name === type.name) ||
36+
(type === Uint8Array && Buffer.isBuffer(obj))
37+
}
38+
39+
test('Uint8Arrays and Buffers from other contexts', (t) => {
40+
// Our buffer is considered a view.
41+
t.ok(ArrayBuffer.isView(Buffer.alloc(0)))
42+
43+
for (const ForeignArray of arrays) {
44+
const buf = new ForeignArray(1)
45+
46+
buf[0] = 1
47+
48+
// Prove that ArrayBuffer.isView and isInstance
49+
// return true for objects from other contexts.
50+
t.ok(!(buf instanceof Object))
51+
t.ok(!(buf instanceof Uint8Array))
52+
t.ok(!(buf instanceof Buffer))
53+
t.ok(ArrayBuffer.isView(buf))
54+
55+
// Now returns true even for Buffers from other contexts:
56+
t.ok(isInstance(buf, Uint8Array))
57+
58+
if (ForeignArray === context.Uint8Array) {
59+
t.ok(!Buffer.isBuffer(buf))
60+
} else {
61+
t.ok(Buffer.isBuffer(buf))
62+
}
63+
64+
// They even behave the same!
65+
const copy = new Uint8Array(buf)
66+
67+
t.ok(copy instanceof Object)
68+
t.ok(copy instanceof Uint8Array)
69+
t.ok(ArrayBuffer.isView(copy))
70+
t.equal(copy[0], 1)
71+
}
72+
73+
t.end()
74+
})
75+
76+
test('should instantiate from foreign arrays', (t) => {
77+
for (const ForeignArray of arrays) {
78+
const arr = new ForeignArray(2)
79+
80+
arr[0] = 1
81+
arr[1] = 2
82+
83+
const buf = Buffer.from(arr)
84+
85+
t.equal(buf.toString('hex'), '0102')
86+
}
87+
88+
t.end()
89+
})
90+
91+
test('should do comparisons with foreign arrays', (t) => {
92+
const a = Buffer.from([1, 2, 3])
93+
const b = new context.Uint8Array(a)
94+
const c = new context.Buffer(a)
95+
96+
t.equal(Buffer.byteLength(a), 3)
97+
t.equal(Buffer.byteLength(b), 3)
98+
t.equal(Buffer.byteLength(c), 3)
99+
t.equal(b[0], 1)
100+
t.equal(c[0], 1)
101+
102+
t.ok(a.equals(b))
103+
t.ok(a.equals(c))
104+
t.ok(a.compare(b) === 0)
105+
t.ok(a.compare(c) === 0)
106+
t.ok(Buffer.compare(a, b) === 0)
107+
t.ok(Buffer.compare(a, c) === 0)
108+
t.ok(Buffer.compare(b, c) === 0)
109+
t.ok(Buffer.compare(c, b) === 0)
110+
111+
a[0] = 0
112+
113+
t.ok(!a.equals(b))
114+
t.ok(!a.equals(c))
115+
t.ok(a.compare(b) < 0)
116+
t.ok(a.compare(c) < 0)
117+
t.ok(Buffer.compare(a, b) < 0)
118+
t.ok(Buffer.compare(a, c) < 0)
119+
120+
b[0] = 0
121+
122+
t.ok(Buffer.compare(b, c) < 0)
123+
t.ok(Buffer.compare(c, b) > 0)
124+
125+
t.end()
126+
})
127+
128+
test('should fill with foreign arrays', (t) => {
129+
for (const ForeignArray of arrays) {
130+
const buf = Buffer.alloc(4)
131+
const arr = new ForeignArray(2)
132+
133+
arr[0] = 1
134+
arr[1] = 2
135+
136+
buf.fill(arr)
137+
138+
t.equal(buf.toString('hex'), '01020102')
139+
}
140+
141+
t.end()
142+
})
143+
144+
test('should do concatenation with foreign arrays', (t) => {
145+
for (const ForeignArray of arrays) {
146+
const a = new ForeignArray(2)
147+
148+
a[0] = 1
149+
a[1] = 2
150+
151+
const b = new ForeignArray(a)
152+
153+
{
154+
const buf = Buffer.concat([a, b])
155+
t.equal(buf.toString('hex'), '01020102')
156+
}
157+
158+
{
159+
const buf = Buffer.concat([a, b], 3)
160+
t.equal(buf.toString('hex'), '010201')
161+
}
162+
}
163+
164+
t.end()
165+
})
166+
167+
test('should copy on to foreign arrays', (t) => {
168+
for (const ForeignArray of arrays) {
169+
const a = Buffer.from([1, 2])
170+
const b = new ForeignArray(2)
171+
172+
a.copy(b)
173+
174+
t.equal(b[0], 1)
175+
t.equal(b[1], 2)
176+
}
177+
178+
t.end()
179+
})

0 commit comments

Comments
 (0)