Skip to content
This repository was archived by the owner on Jul 18, 2018. It is now read-only.

Commit c4c73fb

Browse files
inexorabletashCommit bot
authored and
Commit bot
committed
Indexed DB: Ensure large explicit keys consistently max out generator
Background: Stores can have a key generator which generates successive numeric keys. Storing a record with an explicit numeric key adjusts they key generator to produce values above the explicit key. Once the generator hits 2^53 it stops generating new keys (since that's the maximum integer uniquely representable as a JS number). Chrome's logic for certain values above this limit was "wonky". Values above 2^53 would max out the generator. Values above 2^63 and Infinity would be ignored and not adjust the generator, due to relying on undefined double->int64_t casting behavior. Fix to always max out the generator for large values. Also adds web-platform-tests - other implementations are wonky too. :( Also adds some missing test coverage for key injection cases. Spec discussion: w3c/IndexedDB#147 BUG=691754 Review-Url: https://codereview.chromium.org/2735213002 Cr-Commit-Position: refs/heads/master@{#455256}
1 parent 430eac0 commit c4c73fb

File tree

3 files changed

+278
-6
lines changed

3 files changed

+278
-6
lines changed

content/browser/indexed_db/indexed_db_database.cc

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <math.h>
88

9+
#include <algorithm>
910
#include <limits>
1011
#include <set>
1112

@@ -1183,13 +1184,11 @@ static std::unique_ptr<IndexedDBKey> GenerateKey(
11831184
IndexedDBTransaction* transaction,
11841185
int64_t database_id,
11851186
int64_t object_store_id) {
1186-
const int64_t max_generator_value =
1187-
9007199254740992LL; // Maximum integer storable as ECMAScript number.
1187+
// Maximum integer uniquely representable as ECMAScript number.
1188+
const int64_t max_generator_value = 9007199254740992LL;
11881189
int64_t current_number;
11891190
leveldb::Status s = backing_store->GetKeyGeneratorCurrentNumber(
1190-
transaction->BackingStoreTransaction(),
1191-
database_id,
1192-
object_store_id,
1191+
transaction->BackingStoreTransaction(), database_id, object_store_id,
11931192
&current_number);
11941193
if (!s.ok()) {
11951194
LOG(ERROR) << "Failed to GetKeyGeneratorCurrentNumber";
@@ -1201,16 +1200,24 @@ static std::unique_ptr<IndexedDBKey> GenerateKey(
12011200
return base::MakeUnique<IndexedDBKey>(current_number, WebIDBKeyTypeNumber);
12021201
}
12031202

1203+
// Called at the end of a "put" operation. The key is a number that was either
1204+
// generated by the generator which now needs to be incremented (so
1205+
// |check_current| is false) or was user-supplied so we only conditionally use
1206+
// (and |check_current| is true).
12041207
static leveldb::Status UpdateKeyGenerator(IndexedDBBackingStore* backing_store,
12051208
IndexedDBTransaction* transaction,
12061209
int64_t database_id,
12071210
int64_t object_store_id,
12081211
const IndexedDBKey& key,
12091212
bool check_current) {
12101213
DCHECK_EQ(WebIDBKeyTypeNumber, key.type());
1214+
// Maximum integer uniquely representable as ECMAScript number.
1215+
const double max_generator_value = 9007199254740992.0;
1216+
int64_t value = base::saturated_cast<int64_t>(
1217+
floor(std::min(key.number(), max_generator_value)));
12111218
return backing_store->MaybeUpdateKeyGeneratorCurrentNumber(
12121219
transaction->BackingStoreTransaction(), database_id, object_store_id,
1213-
static_cast<int64_t>(floor(key.number())) + 1, check_current);
1220+
value + 1, check_current);
12141221
}
12151222

12161223
struct IndexedDBDatabase::PutOperationParams {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>Key Generator behavior with explicit keys generator overflow</title>
4+
<link rel=help href="https://w3c.github.io/IndexedDB/#key-generator-construct">
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="support.js"></script>
8+
<script>
9+
10+
function big_key_test(key, description) {
11+
indexeddb_test(
12+
(t, db) => {
13+
assert_equals(indexedDB.cmp(key, key), 0, 'Key is valid');
14+
15+
db.createObjectStore('store', {autoIncrement: true});
16+
},
17+
(t, db) => {
18+
const tx = db.transaction('store', 'readwrite');
19+
const store = tx.objectStore('store');
20+
const value = 0;
21+
let request;
22+
23+
request = store.put(value);
24+
request.onerror = t.unreached_func('put should succeed');
25+
request.onsuccess = t.step_func(e => {
26+
assert_equals(e.target.result, 1,
27+
'Key generator should initially be 1');
28+
});
29+
30+
request = store.put(value);
31+
request.onerror = t.unreached_func('put should succeed');
32+
request.onsuccess = t.step_func(e => {
33+
assert_equals(e.target.result, 2,
34+
'Key generator should increment');
35+
});
36+
37+
request = store.put(value, 1000);
38+
request.onerror = t.unreached_func('put should succeed');
39+
request.onsuccess = t.step_func(e => {
40+
assert_equals(e.target.result, 1000,
41+
'Explicit key should be used');
42+
});
43+
44+
request = store.put(value);
45+
request.onerror = t.unreached_func('put should succeed');
46+
request.onsuccess = t.step_func(e => {
47+
assert_equals(e.target.result, 1001,
48+
'Key generator should have updated');
49+
});
50+
51+
request = store.put(value, key);
52+
request.onerror = t.unreached_func('put should succeed');
53+
request.onsuccess = t.step_func(e => {
54+
assert_equals(e.target.result, key,
55+
'Explicit key should be used');
56+
});
57+
58+
if (key >= 0) {
59+
// Large positive values will max out the key generator, so it
60+
// can no longer produce keys.
61+
request = store.put(value);
62+
request.onsuccess = t.unreached_func('put should fail');
63+
request.onerror = t.step_func(e => {
64+
e.preventDefault();
65+
assert_equals(e.target.error.name, 'ConstraintError',
66+
'Key generator should have returned failure');
67+
});
68+
} else {
69+
// Large negative values are always lower than the key generator's
70+
// current number, so have no effect on the generator.
71+
request = store.put(value);
72+
request.onerror = t.unreached_func('put should succeed');
73+
request.onsuccess = t.step_func(e => {
74+
assert_equals(e.target.result, 1002,
75+
'Key generator should have updated');
76+
});
77+
}
78+
79+
request = store.put(value, 2000);
80+
request.onerror = t.unreached_func('put should succeed');
81+
request.onsuccess = t.step_func(e => {
82+
assert_equals(e.target.result, 2000,
83+
'Explicit key should be used');
84+
});
85+
86+
tx.onabort = t.step_func(() => {
87+
assert_unreached(`Transaction aborted: ${tx.error.message}`);
88+
});
89+
tx.oncomplete = t.step_func(() => { t.done(); });
90+
},
91+
description);
92+
}
93+
94+
[
95+
{
96+
key: Number.MAX_SAFE_INTEGER + 1,
97+
description: '53 bits'
98+
},
99+
{
100+
key: Math.pow(2, 60),
101+
description: 'greater than 53 bits, less than 64 bits'
102+
},
103+
{
104+
key: -Math.pow(2, 60),
105+
description: 'greater than 53 bits, less than 64 bits (negative)'
106+
},
107+
{
108+
key: Math.pow(2, 63),
109+
description: '63 bits'
110+
},
111+
{
112+
key: -Math.pow(2, 63),
113+
description: '63 bits (negative)'
114+
},
115+
{
116+
key: Math.pow(2, 64),
117+
description: '64 bits'
118+
},
119+
{
120+
key: -Math.pow(2, 64),
121+
description: '64 bits (negative)'
122+
},
123+
{
124+
key: Math.pow(2, 70),
125+
description: 'greater than 64 bits, but still finite'
126+
},
127+
{
128+
key: -Math.pow(2, 70),
129+
description: 'greater than 64 bits, but still finite (negative)'
130+
},
131+
{
132+
key: Infinity,
133+
description: 'equal to Infinity'
134+
},
135+
{
136+
key: -Infinity,
137+
description: 'equal to -Infinity'
138+
}
139+
].forEach(function(testCase) {
140+
big_key_test(testCase.key,
141+
`Key generator vs. explicit key ${testCase.description}`);
142+
});
143+
144+
145+
146+
</script>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>Key Generator behavior with explicit keys and value injection</title>
4+
<link rel=help href="https://w3c.github.io/IndexedDB/#inject-key-into-value">
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="support.js"></script>
8+
<script>
9+
10+
indexeddb_test(
11+
(t, db) => {
12+
db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'});
13+
},
14+
(t, db) => {
15+
const tx = db.transaction('store', 'readwrite');
16+
t.onabort = t.unreached_func('transaction should not abort');
17+
18+
const store = tx.objectStore('store');
19+
20+
store.put({name: 'n'}).onsuccess = t.step_func(e => {
21+
const key = e.target.result;
22+
assert_equals(key, 1, 'Key generator initial value should be 1');
23+
store.get(key).onsuccess = t.step_func(e => {
24+
const value = e.target.result;
25+
assert_equals(typeof value, 'object', 'Result should be object');
26+
assert_equals(value.name, 'n', 'Result should have name property');
27+
assert_equals(value.id, key, 'Key should be injected');
28+
t.done();
29+
});
30+
});
31+
},
32+
'Key is injected into value - single segment path');
33+
34+
indexeddb_test(
35+
(t, db) => {
36+
db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'});
37+
},
38+
(t, db) => {
39+
const tx = db.transaction('store', 'readwrite');
40+
t.onabort = t.unreached_func('transaction should not abort');
41+
42+
const store = tx.objectStore('store');
43+
44+
store.put({name: 'n'}).onsuccess = t.step_func(e => {
45+
const key = e.target.result;
46+
assert_equals(key, 1, 'Key generator initial value should be 1');
47+
store.get(key).onsuccess = t.step_func(e => {
48+
const value = e.target.result;
49+
assert_equals(typeof value, 'object', 'Result should be object');
50+
assert_equals(value.name, 'n', 'Result should have name property');
51+
assert_equals(value.a.b.id, key, 'Key should be injected');
52+
t.done();
53+
});
54+
});
55+
},
56+
'Key is injected into value - multi-segment path');
57+
58+
indexeddb_test(
59+
(t, db) => {
60+
db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'});
61+
},
62+
(t, db) => {
63+
const tx = db.transaction('store', 'readwrite');
64+
t.onabort = t.unreached_func('transaction should not abort');
65+
66+
const store = tx.objectStore('store');
67+
68+
store.put({name: 'n1', b: {name: 'n2'}}).onsuccess = t.step_func(e => {
69+
const key = e.target.result;
70+
assert_equals(key, 1, 'Key generator initial value should be 1');
71+
store.get(key).onsuccess = t.step_func(e => {
72+
const value = e.target.result;
73+
assert_equals(typeof value, 'object', 'Result should be object');
74+
assert_equals(value.name, 'n1', 'Result should have name property');
75+
assert_equals(value.b.name, 'n2', 'Result should have name property');
76+
assert_equals(value.a.b.id, key, 'Key should be injected');
77+
t.done();
78+
});
79+
});
80+
},
81+
'Key is injected into value - multi-segment path, partially populated');
82+
83+
indexeddb_test(
84+
(t, db) => {
85+
db.createObjectStore('store', {autoIncrement: true, keyPath: 'id'});
86+
},
87+
(t, db) => {
88+
const tx = db.transaction('store', 'readwrite');
89+
const store = tx.objectStore('store');
90+
91+
assert_throws('DataError', () => {
92+
store.put(123);
93+
}, 'Key path should be checked against value');
94+
95+
t.done();
96+
},
97+
'put() throws if key cannot be injected - single segment path');
98+
99+
indexeddb_test(
100+
(t, db) => {
101+
db.createObjectStore('store', {autoIncrement: true, keyPath: 'a.b.id'});
102+
},
103+
(t, db) => {
104+
const tx = db.transaction('store', 'readwrite');
105+
const store = tx.objectStore('store');
106+
107+
assert_throws('DataError', () => {
108+
store.put({a: 123});
109+
}, 'Key path should be checked against value');
110+
111+
assert_throws('DataError', () => {
112+
store.put({a: {b: 123} });
113+
}, 'Key path should be checked against value');
114+
115+
t.done();
116+
},
117+
'put() throws if key cannot be injected - multi-segment path');
118+
119+
</script>

0 commit comments

Comments
 (0)