Source code

Revision control

Copy as Markdown

Other Tools

diff --git a/src/hashmgr.cxx b/src/hashmgr.cxx
--- a/src/hashmgr.cxx
+++ b/src/hashmgr.cxx
@@ -63,16 +63,17 @@
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
+#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <limits>
#include <sstream>
#include "hashmgr.hxx"
@@ -118,52 +119,54 @@ HashMgr::~HashMgr() {
// go through column by column of the table
for (int i = 0; i < tablesize; i++) {
struct hentry* pt = tableptr[i];
struct hentry* nt = NULL;
while (pt) {
nt = pt->next;
if (pt->astr &&
(!aliasf || TESTAFF(pt->astr, ONLYUPCASEFLAG, pt->alen)))
- free(pt->astr);
- free(pt);
+ arena_free(pt->astr);
+ arena_free(pt);
pt = nt;
}
}
free(tableptr);
}
tablesize = 0;
if (aliasf) {
for (int j = 0; j < (numaliasf); j++)
- free(aliasf[j]);
- free(aliasf);
+ arena_free(aliasf[j]);
+ arena_free(aliasf);
aliasf = NULL;
if (aliasflen) {
- free(aliasflen);
+ arena_free(aliasflen);
aliasflen = NULL;
}
}
if (aliasm) {
for (int j = 0; j < (numaliasm); j++)
- free(aliasm[j]);
- free(aliasm);
+ arena_free(aliasm[j]);
+ arena_free(aliasm);
aliasm = NULL;
}
#ifndef OPENOFFICEORG
#ifndef MOZILLA_CLIENT
if (utf8)
free_utf_tbl();
#endif
#endif
#ifdef MOZILLA_CLIENT
delete[] csconv;
#endif
+
+ assert(outstanding_arena_allocations == 0);
}
// lookup a root word in the hashtable
struct hentry* HashMgr::lookup(const char* word) const {
struct hentry* dp;
if (tableptr) {
dp = tableptr[hash(word)];
@@ -222,17 +225,17 @@ int HashMgr::add_word(const std::string&
word = word_copy;
}
bool upcasehomonym = false;
int descl = desc ? (aliasm ? sizeof(char*) : desc->size() + 1) : 0;
// variable-length hash record with word and optional fields
struct hentry* hp =
- (struct hentry*)malloc(sizeof(struct hentry) + word->size() + descl);
+ (struct hentry*)arena_alloc(sizeof(struct hentry) + word->size() + descl);
if (!hp) {
delete desc_copy;
delete word_copy;
return 1;
}
char* hpw = hp->word;
strcpy(hpw, word->c_str());
@@ -366,57 +369,57 @@ int HashMgr::add_word(const std::string&
delete word_copy;
return 0;
}
while (dp->next != NULL) {
if ((!dp->next_homonym) && (strcmp(hp->word, dp->word) == 0)) {
// remove hidden onlyupcase homonym
if (!onlyupcase) {
if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) {
- free(dp->astr);
+ arena_free(dp->astr);
dp->astr = hp->astr;
dp->alen = hp->alen;
- free(hp);
+ arena_free(hp);
delete desc_copy;
delete word_copy;
return 0;
} else {
dp->next_homonym = hp;
}
} else {
upcasehomonym = true;
}
}
dp = dp->next;
}
if (strcmp(hp->word, dp->word) == 0) {
// remove hidden onlyupcase homonym
if (!onlyupcase) {
if ((dp->astr) && TESTAFF(dp->astr, ONLYUPCASEFLAG, dp->alen)) {
- free(dp->astr);
+ arena_free(dp->astr);
dp->astr = hp->astr;
dp->alen = hp->alen;
- free(hp);
+ arena_free(hp);
delete desc_copy;
delete word_copy;
return 0;
} else {
dp->next_homonym = hp;
}
} else {
upcasehomonym = true;
}
}
if (!upcasehomonym) {
dp->next = hp;
} else {
// remove hidden onlyupcase homonym
if (hp->astr)
- free(hp->astr);
- free(hp);
+ arena_free(hp->astr);
+ arena_free(hp);
}
delete desc_copy;
delete word_copy;
return 0;
}
int HashMgr::add_hidden_capitalized_word(const std::string& word,
@@ -430,17 +433,17 @@ int HashMgr::add_hidden_capitalized_word
// add inner capitalized forms to handle the following allcap forms:
// Mixed caps: OpenOffice.org -> OPENOFFICE.ORG
// Allcaps with suffixes: CIA's -> CIA'S
if (((captype == HUHCAP) || (captype == HUHINITCAP) ||
((captype == ALLCAP) && (flagslen != 0))) &&
!((flagslen != 0) && TESTAFF(flags, forbiddenword, flagslen))) {
unsigned short* flags2 =
- (unsigned short*)malloc(sizeof(unsigned short) * (flagslen + 1));
+ (unsigned short*)arena_alloc(sizeof(unsigned short) * (flagslen + 1));
if (!flags2)
return 1;
if (flagslen)
memcpy(flags2, flags, flagslen * sizeof(unsigned short));
flags2[flagslen] = ONLYUPCASEFLAG;
if (utf8) {
std::string st;
std::vector<w_char> w;
@@ -479,23 +482,23 @@ int HashMgr::get_clen_and_captype(const
}
// remove word (personal dictionary function for standalone applications)
int HashMgr::remove(const std::string& word) {
struct hentry* dp = lookup(word.c_str());
while (dp) {
if (dp->alen == 0 || !TESTAFF(dp->astr, forbiddenword, dp->alen)) {
unsigned short* flags =
- (unsigned short*)malloc(sizeof(unsigned short) * (dp->alen + 1));
+ (unsigned short*)arena_alloc(sizeof(unsigned short) * (dp->alen + 1));
if (!flags)
return 1;
for (int i = 0; i < dp->alen; i++)
flags[i] = dp->astr[i];
flags[dp->alen] = forbiddenword;
- free(dp->astr);
+ arena_free(dp->astr);
dp->astr = flags;
dp->alen++;
std::sort(flags, flags + dp->alen);
}
dp = dp->next_homonym;
}
return 0;
}
@@ -533,17 +536,17 @@ int HashMgr::add_with_affix(const std::s
remove_forbidden_flag(word);
if (dp && dp->astr) {
int captype;
int wcl = get_clen_and_captype(word, &captype);
if (aliasf) {
add_word(word, wcl, dp->astr, dp->alen, NULL, false, captype);
} else {
unsigned short* flags =
- (unsigned short*)malloc(dp->alen * sizeof(unsigned short));
+ (unsigned short*) arena_alloc(dp->alen * sizeof(unsigned short));
if (flags) {
memcpy((void*)flags, (void*)dp->astr,
dp->alen * sizeof(unsigned short));
add_word(word, wcl, flags, dp->alen, NULL, false, captype);
} else
return 1;
}
return add_hidden_capitalized_word(word, wcl, dp->astr,
@@ -668,17 +671,17 @@ int HashMgr::load_tables(const char* tpa
if (aliasf) {
int index = atoi(ap.c_str());
al = get_aliasf(index, &flags, dict);
if (!al) {
HUNSPELL_WARNING(stderr, "error: line %d: bad flag vector alias\n",
dict->getlinenum());
}
} else {
- al = decode_flags(&flags, ap.c_str(), dict);
+ al = decode_flags(&flags, ap.c_str(), dict, /* arena = */ true);
if (al == -1) {
HUNSPELL_WARNING(stderr, "Can't allocate memory.\n");
delete dict;
return 6;
}
std::sort(flags, flags + al);
}
} else {
@@ -709,47 +712,48 @@ int HashMgr::hash(const char* word) cons
hv = (hv << 8) | (*word++);
while (*word != 0) {
ROTATE(hv, ROTATE_LEN);
hv ^= (*word++);
}
return (unsigned long)hv % tablesize;
}
-int HashMgr::decode_flags(unsigned short** result, const std::string& flags, FileMgr* af) const {
+int HashMgr::decode_flags(unsigned short** result, const std::string& flags, FileMgr* af, bool arena) const {
+ auto alloc = [arena, this](int n) { return arena ? this->arena_alloc(n) : malloc(n); };
int len;
if (flags.empty()) {
*result = NULL;
return 0;
}
switch (flag_mode) {
case FLAG_LONG: { // two-character flags (1x2yZz -> 1x 2y Zz)
len = flags.size();
if (len % 2 == 1)
HUNSPELL_WARNING(stderr, "error: line %d: bad flagvector\n",
af->getlinenum());
len /= 2;
- *result = (unsigned short*)malloc(len * sizeof(unsigned short));
+ *result = (unsigned short*)alloc(len * sizeof(unsigned short));
if (!*result)
return -1;
for (int i = 0; i < len; i++) {
(*result)[i] = ((unsigned short)((unsigned char)flags[i * 2]) << 8) +
(unsigned char)flags[i * 2 + 1];
}
break;
}
case FLAG_NUM: { // decimal numbers separated by comma (4521,23,233 -> 4521
// 23 233)
len = 1;
unsigned short* dest;
for (size_t i = 0; i < flags.size(); ++i) {
if (flags[i] == ',')
len++;
}
- *result = (unsigned short*)malloc(len * sizeof(unsigned short));
+ *result = (unsigned short*)alloc(len * sizeof(unsigned short));
if (!*result)
return -1;
dest = *result;
const char* src = flags.c_str();
for (const char* p = src; *p; p++) {
if (*p == ',') {
int i = atoi(src);
if (i >= DEFAULTFLAGS)
@@ -774,26 +778,26 @@ int HashMgr::decode_flags(unsigned short
HUNSPELL_WARNING(stderr, "error: line %d: 0 is wrong flag id\n",
af->getlinenum());
break;
}
case FLAG_UNI: { // UTF-8 characters
std::vector<w_char> w;
u8_u16(w, flags);
len = w.size();
- *result = (unsigned short*)malloc(len * sizeof(unsigned short));
+ *result = (unsigned short*)alloc(len * sizeof(unsigned short));
if (!*result)
return -1;
memcpy(*result, w.data(), len * sizeof(short));
break;
}
default: { // Ispell's one-character flags (erfg -> e r f g)
unsigned short* dest;
len = flags.size();
- *result = (unsigned short*)malloc(len * sizeof(unsigned short));
+ *result = (unsigned short*)alloc(len * sizeof(unsigned short));
if (!*result)
return -1;
dest = *result;
for (size_t i = 0; i < flags.size(); ++i) {
*dest = (unsigned char)flags[i];
dest++;
}
}
@@ -890,16 +894,18 @@ unsigned short HashMgr::decode_flag(cons
default:
s = *(unsigned char*)f;
}
if (s == 0)
HUNSPELL_WARNING(stderr, "error: 0 is wrong flag id\n");
return s;
}
+// This function is only called by external consumers, and so using the default
+// allocator with mystrdup is correct.
char* HashMgr::encode_flag(unsigned short f) const {
if (f == 0)
return mystrdup("(NULL)");
std::string ch;
if (flag_mode == FLAG_LONG) {
ch.push_back((unsigned char)(f >> 8));
ch.push_back((unsigned char)(f - ((f >> 8) << 8)));
} else if (flag_mode == FLAG_NUM) {
@@ -1070,42 +1076,42 @@ bool HashMgr::parse_aliasf(const std::st
numaliasf = 0;
aliasf = NULL;
aliasflen = NULL;
HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n",
af->getlinenum());
return false;
}
aliasf =
- (unsigned short**)malloc(numaliasf * sizeof(unsigned short*));
+ (unsigned short**)arena_alloc(numaliasf * sizeof(unsigned short*));
aliasflen =
- (unsigned short*)malloc(numaliasf * sizeof(unsigned short));
+ (unsigned short*)arena_alloc(numaliasf * sizeof(unsigned short));
if (!aliasf || !aliasflen) {
numaliasf = 0;
if (aliasf)
- free(aliasf);
+ arena_free(aliasf);
if (aliasflen)
- free(aliasflen);
+ arena_free(aliasflen);
aliasf = NULL;
aliasflen = NULL;
return false;
}
np++;
break;
}
default:
break;
}
++i;
start_piece = mystrsep(line, iter);
}
if (np != 2) {
numaliasf = 0;
- free(aliasf);
- free(aliasflen);
+ arena_free(aliasf);
+ arena_free(aliasflen);
aliasf = NULL;
aliasflen = NULL;
HUNSPELL_WARNING(stderr, "error: line %d: missing data\n",
af->getlinenum());
return false;
}
/* now parse the numaliasf lines to read in the remainder of the table */
@@ -1126,33 +1132,33 @@ bool HashMgr::parse_aliasf(const std::st
errored = true;
break;
}
break;
}
case 1: {
std::string piece(start_piece, iter);
aliasflen[j] =
- (unsigned short)decode_flags(&(aliasf[j]), piece, af);
+ (unsigned short)decode_flags(&(aliasf[j]), piece, af, /* arena = */ true);
std::sort(aliasf[j], aliasf[j] + aliasflen[j]);
break;
}
default:
break;
}
++i;
start_piece = mystrsep(nl, iter);
}
}
if (!aliasf[j]) {
for (int k = 0; k < j; ++k) {
- free(aliasf[k]);
+ arena_free(aliasf[k]);
}
- free(aliasf);
- free(aliasflen);
+ arena_free(aliasf);
+ arena_free(aliasflen);
aliasf = NULL;
aliasflen = NULL;
numaliasf = 0;
HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n",
af->getlinenum());
return false;
}
}
@@ -1193,33 +1199,33 @@ bool HashMgr::parse_aliasm(const std::st
}
case 1: {
numaliasm = atoi(std::string(start_piece, iter).c_str());
if (numaliasm < 1) {
HUNSPELL_WARNING(stderr, "error: line %d: bad entry number\n",
af->getlinenum());
return false;
}
- aliasm = (char**)malloc(numaliasm * sizeof(char*));
+ aliasm = (char**)arena_alloc(numaliasm * sizeof(char*));
if (!aliasm) {
numaliasm = 0;
return false;
}
np++;
break;
}
default:
break;
}
++i;
start_piece = mystrsep(line, iter);
}
if (np != 2) {
numaliasm = 0;
- free(aliasm);
+ arena_free(aliasm);
aliasm = NULL;
HUNSPELL_WARNING(stderr, "error: line %d: missing data\n",
af->getlinenum());
return false;
}
/* now parse the numaliasm lines to read in the remainder of the table */
for (int j = 0; j < numaliasm; j++) {
@@ -1245,32 +1251,36 @@ bool HashMgr::parse_aliasm(const std::st
std::string::const_iterator end = nl.end();
std::string chunk(start_piece, end);
if (complexprefixes) {
if (utf8)
reverseword_utf(chunk);
else
reverseword(chunk);
}
- aliasm[j] = mystrdup(chunk.c_str());
+ size_t sl = chunk.length() + 1;
+ aliasm[j] = (char*)arena_alloc(sl);
+ if (aliasm[j]) {
+ memcpy(aliasm[j], chunk.c_str(), sl);
+ }
break;
}
default:
break;
}
++i;
start_piece = mystrsep(nl, iter);
}
}
if (!aliasm[j]) {
numaliasm = 0;
for (int k = 0; k < j; ++k) {
- free(aliasm[k]);
+ arena_free(aliasm[k]);
}
- free(aliasm);
+ arena_free(aliasm);
aliasm = NULL;
HUNSPELL_WARNING(stderr, "error: line %d: table is corrupt\n",
af->getlinenum());
return false;
}
}
return true;
}
@@ -1379,8 +1389,27 @@ bool HashMgr::parse_reptable(const std::
}
return true;
}
// return replacing table
const std::vector<replentry>& HashMgr::get_reptable() const {
return reptable;
}
+
+void* HashMgr::arena_alloc(int num_bytes) {
+ static const int MIN_CHUNK_SIZE = 4096;
+ if (arena.empty() || (current_chunk_size - current_chunk_offset < num_bytes)) {
+ current_chunk_size = std::max(MIN_CHUNK_SIZE, num_bytes);
+ arena.push_back(std::make_unique<uint8_t[]>(current_chunk_size));
+ current_chunk_offset = 0;
+ }
+
+ uint8_t* ptr = &arena.back()[current_chunk_offset];
+ current_chunk_offset += num_bytes;
+ outstanding_arena_allocations++;
+ return ptr;
+}
+
+void HashMgr::arena_free(void* ptr) {
+ --outstanding_arena_allocations;
+ assert(outstanding_arena_allocations >= 0);
+}
diff --git a/src/hashmgr.hxx b/src/hashmgr.hxx
--- a/src/hashmgr.hxx
+++ b/src/hashmgr.hxx
@@ -67,16 +67,18 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef HASHMGR_HXX_
#define HASHMGR_HXX_
#include <stdio.h>
+#include <stdint.h>
+#include <memory>
#include <string>
#include <vector>
#include "htypes.hxx"
#include "filemgr.hxx"
#include "w_char.hxx"
enum flag { FLAG_CHAR, FLAG_LONG, FLAG_NUM, FLAG_UNI };
@@ -116,17 +118,23 @@ class HashMgr {
struct hentry* lookup(const char*) const;
int hash(const char*) const;
struct hentry* walk_hashtable(int& col, struct hentry* hp) const;
int add(const std::string& word);
int add_with_affix(const std::string& word, const std::string& pattern);
int remove(const std::string& word);
- int decode_flags(unsigned short** result, const std::string& flags, FileMgr* af) const;
+private:
+ // Only internal consumers are allowed to arena-allocate.
+ int decode_flags(unsigned short** result, const std::string& flags, FileMgr* af, bool arena) const;
+public:
+ int decode_flags(unsigned short** result, const std::string& flags, FileMgr* af) const {
+ return decode_flags(result, flags, af, /* arena = */ false);
+ }
bool decode_flags(std::vector<unsigned short>& result, const std::string& flags, FileMgr* af) const;
unsigned short decode_flag(const char* flag) const;
char* encode_flag(unsigned short flag) const;
int is_aliasf() const;
int get_aliasf(int index, unsigned short** fvec, FileMgr* af) const;
int is_aliasm() const;
char* get_aliasm(int index) const;
const std::vector<replentry>& get_reptable() const;
@@ -148,11 +156,27 @@ class HashMgr {
int wcl,
unsigned short* flags,
int al,
const std::string* dp,
int captype);
bool parse_aliasm(const std::string& line, FileMgr* af);
bool parse_reptable(const std::string& line, FileMgr* af);
int remove_forbidden_flag(const std::string& word);
+
+ // Our Mozilla fork uses a simple arena allocator for certain strings which
+ // persist for the lifetime of the HashMgr in order to avoid heap fragmentation.
+ // It's a simple bump-allocator, so we can't actually free() memory midway
+ // through the lifecycle, but we have a dummy free() implementation to ensure
+ // that our calls to arena_alloc() and arena_free() are balanced.
+ void* arena_alloc(int num_bytes);
+ void* arena_alloc(int num_bytes) const {
+ return const_cast<HashMgr*>(this)->arena_alloc(num_bytes);
+ }
+ void arena_free(void* ptr);
+
+ std::vector<std::unique_ptr<uint8_t[]>> arena;
+ int current_chunk_size = 0;
+ int current_chunk_offset = 0;
+ int outstanding_arena_allocations = 0;
};
#endif