protect against non-integer array indexing (#1595)

* do not assume non-iteger array bracket access will return undefined

* revert to spec
This commit is contained in:
Bruce Martin
2020-06-29 09:37:59 -07:00
committed by GitHub
parent b79f09c550
commit 8898a9817c
3 changed files with 214 additions and 37 deletions
@@ -1029,3 +1029,187 @@ describe("label indexing", () => {
expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]); expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]);
}); });
}); });
describe("corner cases", () => {
/* error/corner cases */
test("identity integer index rejects non-integer labels", () => {
const idx = new Dataframe.IdentityInt32Index(10);
expect(idx.getOffset(0)).toBe(0);
expect(idx.getOffset(9)).toBe(9);
expect(idx.getOffset(10)).toBeUndefined();
expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getLabel(0)).toBe(0);
expect(idx.getLabel(9)).toBe(9);
expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([])).toBeUndefined();
expect(idx.getLabel(new Float32Array())).toBeUndefined();
expect(idx.getLabel("__proto__")).toBeUndefined();
});
test("dense integer index rejects non-integer labels", () => {
const idx = new Dataframe.DenseInt32Index([-10, 0, 3, 9, 10]);
expect(idx.getOffset(0)).toBe(1);
expect(idx.getOffset(9)).toBe(3);
expect(idx.getOffset(1)).toBeUndefined();
expect(idx.getOffset(11)).toBeUndefined();
expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getLabel(0)).toBe(-10);
expect(idx.getLabel(4)).toBe(10);
expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([])).toBeUndefined();
expect(idx.getLabel(new Float32Array())).toBeUndefined();
expect(idx.getLabel("__proto__")).toBeUndefined();
});
test("Empty dataframe rejects bogus labels", () => {
const df = Dataframe.Dataframe.empty();
expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined)).toBeFalsy();
expect(df.col("sort")).toBeUndefined();
expect(df.col(0)).toBeUndefined();
expect(df.col(true)).toBeUndefined();
expect(df.col(false)).toBeUndefined();
expect(df.col([])).toBeUndefined();
expect(df.col({})).toBeUndefined();
expect(df.col(null)).toBeUndefined();
expect(df.col(undefined)).toBeUndefined();
expect(df.icol("sort")).toBeUndefined();
expect(df.icol(0)).toBeUndefined();
expect(df.icol(true)).toBeUndefined();
expect(df.icol(false)).toBeUndefined();
expect(df.icol([])).toBeUndefined();
expect(df.icol({})).toBeUndefined();
expect(df.icol(null)).toBeUndefined();
expect(df.icol(undefined)).toBeUndefined();
expect(df.ihas("sort", "length")).toBeFalsy();
expect(df.ihas("0", "0")).toBeFalsy();
expect(df.ihas("", "")).toBeFalsy();
expect(df.ihas(null, null)).toBeFalsy();
expect(df.ihas(undefined, undefined)).toBeFalsy();
expect(df.ihas(true, true)).toBeFalsy();
expect(df.ihas([], [])).toBeFalsy();
expect(df.ihas({}, {})).toBeFalsy();
});
test("Dataframe rejects bogus labels", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[
[true, false],
[1, 0],
],
null,
new Dataframe.KeyIndex(["A", "B"])
);
expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol("__proto__")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined)).toBeFalsy();
expect(df.col("sort")).toBeUndefined();
expect(df.col("__proto__")).toBeUndefined();
expect(df.col(0)).toBeUndefined();
expect(df.col(true)).toBeUndefined();
expect(df.col(false)).toBeUndefined();
expect(df.col([])).toBeUndefined();
expect(df.col({})).toBeUndefined();
expect(df.col(null)).toBeUndefined();
expect(df.col(undefined)).toBeUndefined();
expect(df.icol("sort")).toBeUndefined();
expect(df.icol("__proto__")).toBeUndefined();
expect(df.icol(-1)).toBeUndefined();
expect(df.icol(true)).toBeUndefined();
expect(df.icol(false)).toBeUndefined();
expect(df.icol([])).toBeUndefined();
expect(df.icol({})).toBeUndefined();
expect(df.icol(null)).toBeUndefined();
expect(df.icol(undefined)).toBeUndefined();
expect(df.ihas("sort", "length")).toBeFalsy();
expect(df.ihas("__proto__", "__proto__")).toBeFalsy();
expect(df.ihas(-1, 0)).toBeFalsy();
expect(df.ihas("0", 0)).toBeFalsy();
expect(df.ihas("", 0)).toBeFalsy();
expect(df.ihas(null, 0)).toBeFalsy();
expect(df.ihas(undefined, 0)).toBeFalsy();
expect(df.ihas([], 0)).toBeFalsy();
expect(df.ihas({}, 0)).toBeFalsy();
expect(df.ihas(0, -1)).toBeFalsy();
expect(df.ihas(0, "0")).toBeFalsy();
expect(df.ihas(0, "")).toBeFalsy();
expect(df.ihas(0, null)).toBeFalsy();
expect(df.ihas(0, undefined)).toBeFalsy();
expect(df.ihas(0, [])).toBeFalsy();
expect(df.ihas(0, {})).toBeFalsy();
expect(df.has("sort", "length")).toBeFalsy();
expect(df.has("length", "sort")).toBeFalsy();
expect(df.has("__proto__", "__proto__")).toBeFalsy();
expect(df.has(-1, "A")).toBeFalsy();
expect(df.has("0", "A")).toBeFalsy();
expect(df.has("", "A")).toBeFalsy();
expect(df.has(null, "A")).toBeFalsy();
expect(df.has(undefined, "A")).toBeFalsy();
expect(df.has([], "A")).toBeFalsy();
expect(df.has({}, "A")).toBeFalsy();
expect(df.has(0, -1)).toBeFalsy();
expect(df.has(0, "0")).toBeFalsy();
expect(df.has(0, "")).toBeFalsy();
expect(df.has(0, null)).toBeFalsy();
expect(df.has(0, undefined)).toBeFalsy();
expect(df.has(0, [])).toBeFalsy();
expect(df.has(0, {})).toBeFalsy();
});
});
+13 -6
View File
@@ -290,10 +290,8 @@ class Dataframe {
Use an existing accessor if provided, else compile a new one. Use an existing accessor if provided, else compile a new one.
*/ */
const { const getRowByOffset = this.rowIndex.getOffset.bind(this.rowIndex);
getOffset: getRowByOffset, const getRowByLabel = this.rowIndex.getLabel.bind(this.rowIndex);
getLabel: getRowByLabel,
} = this.rowIndex;
this.__columnsAccessor = this.__columns.map((column, idx) => { this.__columnsAccessor = this.__columns.map((column, idx) => {
if (accessors[idx]) { if (accessors[idx]) {
return accessors[idx]; return accessors[idx];
@@ -704,7 +702,9 @@ class Dataframe {
/* /*
Return column accessor by offset. Return column accessor by offset.
*/ */
return this.__columnsAccessor[columnOffset]; return Number.isInteger(columnOffset)
? this.__columnsAccessor[columnOffset]
: undefined;
} }
at(r, c) { at(r, c) {
@@ -753,7 +753,14 @@ class Dataframe {
dataframe - returns true/false dataframe - returns true/false
*/ */
const [nRows, nCols] = this.dims; const [nRows, nCols] = this.dims;
return c >= 0 && c < nCols && r >= 0 && r < nRows; return (
Number.isInteger(r) &&
Number.isInteger(c) &&
c >= 0 &&
c < nCols &&
r >= 0 &&
r < nRows
);
} }
hasCol(c) { hasCol(c) {
+17 -31
View File
@@ -41,31 +41,29 @@ class IdentityInt32Index {
return k; return k;
} }
/* eslint-disable class-methods-use-this -- having these accessors as class methods allows for polymorphism */
getOffset(i) { getOffset(i) {
// label to offset // label to offset
return i; return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
} }
getOffsets(arr) { getOffsets(arr) {
// labels to offsets // labels to offsets
return arr; return arr.map((i) => this.getOffset(i));
} }
getLabel(i) { getLabel(i) {
// offset to label // offset to label
return i; return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
} }
getLabels(arr) { getLabels(arr) {
// offsets to labels // offsets to labels
return arr; return arr.map((i) => this.getLabel(i));
} }
size() { size() {
return this.maxOffset; return this.maxOffset;
} }
/* eslint-enable class-methods-use-this -- enable */
__promote(labelArray) { __promote(labelArray) {
/* /*
@@ -89,7 +87,7 @@ class IdentityInt32Index {
const { maxOffset } = this; const { maxOffset } = this;
for (let i = 0, l = labels.length; i < l; i += 1) { for (let i = 0, l = labels.length; i < l; i += 1) {
const label = labels[i]; const label = labels[i];
if (label < 0 || label >= maxOffset) if (!Number.isInteger(label) || label < 0 || label >= maxOffset)
throw new RangeError(`offset or label: ${label}`); throw new RangeError(`offset or label: ${label}`);
} }
return this.__promote(labels); return this.__promote(labels);
@@ -153,27 +151,21 @@ class DenseInt32Index {
__compile() { __compile() {
const { minLabel, index, rindex } = this; const { minLabel, index, rindex } = this;
this.getOffset = function getOffset(l) { this.getOffset = function getOffset(l) {
return index[l - minLabel]; if (!Number.isInteger(l)) return undefined;
const offset = index[l - minLabel];
return offset === -1 ? undefined : offset;
}; };
this.getOffsets = function getOffsets(arr) { this.getOffsets = function getOffsets(arr) {
const res = new arr.constructor(arr.length); return arr.map((i) => this.getOffset(i));
for (let i = 0, len = arr.length; i < len; i += 1) {
res[i] = index[arr[i] - minLabel];
}
return res;
}; };
this.getLabel = function getLabel(i) { this.getLabel = function getLabel(i) {
return rindex[i]; return Number.isInteger(i) ? rindex[i] : undefined;
}; };
this.getLabels = function getLabels(arr) { this.getLabels = function getLabels(arr) {
const res = new arr.constructor(arr.length); return arr.map((i) => this.getLabel(i));
for (let i = 0, len = arr.length; i < len; i += 1) {
res[i] = rindex[arr[i]];
}
return res;
}; };
} }
@@ -275,23 +267,15 @@ class KeyIndex {
}; };
this.getOffsets = function getOffsets(arr) { this.getOffsets = function getOffsets(arr) {
const res = new arr.constructor(arr.length); return arr.map((l) => this.getOffset(l));
for (let i = 0, len = arr.length; i < len; i += 1) {
res[i] = index.get(arr[i]);
}
return res;
}; };
this.getLabel = function getLabel(i) { this.getLabel = function getLabel(i) {
return rindex[i]; return Number.isInteger(i) ? rindex[i] : undefined;
}; };
this.getLabels = function getLabels(arr) { this.getLabels = function getLabels(arr) {
const res = new arr.constructor(arr.length); return arr.map((i) => this.getLabel(i));
for (let i = 0, len = arr.length; i < len; i += 1) {
res[i] = rindex[arr[i]];
}
return res;
}; };
} }
@@ -308,7 +292,9 @@ class KeyIndex {
for (let i = 0, l = labels.length; i < l; i += 1) { for (let i = 0, l = labels.length; i < l; i += 1) {
const label = labels[i]; const label = labels[i];
const offset = this.getOffset(label); const offset = this.getOffset(label);
if (offset === undefined) throw new RangeError(`unknown label: ${label}`); if (offset === undefined || offset === -1) {
throw new RangeError(`unknown label: ${label}`);
}
} }
return new KeyIndex(labels); return new KeyIndex(labels);