Bug 1734063 [wpt PR 31107] - [GridNG] Fix rounding of distributed free space to flexi...
[gecko.git] / xpcom / ds / nsPersistentProperties.cpp
blob2251e0cbbc3eef0dc34d4557d3c5ac97ff4ffabc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsArrayEnumerator.h"
8 #include "nsID.h"
9 #include "nsCOMArray.h"
10 #include "nsUnicharInputStream.h"
11 #include "nsPrintfCString.h"
13 #include "nsPersistentProperties.h"
14 #include "nsIProperties.h"
16 #include "mozilla/ArenaAllocatorExtensions.h"
18 using mozilla::ArenaStrdup;
20 struct PropertyTableEntry : public PLDHashEntryHdr {
21 // both of these are arena-allocated
22 const char* mKey;
23 const char16_t* mValue;
26 static const struct PLDHashTableOps property_HashTableOps = {
27 PLDHashTable::HashStringKey,
28 PLDHashTable::MatchStringKey,
29 PLDHashTable::MoveEntryStub,
30 PLDHashTable::ClearEntryStub,
31 nullptr,
35 // parser stuff
37 enum EParserState {
38 eParserState_AwaitingKey,
39 eParserState_Key,
40 eParserState_AwaitingValue,
41 eParserState_Value,
42 eParserState_Comment
45 enum EParserSpecial {
46 eParserSpecial_None, // not parsing a special character
47 eParserSpecial_Escaped, // awaiting a special character
48 eParserSpecial_Unicode // parsing a \Uxxx value
51 class MOZ_STACK_CLASS nsPropertiesParser {
52 public:
53 explicit nsPropertiesParser(nsIPersistentProperties* aProps)
54 : mUnicodeValuesRead(0),
55 mUnicodeValue(u'\0'),
56 mHaveMultiLine(false),
57 mMultiLineCanSkipN(false),
58 mMinLength(0),
59 mState(eParserState_AwaitingKey),
60 mSpecialState(eParserSpecial_None),
61 mProps(aProps) {}
63 void FinishValueState(nsAString& aOldValue) {
64 static const char trimThese[] = " \t";
65 mKey.Trim(trimThese, false, true);
67 // This is really ugly hack but it should be fast
68 char16_t backup_char;
69 uint32_t minLength = mMinLength;
70 if (minLength) {
71 backup_char = mValue[minLength - 1];
72 mValue.SetCharAt('x', minLength - 1);
74 mValue.Trim(trimThese, false, true);
75 if (minLength) {
76 mValue.SetCharAt(backup_char, minLength - 1);
79 mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
80 mSpecialState = eParserSpecial_None;
81 WaitForKey();
84 EParserState GetState() { return mState; }
86 static nsresult SegmentWriter(nsIUnicharInputStream* aStream, void* aClosure,
87 const char16_t* aFromSegment,
88 uint32_t aToOffset, uint32_t aCount,
89 uint32_t* aWriteCount);
91 nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
93 private:
94 bool ParseValueCharacter(
95 char16_t aChar, // character that is just being parsed
96 const char16_t* aCur, // pointer to character aChar in the buffer
97 const char16_t*& aTokenStart, // string copying is done in blocks as big
98 // as possible, aTokenStart points to the
99 // beginning of this block
100 nsAString& aOldValue); // when duplicate property is found, new value
101 // is stored into hashtable and the old one is
102 // placed in this variable
104 void WaitForKey() { mState = eParserState_AwaitingKey; }
106 void EnterKeyState() {
107 mKey.Truncate();
108 mState = eParserState_Key;
111 void WaitForValue() { mState = eParserState_AwaitingValue; }
113 void EnterValueState() {
114 mValue.Truncate();
115 mMinLength = 0;
116 mState = eParserState_Value;
117 mSpecialState = eParserSpecial_None;
120 void EnterCommentState() { mState = eParserState_Comment; }
122 nsAutoString mKey;
123 nsAutoString mValue;
125 uint32_t mUnicodeValuesRead; // should be 4!
126 char16_t mUnicodeValue; // currently parsed unicode value
127 bool mHaveMultiLine; // is TRUE when last processed characters form
128 // any of following sequences:
129 // - "\\\r"
130 // - "\\\n"
131 // - "\\\r\n"
132 // - any sequence above followed by any
133 // combination of ' ' and '\t'
134 bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected
135 uint32_t mMinLength; // limit right trimming at the end to not trim
136 // escaped whitespaces
137 EParserState mState;
138 // if we see a '\' then we enter this special state
139 EParserSpecial mSpecialState;
140 nsCOMPtr<nsIPersistentProperties> mProps;
143 inline bool IsWhiteSpace(char16_t aChar) {
144 return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') ||
145 (aChar == '\n');
148 inline bool IsEOL(char16_t aChar) { return (aChar == '\r') || (aChar == '\n'); }
150 bool nsPropertiesParser::ParseValueCharacter(char16_t aChar,
151 const char16_t* aCur,
152 const char16_t*& aTokenStart,
153 nsAString& aOldValue) {
154 switch (mSpecialState) {
155 // the normal state - look for special characters
156 case eParserSpecial_None:
157 switch (aChar) {
158 case '\\':
159 if (mHaveMultiLine) {
160 // there is nothing to append to mValue yet
161 mHaveMultiLine = false;
162 } else {
163 mValue += Substring(aTokenStart, aCur);
166 mSpecialState = eParserSpecial_Escaped;
167 break;
169 case '\n':
170 // if we detected multiline and got only "\\\r" ignore next "\n" if
171 // any
172 if (mHaveMultiLine && mMultiLineCanSkipN) {
173 // but don't allow another '\n' to be skipped
174 mMultiLineCanSkipN = false;
175 // Now there is nothing to append to the mValue since we are
176 // skipping whitespaces at the beginning of the new line of the
177 // multiline property. Set aTokenStart properly to ensure that
178 // nothing is appended if we find regular line-end or the end of the
179 // buffer.
180 aTokenStart = aCur + 1;
181 break;
183 [[fallthrough]];
185 case '\r':
186 // we're done! We have a key and value
187 mValue += Substring(aTokenStart, aCur);
188 FinishValueState(aOldValue);
189 mHaveMultiLine = false;
190 break;
192 default:
193 // there is nothing to do with normal characters,
194 // but handle multilines correctly
195 if (mHaveMultiLine) {
196 if (aChar == ' ' || aChar == '\t') {
197 // don't allow another '\n' to be skipped
198 mMultiLineCanSkipN = false;
199 // Now there is nothing to append to the mValue since we are
200 // skipping whitespaces at the beginning of the new line of the
201 // multiline property. Set aTokenStart properly to ensure that
202 // nothing is appended if we find regular line-end or the end of
203 // the buffer.
204 aTokenStart = aCur + 1;
205 break;
207 mHaveMultiLine = false;
208 aTokenStart = aCur;
210 break; // from switch on (aChar)
212 break; // from switch on (mSpecialState)
214 // saw a \ character, so parse the character after that
215 case eParserSpecial_Escaped:
216 // probably want to start parsing at the next token
217 // other characters, like 'u' might override this
218 aTokenStart = aCur + 1;
219 mSpecialState = eParserSpecial_None;
221 switch (aChar) {
222 // the easy characters - \t, \n, and so forth
223 case 't':
224 mValue += char16_t('\t');
225 mMinLength = mValue.Length();
226 break;
227 case 'n':
228 mValue += char16_t('\n');
229 mMinLength = mValue.Length();
230 break;
231 case 'r':
232 mValue += char16_t('\r');
233 mMinLength = mValue.Length();
234 break;
235 case '\\':
236 mValue += char16_t('\\');
237 break;
239 // switch to unicode mode!
240 case 'u':
241 case 'U':
242 mSpecialState = eParserSpecial_Unicode;
243 mUnicodeValuesRead = 0;
244 mUnicodeValue = 0;
245 break;
247 // a \ immediately followed by a newline means we're going multiline
248 case '\r':
249 case '\n':
250 mHaveMultiLine = true;
251 mMultiLineCanSkipN = (aChar == '\r');
252 mSpecialState = eParserSpecial_None;
253 break;
255 default:
256 // don't recognize the character, so just append it
257 mValue += aChar;
258 break;
260 break;
262 // we're in the middle of parsing a 4-character unicode value
263 // like \u5f39
264 case eParserSpecial_Unicode:
265 if ('0' <= aChar && aChar <= '9') {
266 mUnicodeValue = (mUnicodeValue << 4) | (aChar - '0');
267 } else if ('a' <= aChar && aChar <= 'f') {
268 mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'a' + 0x0a);
269 } else if ('A' <= aChar && aChar <= 'F') {
270 mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'A' + 0x0a);
271 } else {
272 // non-hex character. Append what we have, and move on.
273 mValue += mUnicodeValue;
274 mMinLength = mValue.Length();
275 mSpecialState = eParserSpecial_None;
277 // leave aTokenStart at this unknown character, so it gets appended
278 aTokenStart = aCur;
280 // ensure parsing this non-hex character again
281 return false;
284 if (++mUnicodeValuesRead >= 4) {
285 aTokenStart = aCur + 1;
286 mSpecialState = eParserSpecial_None;
287 mValue += mUnicodeValue;
288 mMinLength = mValue.Length();
291 break;
294 return true;
297 nsresult nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
298 void* aClosure,
299 const char16_t* aFromSegment,
300 uint32_t aToOffset, uint32_t aCount,
301 uint32_t* aWriteCount) {
302 nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure);
303 parser->ParseBuffer(aFromSegment, aCount);
305 *aWriteCount = aCount;
306 return NS_OK;
309 nsresult nsPropertiesParser::ParseBuffer(const char16_t* aBuffer,
310 uint32_t aBufferLength) {
311 const char16_t* cur = aBuffer;
312 const char16_t* end = aBuffer + aBufferLength;
314 // points to the start/end of the current key or value
315 const char16_t* tokenStart = nullptr;
317 // if we're in the middle of parsing a key or value, make sure
318 // the current token points to the beginning of the current buffer
319 if (mState == eParserState_Key || mState == eParserState_Value) {
320 tokenStart = aBuffer;
323 nsAutoString oldValue;
325 while (cur != end) {
326 char16_t c = *cur;
328 switch (mState) {
329 case eParserState_AwaitingKey:
330 if (c == '#' || c == '!') {
331 EnterCommentState();
334 else if (!IsWhiteSpace(c)) {
335 // not a comment, not whitespace, we must have found a key!
336 EnterKeyState();
337 tokenStart = cur;
339 break;
341 case eParserState_Key:
342 if (c == '=' || c == ':') {
343 mKey += Substring(tokenStart, cur);
344 WaitForValue();
346 break;
348 case eParserState_AwaitingValue:
349 if (IsEOL(c)) {
350 // no value at all! mimic the normal value-ending
351 EnterValueState();
352 FinishValueState(oldValue);
355 // ignore white space leading up to the value
356 else if (!IsWhiteSpace(c)) {
357 tokenStart = cur;
358 EnterValueState();
360 // make sure to handle this first character
361 if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
362 cur++;
364 // If the character isn't consumed, don't do cur++ and parse
365 // the character again. This can happen f.e. for char 'X' in sequence
366 // "\u00X". This character can be control character and must be
367 // processed again.
368 continue;
370 break;
372 case eParserState_Value:
373 if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
374 cur++;
376 // See few lines above for reason of doing this
377 continue;
379 case eParserState_Comment:
380 // stay in this state till we hit EOL
381 if (c == '\r' || c == '\n') {
382 WaitForKey();
384 break;
387 // finally, advance to the next character
388 cur++;
391 // if we're still parsing the value and are in eParserSpecial_None, then
392 // append whatever we have..
393 if (mState == eParserState_Value && tokenStart &&
394 mSpecialState == eParserSpecial_None) {
395 mValue += Substring(tokenStart, cur);
397 // if we're still parsing the key, then append whatever we have..
398 else if (mState == eParserState_Key && tokenStart) {
399 mKey += Substring(tokenStart, cur);
402 return NS_OK;
405 nsPersistentProperties::nsPersistentProperties()
406 : mIn(nullptr),
407 mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16),
408 mArena() {}
410 nsPersistentProperties::~nsPersistentProperties() = default;
412 size_t nsPersistentProperties::SizeOfIncludingThis(
413 mozilla::MallocSizeOf aMallocSizeOf) const {
414 // The memory used by mTable is accounted for in mArena.
415 size_t n = 0;
416 n += mArena.SizeOfExcludingThis(aMallocSizeOf);
417 n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
418 return aMallocSizeOf(this) + n;
421 NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties,
422 nsIProperties)
424 NS_IMETHODIMP
425 nsPersistentProperties::Load(nsIInputStream* aIn) {
426 nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn));
428 if (rv != NS_OK) {
429 NS_WARNING("Error creating UnicharInputStream");
430 return NS_ERROR_FAILURE;
433 nsPropertiesParser parser(this);
435 uint32_t nProcessed;
436 // If this 4096 is changed to some other value, make sure to adjust
437 // the bug121341.properties test file accordingly.
438 while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter,
439 &parser, 4096, &nProcessed)) &&
440 nProcessed != 0)
442 mIn = nullptr;
443 if (NS_FAILED(rv)) {
444 return rv;
447 // We may have an unprocessed value at this point
448 // if the last line did not have a proper line ending.
449 if (parser.GetState() == eParserState_Value) {
450 nsAutoString oldValue;
451 parser.FinishValueState(oldValue);
454 return NS_OK;
457 NS_IMETHODIMP
458 nsPersistentProperties::SetStringProperty(const nsACString& aKey,
459 const nsAString& aNewValue,
460 nsAString& aOldValue) {
461 const nsCString& flatKey = PromiseFlatCString(aKey);
462 auto entry = static_cast<PropertyTableEntry*>(mTable.Add(flatKey.get()));
464 if (entry->mKey) {
465 aOldValue = entry->mValue;
466 NS_WARNING(
467 nsPrintfCString("the property %s already exists", flatKey.get()).get());
468 } else {
469 aOldValue.Truncate();
472 entry->mKey = ArenaStrdup(flatKey, mArena);
473 entry->mValue = ArenaStrdup(aNewValue, mArena);
475 return NS_OK;
478 NS_IMETHODIMP
479 nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) {
480 return NS_ERROR_NOT_IMPLEMENTED;
483 NS_IMETHODIMP
484 nsPersistentProperties::GetStringProperty(const nsACString& aKey,
485 nsAString& aValue) {
486 const nsCString& flatKey = PromiseFlatCString(aKey);
488 auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get()));
489 if (!entry) {
490 return NS_ERROR_FAILURE;
493 aValue = entry->mValue;
494 return NS_OK;
497 NS_IMETHODIMP
498 nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) {
499 nsCOMArray<nsIPropertyElement> props;
501 // We know the necessary size; we can avoid growing it while adding elements
502 props.SetCapacity(mTable.EntryCount());
504 // Step through hash entries populating a transient array
505 for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
506 auto entry = static_cast<PropertyTableEntry*>(iter.Get());
508 RefPtr<nsPropertyElement> element = new nsPropertyElement(
509 nsDependentCString(entry->mKey), nsDependentString(entry->mValue));
511 if (!props.AppendObject(element)) {
512 return NS_ERROR_OUT_OF_MEMORY;
516 return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement));
519 ////////////////////////////////////////////////////////////////////////////////
520 // XXX Some day we'll unify the nsIPersistentProperties interface with
521 // nsIProperties, but until now...
523 NS_IMETHODIMP
524 nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID,
525 void** aResult) {
526 return NS_ERROR_NOT_IMPLEMENTED;
529 NS_IMETHODIMP
530 nsPersistentProperties::Set(const char* aProp, nsISupports* value) {
531 return NS_ERROR_NOT_IMPLEMENTED;
533 NS_IMETHODIMP
534 nsPersistentProperties::Undefine(const char* aProp) {
535 return NS_ERROR_NOT_IMPLEMENTED;
538 NS_IMETHODIMP
539 nsPersistentProperties::Has(const char* aProp, bool* aResult) {
540 *aResult = !!mTable.Search(aProp);
541 return NS_OK;
544 NS_IMETHODIMP
545 nsPersistentProperties::GetKeys(nsTArray<nsCString>& aKeys) {
546 return NS_ERROR_NOT_IMPLEMENTED;
549 ////////////////////////////////////////////////////////////////////////////////
550 // PropertyElement
551 ////////////////////////////////////////////////////////////////////////////////
553 nsresult nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID,
554 void** aResult) {
555 if (aOuter) {
556 return NS_ERROR_NO_AGGREGATION;
558 RefPtr<nsPropertyElement> propElem = new nsPropertyElement();
559 return propElem->QueryInterface(aIID, aResult);
562 NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
564 NS_IMETHODIMP
565 nsPropertyElement::GetKey(nsACString& aReturnKey) {
566 aReturnKey = mKey;
567 return NS_OK;
570 NS_IMETHODIMP
571 nsPropertyElement::GetValue(nsAString& aReturnValue) {
572 aReturnValue = mValue;
573 return NS_OK;
576 NS_IMETHODIMP
577 nsPropertyElement::SetKey(const nsACString& aKey) {
578 mKey = aKey;
579 return NS_OK;
582 NS_IMETHODIMP
583 nsPropertyElement::SetValue(const nsAString& aValue) {
584 mValue = aValue;
585 return NS_OK;
588 ////////////////////////////////////////////////////////////////////////////////