| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * arena.c |
| * |
| * This contains the implementation of NSS's thread-safe arenas. |
| */ |
| |
| #ifndef BASE_H |
| #include "base.h" |
| #endif /* BASE_H */ |
| |
| #ifdef ARENA_THREADMARK |
| #include "prthread.h" |
| #endif /* ARENA_THREADMARK */ |
| |
| #include "prlock.h" |
| #include "plarena.h" |
| |
| #include <string.h> |
| |
| /* |
| * NSSArena |
| * |
| * This is based on NSPR's arena code, but it is threadsafe. |
| * |
| * The public methods relating to this type are: |
| * |
| * NSSArena_Create -- constructor |
| * NSSArena_Destroy |
| * NSS_ZAlloc |
| * NSS_ZRealloc |
| * NSS_ZFreeIf |
| * |
| * The nonpublic methods relating to this type are: |
| * |
| * nssArena_Create -- constructor |
| * nssArena_Destroy |
| * nssArena_Mark |
| * nssArena_Release |
| * nssArena_Unmark |
| * |
| * nss_ZAlloc |
| * nss_ZFreeIf |
| * nss_ZRealloc |
| * |
| * In debug builds, the following calls are available: |
| * |
| * nssArena_verifyPointer |
| * nssArena_registerDestructor |
| * nssArena_deregisterDestructor |
| */ |
| |
| struct NSSArenaStr { |
| PLArenaPool pool; |
| PRLock *lock; |
| #ifdef ARENA_THREADMARK |
| PRThread *marking_thread; |
| nssArenaMark *first_mark; |
| nssArenaMark *last_mark; |
| #endif /* ARENA_THREADMARK */ |
| #ifdef ARENA_DESTRUCTOR_LIST |
| struct arena_destructor_node *first_destructor; |
| struct arena_destructor_node *last_destructor; |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| }; |
| |
| /* |
| * nssArenaMark |
| * |
| * This type is used to mark the current state of an NSSArena. |
| */ |
| |
| struct nssArenaMarkStr { |
| PRUint32 magic; |
| void *mark; |
| #ifdef ARENA_THREADMARK |
| nssArenaMark *next; |
| #endif /* ARENA_THREADMARK */ |
| #ifdef ARENA_DESTRUCTOR_LIST |
| struct arena_destructor_node *next_destructor; |
| struct arena_destructor_node *prev_destructor; |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| }; |
| |
| #define MARK_MAGIC 0x4d41524b /* "MARK" how original */ |
| |
| /* |
| * But first, the pointer-tracking code |
| */ |
| #ifdef DEBUG |
| extern const NSSError NSS_ERROR_INTERNAL_ERROR; |
| |
| static nssPointerTracker arena_pointer_tracker; |
| |
| static PRStatus |
| arena_add_pointer(const NSSArena *arena) |
| { |
| PRStatus rv; |
| |
| rv = nssPointerTracker_initialize(&arena_pointer_tracker); |
| if (PR_SUCCESS != rv) { |
| return rv; |
| } |
| |
| rv = nssPointerTracker_add(&arena_pointer_tracker, arena); |
| if (PR_SUCCESS != rv) { |
| NSSError e = NSS_GetError(); |
| if (NSS_ERROR_NO_MEMORY != e) { |
| nss_SetError(NSS_ERROR_INTERNAL_ERROR); |
| } |
| |
| return rv; |
| } |
| |
| return PR_SUCCESS; |
| } |
| |
| static PRStatus |
| arena_remove_pointer(const NSSArena *arena) |
| { |
| PRStatus rv; |
| |
| rv = nssPointerTracker_remove(&arena_pointer_tracker, arena); |
| if (PR_SUCCESS != rv) { |
| nss_SetError(NSS_ERROR_INTERNAL_ERROR); |
| } |
| |
| return rv; |
| } |
| |
| /* |
| * nssArena_verifyPointer |
| * |
| * This method is only present in debug builds. |
| * |
| * If the specified pointer is a valid pointer to an NSSArena object, |
| * this routine will return PR_SUCCESS. Otherwise, it will put an |
| * error on the error stack and return PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * |
| * Return value: |
| * PR_SUCCESS if the pointer is valid |
| * PR_FAILURE if it isn't |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_verifyPointer(const NSSArena *arena) |
| { |
| PRStatus rv; |
| |
| rv = nssPointerTracker_initialize(&arena_pointer_tracker); |
| if (PR_SUCCESS != rv) { |
| /* |
| * This is a little disingenious. We have to initialize the |
| * tracker, because someone could "legitimately" try to verify |
| * an arena pointer before one is ever created. And this step |
| * might fail, due to lack of memory. But the only way that |
| * this step can fail is if it's doing the call_once stuff, |
| * (later calls just no-op). And if it didn't no-op, there |
| * aren't any valid arenas.. so the argument certainly isn't one. |
| */ |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return PR_FAILURE; |
| } |
| |
| rv = nssPointerTracker_verify(&arena_pointer_tracker, arena); |
| if (PR_SUCCESS != rv) { |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return PR_FAILURE; |
| } |
| |
| return PR_SUCCESS; |
| } |
| #endif /* DEBUG */ |
| |
| #ifdef ARENA_DESTRUCTOR_LIST |
| |
| struct arena_destructor_node { |
| struct arena_destructor_node *next; |
| struct arena_destructor_node *prev; |
| void (*destructor)(void *argument); |
| void *arg; |
| }; |
| |
| /* |
| * nssArena_registerDestructor |
| * |
| * This routine stores a pointer to a callback and an arbitrary |
| * pointer-sized argument in the arena, at the current point in |
| * the mark stack. If the arena is destroyed, or an "earlier" |
| * mark is released, then this destructor will be called at that |
| * time. Note that the destructor will be called with the arena |
| * locked, which means the destructor may free memory in that |
| * arena, but it may not allocate or cause to be allocated any |
| * memory. This callback facility was included to support our |
| * debug-version pointer-tracker feature; overuse runs counter to |
| * the the original intent of arenas. This routine returns a |
| * PRStatus value; if successful, it will return PR_SUCCESS. If |
| * unsuccessful, it will set an error on the error stack and |
| * return PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_NO_MEMORY |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_registerDestructor(NSSArena *arena, void (*destructor)(void *argument), |
| void *arg) |
| { |
| struct arena_destructor_node *it; |
| |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* NSSDEBUG */ |
| |
| it = nss_ZNEW(arena, struct arena_destructor_node); |
| if ((struct arena_destructor_node *)NULL == it) { |
| return PR_FAILURE; |
| } |
| |
| it->prev = arena->last_destructor; |
| arena->last_destructor->next = it; |
| arena->last_destructor = it; |
| it->destructor = destructor; |
| it->arg = arg; |
| |
| if ((nssArenaMark *)NULL != arena->last_mark) { |
| arena->last_mark->prev_destructor = it->prev; |
| arena->last_mark->next_destructor = it->next; |
| } |
| |
| return PR_SUCCESS; |
| } |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_deregisterDestructor(NSSArena *arena, |
| void (*destructor)(void *argument), void *arg) |
| { |
| struct arena_destructor_node *it; |
| |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* NSSDEBUG */ |
| |
| for (it = arena->first_destructor; it; it = it->next) { |
| if ((it->destructor == destructor) && (it->arg == arg)) { |
| break; |
| } |
| } |
| |
| if ((struct arena_destructor_node *)NULL == it) { |
| nss_SetError(NSS_ERROR_NOT_FOUND); |
| return PR_FAILURE; |
| } |
| |
| if (it == arena->first_destructor) { |
| arena->first_destructor = it->next; |
| } |
| |
| if (it == arena->last_destructor) { |
| arena->last_destructor = it->prev; |
| } |
| |
| if ((struct arena_destructor_node *)NULL != it->prev) { |
| it->prev->next = it->next; |
| } |
| |
| if ((struct arena_destructor_node *)NULL != it->next) { |
| it->next->prev = it->prev; |
| } |
| |
| { |
| nssArenaMark *m; |
| for (m = arena->first_mark; m; m = m->next) { |
| if (m->next_destructor == it) { |
| m->next_destructor = it->next; |
| } |
| if (m->prev_destructor == it) { |
| m->prev_destructor = it->prev; |
| } |
| } |
| } |
| |
| nss_ZFreeIf(it); |
| return PR_SUCCESS; |
| } |
| |
| static void |
| nss_arena_call_destructor_chain(struct arena_destructor_node *it) |
| { |
| for (; it; it = it->next) { |
| (*(it->destructor))(it->arg); |
| } |
| } |
| |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| |
| /* |
| * NSSArena_Create |
| * |
| * This routine creates a new memory arena. This routine may return |
| * NULL upon error, in which case it will have created an error stack. |
| * |
| * The top-level error may be one of the following values: |
| * NSS_ERROR_NO_MEMORY |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to an NSSArena upon success |
| */ |
| |
| NSS_IMPLEMENT NSSArena * |
| NSSArena_Create(void) |
| { |
| nss_ClearErrorStack(); |
| return nssArena_Create(); |
| } |
| |
| /* |
| * nssArena_Create |
| * |
| * This routine creates a new memory arena. This routine may return |
| * NULL upon error, in which case it will have set an error on the |
| * error stack. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_NO_MEMORY |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to an NSSArena upon success |
| */ |
| |
| NSS_IMPLEMENT NSSArena * |
| nssArena_Create(void) |
| { |
| NSSArena *rv = (NSSArena *)NULL; |
| |
| rv = nss_ZNEW((NSSArena *)NULL, NSSArena); |
| if ((NSSArena *)NULL == rv) { |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (NSSArena *)NULL; |
| } |
| |
| rv->lock = PR_NewLock(); |
| if ((PRLock *)NULL == rv->lock) { |
| (void)nss_ZFreeIf(rv); |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (NSSArena *)NULL; |
| } |
| |
| /* |
| * Arena sizes. The current security code has 229 occurrences of |
| * PORT_NewArena. The default chunksizes specified break down as |
| * |
| * Size Mult. Specified as |
| * 512 1 512 |
| * 1024 7 1024 |
| * 2048 5 2048 |
| * 2048 5 CRMF_DEFAULT_ARENA_SIZE |
| * 2048 190 DER_DEFAULT_CHUNKSIZE |
| * 2048 20 SEC_ASN1_DEFAULT_ARENA_SIZE |
| * 4096 1 4096 |
| * |
| * Obviously this "default chunksize" flexibility isn't very |
| * useful to us, so I'll just pick 2048. |
| */ |
| |
| PL_InitArenaPool(&rv->pool, "NSS", 2048, sizeof(double)); |
| |
| #ifdef DEBUG |
| { |
| PRStatus st; |
| st = arena_add_pointer(rv); |
| if (PR_SUCCESS != st) { |
| PL_FinishArenaPool(&rv->pool); |
| PR_DestroyLock(rv->lock); |
| (void)nss_ZFreeIf(rv); |
| return (NSSArena *)NULL; |
| } |
| } |
| #endif /* DEBUG */ |
| |
| return rv; |
| } |
| |
| /* |
| * NSSArena_Destroy |
| * |
| * This routine will destroy the specified arena, freeing all memory |
| * allocated from it. This routine returns a PRStatus value; if |
| * successful, it will return PR_SUCCESS. If unsuccessful, it will |
| * create an error stack and return PR_FAILURE. |
| * |
| * The top-level error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * |
| * Return value: |
| * PR_SUCCESS upon success |
| * PR_FAILURE upon failure |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| NSSArena_Destroy(NSSArena *arena) |
| { |
| nss_ClearErrorStack(); |
| |
| #ifdef DEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* DEBUG */ |
| |
| return nssArena_Destroy(arena); |
| } |
| |
| /* |
| * nssArena_Destroy |
| * |
| * This routine will destroy the specified arena, freeing all memory |
| * allocated from it. This routine returns a PRStatus value; if |
| * successful, it will return PR_SUCCESS. If unsuccessful, it will |
| * set an error on the error stack and return PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_Destroy(NSSArena *arena) |
| { |
| PRLock *lock; |
| |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if ((PRLock *)NULL == arena->lock) { |
| /* Just got destroyed */ |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return PR_FAILURE; |
| } |
| PR_Lock(arena->lock); |
| |
| #ifdef DEBUG |
| if (PR_SUCCESS != arena_remove_pointer(arena)) { |
| PR_Unlock(arena->lock); |
| return PR_FAILURE; |
| } |
| #endif /* DEBUG */ |
| |
| #ifdef ARENA_DESTRUCTOR_LIST |
| /* Note that the arena is locked at this time */ |
| nss_arena_call_destructor_chain(arena->first_destructor); |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| |
| PL_FinishArenaPool(&arena->pool); |
| lock = arena->lock; |
| arena->lock = (PRLock *)NULL; |
| PR_Unlock(lock); |
| PR_DestroyLock(lock); |
| (void)nss_ZFreeIf(arena); |
| return PR_SUCCESS; |
| } |
| |
| static void *nss_zalloc_arena_locked(NSSArena *arena, PRUint32 size); |
| |
| /* |
| * nssArena_Mark |
| * |
| * This routine "marks" the current state of an arena. Space |
| * allocated after the arena has been marked can be freed by |
| * releasing the arena back to the mark with nssArena_Release, |
| * or committed by calling nssArena_Unmark. When successful, |
| * this routine returns a valid nssArenaMark pointer. This |
| * routine may return NULL upon error, in which case it will |
| * have set an error on the error stack. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_NO_MEMORY |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * NULL upon failure |
| * An nssArenaMark pointer upon success |
| */ |
| |
| NSS_IMPLEMENT nssArenaMark * |
| nssArena_Mark(NSSArena *arena) |
| { |
| nssArenaMark *rv; |
| void *p; |
| |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return (nssArenaMark *)NULL; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if ((PRLock *)NULL == arena->lock) { |
| /* Just got destroyed */ |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return (nssArenaMark *)NULL; |
| } |
| PR_Lock(arena->lock); |
| |
| #ifdef ARENA_THREADMARK |
| if ((PRThread *)NULL == arena->marking_thread) { |
| /* Unmarked. Store our thread ID */ |
| arena->marking_thread = PR_GetCurrentThread(); |
| /* This call never fails. */ |
| } else { |
| /* Marked. Verify it's the current thread */ |
| if (PR_GetCurrentThread() != arena->marking_thread) { |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD); |
| return (nssArenaMark *)NULL; |
| } |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| p = PL_ARENA_MARK(&arena->pool); |
| /* No error possible */ |
| |
| /* Do this after the mark */ |
| rv = (nssArenaMark *)nss_zalloc_arena_locked(arena, sizeof(nssArenaMark)); |
| if ((nssArenaMark *)NULL == rv) { |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (nssArenaMark *)NULL; |
| } |
| |
| #ifdef ARENA_THREADMARK |
| if ((nssArenaMark *)NULL == arena->first_mark) { |
| arena->first_mark = rv; |
| arena->last_mark = rv; |
| } else { |
| arena->last_mark->next = rv; |
| arena->last_mark = rv; |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| rv->mark = p; |
| rv->magic = MARK_MAGIC; |
| |
| #ifdef ARENA_DESTRUCTOR_LIST |
| rv->prev_destructor = arena->last_destructor; |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| |
| PR_Unlock(arena->lock); |
| |
| return rv; |
| } |
| |
| /* |
| * nss_arena_unmark_release |
| * |
| * This static routine implements the routines nssArena_Release |
| * ans nssArena_Unmark, which are almost identical. |
| */ |
| |
| static PRStatus |
| nss_arena_unmark_release(NSSArena *arena, nssArenaMark *arenaMark, |
| PRBool release) |
| { |
| void *inner_mark; |
| |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if (MARK_MAGIC != arenaMark->magic) { |
| nss_SetError(NSS_ERROR_INVALID_ARENA_MARK); |
| return PR_FAILURE; |
| } |
| |
| if ((PRLock *)NULL == arena->lock) { |
| /* Just got destroyed */ |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return PR_FAILURE; |
| } |
| PR_Lock(arena->lock); |
| |
| #ifdef ARENA_THREADMARK |
| if ((PRThread *)NULL != arena->marking_thread) { |
| if (PR_GetCurrentThread() != arena->marking_thread) { |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD); |
| return PR_FAILURE; |
| } |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| if (MARK_MAGIC != arenaMark->magic) { |
| /* Just got released */ |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_INVALID_ARENA_MARK); |
| return PR_FAILURE; |
| } |
| |
| arenaMark->magic = 0; |
| inner_mark = arenaMark->mark; |
| |
| #ifdef ARENA_THREADMARK |
| { |
| nssArenaMark **pMark = &arena->first_mark; |
| nssArenaMark *rest; |
| nssArenaMark *last = (nssArenaMark *)NULL; |
| |
| /* Find this mark */ |
| while (*pMark != arenaMark) { |
| last = *pMark; |
| pMark = &(*pMark)->next; |
| } |
| |
| /* Remember the pointer, then zero it */ |
| rest = (*pMark)->next; |
| *pMark = (nssArenaMark *)NULL; |
| |
| arena->last_mark = last; |
| |
| /* Invalidate any later marks being implicitly released */ |
| for (; (nssArenaMark *)NULL != rest; rest = rest->next) { |
| rest->magic = 0; |
| } |
| |
| /* If we just got rid of the first mark, clear the thread ID */ |
| if ((nssArenaMark *)NULL == arena->first_mark) { |
| arena->marking_thread = (PRThread *)NULL; |
| } |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| if (release) { |
| #ifdef ARENA_DESTRUCTOR_LIST |
| if ((struct arena_destructor_node *)NULL != |
| arenaMark->prev_destructor) { |
| arenaMark->prev_destructor->next = |
| (struct arena_destructor_node *)NULL; |
| } |
| arena->last_destructor = arenaMark->prev_destructor; |
| |
| /* Note that the arena is locked at this time */ |
| nss_arena_call_destructor_chain(arenaMark->next_destructor); |
| #endif /* ARENA_DESTRUCTOR_LIST */ |
| |
| PL_ARENA_RELEASE(&arena->pool, inner_mark); |
| /* No error return */ |
| } |
| |
| PR_Unlock(arena->lock); |
| return PR_SUCCESS; |
| } |
| |
| /* |
| * nssArena_Release |
| * |
| * This routine invalidates and releases all memory allocated from |
| * the specified arena after the point at which the specified mark |
| * was obtained. This routine returns a PRStatus value; if successful, |
| * it will return PR_SUCCESS. If unsuccessful, it will set an error |
| * on the error stack and return PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_INVALID_ARENA_MARK |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_Release(NSSArena *arena, nssArenaMark *arenaMark) |
| { |
| return nss_arena_unmark_release(arena, arenaMark, PR_TRUE); |
| } |
| |
| /* |
| * nssArena_Unmark |
| * |
| * This routine "commits" the indicated mark and any marks after |
| * it, making them unreleasable. Note that any earlier marks can |
| * still be released, and such a release will invalidate these |
| * later unmarked regions. If an arena is to be safely shared by |
| * more than one thread, all marks must be either released or |
| * unmarked. This routine returns a PRStatus value; if successful, |
| * it will return PR_SUCCESS. If unsuccessful, it will set an error |
| * on the error stack and return PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_INVALID_ARENA_MARK |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nssArena_Unmark(NSSArena *arena, nssArenaMark *arenaMark) |
| { |
| return nss_arena_unmark_release(arena, arenaMark, PR_FALSE); |
| } |
| |
| /* |
| * We prefix this header to all allocated blocks. It is a multiple |
| * of the alignment size. Note that this usage of a header may make |
| * purify spew bogus warnings about "potentially leaked blocks" of |
| * memory; if that gets too annoying we can add in a pointer to the |
| * header in the header itself. There's not a lot of safety here; |
| * maybe we should add a magic value? |
| */ |
| struct pointer_header { |
| NSSArena *arena; |
| PRUint32 size; |
| }; |
| |
| static void * |
| nss_zalloc_arena_locked(NSSArena *arena, PRUint32 size) |
| { |
| void *p; |
| void *rv; |
| struct pointer_header *h; |
| PRUint32 my_size = size + sizeof(struct pointer_header); |
| PL_ARENA_ALLOCATE(p, &arena->pool, my_size); |
| if ((void *)NULL == p) { |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| /* |
| * Do this before we unlock. This way if the user is using |
| * an arena in one thread while destroying it in another, he'll |
| * fault/FMR in his code, not ours. |
| */ |
| h = (struct pointer_header *)p; |
| h->arena = arena; |
| h->size = size; |
| rv = (void *)((char *)h + sizeof(struct pointer_header)); |
| (void)nsslibc_memset(rv, 0, size); |
| return rv; |
| } |
| |
| /* |
| * NSS_ZAlloc |
| * |
| * This routine allocates and zeroes a section of memory of the |
| * size, and returns to the caller a pointer to that memory. If |
| * the optional arena argument is non-null, the memory will be |
| * obtained from that arena; otherwise, the memory will be obtained |
| * from the heap. This routine may return NULL upon error, in |
| * which case it will have set an error upon the error stack. The |
| * value specified for size may be zero; in which case a valid |
| * zero-length block of memory will be allocated. This block may |
| * be expanded by calling NSS_ZRealloc. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_NO_MEMORY |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to the new segment of zeroed memory |
| */ |
| |
| NSS_IMPLEMENT void * |
| NSS_ZAlloc(NSSArena *arenaOpt, PRUint32 size) |
| { |
| return nss_ZAlloc(arenaOpt, size); |
| } |
| |
| /* |
| * nss_ZAlloc |
| * |
| * This routine allocates and zeroes a section of memory of the |
| * size, and returns to the caller a pointer to that memory. If |
| * the optional arena argument is non-null, the memory will be |
| * obtained from that arena; otherwise, the memory will be obtained |
| * from the heap. This routine may return NULL upon error, in |
| * which case it will have set an error upon the error stack. The |
| * value specified for size may be zero; in which case a valid |
| * zero-length block of memory will be allocated. This block may |
| * be expanded by calling nss_ZRealloc. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_ARENA |
| * NSS_ERROR_NO_MEMORY |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to the new segment of zeroed memory |
| */ |
| |
| NSS_IMPLEMENT void * |
| nss_ZAlloc(NSSArena *arenaOpt, PRUint32 size) |
| { |
| struct pointer_header *h; |
| PRUint32 my_size = size + sizeof(struct pointer_header); |
| |
| if (my_size < sizeof(struct pointer_header)) { |
| /* Wrapped */ |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| |
| if ((NSSArena *)NULL == arenaOpt) { |
| /* Heap allocation, no locking required. */ |
| h = (struct pointer_header *)PR_Calloc(1, my_size); |
| if ((struct pointer_header *)NULL == h) { |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| |
| h->arena = (NSSArena *)NULL; |
| h->size = size; |
| /* We used calloc: it's already zeroed */ |
| |
| return (void *)((char *)h + sizeof(struct pointer_header)); |
| } else { |
| void *rv; |
| /* Arena allocation */ |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arenaOpt)) { |
| return (void *)NULL; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if ((PRLock *)NULL == arenaOpt->lock) { |
| /* Just got destroyed */ |
| nss_SetError(NSS_ERROR_INVALID_ARENA); |
| return (void *)NULL; |
| } |
| PR_Lock(arenaOpt->lock); |
| |
| #ifdef ARENA_THREADMARK |
| if ((PRThread *)NULL != arenaOpt->marking_thread) { |
| if (PR_GetCurrentThread() != arenaOpt->marking_thread) { |
| nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD); |
| PR_Unlock(arenaOpt->lock); |
| return (void *)NULL; |
| } |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| rv = nss_zalloc_arena_locked(arenaOpt, size); |
| |
| PR_Unlock(arenaOpt->lock); |
| return rv; |
| } |
| /*NOTREACHED*/ |
| } |
| |
| /* |
| * NSS_ZFreeIf |
| * |
| * If the specified pointer is non-null, then the region of memory |
| * to which it points -- which must have been allocated with |
| * NSS_ZAlloc -- will be zeroed and released. This routine |
| * returns a PRStatus value; if successful, it will return PR_SUCCESS. |
| * If unsuccessful, it will set an error on the error stack and return |
| * PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_POINTER |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| NSS_IMPLEMENT PRStatus |
| NSS_ZFreeIf(void *pointer) |
| { |
| return nss_ZFreeIf(pointer); |
| } |
| |
| /* |
| * nss_ZFreeIf |
| * |
| * If the specified pointer is non-null, then the region of memory |
| * to which it points -- which must have been allocated with |
| * nss_ZAlloc -- will be zeroed and released. This routine |
| * returns a PRStatus value; if successful, it will return PR_SUCCESS. |
| * If unsuccessful, it will set an error on the error stack and return |
| * PR_FAILURE. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_POINTER |
| * |
| * Return value: |
| * PR_SUCCESS |
| * PR_FAILURE |
| */ |
| |
| NSS_IMPLEMENT PRStatus |
| nss_ZFreeIf(void *pointer) |
| { |
| struct pointer_header *h; |
| |
| if ((void *)NULL == pointer) { |
| return PR_SUCCESS; |
| } |
| |
| h = (struct pointer_header *)((char *)pointer - |
| sizeof(struct pointer_header)); |
| |
| /* Check any magic here */ |
| |
| if ((NSSArena *)NULL == h->arena) { |
| /* Heap */ |
| (void)nsslibc_memset(pointer, 0, h->size); |
| PR_Free(h); |
| return PR_SUCCESS; |
| } else { |
| /* Arena */ |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(h->arena)) { |
| return PR_FAILURE; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if ((PRLock *)NULL == h->arena->lock) { |
| /* Just got destroyed.. so this pointer is invalid */ |
| nss_SetError(NSS_ERROR_INVALID_POINTER); |
| return PR_FAILURE; |
| } |
| PR_Lock(h->arena->lock); |
| |
| (void)nsslibc_memset(pointer, 0, h->size); |
| |
| /* No way to "free" it within an NSPR arena. */ |
| |
| PR_Unlock(h->arena->lock); |
| return PR_SUCCESS; |
| } |
| /*NOTREACHED*/ |
| } |
| |
| /* |
| * NSS_ZRealloc |
| * |
| * This routine reallocates a block of memory obtained by calling |
| * nss_ZAlloc or nss_ZRealloc. The portion of memory |
| * between the new and old sizes -- which is either being newly |
| * obtained or released -- is in either case zeroed. This routine |
| * may return NULL upon failure, in which case it will have placed |
| * an error on the error stack. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_POINTER |
| * NSS_ERROR_NO_MEMORY |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to the replacement segment of memory |
| */ |
| |
| NSS_EXTERN void * |
| NSS_ZRealloc(void *pointer, PRUint32 newSize) |
| { |
| return nss_ZRealloc(pointer, newSize); |
| } |
| |
| /* |
| * nss_ZRealloc |
| * |
| * This routine reallocates a block of memory obtained by calling |
| * nss_ZAlloc or nss_ZRealloc. The portion of memory |
| * between the new and old sizes -- which is either being newly |
| * obtained or released -- is in either case zeroed. This routine |
| * may return NULL upon failure, in which case it will have placed |
| * an error on the error stack. |
| * |
| * The error may be one of the following values: |
| * NSS_ERROR_INVALID_POINTER |
| * NSS_ERROR_NO_MEMORY |
| * NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD |
| * |
| * Return value: |
| * NULL upon error |
| * A pointer to the replacement segment of memory |
| */ |
| |
| NSS_EXTERN void * |
| nss_ZRealloc(void *pointer, PRUint32 newSize) |
| { |
| NSSArena *arena; |
| struct pointer_header *h, *new_h; |
| PRUint32 my_newSize = newSize + sizeof(struct pointer_header); |
| void *rv; |
| |
| if (my_newSize < sizeof(struct pointer_header)) { |
| /* Wrapped */ |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| |
| if ((void *)NULL == pointer) { |
| nss_SetError(NSS_ERROR_INVALID_POINTER); |
| return (void *)NULL; |
| } |
| |
| h = (struct pointer_header *)((char *)pointer - |
| sizeof(struct pointer_header)); |
| |
| /* Check any magic here */ |
| |
| if (newSize == h->size) { |
| /* saves thrashing */ |
| return pointer; |
| } |
| |
| arena = h->arena; |
| if (!arena) { |
| /* Heap */ |
| new_h = (struct pointer_header *)PR_Calloc(1, my_newSize); |
| if ((struct pointer_header *)NULL == new_h) { |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| |
| new_h->arena = (NSSArena *)NULL; |
| new_h->size = newSize; |
| rv = (void *)((char *)new_h + sizeof(struct pointer_header)); |
| |
| if (newSize > h->size) { |
| (void)nsslibc_memcpy(rv, pointer, h->size); |
| (void)nsslibc_memset(&((char *)rv)[h->size], 0, |
| (newSize - h->size)); |
| } else { |
| (void)nsslibc_memcpy(rv, pointer, newSize); |
| } |
| |
| (void)nsslibc_memset(pointer, 0, h->size); |
| h->size = 0; |
| PR_Free(h); |
| |
| return rv; |
| } else { |
| void *p; |
| /* Arena */ |
| #ifdef NSSDEBUG |
| if (PR_SUCCESS != nssArena_verifyPointer(arena)) { |
| return (void *)NULL; |
| } |
| #endif /* NSSDEBUG */ |
| |
| if (!arena->lock) { |
| /* Just got destroyed.. so this pointer is invalid */ |
| nss_SetError(NSS_ERROR_INVALID_POINTER); |
| return (void *)NULL; |
| } |
| PR_Lock(arena->lock); |
| |
| #ifdef ARENA_THREADMARK |
| if (arena->marking_thread) { |
| if (PR_GetCurrentThread() != arena->marking_thread) { |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD); |
| return (void *)NULL; |
| } |
| } |
| #endif /* ARENA_THREADMARK */ |
| |
| if (newSize < h->size) { |
| /* |
| * We have no general way of returning memory to the arena |
| * (mark/release doesn't work because things may have been |
| * allocated after this object), so the memory is gone |
| * anyway. We might as well just return the same pointer to |
| * the user, saying "yeah, uh-hunh, you can only use less of |
| * it now." We'll zero the leftover part, of course. And |
| * in fact we might as well *not* adjust h->size-- this way, |
| * if the user reallocs back up to something not greater than |
| * the original size, then voila, there's the memory! This |
| * way a thrash big/small/big/small doesn't burn up the arena. |
| */ |
| char *extra = &((char *)pointer)[newSize]; |
| (void)nsslibc_memset(extra, 0, (h->size - newSize)); |
| PR_Unlock(arena->lock); |
| return pointer; |
| } |
| |
| PL_ARENA_ALLOCATE(p, &arena->pool, my_newSize); |
| if ((void *)NULL == p) { |
| PR_Unlock(arena->lock); |
| nss_SetError(NSS_ERROR_NO_MEMORY); |
| return (void *)NULL; |
| } |
| |
| new_h = (struct pointer_header *)p; |
| new_h->arena = arena; |
| new_h->size = newSize; |
| rv = (void *)((char *)new_h + sizeof(struct pointer_header)); |
| if (rv != pointer) { |
| (void)nsslibc_memcpy(rv, pointer, h->size); |
| (void)nsslibc_memset(pointer, 0, h->size); |
| } |
| (void)nsslibc_memset(&((char *)rv)[h->size], 0, (newSize - h->size)); |
| h->arena = (NSSArena *)NULL; |
| h->size = 0; |
| PR_Unlock(arena->lock); |
| return rv; |
| } |
| /*NOTREACHED*/ |
| } |
| |
| PRStatus |
| nssArena_Shutdown(void) |
| { |
| PRStatus rv = PR_SUCCESS; |
| #ifdef DEBUG |
| rv = nssPointerTracker_finalize(&arena_pointer_tracker); |
| #endif |
| return rv; |
| } |