/* +----------------------------------------------------------------------+ | Zend Engine | +----------------------------------------------------------------------+ | Copyright (c) 1998-2014 Zend Technologies Ltd. (http://www.zend.com) | +----------------------------------------------------------------------+ | This source file is subject to version 2.00 of the Zend license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.zend.com/license/2_00.txt. | | If you did not receive a copy of the Zend license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@zend.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Andi Gutmans | | Zeev Suraski | +----------------------------------------------------------------------+ */ /* $Id$ */ #include "zend.h" #include "zend_globals.h" #if ZEND_DEBUG /* #define HASH_MASK_CONSISTENCY 0x60 */ #define HT_OK 0x00 #define HT_IS_DESTROYING 0x20 #define HT_DESTROYED 0x40 #define HT_CLEANING 0x60 static void _zend_is_inconsistent(const HashTable *ht, const char *file, int line) { if ((ht->flags & HASH_MASK_CONSISTENCY) == HT_OK) { return; } switch ((ht->flags & HASH_MASK_CONSISTENCY)) { case HT_IS_DESTROYING: zend_output_debug_string(1, "%s(%d) : ht=%p is being destroyed", file, line, ht); break; case HT_DESTROYED: zend_output_debug_string(1, "%s(%d) : ht=%p is already destroyed", file, line, ht); break; case HT_CLEANING: zend_output_debug_string(1, "%s(%d) : ht=%p is being cleaned", file, line, ht); break; default: zend_output_debug_string(1, "%s(%d) : ht=%p is inconsistent", file, line, ht); break; } zend_bailout(); } #define IS_CONSISTENT(a) _zend_is_inconsistent(a, __FILE__, __LINE__); #define SET_INCONSISTENT(n) do { \ (ht)->flags |= n; \ } while (0) #else #define IS_CONSISTENT(a) #define SET_INCONSISTENT(n) #endif #define HASH_PROTECT_RECURSION(ht) \ if ((ht)->flags & HASH_FLAG_APPLY_PROTECTION) { \ if ((ht)->nApplyCount++ >= 3) { \ zend_error(E_ERROR, "Nesting level too deep - recursive dependency?"); \ } \ } #define HASH_UNPROTECT_RECURSION(ht) \ if ((ht)->flags & HASH_FLAG_APPLY_PROTECTION) { \ (ht)->nApplyCount--; \ } #define ZEND_HASH_IF_FULL_DO_RESIZE(ht) \ if ((ht)->nNumUsed >= (ht)->nTableSize) { \ zend_hash_do_resize(ht); \ } static void zend_hash_do_resize(HashTable *ht); #define CHECK_INIT(ht, packed) do { \ if (UNEXPECTED((ht)->nTableMask == 0)) { \ (ht)->arData = (Bucket *) pecalloc((ht)->nTableSize, sizeof(Bucket), (ht)->flags & HASH_FLAG_PERSISTENT); \ if (packed) { \ (ht)->flags |= HASH_FLAG_PACKED; \ } else { \ (ht)->arHash = (zend_uint*) pecalloc((ht)->nTableSize, sizeof(zend_uint), (ht)->flags & HASH_FLAG_PERSISTENT); \ memset((ht)->arHash, INVALID_IDX, (ht)->nTableSize * sizeof(zend_uint)); \ } \ (ht)->nTableMask = (ht)->nTableSize - 1; \ } \ } while (0) static const zend_uint uninitialized_bucket = {INVALID_IDX}; ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) { uint i = 3; SET_INCONSISTENT(HT_OK); if (nSize >= 0x80000000) { /* prevent overflow */ ht->nTableSize = 0x80000000; } else { while ((1U << i) < nSize) { i++; } ht->nTableSize = 1 << i; } ht->nTableMask = 0; /* 0 means that ht->arBuckets is uninitialized */ ht->pDestructor = pDestructor; ht->arData = NULL; ht->arHash = (zend_uint*)&uninitialized_bucket; ht->nNumUsed = 0; ht->nNumOfElements = 0; ht->nNextFreeElement = 0; ht->nInternalPointer = INVALID_IDX; ht->flags = HASH_FLAG_APPLY_PROTECTION; if (persistent) { ht->flags |= HASH_FLAG_PERSISTENT; } ht->nApplyCount = 0; return SUCCESS; } static void zend_hash_packed_grow(HashTable *ht) { HANDLE_BLOCK_INTERRUPTIONS(); ht->arData = (Bucket *) perealloc(ht->arData, (ht->nTableSize << 1) * sizeof(Bucket), ht->flags & HASH_FLAG_PERSISTENT); memset(ht->arData + ht->nTableSize, 0, sizeof(Bucket) * ht->nTableSize); ht->nTableSize = (ht->nTableSize << 1); ht->nTableMask = ht->nTableSize - 1; HANDLE_UNBLOCK_INTERRUPTIONS(); } ZEND_API void zend_hash_packed_to_hash(HashTable *ht) { HANDLE_BLOCK_INTERRUPTIONS(); ht->flags &= ~HASH_FLAG_PACKED; ht->arHash = (zend_uint*) pecalloc(ht->nTableSize, sizeof(zend_uint), ht->flags & HASH_FLAG_PERSISTENT); zend_hash_rehash(ht); HANDLE_UNBLOCK_INTERRUPTIONS(); } ZEND_API void zend_hash_to_packed(HashTable *ht) { HANDLE_BLOCK_INTERRUPTIONS(); ht->flags |= HASH_FLAG_PACKED; pefree(ht->arHash, ht->flags & HASH_FLAG_PERSISTENT); ht->arHash = (zend_uint*)&uninitialized_bucket; HANDLE_UNBLOCK_INTERRUPTIONS(); } ZEND_API int _zend_hash_init_ex(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent, zend_bool bApplyProtection ZEND_FILE_LINE_DC) { int retval = _zend_hash_init(ht, nSize, pDestructor, persistent ZEND_FILE_LINE_CC); if (!bApplyProtection) { ht->flags &= ~HASH_FLAG_APPLY_PROTECTION; } return retval; } ZEND_API void zend_hash_set_apply_protection(HashTable *ht, zend_bool bApplyProtection) { if (bApplyProtection) { ht->flags |= HASH_FLAG_APPLY_PROTECTION; } else { ht->flags &= ~HASH_FLAG_APPLY_PROTECTION; } } static zend_always_inline Bucket *zend_hash_find_bucket(const HashTable *ht, zend_string *key) { ulong h; uint nIndex; uint idx; Bucket *p; h = STR_HASH_VAL(key); nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if ((p->key == key) || /* check for the the same interned string */ (p->h == h && p->key && p->key->len == key->len && memcmp(p->key->val, key->val, key->len) == 0)) { return p; } idx = p->val.u.next; } return NULL; } static zend_always_inline Bucket *zend_hash_str_find_bucket(const HashTable *ht, const char *str, int len, ulong h) { uint nIndex; uint idx; Bucket *p; nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if ((p->h == h) && p->key && (p->key->len == len) && !memcmp(p->key->val, str, len)) { return p; } idx = p->val.u.next; } return NULL; } static zend_always_inline Bucket *zend_hash_index_find_bucket(const HashTable *ht, ulong h) { uint nIndex; uint idx; Bucket *p; nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if (p->h == h && !p->key) { return p; } idx = p->val.u.next; } return NULL; } ZEND_API zval *_zend_hash_add_or_update(HashTable *ht, zend_string *key, zval *pData, int flag ZEND_FILE_LINE_DC) { ulong h; uint nIndex; uint idx; Bucket *p; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); CHECK_INIT(ht, 0); if (ht->flags & HASH_FLAG_PACKED) { zend_hash_packed_to_hash(ht); } h = STR_HASH_VAL(key); p = zend_hash_find_bucket(ht, key); if (p) { if (flag & HASH_ADD) { return NULL; } ZEND_ASSERT(&p->val != pData); HANDLE_BLOCK_INTERRUPTIONS(); if (ht->pDestructor) { ht->pDestructor(&p->val); } ZVAL_COPY_VALUE(&p->val, pData); HANDLE_UNBLOCK_INTERRUPTIONS(); return &p->val; } ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */ HANDLE_BLOCK_INTERRUPTIONS(); idx = ht->nNumUsed++; ht->nNumOfElements++; if (ht->nInternalPointer == INVALID_IDX) { ht->nInternalPointer = idx; } p = ht->arData + idx; p->h = h; p->key = key; STR_ADDREF(key); ZVAL_COPY_VALUE(&p->val, pData); nIndex = h & ht->nTableMask; p->val.u.next = ht->arHash[nIndex]; ht->arHash[nIndex] = idx; HANDLE_UNBLOCK_INTERRUPTIONS(); return &p->val; } ZEND_API zval *_zend_hash_str_add_or_update(HashTable *ht, const char *str, int len, zval *pData, int flag ZEND_FILE_LINE_DC) { zend_string *key = STR_INIT(str, len, ht->flags & HASH_FLAG_PERSISTENT); zval *ret = _zend_hash_add_or_update(ht, key, pData, flag ZEND_FILE_LINE_CC); STR_RELEASE(key); return ret; } ZEND_API zval *zend_hash_add_empty_element(HashTable *ht, zend_string *key) { zval dummy; ZVAL_NULL(&dummy); return zend_hash_add(ht, key, &dummy); } ZEND_API zval *zend_hash_str_add_empty_element(HashTable *ht, const char *str, int len) { zval dummy; ZVAL_NULL(&dummy); return zend_hash_str_add(ht, str, len, &dummy); } ZEND_API zval *_zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, zval *pData, int flag ZEND_FILE_LINE_DC) { uint nIndex; uint idx; Bucket *p; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); if (flag & HASH_NEXT_INSERT) { h = ht->nNextFreeElement; } CHECK_INIT(ht, h >= 0 && h < ht->nTableSize); if (ht->flags & HASH_FLAG_PACKED) { if (h >= 0 && h < ht->nTableSize) { p = ht->arData + h; if (Z_TYPE(p->val) != IS_UNDEF) { if (ht->pDestructor) { ht->pDestructor(&p->val); } ZVAL_COPY_VALUE(&p->val, pData); HANDLE_UNBLOCK_INTERRUPTIONS(); if ((long)h >= (long)ht->nNextFreeElement) { ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX; } return &p->val; } if (h >= ht->nNumUsed) { /* we have to keep the order :( */ goto new_packed; } else { goto convert_to_hash; } } else if (h >= ht->nNumUsed && /* we have to keep the order :( */ h < ht->nTableSize * 2 && ht->nTableSize - ht->nNumOfElements < ht->nTableSize / 2) { zend_hash_packed_grow(ht); new_packed: HANDLE_BLOCK_INTERRUPTIONS(); if (h >= ht->nNumUsed) { ht->nNumUsed = h + 1; } ht->nNumOfElements++; if (ht->nInternalPointer == INVALID_IDX) { ht->nInternalPointer = h; } if ((long)h >= (long)ht->nNextFreeElement) { ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX; } p = ht->arData + h; p->h = h; p->key = NULL; ZVAL_COPY_VALUE(&p->val, pData); p->val.u.next = INVALID_IDX; HANDLE_UNBLOCK_INTERRUPTIONS(); return &p->val; } else { convert_to_hash: zend_hash_packed_to_hash(ht); } } p = zend_hash_index_find_bucket(ht, h); if (p) { if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) { return NULL; } ZEND_ASSERT(&p->val != pData); HANDLE_BLOCK_INTERRUPTIONS(); if (ht->pDestructor) { ht->pDestructor(&p->val); } ZVAL_COPY_VALUE(&p->val, pData); HANDLE_UNBLOCK_INTERRUPTIONS(); if ((long)h >= (long)ht->nNextFreeElement) { ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX; } return &p->val; } ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */ HANDLE_BLOCK_INTERRUPTIONS(); idx = ht->nNumUsed++; ht->nNumOfElements++; if (ht->nInternalPointer == INVALID_IDX) { ht->nInternalPointer = idx; } if ((long)h >= (long)ht->nNextFreeElement) { ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX; } p = ht->arData + idx; p->h = h; p->key = NULL; nIndex = h & ht->nTableMask; ZVAL_COPY_VALUE(&p->val, pData); p->val.u.next = ht->arHash[nIndex]; ht->arHash[nIndex] = idx; HANDLE_UNBLOCK_INTERRUPTIONS(); return &p->val; } static void zend_hash_do_resize(HashTable *ht) { #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); if (ht->nNumUsed < ht->nNumOfElements) { HANDLE_BLOCK_INTERRUPTIONS(); zend_hash_rehash(ht); HANDLE_UNBLOCK_INTERRUPTIONS(); } else if ((ht->nTableSize << 1) > 0) { /* Let's double the table size */ HANDLE_BLOCK_INTERRUPTIONS(); ht->arData = (Bucket *) perealloc(ht->arData, (ht->nTableSize << 1) * sizeof(Bucket), ht->flags & HASH_FLAG_PERSISTENT); ht->arHash = (zend_uint *) perealloc(ht->arHash, (ht->nTableSize << 1) * sizeof(zend_uint), ht->flags & HASH_FLAG_PERSISTENT); ht->nTableSize = (ht->nTableSize << 1); ht->nTableMask = ht->nTableSize - 1; zend_hash_rehash(ht); HANDLE_UNBLOCK_INTERRUPTIONS(); } } ZEND_API int zend_hash_rehash(HashTable *ht) { Bucket *p; uint nIndex, i, j; IS_CONSISTENT(ht); memset(ht->arHash, INVALID_IDX, ht->nTableSize * sizeof(zend_uint)); if (UNEXPECTED(ht->nNumOfElements == 0)) { return SUCCESS; } for (i = 0, j = 0; i < ht->nNumUsed; i++) { p = ht->arData + i; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (i != j) { ht->arData[j] = ht->arData[i]; if (ht->nInternalPointer == i) { ht->nInternalPointer = j; } } nIndex = ht->arData[j].h & ht->nTableMask; ht->arData[j].val.u.next = ht->arHash[nIndex]; ht->arHash[nIndex] = j; j++; } ht->nNumUsed = j; return SUCCESS; } static zend_always_inline void _zend_hash_del_el_ex(HashTable *ht, uint idx, Bucket *p, Bucket *prev) { if (!(ht->flags & HASH_FLAG_PACKED)) { if (prev) { prev->val.u.next = p->val.u.next; } else { ht->arHash[p->h & ht->nTableMask] = p->val.u.next; } } if (ht->nNumUsed - 1 == idx) { do { ht->nNumUsed--; } while (ht->nNumUsed > 0 && (Z_TYPE(ht->arData[ht->nNumUsed-1].val) == IS_UNDEF)); } ht->nNumOfElements--; if (ht->nInternalPointer == idx) { while (1) { idx++; if (idx >= ht->nNumUsed) { ht->nInternalPointer = INVALID_IDX; break; } else if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { ht->nInternalPointer = idx; break; } } } if (ht->pDestructor) { ht->pDestructor(&p->val); } if (p->key) { STR_RELEASE(p->key); } Z_TYPE(p->val) = IS_UNDEF; } static zend_always_inline void _zend_hash_del_el(HashTable *ht, uint idx, Bucket *p) { uint nIndex; Bucket *prev = NULL; if (!(ht->flags & HASH_FLAG_PACKED)) { nIndex = p->h & ht->nTableMask; idx = ht->arHash[nIndex]; if (p != ht->arData + idx) { prev = ht->arData + idx; while (ht->arData + prev->val.u.next != p) { idx = prev->val.u.next; prev = ht->arData + idx; } idx = prev->val.u.next; } } _zend_hash_del_el_ex(ht, idx, p, prev); } ZEND_API int zend_hash_del(HashTable *ht, zend_string *key) { ulong h; uint nIndex; uint idx; Bucket *p; Bucket *prev = NULL; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { return FAILURE; } h = STR_HASH_VAL(key); nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if ((p->key == key) || (p->h == h && p->key && p->key->len == key->len && memcmp(p->key->val, key->val, key->len) == 0)) { HANDLE_BLOCK_INTERRUPTIONS(); _zend_hash_del_el_ex(ht, idx, p, prev); HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } prev = p; idx = p->val.u.next; } return FAILURE; } ZEND_API int zend_hash_str_del(HashTable *ht, const char *str, int len) { ulong h; uint nIndex; uint idx; Bucket *p; Bucket *prev = NULL; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); h = zend_inline_hash_func(str, len + 1); nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if ((p->h == h) && p->key && (p->key->len == len) && !memcmp(p->key->val, str, len)) { HANDLE_BLOCK_INTERRUPTIONS(); _zend_hash_del_el_ex(ht, idx, p, prev); HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } prev = p; idx = p->val.u.next; } return FAILURE; } ZEND_API int zend_hash_index_del(HashTable *ht, ulong h) { uint nIndex; uint idx; Bucket *p; Bucket *prev = NULL; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { if (h >=0 && h < ht->nTableSize) { p = ht->arData + h; if (Z_TYPE(p->val) != IS_UNDEF) { HANDLE_BLOCK_INTERRUPTIONS(); _zend_hash_del_el_ex(ht, h, p, NULL); HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } } return FAILURE; } nIndex = h & ht->nTableMask; idx = ht->arHash[nIndex]; while (idx != INVALID_IDX) { p = ht->arData + idx; if ((p->h == h) && (p->key == NULL)) { HANDLE_BLOCK_INTERRUPTIONS(); _zend_hash_del_el_ex(ht, idx, p, prev); HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } prev = p; idx = p->val.u.next; } return FAILURE; } ZEND_API void zend_hash_destroy(HashTable *ht) { uint idx; Bucket *p; IS_CONSISTENT(ht); SET_INCONSISTENT(HT_IS_DESTROYING); for (idx = 0 ; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (ht->pDestructor) { ht->pDestructor(&p->val); } if (p->key) { STR_RELEASE(p->key); } } if (ht->nTableMask) { pefree(ht->arData, ht->flags & HASH_FLAG_PERSISTENT); if (!(ht->flags & HASH_FLAG_PACKED)) { pefree(ht->arHash, ht->flags & HASH_FLAG_PERSISTENT); } } SET_INCONSISTENT(HT_DESTROYED); } ZEND_API void zend_hash_clean(HashTable *ht) { uint idx; Bucket *p; IS_CONSISTENT(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (ht->pDestructor) { ht->pDestructor(&p->val); } if (p->key) { STR_RELEASE(p->key); } } ht->nNumUsed = 0; ht->nNumOfElements = 0; ht->nNextFreeElement = 0; ht->nInternalPointer = INVALID_IDX; if (ht->nTableMask) { memset(ht->arData, 0, ht->nTableSize*sizeof(Bucket)); if (!(ht->flags & HASH_FLAG_PACKED)) { memset(ht->arHash, INVALID_IDX, ht->nTableSize * sizeof(zend_uint)); } } } /* This function is used by the various apply() functions. * It deletes the passed bucket, and returns the address of the * next bucket. The hash *may* be altered during that time, the * returned value will still be valid. */ static void zend_hash_apply_deleter(HashTable *ht, uint idx, Bucket *p) { #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif HANDLE_BLOCK_INTERRUPTIONS(); _zend_hash_del_el(ht, idx, p); HANDLE_UNBLOCK_INTERRUPTIONS(); } ZEND_API void zend_hash_graceful_destroy(HashTable *ht) { uint idx; Bucket *p; IS_CONSISTENT(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; zend_hash_apply_deleter(ht, idx, p); } if (ht->nTableMask) { pefree(ht->arData, ht->flags & HASH_FLAG_PERSISTENT); if (!(ht->flags & HASH_FLAG_PACKED)) { pefree(ht->arHash, ht->flags & HASH_FLAG_PERSISTENT); } } SET_INCONSISTENT(HT_DESTROYED); } ZEND_API void zend_hash_graceful_reverse_destroy(HashTable *ht) { uint idx; Bucket *p; IS_CONSISTENT(ht); idx = ht->nNumUsed; while (idx > 0) { idx--; p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; zend_hash_apply_deleter(ht, idx, p); } if (ht->nTableMask) { pefree(ht->arData, ht->flags & HASH_FLAG_PERSISTENT); if (!(ht->flags & HASH_FLAG_PACKED)) { pefree(ht->arHash, ht->flags & HASH_FLAG_PERSISTENT); } } SET_INCONSISTENT(HT_DESTROYED); } /* This is used to recurse elements and selectively delete certain entries * from a hashtable. apply_func() receives the data and decides if the entry * should be deleted or recursion should be stopped. The following three * return codes are possible: * ZEND_HASH_APPLY_KEEP - continue * ZEND_HASH_APPLY_STOP - stop iteration * ZEND_HASH_APPLY_REMOVE - delete the element, combineable with the former */ ZEND_API void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC) { uint idx; Bucket *p; int result; IS_CONSISTENT(ht); HASH_PROTECT_RECURSION(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; result = apply_func(&p->val TSRMLS_CC); if (result & ZEND_HASH_APPLY_REMOVE) { zend_hash_apply_deleter(ht, idx, p); } if (result & ZEND_HASH_APPLY_STOP) { break; } } HASH_UNPROTECT_RECURSION(ht); } ZEND_API void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t apply_func, void *argument TSRMLS_DC) { uint idx; Bucket *p; int result; IS_CONSISTENT(ht); HASH_PROTECT_RECURSION(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; result = apply_func(&p->val, argument TSRMLS_CC); if (result & ZEND_HASH_APPLY_REMOVE) { zend_hash_apply_deleter(ht, idx, p); } if (result & ZEND_HASH_APPLY_STOP) { break; } } HASH_UNPROTECT_RECURSION(ht); } ZEND_API void zend_hash_apply_with_arguments(HashTable *ht TSRMLS_DC, apply_func_args_t apply_func, int num_args, ...) { uint idx; Bucket *p; va_list args; zend_hash_key hash_key; int result; IS_CONSISTENT(ht); HASH_PROTECT_RECURSION(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; va_start(args, num_args); hash_key.h = p->h; hash_key.key = p->key; result = apply_func(&p->val TSRMLS_CC, num_args, args, &hash_key); if (result & ZEND_HASH_APPLY_REMOVE) { zend_hash_apply_deleter(ht, idx, p); } if (result & ZEND_HASH_APPLY_STOP) { va_end(args); break; } va_end(args); } HASH_UNPROTECT_RECURSION(ht); } ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC) { uint idx; Bucket *p; int result; IS_CONSISTENT(ht); HASH_PROTECT_RECURSION(ht); idx = ht->nNumUsed; while (idx > 0) { idx--; p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; result = apply_func(&p->val TSRMLS_CC); if (result & ZEND_HASH_APPLY_REMOVE) { zend_hash_apply_deleter(ht, idx, p); } if (result & ZEND_HASH_APPLY_STOP) { break; } } HASH_UNPROTECT_RECURSION(ht); } ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor) { uint idx; Bucket *p; zval *new_entry; zend_bool setTargetPointer; IS_CONSISTENT(source); IS_CONSISTENT(target); setTargetPointer = (target->nInternalPointer == INVALID_IDX); for (idx = 0; idx < source->nNumUsed; idx++) { p = source->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (setTargetPointer && source->nInternalPointer == idx) { target->nInternalPointer = INVALID_IDX; } if (p->key) { new_entry = zend_hash_update(target, p->key, &p->val); } else { new_entry = zend_hash_index_update(target, p->h, &p->val); } if (pCopyConstructor) { pCopyConstructor(new_entry); } } if (target->nInternalPointer == INVALID_IDX && target->nNumOfElements > 0) { idx = 0; while (Z_TYPE(target->arData[idx].val) == IS_UNDEF) { idx++; } target->nInternalPointer = idx; } } ZEND_API void _zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, int overwrite ZEND_FILE_LINE_DC) { uint idx; Bucket *p; zval *t; int mode = (overwrite?HASH_UPDATE:HASH_ADD); IS_CONSISTENT(source); IS_CONSISTENT(target); for (idx = 0; idx < source->nNumUsed; idx++) { p = source->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (p->key) { t = _zend_hash_add_or_update(target, p->key, &p->val, mode ZEND_FILE_LINE_RELAY_CC); if (t && pCopyConstructor) { pCopyConstructor(t); } } else { if ((mode==HASH_UPDATE || !zend_hash_index_exists(target, p->h))) { t = zend_hash_index_update(target, p->h, &p->val); if (t && pCopyConstructor) { pCopyConstructor(t); } } } } if (target->nNumOfElements > 0) { idx = 0; while (Z_TYPE(target->arData[idx].val) == IS_UNDEF) { idx++; } target->nInternalPointer = idx; } } static zend_bool zend_hash_replace_checker_wrapper(HashTable *target, zval *source_data, Bucket *p, void *pParam, merge_checker_func_t merge_checker_func) { zend_hash_key hash_key; hash_key.h = p->h; hash_key.key = p->key; return merge_checker_func(target, source_data, &hash_key, pParam); } ZEND_API void zend_hash_merge_ex(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, merge_checker_func_t pMergeSource, void *pParam) { uint idx; Bucket *p; zval *t; IS_CONSISTENT(source); IS_CONSISTENT(target); for (idx = 0; idx < source->nNumUsed; idx++) { p = source->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (zend_hash_replace_checker_wrapper(target, &p->val, p, pParam, pMergeSource)) { t = zend_hash_update(target, p->key, &p->val); if (t && pCopyConstructor) { pCopyConstructor(t); } } } if (target->nNumOfElements > 0) { idx = 0; while (Z_TYPE(target->arData[idx].val) == IS_UNDEF) { idx++; } target->nInternalPointer = idx; } } /* Returns SUCCESS if found and FAILURE if not. The pointer to the * data is returned in pData. The reason is that there's no reason * someone using the hash table might not want to have NULL data */ ZEND_API zval *zend_hash_find(const HashTable *ht, zend_string *key) { Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { return NULL; } p = zend_hash_find_bucket(ht, key); return p ? &p->val : NULL; } ZEND_API zval *zend_hash_str_find(const HashTable *ht, const char *str, int len) { ulong h; Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { return NULL; } h = zend_inline_hash_func(str, len + 1); p = zend_hash_str_find_bucket(ht, str, len, h); return p ? &p->val : NULL; } ZEND_API int zend_hash_exists(const HashTable *ht, zend_string *key) { Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { return 0; } p = zend_hash_find_bucket(ht, key); return p ? 1 : 0; } ZEND_API int zend_hash_str_exists(const HashTable *ht, const char *str, int len) { ulong h; Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { return 0; } h = zend_inline_hash_func(str, len + 1); p = zend_hash_str_find_bucket(ht, str, len, h); return p ? 1 : 0; } ZEND_API zval *zend_hash_index_find(const HashTable *ht, ulong h) { Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { if (h >= 0 && h < ht->nTableSize) { p = ht->arData + h; if (Z_TYPE(p->val) != IS_UNDEF) { return &p->val; } } return NULL; } p = zend_hash_index_find_bucket(ht, h); return p ? &p->val : NULL; } ZEND_API int zend_hash_index_exists(const HashTable *ht, ulong h) { Bucket *p; IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { if (h >= 0 && h < ht->nTableSize) { if (Z_TYPE(ht->arData[h].val) != IS_UNDEF) { return 1; } } return 0; } p = zend_hash_index_find_bucket(ht, h); return p ? 1 : 0; } ZEND_API int zend_hash_num_elements(const HashTable *ht) { IS_CONSISTENT(ht); return ht->nNumOfElements; } ZEND_API int zend_hash_get_pointer(const HashTable *ht, HashPointer *ptr) { ptr->pos = ht->nInternalPointer; ptr->ht = (HashTable*)ht; if (ht->nInternalPointer != INVALID_IDX) { ptr->h = ht->arData[ht->nInternalPointer].h; return 1; } else { ptr->h = 0; return 0; } } ZEND_API int zend_hash_set_pointer(HashTable *ht, const HashPointer *ptr) { uint idx; if (ptr->pos == INVALID_IDX) { ht->nInternalPointer = INVALID_IDX; } else if (ptr->ht != ht) { IS_CONSISTENT(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { ht->nInternalPointer = idx; return 0; } } idx = INVALID_IDX; return 0; } else if (ht->nInternalPointer != ptr->pos) { IS_CONSISTENT(ht); if (ht->flags & HASH_FLAG_PACKED) { if (Z_TYPE(ht->arData[ptr->h].val) != IS_UNDEF) { ht->nInternalPointer = ptr->h; return 1; } } else { idx = ht->arHash[ptr->h & ht->nTableMask]; while (idx != INVALID_IDX) { if (idx == ptr->pos) { ht->nInternalPointer = idx; return 1; } idx = ht->arData[idx].val.u.next; } } return 0; } return 1; } ZEND_API void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos) { uint idx; IS_CONSISTENT(ht); for (idx = 0; idx < ht->nNumUsed; idx++) { if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { if (pos) { *pos = idx; } else { ht->nInternalPointer = idx; } return; } } if (pos) { *pos = INVALID_IDX; } else { ht->nInternalPointer = INVALID_IDX; } } /* This function will be extremely optimized by remembering * the end of the list */ ZEND_API void zend_hash_internal_pointer_end_ex(HashTable *ht, HashPosition *pos) { uint idx; IS_CONSISTENT(ht); idx = ht->nNumUsed; while (idx > 0) { idx--; if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { if (pos) { *pos = idx; } else { ht->nInternalPointer = idx; } return; } } if (pos) { *pos = INVALID_IDX; } else { ht->nInternalPointer = INVALID_IDX; } } ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos) { HashPosition *current = pos ? pos : &ht->nInternalPointer; uint idx = *current; IS_CONSISTENT(ht); if (idx != INVALID_IDX) { while (1) { idx++; if (idx >= ht->nNumUsed) { *current = INVALID_IDX; return SUCCESS; } if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { *current = idx; return SUCCESS; } } } else { return FAILURE; } } ZEND_API int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos) { HashPosition *current = pos ? pos : &ht->nInternalPointer; uint idx = *current; IS_CONSISTENT(ht); if (idx != INVALID_IDX) { while (idx > 0) { idx--; if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) { *current = idx; return SUCCESS; } } *current = INVALID_IDX; return SUCCESS; } else { return FAILURE; } } /* This function should be made binary safe */ ZEND_API int zend_hash_get_current_key_ex(const HashTable *ht, zend_string **str_index, ulong *num_index, zend_bool duplicate, HashPosition *pos) { uint idx; Bucket *p; idx = pos ? (*pos) : ht->nInternalPointer; IS_CONSISTENT(ht); if (idx != INVALID_IDX) { p = ht->arData + idx; if (p->key) { if (duplicate) { *str_index = STR_DUP(p->key, 0); } else { *str_index = p->key; } return HASH_KEY_IS_STRING; } else { *num_index = p->h; return HASH_KEY_IS_LONG; } } return HASH_KEY_NON_EXISTENT; } ZEND_API void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { uint idx; Bucket *p; IS_CONSISTENT(ht); idx = pos ? (*pos) : ht->nInternalPointer; if (idx == INVALID_IDX) { Z_TYPE_P(key) = IS_NULL; } else { p = ht->arData + idx; if (p->key) { ZVAL_STR(key, p->key); STR_ADDREF(p->key); } else { ZVAL_LONG(key, p->h); } } } ZEND_API int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos) { uint idx; Bucket *p; idx = pos ? (*pos) : ht->nInternalPointer; IS_CONSISTENT(ht); if (idx != INVALID_IDX) { p = ht->arData + idx; if (p->key) { return HASH_KEY_IS_STRING; } else { return HASH_KEY_IS_LONG; } } return HASH_KEY_NON_EXISTENT; } ZEND_API zval *zend_hash_get_current_data_ex(HashTable *ht, HashPosition *pos) { uint idx; Bucket *p; idx = pos ? (*pos) : ht->nInternalPointer; IS_CONSISTENT(ht); if (idx != INVALID_IDX) { p = ht->arData + idx; return &p->val; } else { return NULL; } } /* This function changes key of current element without changing elements' * order. If element with target key already exists, it will be deleted first. */ ZEND_API int zend_hash_update_current_key_ex(HashTable *ht, int key_type, zend_string *str_index, ulong num_index, int mode, HashPosition *pos) { uint idx1, idx2; Bucket *p, *q; ulong h; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif idx1 = pos ? (*pos) : ht->nInternalPointer; IS_CONSISTENT(ht); if (idx1 != INVALID_IDX) { p = ht->arData + idx1; if (key_type == HASH_KEY_IS_LONG) { if (p->h == num_index && p->key == NULL) { return SUCCESS; } idx2 = ht->arHash[num_index & ht->nTableMask]; while (idx2 != INVALID_IDX) { q = ht->arData + idx2; if (q->h == num_index && q->key == NULL) { break; } idx2 = q->val.u.next; } } else if (key_type == HASH_KEY_IS_STRING) { h = STR_HASH_VAL(str_index); if (p->key == str_index || (p->h == h && p->key && p->key->len == str_index->len && memcmp(p->key->val, str_index->val, str_index->len) == 0)) { return SUCCESS; } idx2 = ht->arHash[h & ht->nTableMask]; while (idx2 != INVALID_IDX) { q = ht->arData + idx2; if (q->key == str_index || (q->h == h && q->key && q->key->len == str_index->len && memcmp(q->key->val, str_index->val, str_index->len) == 0)) { break; } idx2 = q->val.u.next; } } else { return FAILURE; } HANDLE_BLOCK_INTERRUPTIONS(); if (idx2 != INVALID_IDX) { /* we have another bucket with the key equal to new one */ if (mode != HASH_UPDATE_KEY_ANYWAY) { int found = (idx1 < idx2) ? HASH_UPDATE_KEY_IF_BEFORE : HASH_UPDATE_KEY_IF_AFTER; if (mode & found) { /* delete current bucket */ _zend_hash_del_el(ht, idx1, p); HANDLE_UNBLOCK_INTERRUPTIONS(); return FAILURE; } } /* delete another bucket with the same key */ _zend_hash_del_el(ht, idx2, q); } /* remove old key from hash */ if (ht->arHash[p->h & ht->nTableMask] == idx1) { ht->arHash[p->h & ht->nTableMask] = p->val.u.next; } else { uint idx3 = ht->arHash[p->h & ht->nTableMask]; while (ht->arData[idx3].val.u.next != idx1) { idx3 = ht->arData[idx3].val.u.next; } ht->arData[idx3].val.u.next = p->val.u.next; } /* update key */ if (p->key) { STR_RELEASE(p->key); } if (key_type == HASH_KEY_IS_LONG) { p->h = num_index; p->key = NULL; } else { p->h = h; p->key = str_index; STR_ADDREF(str_index); } /* insert new key into hash */ p->val.u.next = ht->arHash[p->h & ht->nTableMask]; ht->arHash[p->h & ht->nTableMask] = idx1; HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } else { return FAILURE; } } ZEND_API int zend_hash_sort(HashTable *ht, sort_func_t sort_func, compare_func_t compar, int renumber TSRMLS_DC) { Bucket *p; int i, j; IS_CONSISTENT(ht); if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */ return SUCCESS; } if (ht->nNumUsed == ht->nNumOfElements) { i = ht->nNumUsed; } else { for (j = 0, i = 0; j < ht->nNumUsed; j++) { p = ht->arData + j; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (i != j) { ht->arData[i] = *p; } i++; } } (*sort_func)((void *) ht->arData, i, sizeof(Bucket), compar TSRMLS_CC); HANDLE_BLOCK_INTERRUPTIONS(); ht->nNumUsed = i; ht->nInternalPointer = 0; if (renumber) { for (j = 0; j < i; j++) { p = ht->arData + j; p->h = j; if (p->key) { STR_RELEASE(p->key); p->key = NULL; } } } if (renumber) { ht->nNextFreeElement = i; } if (ht->flags & HASH_FLAG_PACKED) { if (!renumber) { zend_hash_packed_to_hash(ht); } } else { if (renumber) { ht->flags |= HASH_FLAG_PACKED; pefree(ht->arHash, ht->flags & HASH_FLAG_PERSISTENT); ht->arHash = (zend_uint*)&uninitialized_bucket; } else { zend_hash_rehash(ht); } } HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } ZEND_API int zend_hash_compare(HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC) { uint idx1, idx2; Bucket *p1, *p2 = NULL; int result; zval *pData2; IS_CONSISTENT(ht1); IS_CONSISTENT(ht2); HASH_PROTECT_RECURSION(ht1); HASH_PROTECT_RECURSION(ht2); result = ht1->nNumOfElements - ht2->nNumOfElements; if (result!=0) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return result; } for (idx1 = 0, idx2 = 0; idx1 < ht1->nNumUsed; idx1++) { p1 = ht1->arData + idx1; if (Z_TYPE(p1->val) == IS_UNDEF) continue; if (ordered) { while (1) { p2 = ht2->arData + idx2; if (idx2 == ht2->nNumUsed) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return 1; /* That's not supposed to happen */ } if (p2) break; idx2++; } if (p1->key == NULL && p2->key == NULL) { /* numeric indices */ result = p1->h - p2->h; if (result != 0) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return result; } } else { /* string indices */ result = (p1->key ? p1->key->len : 0) - (p2->key ? p2->key->len : 0); if (result != 0) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return result; } result = memcmp(p1->key->val, p2->key->val, p1->key->len); if (result != 0) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return result; } } pData2 = &p2->val; } else { if (p1->key == NULL) { /* numeric index */ pData2 = zend_hash_index_find(ht2, p1->h); if (pData2 == NULL) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return 1; } } else { /* string index */ pData2 = zend_hash_find(ht2, p1->key); if (pData2 == NULL) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return 1; } } } result = compar(&p1->val, pData2 TSRMLS_CC); if (result != 0) { HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return result; } if (ordered) { idx2++; } } HASH_UNPROTECT_RECURSION(ht1); HASH_UNPROTECT_RECURSION(ht2); return 0; } ZEND_API zval *zend_hash_minmax(const HashTable *ht, compare_func_t compar, int flag TSRMLS_DC) { uint idx; Bucket *p, *res; IS_CONSISTENT(ht); if (ht->nNumOfElements == 0 ) { return NULL; } idx = 0; while (1) { if (idx == ht->nNumUsed) { return NULL; } if (Z_TYPE(ht->arData[idx].val) != IS_UNDEF) break; idx++; } res = ht->arData + idx; for (; idx < ht->nNumUsed; idx++) { p = ht->arData + idx; if (Z_TYPE(p->val) == IS_UNDEF) continue; if (flag) { if (compar(res, p TSRMLS_CC) < 0) { /* max */ res = p; } } else { if (compar(res, p TSRMLS_CC) > 0) { /* min */ res = p; } } } return &res->val; } ZEND_API ulong zend_hash_next_free_element(const HashTable *ht) { IS_CONSISTENT(ht); return ht->nNextFreeElement; } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * indent-tabs-mode: t * End: */