OpenFPGA/libs/EXTERNAL/tcl8.6.12/generic/tclOOCall.c

1589 lines
44 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* tclOOCall.c --
*
* This file contains the method call chain management code for the
* object-system core.
*
* Copyright (c) 2005-2012 by Donal K. Fellows
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "tclInt.h"
#include "tclOOInt.h"
/*
* Structure containing a CallContext and any other values needed only during
* the construction of the CallContext.
*/
struct ChainBuilder {
CallChain *callChainPtr; /* The call chain being built. */
int filterLength; /* Number of entries in the call chain that
* are due to processing filters and not the
* main call chain. */
Object *oPtr; /* The object that we are building the chain
* for. */
};
/*
* Extra flags used for call chain management.
*/
#define DEFINITE_PROTECTED 0x100000
#define DEFINITE_PUBLIC 0x200000
#define KNOWN_STATE (DEFINITE_PROTECTED | DEFINITE_PUBLIC)
#define SPECIAL (CONSTRUCTOR | DESTRUCTOR | FORCE_UNKNOWN)
#define BUILDING_MIXINS 0x400000
#define TRAVERSED_MIXIN 0x800000
#define OBJECT_MIXIN 0x1000000
#define MIXIN_CONSISTENT(flags) \
(((flags) & OBJECT_MIXIN) || \
!((flags) & BUILDING_MIXINS) == !((flags) & TRAVERSED_MIXIN))
/*
* Function declarations for things defined in this file.
*/
static void AddClassFiltersToCallContext(Object *const oPtr,
Class *clsPtr, struct ChainBuilder *const cbPtr,
Tcl_HashTable *const doneFilters, int flags);
static void AddClassMethodNames(Class *clsPtr, const int flags,
Tcl_HashTable *const namesPtr,
Tcl_HashTable *const examinedClassesPtr);
static inline void AddMethodToCallChain(Method *const mPtr,
struct ChainBuilder *const cbPtr,
Tcl_HashTable *const doneFilters,
Class *const filterDecl, int flags);
static inline void AddSimpleChainToCallContext(Object *const oPtr,
Tcl_Obj *const methodNameObj,
struct ChainBuilder *const cbPtr,
Tcl_HashTable *const doneFilters, int flags,
Class *const filterDecl);
static void AddSimpleClassChainToCallContext(Class *classPtr,
Tcl_Obj *const methodNameObj,
struct ChainBuilder *const cbPtr,
Tcl_HashTable *const doneFilters, int flags,
Class *const filterDecl);
static int CmpStr(const void *ptr1, const void *ptr2);
static void DupMethodNameRep(Tcl_Obj *srcPtr, Tcl_Obj *dstPtr);
static Tcl_NRPostProc FinalizeMethodRefs;
static void FreeMethodNameRep(Tcl_Obj *objPtr);
static inline int IsStillValid(CallChain *callPtr, Object *oPtr,
int flags, int reuseMask);
static Tcl_NRPostProc ResetFilterFlags;
static Tcl_NRPostProc SetFilterFlags;
static inline void StashCallChain(Tcl_Obj *objPtr, CallChain *callPtr);
/*
* Object type used to manage type caches attached to method names.
*/
static const Tcl_ObjType methodNameType = {
"TclOO method name",
FreeMethodNameRep,
DupMethodNameRep,
NULL,
NULL
};
/*
* ----------------------------------------------------------------------
*
* TclOODeleteContext --
*
* Destroys a method call-chain context, which should not be in use.
*
* ----------------------------------------------------------------------
*/
void
TclOODeleteContext(
CallContext *contextPtr)
{
Object *oPtr = contextPtr->oPtr;
TclOODeleteChain(contextPtr->callPtr);
if (oPtr != NULL) {
TclStackFree(oPtr->fPtr->interp, contextPtr);
/*
* Corresponding AddRef() in TclOO.c/TclOOObjectCmdCore
*/
TclOODecrRefCount(oPtr);
}
}
/*
* ----------------------------------------------------------------------
*
* TclOODeleteChainCache --
*
* Destroy the cache of method call-chains.
*
* ----------------------------------------------------------------------
*/
void
TclOODeleteChainCache(
Tcl_HashTable *tablePtr)
{
FOREACH_HASH_DECLS;
CallChain *callPtr;
FOREACH_HASH_VALUE(callPtr, tablePtr) {
if (callPtr) {
TclOODeleteChain(callPtr);
}
}
Tcl_DeleteHashTable(tablePtr);
ckfree(tablePtr);
}
/*
* ----------------------------------------------------------------------
*
* TclOODeleteChain --
*
* Destroys a method call-chain.
*
* ----------------------------------------------------------------------
*/
void
TclOODeleteChain(
CallChain *callPtr)
{
if (callPtr == NULL || callPtr->refCount-- > 1) {
return;
}
if (callPtr->chain != callPtr->staticChain) {
ckfree(callPtr->chain);
}
ckfree(callPtr);
}
/*
* ----------------------------------------------------------------------
*
* TclOOStashContext --
*
* Saves a reference to a method call context in a Tcl_Obj's internal
* representation.
*
* ----------------------------------------------------------------------
*/
static inline void
StashCallChain(
Tcl_Obj *objPtr,
CallChain *callPtr)
{
callPtr->refCount++;
TclGetString(objPtr);
TclFreeIntRep(objPtr);
objPtr->typePtr = &methodNameType;
objPtr->internalRep.twoPtrValue.ptr1 = callPtr;
}
void
TclOOStashContext(
Tcl_Obj *objPtr,
CallContext *contextPtr)
{
StashCallChain(objPtr, contextPtr->callPtr);
}
/*
* ----------------------------------------------------------------------
*
* DupMethodNameRep, FreeMethodNameRep --
*
* Functions to implement the required parts of the Tcl_Obj guts needed
* for caching of method contexts in Tcl_Objs.
*
* ----------------------------------------------------------------------
*/
static void
DupMethodNameRep(
Tcl_Obj *srcPtr,
Tcl_Obj *dstPtr)
{
CallChain *callPtr = srcPtr->internalRep.twoPtrValue.ptr1;
dstPtr->typePtr = &methodNameType;
dstPtr->internalRep.twoPtrValue.ptr1 = callPtr;
callPtr->refCount++;
}
static void
FreeMethodNameRep(
Tcl_Obj *objPtr)
{
CallChain *callPtr = objPtr->internalRep.twoPtrValue.ptr1;
TclOODeleteChain(callPtr);
objPtr->typePtr = NULL;
}
/*
* ----------------------------------------------------------------------
*
* TclOOInvokeContext --
*
* Invokes a single step along a method call-chain context. Note that the
* invocation of a step along the chain can cause further steps along the
* chain to be invoked. Note that this function is written to be as light
* in stack usage as possible.
*
* ----------------------------------------------------------------------
*/
int
TclOOInvokeContext(
ClientData clientData, /* The method call context. */
Tcl_Interp *interp, /* Interpreter for error reporting, and many
* other sorts of context handling (e.g.,
* commands, variables) depending on method
* implementation. */
int objc, /* The number of arguments. */
Tcl_Obj *const objv[]) /* The arguments as actually seen. */
{
CallContext *const contextPtr = clientData;
Method *const mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr;
const int isFilter =
contextPtr->callPtr->chain[contextPtr->index].isFilter;
/*
* If this is the first step along the chain, we preserve the method
* entries in the chain so that they do not get deleted out from under our
* feet.
*/
if (contextPtr->index == 0) {
int i;
for (i = 0 ; i < contextPtr->callPtr->numChain ; i++) {
AddRef(contextPtr->callPtr->chain[i].mPtr);
}
/*
* Ensure that the method name itself is part of the arguments when
* we're doing unknown processing.
*/
if (contextPtr->callPtr->flags & OO_UNKNOWN_METHOD) {
contextPtr->skip--;
}
/*
* Add a callback to ensure that method references are dropped once
* this call is finished.
*/
TclNRAddCallback(interp, FinalizeMethodRefs, contextPtr, NULL, NULL,
NULL);
}
/*
* Save whether we were in a filter and set up whether we are now.
*/
if (contextPtr->oPtr->flags & FILTER_HANDLING) {
TclNRAddCallback(interp, SetFilterFlags, contextPtr, NULL,NULL,NULL);
} else {
TclNRAddCallback(interp, ResetFilterFlags,contextPtr,NULL,NULL,NULL);
}
if (isFilter || contextPtr->callPtr->flags & FILTER_HANDLING) {
contextPtr->oPtr->flags |= FILTER_HANDLING;
} else {
contextPtr->oPtr->flags &= ~FILTER_HANDLING;
}
/*
* Run the method implementation.
*/
return mPtr->typePtr->callProc(mPtr->clientData, interp,
(Tcl_ObjectContext) contextPtr, objc, objv);
}
static int
SetFilterFlags(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CallContext *contextPtr = data[0];
contextPtr->oPtr->flags |= FILTER_HANDLING;
return result;
}
static int
ResetFilterFlags(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CallContext *contextPtr = data[0];
contextPtr->oPtr->flags &= ~FILTER_HANDLING;
return result;
}
static int
FinalizeMethodRefs(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CallContext *contextPtr = data[0];
int i;
for (i = 0 ; i < contextPtr->callPtr->numChain ; i++) {
TclOODelMethodRef(contextPtr->callPtr->chain[i].mPtr);
}
return result;
}
/*
* ----------------------------------------------------------------------
*
* TclOOGetSortedMethodList, TclOOGetSortedClassMethodList --
*
* Discovers the list of method names supported by an object or class.
*
* ----------------------------------------------------------------------
*/
int
TclOOGetSortedMethodList(
Object *oPtr, /* The object to get the method names for. */
int flags, /* Whether we just want the public method
* names. */
const char ***stringsPtr) /* Where to write a pointer to the array of
* strings to. */
{
Tcl_HashTable names; /* Tcl_Obj* method name to "wanted in list"
* mapping. */
Tcl_HashTable examinedClasses;
/* Used to track what classes have been looked
* at. Is set-like in nature and keyed by
* pointer to class. */
FOREACH_HASH_DECLS;
int i;
Class *mixinPtr;
Tcl_Obj *namePtr;
Method *mPtr;
int isWantedIn;
void *isWanted;
Tcl_InitObjHashTable(&names);
Tcl_InitHashTable(&examinedClasses, TCL_ONE_WORD_KEYS);
/*
* Name the bits used in the names table values.
*/
#define IN_LIST 1
#define NO_IMPLEMENTATION 2
/*
* Process method names due to the object.
*/
if (oPtr->methodsPtr) {
FOREACH_HASH(namePtr, mPtr, oPtr->methodsPtr) {
int isNew;
if ((mPtr->flags & PRIVATE_METHOD) && !(flags & PRIVATE_METHOD)) {
continue;
}
hPtr = Tcl_CreateHashEntry(&names, (char *) namePtr, &isNew);
if (isNew) {
isWantedIn = ((!(flags & PUBLIC_METHOD)
|| mPtr->flags & PUBLIC_METHOD) ? IN_LIST : 0);
isWantedIn |= (mPtr->typePtr == NULL ? NO_IMPLEMENTATION : 0);
Tcl_SetHashValue(hPtr, INT2PTR(isWantedIn));
}
}
}
/*
* Process method names due to private methods on the object's class.
*/
if (flags & PRIVATE_METHOD) {
FOREACH_HASH(namePtr, mPtr, &oPtr->selfCls->classMethods) {
if (mPtr->flags & PRIVATE_METHOD) {
int isNew;
hPtr = Tcl_CreateHashEntry(&names, (char *) namePtr, &isNew);
if (isNew) {
isWantedIn = IN_LIST;
if (mPtr->typePtr == NULL) {
isWantedIn |= NO_IMPLEMENTATION;
}
Tcl_SetHashValue(hPtr, INT2PTR(isWantedIn));
} else if (mPtr->typePtr != NULL) {
isWantedIn = PTR2INT(Tcl_GetHashValue(hPtr));
if (isWantedIn & NO_IMPLEMENTATION) {
isWantedIn &= ~NO_IMPLEMENTATION;
Tcl_SetHashValue(hPtr, INT2PTR(isWantedIn));
}
}
}
}
}
/*
* Process (normal) method names from the class hierarchy and the mixin
* hierarchy.
*/
AddClassMethodNames(oPtr->selfCls, flags, &names, &examinedClasses);
FOREACH(mixinPtr, oPtr->mixins) {
AddClassMethodNames(mixinPtr, flags|TRAVERSED_MIXIN, &names,
&examinedClasses);
}
Tcl_DeleteHashTable(&examinedClasses);
/*
* See how many (visible) method names there are. If none, we do not (and
* should not) try to sort the list of them.
*/
i = 0;
if (names.numEntries != 0) {
const char **strings;
/*
* We need to build the list of methods to sort. We will be using
* qsort() for this, because it is very unlikely that the list will be
* heavily sorted when it is long enough to matter.
*/
strings = ckalloc(sizeof(char *) * names.numEntries);
FOREACH_HASH(namePtr, isWanted, &names) {
if (!(flags & PUBLIC_METHOD) || (PTR2INT(isWanted) & IN_LIST)) {
if (PTR2INT(isWanted) & NO_IMPLEMENTATION) {
continue;
}
strings[i++] = TclGetString(namePtr);
}
}
/*
* Note that 'i' may well be less than names.numEntries when we are
* dealing with public method names.
*/
if (i > 0) {
if (i > 1) {
qsort((void *) strings, i, sizeof(char *), CmpStr);
}
*stringsPtr = strings;
} else {
ckfree(strings);
}
}
Tcl_DeleteHashTable(&names);
return i;
}
int
TclOOGetSortedClassMethodList(
Class *clsPtr, /* The class to get the method names for. */
int flags, /* Whether we just want the public method
* names. */
const char ***stringsPtr) /* Where to write a pointer to the array of
* strings to. */
{
Tcl_HashTable names; /* Tcl_Obj* method name to "wanted in list"
* mapping. */
Tcl_HashTable examinedClasses;
/* Used to track what classes have been looked
* at. Is set-like in nature and keyed by
* pointer to class. */
FOREACH_HASH_DECLS;
int i;
Tcl_Obj *namePtr;
void *isWanted;
Tcl_InitObjHashTable(&names);
Tcl_InitHashTable(&examinedClasses, TCL_ONE_WORD_KEYS);
/*
* Process method names from the class hierarchy and the mixin hierarchy.
*/
AddClassMethodNames(clsPtr, flags, &names, &examinedClasses);
Tcl_DeleteHashTable(&examinedClasses);
/*
* See how many (visible) method names there are. If none, we do not (and
* should not) try to sort the list of them.
*/
i = 0;
if (names.numEntries != 0) {
const char **strings;
/*
* We need to build the list of methods to sort. We will be using
* qsort() for this, because it is very unlikely that the list will be
* heavily sorted when it is long enough to matter.
*/
strings = ckalloc(sizeof(char *) * names.numEntries);
FOREACH_HASH(namePtr, isWanted, &names) {
if (!(flags & PUBLIC_METHOD) || (PTR2INT(isWanted) & IN_LIST)) {
if (PTR2INT(isWanted) & NO_IMPLEMENTATION) {
continue;
}
strings[i++] = TclGetString(namePtr);
}
}
/*
* Note that 'i' may well be less than names.numEntries when we are
* dealing with public method names.
*/
if (i > 0) {
if (i > 1) {
qsort((void *) strings, i, sizeof(char *), CmpStr);
}
*stringsPtr = strings;
} else {
ckfree(strings);
}
}
Tcl_DeleteHashTable(&names);
return i;
}
/*
* Comparator for GetSortedMethodList
*/
static int
CmpStr(
const void *ptr1,
const void *ptr2)
{
const char **strPtr1 = (const char **) ptr1;
const char **strPtr2 = (const char **) ptr2;
return TclpUtfNcmp2(*strPtr1, *strPtr2, strlen(*strPtr1) + 1);
}
/*
* ----------------------------------------------------------------------
*
* AddClassMethodNames --
*
* Adds the method names defined by a class (or its superclasses) to the
* collection being built. The collection is built in a hash table to
* ensure that duplicates are excluded. Helper for GetSortedMethodList().
*
* ----------------------------------------------------------------------
*/
static void
AddClassMethodNames(
Class *clsPtr, /* Class to get method names from. */
const int flags, /* Whether we are interested in just the
* public method names. */
Tcl_HashTable *const namesPtr,
/* Reference to the hash table to put the
* information in. The hash table maps the
* Tcl_Obj * method name to an integral value
* describing whether the method is wanted.
* This ensures that public/private override
* semantics are handled correctly. */
Tcl_HashTable *const examinedClassesPtr)
/* Hash table that tracks what classes have
* already been looked at. The keys are the
* pointers to the classes, and the values are
* immaterial. */
{
/*
* If we've already started looking at this class, stop working on it now
* to prevent repeated work.
*/
if (Tcl_FindHashEntry(examinedClassesPtr, (char *) clsPtr)) {
return;
}
/*
* Scope all declarations so that the compiler can stand a good chance of
* making the recursive step highly efficient. We also hand-implement the
* tail-recursive case using a while loop; C compilers typically cannot do
* tail-recursion optimization usefully.
*/
while (1) {
FOREACH_HASH_DECLS;
Tcl_Obj *namePtr;
Method *mPtr;
int isNew;
(void) Tcl_CreateHashEntry(examinedClassesPtr, (char *) clsPtr,
&isNew);
if (!isNew) {
break;
}
if (clsPtr->mixins.num != 0) {
Class *mixinPtr;
int i;
FOREACH(mixinPtr, clsPtr->mixins) {
if (mixinPtr != clsPtr) {
AddClassMethodNames(mixinPtr, flags|TRAVERSED_MIXIN,
namesPtr, examinedClassesPtr);
}
}
}
FOREACH_HASH(namePtr, mPtr, &clsPtr->classMethods) {
hPtr = Tcl_CreateHashEntry(namesPtr, (char *) namePtr, &isNew);
if (isNew) {
int isWanted = (!(flags & PUBLIC_METHOD)
|| (mPtr->flags & PUBLIC_METHOD)) ? IN_LIST : 0;
isWanted |= (mPtr->typePtr == NULL ? NO_IMPLEMENTATION : 0);
Tcl_SetHashValue(hPtr, INT2PTR(isWanted));
} else if ((PTR2INT(Tcl_GetHashValue(hPtr)) & NO_IMPLEMENTATION)
&& mPtr->typePtr != NULL) {
int isWanted = PTR2INT(Tcl_GetHashValue(hPtr));
isWanted &= ~NO_IMPLEMENTATION;
Tcl_SetHashValue(hPtr, INT2PTR(isWanted));
}
}
if (clsPtr->superclasses.num != 1) {
break;
}
clsPtr = clsPtr->superclasses.list[0];
}
if (clsPtr->superclasses.num != 0) {
Class *superPtr;
int i;
FOREACH(superPtr, clsPtr->superclasses) {
AddClassMethodNames(superPtr, flags, namesPtr,
examinedClassesPtr);
}
}
}
/*
* ----------------------------------------------------------------------
*
* AddSimpleChainToCallContext --
*
* The core of the call-chain construction engine, this handles calling a
* particular method on a particular object. Note that filters and
* unknown handling are already handled by the logic that uses this
* function.
*
* ----------------------------------------------------------------------
*/
static inline void
AddSimpleChainToCallContext(
Object *const oPtr, /* Object to add call chain entries for. */
Tcl_Obj *const methodNameObj,
/* Name of method to add the call chain
* entries for. */
struct ChainBuilder *const cbPtr,
/* Where to add the call chain entries. */
Tcl_HashTable *const doneFilters,
/* Where to record what call chain entries
* have been processed. */
int flags, /* What sort of call chain are we building. */
Class *const filterDecl) /* The class that declared the filter. If
* NULL, either the filter was declared by the
* object or this isn't a filter. */
{
int i;
if (!(flags & (KNOWN_STATE | SPECIAL)) && oPtr->methodsPtr) {
Tcl_HashEntry *hPtr = Tcl_FindHashEntry(oPtr->methodsPtr,
(char *) methodNameObj);
if (hPtr != NULL) {
Method *mPtr = Tcl_GetHashValue(hPtr);
if (flags & PUBLIC_METHOD) {
if (!(mPtr->flags & PUBLIC_METHOD)) {
return;
} else {
flags |= DEFINITE_PUBLIC;
}
} else {
flags |= DEFINITE_PROTECTED;
}
}
}
if (!(flags & SPECIAL)) {
Tcl_HashEntry *hPtr;
Class *mixinPtr;
FOREACH(mixinPtr, oPtr->mixins) {
AddSimpleClassChainToCallContext(mixinPtr, methodNameObj, cbPtr,
doneFilters, flags|TRAVERSED_MIXIN, filterDecl);
}
if (oPtr->methodsPtr) {
hPtr = Tcl_FindHashEntry(oPtr->methodsPtr, (char*) methodNameObj);
if (hPtr != NULL) {
AddMethodToCallChain(Tcl_GetHashValue(hPtr), cbPtr,
doneFilters, filterDecl, flags);
}
}
}
AddSimpleClassChainToCallContext(oPtr->selfCls, methodNameObj, cbPtr,
doneFilters, flags, filterDecl);
}
/*
* ----------------------------------------------------------------------
*
* AddMethodToCallChain --
*
* Utility method that manages the adding of a particular method
* implementation to a call-chain.
*
* ----------------------------------------------------------------------
*/
static inline void
AddMethodToCallChain(
Method *const mPtr, /* Actual method implementation to add to call
* chain (or NULL, a no-op). */
struct ChainBuilder *const cbPtr,
/* The call chain to add the method
* implementation to. */
Tcl_HashTable *const doneFilters,
/* Where to record what filters have been
* processed. If NULL, not processing filters.
* Note that this function does not update
* this hashtable. */
Class *const filterDecl, /* The class that declared the filter. If
* NULL, either the filter was declared by the
* object or this isn't a filter. */
int flags) /* Used to check if we're mixin-consistent
* only. Mixin-consistent means that either
* we're looking to add things from a mixin
* and we have passed a mixin, or we're not
* looking to add things from a mixin and have
* not passed a mixin. */
{
CallChain *callPtr = cbPtr->callChainPtr;
int i;
/*
* Return if this is just an entry used to record whether this is a public
* method. If so, there's nothing real to call and so nothing to add to
* the call chain.
*
* This is also where we enforce mixin-consistency.
*/
if (mPtr == NULL || mPtr->typePtr == NULL || !MIXIN_CONSISTENT(flags)) {
return;
}
/*
* Enforce real private method handling here. We will skip adding this
* method IF
* 1) we are not allowing private methods, AND
* 2) this is a private method, AND
* 3) this is a class method, AND
* 4) this method was not declared by the class of the current object.
*
* This does mean that only classes really handle private methods. This
* should be sufficient for [incr Tcl] support though.
*/
if (!(callPtr->flags & PRIVATE_METHOD)
&& (mPtr->flags & PRIVATE_METHOD)
&& (mPtr->declaringClassPtr != NULL)
&& (mPtr->declaringClassPtr != cbPtr->oPtr->selfCls)) {
return;
}
/*
* First test whether the method is already in the call chain. Skip over
* any leading filters.
*/
for (i = cbPtr->filterLength ; i < callPtr->numChain ; i++) {
if (callPtr->chain[i].mPtr == mPtr &&
callPtr->chain[i].isFilter == (doneFilters != NULL)) {
/*
* Call chain semantics states that methods come as *late* in the
* call chain as possible. This is done by copying down the
* following methods. Note that this does not change the number of
* method invocations in the call chain; it just rearranges them.
*/
Class *declCls = callPtr->chain[i].filterDeclarer;
for (; i + 1 < callPtr->numChain ; i++) {
callPtr->chain[i] = callPtr->chain[i + 1];
}
callPtr->chain[i].mPtr = mPtr;
callPtr->chain[i].isFilter = (doneFilters != NULL);
callPtr->chain[i].filterDeclarer = declCls;
return;
}
}
/*
* Need to really add the method. This is made a bit more complex by the
* fact that we are using some "static" space initially, and only start
* realloc-ing if the chain gets long.
*/
if (callPtr->numChain == CALL_CHAIN_STATIC_SIZE) {
callPtr->chain =
ckalloc(sizeof(struct MInvoke) * (callPtr->numChain + 1));
memcpy(callPtr->chain, callPtr->staticChain,
sizeof(struct MInvoke) * callPtr->numChain);
} else if (callPtr->numChain > CALL_CHAIN_STATIC_SIZE) {
callPtr->chain = ckrealloc(callPtr->chain,
sizeof(struct MInvoke) * (callPtr->numChain + 1));
}
callPtr->chain[i].mPtr = mPtr;
callPtr->chain[i].isFilter = (doneFilters != NULL);
callPtr->chain[i].filterDeclarer = filterDecl;
callPtr->numChain++;
}
/*
* ----------------------------------------------------------------------
*
* InitCallChain --
* Encoding of the policy of how to set up a call chain. Doesn't populate
* the chain with the method implementation data.
*
* ----------------------------------------------------------------------
*/
static inline void
InitCallChain(
CallChain *callPtr,
Object *oPtr,
int flags)
{
callPtr->flags = flags &
(PUBLIC_METHOD | PRIVATE_METHOD | SPECIAL | FILTER_HANDLING);
if (oPtr->flags & USE_CLASS_CACHE) {
oPtr = oPtr->selfCls->thisPtr;
callPtr->flags |= USE_CLASS_CACHE;
}
callPtr->epoch = oPtr->fPtr->epoch;
callPtr->objectCreationEpoch = oPtr->creationEpoch;
callPtr->objectEpoch = oPtr->epoch;
callPtr->refCount = 1;
callPtr->numChain = 0;
callPtr->chain = callPtr->staticChain;
}
/*
* ----------------------------------------------------------------------
*
* IsStillValid --
* Calculates whether the given call chain can be used for executing a
* method for the given object. The condition on a chain from a cached
* location being reusable is:
* - Refers to the same object (same creation epoch), and
* - Still across the same class structure (same global epoch), and
* - Still across the same object strucutre (same local epoch), and
* - No public/private/filter magic leakage (same flags, modulo the fact
* that a public chain will satisfy a non-public call).
*
* ----------------------------------------------------------------------
*/
static inline int
IsStillValid(
CallChain *callPtr,
Object *oPtr,
int flags,
int mask)
{
if ((oPtr->flags & USE_CLASS_CACHE)) {
oPtr = oPtr->selfCls->thisPtr;
flags |= USE_CLASS_CACHE;
}
return ((callPtr->objectCreationEpoch == oPtr->creationEpoch)
&& (callPtr->epoch == oPtr->fPtr->epoch)
&& (callPtr->objectEpoch == oPtr->epoch)
&& ((callPtr->flags & mask) == (flags & mask)));
}
/*
* ----------------------------------------------------------------------
*
* TclOOGetCallContext --
*
* Responsible for constructing the call context, an ordered list of all
* method implementations to be called as part of a method invocation.
* This method is central to the whole operation of the OO system.
*
* ----------------------------------------------------------------------
*/
CallContext *
TclOOGetCallContext(
Object *oPtr, /* The object to get the context for. */
Tcl_Obj *methodNameObj, /* The name of the method to get the context
* for. NULL when getting a constructor or
* destructor chain. */
int flags, /* What sort of context are we looking for.
* Only the bits PUBLIC_METHOD, CONSTRUCTOR,
* PRIVATE_METHOD, DESTRUCTOR and
* FILTER_HANDLING are useful. */
Tcl_Obj *cacheInThisObj) /* What object to cache in, or NULL if it is
* to be in the same object as the
* methodNameObj. */
{
CallContext *contextPtr;
CallChain *callPtr;
struct ChainBuilder cb;
int i, count, doFilters;
Tcl_HashEntry *hPtr;
Tcl_HashTable doneFilters;
if (cacheInThisObj == NULL) {
cacheInThisObj = methodNameObj;
}
if (flags&(SPECIAL|FILTER_HANDLING) || (oPtr->flags&FILTER_HANDLING)) {
hPtr = NULL;
doFilters = 0;
/*
* Check if we have a cached valid constructor or destructor.
*/
if (flags & CONSTRUCTOR) {
callPtr = oPtr->selfCls->constructorChainPtr;
if ((callPtr != NULL)
&& (callPtr->objectEpoch == oPtr->selfCls->thisPtr->epoch)
&& (callPtr->epoch == oPtr->fPtr->epoch)) {
callPtr->refCount++;
goto returnContext;
}
} else if (flags & DESTRUCTOR) {
callPtr = oPtr->selfCls->destructorChainPtr;
if ((oPtr->mixins.num == 0) && (callPtr != NULL)
&& (callPtr->objectEpoch == oPtr->selfCls->thisPtr->epoch)
&& (callPtr->epoch == oPtr->fPtr->epoch)) {
callPtr->refCount++;
goto returnContext;
}
}
} else {
/*
* Check if we can get the chain out of the Tcl_Obj method name or out
* of the cache. This is made a bit more complex by the fact that
* there are multiple different layers of cache (in the Tcl_Obj, in
* the object, and in the class).
*/
const int reuseMask = ((flags & PUBLIC_METHOD) ? ~0 : ~PUBLIC_METHOD);
if (cacheInThisObj->typePtr == &methodNameType) {
callPtr = cacheInThisObj->internalRep.twoPtrValue.ptr1;
if (IsStillValid(callPtr, oPtr, flags, reuseMask)) {
callPtr->refCount++;
goto returnContext;
}
FreeMethodNameRep(cacheInThisObj);
}
if (oPtr->flags & USE_CLASS_CACHE) {
if (oPtr->selfCls->classChainCache != NULL) {
hPtr = Tcl_FindHashEntry(oPtr->selfCls->classChainCache,
(char *) methodNameObj);
} else {
hPtr = NULL;
}
} else {
if (oPtr->chainCache != NULL) {
hPtr = Tcl_FindHashEntry(oPtr->chainCache,
(char *) methodNameObj);
} else {
hPtr = NULL;
}
}
if (hPtr != NULL && Tcl_GetHashValue(hPtr) != NULL) {
callPtr = Tcl_GetHashValue(hPtr);
if (IsStillValid(callPtr, oPtr, flags, reuseMask)) {
callPtr->refCount++;
goto returnContext;
}
Tcl_SetHashValue(hPtr, NULL);
TclOODeleteChain(callPtr);
}
doFilters = 1;
}
callPtr = ckalloc(sizeof(CallChain));
InitCallChain(callPtr, oPtr, flags);
cb.callChainPtr = callPtr;
cb.filterLength = 0;
cb.oPtr = oPtr;
/*
* If we're working with a forced use of unknown, do that now.
*/
if (flags & FORCE_UNKNOWN) {
AddSimpleChainToCallContext(oPtr, oPtr->fPtr->unknownMethodNameObj,
&cb, NULL, BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(oPtr, oPtr->fPtr->unknownMethodNameObj,
&cb, NULL, 0, NULL);
callPtr->flags |= OO_UNKNOWN_METHOD;
callPtr->epoch = -1;
if (callPtr->numChain == 0) {
TclOODeleteChain(callPtr);
return NULL;
}
goto returnContext;
}
/*
* Add all defined filters (if any, and if we're going to be processing
* them; they're not processed for constructors, destructors or when we're
* in the middle of processing a filter).
*/
if (doFilters) {
Tcl_Obj *filterObj;
Class *mixinPtr;
doFilters = 1;
Tcl_InitObjHashTable(&doneFilters);
FOREACH(mixinPtr, oPtr->mixins) {
AddClassFiltersToCallContext(oPtr, mixinPtr, &cb, &doneFilters,
TRAVERSED_MIXIN|BUILDING_MIXINS|OBJECT_MIXIN);
AddClassFiltersToCallContext(oPtr, mixinPtr, &cb, &doneFilters,
OBJECT_MIXIN);
}
FOREACH(filterObj, oPtr->filters) {
AddSimpleChainToCallContext(oPtr, filterObj, &cb, &doneFilters,
BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(oPtr, filterObj, &cb, &doneFilters, 0,
NULL);
}
AddClassFiltersToCallContext(oPtr, oPtr->selfCls, &cb, &doneFilters,
BUILDING_MIXINS);
AddClassFiltersToCallContext(oPtr, oPtr->selfCls, &cb, &doneFilters,
0);
Tcl_DeleteHashTable(&doneFilters);
}
count = cb.filterLength = callPtr->numChain;
/*
* Add the actual method implementations. We have to do this twice to
* handle class mixins right.
*/
AddSimpleChainToCallContext(oPtr, methodNameObj, &cb, NULL,
flags|BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(oPtr, methodNameObj, &cb, NULL, flags, NULL);
/*
* Check to see if the method has no implementation. If so, we probably
* need to add in a call to the unknown method. Otherwise, set up the
* cacheing of the method implementation (if relevant).
*/
if (count == callPtr->numChain) {
/*
* Method does not actually exist. If we're dealing with constructors
* or destructors, this isn't a problem.
*/
if (flags & SPECIAL) {
TclOODeleteChain(callPtr);
return NULL;
}
AddSimpleChainToCallContext(oPtr, oPtr->fPtr->unknownMethodNameObj,
&cb, NULL, BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(oPtr, oPtr->fPtr->unknownMethodNameObj,
&cb, NULL, 0, NULL);
callPtr->flags |= OO_UNKNOWN_METHOD;
callPtr->epoch = -1;
if (count == callPtr->numChain) {
TclOODeleteChain(callPtr);
return NULL;
}
} else if (doFilters) {
if (hPtr == NULL) {
if (oPtr->flags & USE_CLASS_CACHE) {
if (oPtr->selfCls->classChainCache == NULL) {
oPtr->selfCls->classChainCache =
ckalloc(sizeof(Tcl_HashTable));
Tcl_InitObjHashTable(oPtr->selfCls->classChainCache);
}
hPtr = Tcl_CreateHashEntry(oPtr->selfCls->classChainCache,
(char *) methodNameObj, &i);
} else {
if (oPtr->chainCache == NULL) {
oPtr->chainCache = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitObjHashTable(oPtr->chainCache);
}
hPtr = Tcl_CreateHashEntry(oPtr->chainCache,
(char *) methodNameObj, &i);
}
}
callPtr->refCount++;
Tcl_SetHashValue(hPtr, callPtr);
StashCallChain(cacheInThisObj, callPtr);
} else if (flags & CONSTRUCTOR) {
if (oPtr->selfCls->constructorChainPtr) {
TclOODeleteChain(oPtr->selfCls->constructorChainPtr);
}
oPtr->selfCls->constructorChainPtr = callPtr;
callPtr->refCount++;
} else if ((flags & DESTRUCTOR) && oPtr->mixins.num == 0) {
if (oPtr->selfCls->destructorChainPtr) {
TclOODeleteChain(oPtr->selfCls->destructorChainPtr);
}
oPtr->selfCls->destructorChainPtr = callPtr;
callPtr->refCount++;
}
returnContext:
contextPtr = TclStackAlloc(oPtr->fPtr->interp, sizeof(CallContext));
contextPtr->oPtr = oPtr;
/*
* Corresponding TclOODecrRefCount() in TclOODeleteContext
*/
AddRef(oPtr);
contextPtr->callPtr = callPtr;
contextPtr->skip = 2;
contextPtr->index = 0;
return contextPtr;
}
/*
* ----------------------------------------------------------------------
*
* TclOOGetStereotypeCallChain --
*
* Construct a call-chain for a method that would be used by a
* stereotypical instance of the given class (i.e., where the object has
* no definitions special to itself).
*
* ----------------------------------------------------------------------
*/
CallChain *
TclOOGetStereotypeCallChain(
Class *clsPtr, /* The object to get the context for. */
Tcl_Obj *methodNameObj, /* The name of the method to get the context
* for. NULL when getting a constructor or
* destructor chain. */
int flags) /* What sort of context are we looking for.
* Only the bits PUBLIC_METHOD, CONSTRUCTOR,
* PRIVATE_METHOD, DESTRUCTOR and
* FILTER_HANDLING are useful. */
{
CallChain *callPtr;
struct ChainBuilder cb;
int i, count;
Foundation *fPtr = clsPtr->thisPtr->fPtr;
Tcl_HashEntry *hPtr;
Tcl_HashTable doneFilters;
Object obj;
/*
* Synthesize a temporary stereotypical object so that we can use existing
* machinery to produce the stereotypical call chain.
*/
memset(&obj, 0, sizeof(Object));
obj.fPtr = fPtr;
obj.selfCls = clsPtr;
obj.refCount = 1;
obj.flags = USE_CLASS_CACHE;
/*
* Check if we can get the chain out of the Tcl_Obj method name or out of
* the cache. This is made a bit more complex by the fact that there are
* multiple different layers of cache (in the Tcl_Obj, in the object, and
* in the class).
*/
if (clsPtr->classChainCache != NULL) {
hPtr = Tcl_FindHashEntry(clsPtr->classChainCache,
(char *) methodNameObj);
if (hPtr != NULL && Tcl_GetHashValue(hPtr) != NULL) {
const int reuseMask =
((flags & PUBLIC_METHOD) ? ~0 : ~PUBLIC_METHOD);
callPtr = Tcl_GetHashValue(hPtr);
if (IsStillValid(callPtr, &obj, flags, reuseMask)) {
callPtr->refCount++;
return callPtr;
}
Tcl_SetHashValue(hPtr, NULL);
TclOODeleteChain(callPtr);
}
} else {
hPtr = NULL;
}
callPtr = ckalloc(sizeof(CallChain));
memset(callPtr, 0, sizeof(CallChain));
callPtr->flags = flags & (PUBLIC_METHOD|PRIVATE_METHOD|FILTER_HANDLING);
callPtr->epoch = fPtr->epoch;
callPtr->objectCreationEpoch = fPtr->tsdPtr->nsCount;
callPtr->objectEpoch = clsPtr->thisPtr->epoch;
callPtr->refCount = 1;
callPtr->chain = callPtr->staticChain;
cb.callChainPtr = callPtr;
cb.filterLength = 0;
cb.oPtr = &obj;
/*
* Add all defined filters (if any, and if we're going to be processing
* them; they're not processed for constructors, destructors or when we're
* in the middle of processing a filter).
*/
Tcl_InitObjHashTable(&doneFilters);
AddClassFiltersToCallContext(&obj, clsPtr, &cb, &doneFilters,
BUILDING_MIXINS);
AddClassFiltersToCallContext(&obj, clsPtr, &cb, &doneFilters, 0);
Tcl_DeleteHashTable(&doneFilters);
count = cb.filterLength = callPtr->numChain;
/*
* Add the actual method implementations.
*/
AddSimpleChainToCallContext(&obj, methodNameObj, &cb, NULL,
flags|BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(&obj, methodNameObj, &cb, NULL, flags, NULL);
/*
* Check to see if the method has no implementation. If so, we probably
* need to add in a call to the unknown method. Otherwise, set up the
* cacheing of the method implementation (if relevant).
*/
if (count == callPtr->numChain) {
AddSimpleChainToCallContext(&obj, fPtr->unknownMethodNameObj, &cb,
NULL, BUILDING_MIXINS, NULL);
AddSimpleChainToCallContext(&obj, fPtr->unknownMethodNameObj, &cb,
NULL, 0, NULL);
callPtr->flags |= OO_UNKNOWN_METHOD;
callPtr->epoch = -1;
if (count == callPtr->numChain) {
TclOODeleteChain(callPtr);
return NULL;
}
} else {
if (hPtr == NULL) {
if (clsPtr->classChainCache == NULL) {
clsPtr->classChainCache = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitObjHashTable(clsPtr->classChainCache);
}
hPtr = Tcl_CreateHashEntry(clsPtr->classChainCache,
(char *) methodNameObj, &i);
}
callPtr->refCount++;
Tcl_SetHashValue(hPtr, callPtr);
StashCallChain(methodNameObj, callPtr);
}
return callPtr;
}
/*
* ----------------------------------------------------------------------
*
* AddClassFiltersToCallContext --
*
* Logic to make extracting all the filters from the class context much
* easier.
*
* ----------------------------------------------------------------------
*/
static void
AddClassFiltersToCallContext(
Object *const oPtr, /* Object that the filters operate on. */
Class *clsPtr, /* Class to get the filters from. */
struct ChainBuilder *const cbPtr,
/* Context to fill with call chain entries. */
Tcl_HashTable *const doneFilters,
/* Where to record what filters have been
* processed. Keys are objects, values are
* ignored. */
int flags) /* Whether we've gone along a mixin link
* yet. */
{
int i, clearedFlags =
flags & ~(TRAVERSED_MIXIN|OBJECT_MIXIN|BUILDING_MIXINS);
Class *superPtr, *mixinPtr;
Tcl_Obj *filterObj;
tailRecurse:
if (clsPtr == NULL) {
return;
}
/*
* Add all the filters defined by classes mixed into the main class
* hierarchy.
*/
FOREACH(mixinPtr, clsPtr->mixins) {
AddClassFiltersToCallContext(oPtr, mixinPtr, cbPtr, doneFilters,
flags|TRAVERSED_MIXIN);
}
/*
* Add all the class filters from the current class. Note that the filters
* are added starting at the object root, as this allows the object to
* override how filters work to extend their behaviour.
*/
if (MIXIN_CONSISTENT(flags)) {
FOREACH(filterObj, clsPtr->filters) {
int isNew;
(void) Tcl_CreateHashEntry(doneFilters, (char *) filterObj,
&isNew);
if (isNew) {
AddSimpleChainToCallContext(oPtr, filterObj, cbPtr,
doneFilters, clearedFlags|BUILDING_MIXINS, clsPtr);
AddSimpleChainToCallContext(oPtr, filterObj, cbPtr,
doneFilters, clearedFlags, clsPtr);
}
}
}
/*
* Now process the recursive case. Notice the tail-call optimization.
*/
switch (clsPtr->superclasses.num) {
case 1:
clsPtr = clsPtr->superclasses.list[0];
goto tailRecurse;
default:
FOREACH(superPtr, clsPtr->superclasses) {
AddClassFiltersToCallContext(oPtr, superPtr, cbPtr, doneFilters,
flags);
}
case 0:
return;
}
}
/*
* ----------------------------------------------------------------------
*
* AddSimpleClassChainToCallContext --
*
* Construct a call-chain from a class hierarchy.
*
* ----------------------------------------------------------------------
*/
static void
AddSimpleClassChainToCallContext(
Class *classPtr, /* Class to add the call chain entries for. */
Tcl_Obj *const methodNameObj,
/* Name of method to add the call chain
* entries for. */
struct ChainBuilder *const cbPtr,
/* Where to add the call chain entries. */
Tcl_HashTable *const doneFilters,
/* Where to record what call chain entries
* have been processed. */
int flags, /* What sort of call chain are we building. */
Class *const filterDecl) /* The class that declared the filter. If
* NULL, either the filter was declared by the
* object or this isn't a filter. */
{
int i;
Class *superPtr;
/*
* We hard-code the tail-recursive form. It's by far the most common case
* *and* it is much more gentle on the stack.
*
* Note that mixins must be processed before the main class hierarchy.
* [Bug 1998221]
*/
tailRecurse:
FOREACH(superPtr, classPtr->mixins) {
AddSimpleClassChainToCallContext(superPtr, methodNameObj, cbPtr,
doneFilters, flags|TRAVERSED_MIXIN, filterDecl);
}
if (flags & CONSTRUCTOR) {
AddMethodToCallChain(classPtr->constructorPtr, cbPtr, doneFilters,
filterDecl, flags);
} else if (flags & DESTRUCTOR) {
AddMethodToCallChain(classPtr->destructorPtr, cbPtr, doneFilters,
filterDecl, flags);
} else {
Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&classPtr->classMethods,
(char *) methodNameObj);
if (hPtr != NULL) {
Method *mPtr = Tcl_GetHashValue(hPtr);
if (!(flags & KNOWN_STATE)) {
if (flags & PUBLIC_METHOD) {
if (mPtr->flags & PUBLIC_METHOD) {
flags |= DEFINITE_PUBLIC;
} else {
return;
}
} else {
flags |= DEFINITE_PROTECTED;
}
}
AddMethodToCallChain(mPtr, cbPtr, doneFilters, filterDecl, flags);
}
}
switch (classPtr->superclasses.num) {
case 1:
classPtr = classPtr->superclasses.list[0];
goto tailRecurse;
default:
FOREACH(superPtr, classPtr->superclasses) {
AddSimpleClassChainToCallContext(superPtr, methodNameObj, cbPtr,
doneFilters, flags, filterDecl);
}
case 0:
return;
}
}
/*
* ----------------------------------------------------------------------
*
* TclOORenderCallChain --
*
* Create a description of a call chain. Used in [info object call],
* [info class call], and [self call].
*
* ----------------------------------------------------------------------
*/
Tcl_Obj *
TclOORenderCallChain(
Tcl_Interp *interp,
CallChain *callPtr)
{
Tcl_Obj *filterLiteral, *methodLiteral, *objectLiteral;
Tcl_Obj *resultObj, *descObjs[4], **objv;
Foundation *fPtr = TclOOGetFoundation(interp);
int i;
/*
* Allocate the literals (potentially) used in our description.
*/
filterLiteral = Tcl_NewStringObj("filter", -1);
Tcl_IncrRefCount(filterLiteral);
methodLiteral = Tcl_NewStringObj("method", -1);
Tcl_IncrRefCount(methodLiteral);
objectLiteral = Tcl_NewStringObj("object", -1);
Tcl_IncrRefCount(objectLiteral);
/*
* Do the actual construction of the descriptions. They consist of a list
* of triples that describe the details of how a method is understood. For
* each triple, the first word is the type of invocation ("method" is
* normal, "unknown" is special because it adds the method name as an
* extra argument when handled by some method types, and "filter" is
* special because it's a filter method). The second word is the name of
* the method in question (which differs for "unknown" and "filter" types)
* and the third word is the full name of the class that declares the
* method (or "object" if it is declared on the instance).
*/
objv = TclStackAlloc(interp, callPtr->numChain * sizeof(Tcl_Obj *));
for (i = 0 ; i < callPtr->numChain ; i++) {
struct MInvoke *miPtr = &callPtr->chain[i];
descObjs[0] = miPtr->isFilter
? filterLiteral
: callPtr->flags & OO_UNKNOWN_METHOD
? fPtr->unknownMethodNameObj
: methodLiteral;
descObjs[1] = callPtr->flags & CONSTRUCTOR
? fPtr->constructorName
: callPtr->flags & DESTRUCTOR
? fPtr->destructorName
: miPtr->mPtr->namePtr;
descObjs[2] = miPtr->mPtr->declaringClassPtr
? Tcl_GetObjectName(interp,
(Tcl_Object) miPtr->mPtr->declaringClassPtr->thisPtr)
: objectLiteral;
descObjs[3] = Tcl_NewStringObj(miPtr->mPtr->typePtr->name, -1);
objv[i] = Tcl_NewListObj(4, descObjs);
}
/*
* Drop the local references to the literals; if they're actually used,
* they'll live on the description itself.
*/
Tcl_DecrRefCount(filterLiteral);
Tcl_DecrRefCount(methodLiteral);
Tcl_DecrRefCount(objectLiteral);
/*
* Finish building the description and return it.
*/
resultObj = Tcl_NewListObj(callPtr->numChain, objv);
TclStackFree(interp, objv);
return resultObj;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/