3038 lines
82 KiB
C
3038 lines
82 KiB
C
/*
|
||
* tclOO.c --
|
||
*
|
||
* This file contains the object-system core (NB: not Tcl_Obj, but ::oo)
|
||
*
|
||
* Copyright (c) 2005-2012 by Donal K. Fellows
|
||
* Copyright (c) 2017 by Nathan Coulter
|
||
*
|
||
* 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"
|
||
|
||
/*
|
||
* Commands in oo::define.
|
||
*/
|
||
|
||
static const struct {
|
||
const char *name;
|
||
Tcl_ObjCmdProc *objProc;
|
||
int flag;
|
||
} defineCmds[] = {
|
||
{"constructor", TclOODefineConstructorObjCmd, 0},
|
||
{"deletemethod", TclOODefineDeleteMethodObjCmd, 0},
|
||
{"destructor", TclOODefineDestructorObjCmd, 0},
|
||
{"export", TclOODefineExportObjCmd, 0},
|
||
{"forward", TclOODefineForwardObjCmd, 0},
|
||
{"method", TclOODefineMethodObjCmd, 0},
|
||
{"renamemethod", TclOODefineRenameMethodObjCmd, 0},
|
||
{"self", TclOODefineSelfObjCmd, 0},
|
||
{"unexport", TclOODefineUnexportObjCmd, 0},
|
||
{NULL, NULL, 0}
|
||
}, objdefCmds[] = {
|
||
{"class", TclOODefineClassObjCmd, 1},
|
||
{"deletemethod", TclOODefineDeleteMethodObjCmd, 1},
|
||
{"export", TclOODefineExportObjCmd, 1},
|
||
{"forward", TclOODefineForwardObjCmd, 1},
|
||
{"method", TclOODefineMethodObjCmd, 1},
|
||
{"renamemethod", TclOODefineRenameMethodObjCmd, 1},
|
||
{"unexport", TclOODefineUnexportObjCmd, 1},
|
||
{NULL, NULL, 0}
|
||
};
|
||
|
||
/*
|
||
* What sort of size of things we like to allocate.
|
||
*/
|
||
|
||
#define ALLOC_CHUNK 8
|
||
|
||
/*
|
||
* Function declarations for things defined in this file.
|
||
*/
|
||
|
||
static Object * AllocObject(Tcl_Interp *interp, const char *nameStr,
|
||
Namespace *nsPtr, const char *nsNameStr);
|
||
static int CloneClassMethod(Tcl_Interp *interp, Class *clsPtr,
|
||
Method *mPtr, Tcl_Obj *namePtr,
|
||
Method **newMPtrPtr);
|
||
static int CloneObjectMethod(Tcl_Interp *interp, Object *oPtr,
|
||
Method *mPtr, Tcl_Obj *namePtr);
|
||
static void DeletedDefineNamespace(ClientData clientData);
|
||
static void DeletedObjdefNamespace(ClientData clientData);
|
||
static void DeletedHelpersNamespace(ClientData clientData);
|
||
static Tcl_NRPostProc FinalizeAlloc;
|
||
static Tcl_NRPostProc FinalizeNext;
|
||
static Tcl_NRPostProc FinalizeObjectCall;
|
||
static void initClassPath(Tcl_Interp * interp, Class *clsPtr);
|
||
static int InitFoundation(Tcl_Interp *interp);
|
||
static void KillFoundation(ClientData clientData,
|
||
Tcl_Interp *interp);
|
||
static void MyDeleted(ClientData clientData);
|
||
static void ObjectNamespaceDeleted(ClientData clientData);
|
||
static void ObjectRenamedTrace(ClientData clientData,
|
||
Tcl_Interp *interp, const char *oldName,
|
||
const char *newName, int flags);
|
||
static inline void SquelchCachedName(Object *oPtr);
|
||
|
||
static int PublicObjectCmd(ClientData clientData,
|
||
Tcl_Interp *interp, int objc,
|
||
Tcl_Obj *const *objv);
|
||
static int PublicNRObjectCmd(ClientData clientData,
|
||
Tcl_Interp *interp, int objc,
|
||
Tcl_Obj *const *objv);
|
||
static int PrivateObjectCmd(ClientData clientData,
|
||
Tcl_Interp *interp, int objc,
|
||
Tcl_Obj *const *objv);
|
||
static int PrivateNRObjectCmd(ClientData clientData,
|
||
Tcl_Interp *interp, int objc,
|
||
Tcl_Obj *const *objv);
|
||
static void RemoveClass(Class ** list, int num, int idx);
|
||
static void RemoveObject(Object ** list, int num, int idx);
|
||
|
||
/*
|
||
* Methods in the oo::object and oo::class classes. First, we define a helper
|
||
* macro that makes building the method type declaration structure a lot
|
||
* easier. No point in making life harder than it has to be!
|
||
*
|
||
* Note that the core methods don't need clone or free proc callbacks.
|
||
*/
|
||
|
||
#define DCM(name,visibility,proc) \
|
||
{name,visibility,\
|
||
{TCL_OO_METHOD_VERSION_CURRENT,"core method: "#name,proc,NULL,NULL}}
|
||
|
||
static const DeclaredClassMethod objMethods[] = {
|
||
DCM("destroy", 1, TclOO_Object_Destroy),
|
||
DCM("eval", 0, TclOO_Object_Eval),
|
||
DCM("unknown", 0, TclOO_Object_Unknown),
|
||
DCM("variable", 0, TclOO_Object_LinkVar),
|
||
DCM("varname", 0, TclOO_Object_VarName),
|
||
{NULL, 0, {0, NULL, NULL, NULL, NULL}}
|
||
}, clsMethods[] = {
|
||
DCM("create", 1, TclOO_Class_Create),
|
||
DCM("new", 1, TclOO_Class_New),
|
||
DCM("createWithNamespace", 0, TclOO_Class_CreateNs),
|
||
{NULL, 0, {0, NULL, NULL, NULL, NULL}}
|
||
};
|
||
|
||
/*
|
||
* And for the oo::class constructor...
|
||
*/
|
||
|
||
static const Tcl_MethodType classConstructor = {
|
||
TCL_OO_METHOD_VERSION_CURRENT,
|
||
"oo::class constructor",
|
||
TclOO_Class_Constructor, NULL, NULL
|
||
};
|
||
|
||
/*
|
||
* Scripted parts of TclOO. First, the main script (cannot be outside this
|
||
* file).
|
||
*/
|
||
|
||
static const char *initScript =
|
||
"package ifneeded TclOO " TCLOO_PATCHLEVEL " {# Already present, OK?};"
|
||
"namespace eval ::oo { variable version " TCLOO_VERSION " };"
|
||
"namespace eval ::oo { variable patchlevel " TCLOO_PATCHLEVEL " };";
|
||
/* "tcl_findLibrary tcloo $oo::version $oo::version" */
|
||
/* " tcloo.tcl OO_LIBRARY oo::library;"; */
|
||
|
||
/*
|
||
* The scripted part of the definitions of slots.
|
||
*/
|
||
|
||
static const char *slotScript =
|
||
"::oo::define ::oo::Slot {\n"
|
||
" method Get {} {error unimplemented}\n"
|
||
" method Set list {error unimplemented}\n"
|
||
" method -set args {\n"
|
||
" uplevel 1 [list [namespace which my] Set $args]\n"
|
||
" }\n"
|
||
" method -append args {\n"
|
||
" uplevel 1 [list [namespace which my] Set [list"
|
||
" {*}[uplevel 1 [list [namespace which my] Get]] {*}$args]]\n"
|
||
" }\n"
|
||
" method -clear {} {uplevel 1 [list [namespace which my] Set {}]}\n"
|
||
" forward --default-operation my -append\n"
|
||
" method unknown {args} {\n"
|
||
" set def --default-operation\n"
|
||
" if {[llength $args] == 0} {\n"
|
||
" return [uplevel 1 [list [namespace which my] $def]]\n"
|
||
" } elseif {![string match -* [lindex $args 0]]} {\n"
|
||
" return [uplevel 1 [list [namespace which my] $def {*}$args]]\n"
|
||
" }\n"
|
||
" next {*}$args\n"
|
||
" }\n"
|
||
" export -set -append -clear\n"
|
||
" unexport unknown destroy\n"
|
||
"}\n"
|
||
"::oo::objdefine ::oo::define::superclass forward --default-operation my -set\n"
|
||
"::oo::objdefine ::oo::define::mixin forward --default-operation my -set\n"
|
||
"::oo::objdefine ::oo::objdefine::mixin forward --default-operation my -set\n";
|
||
|
||
/*
|
||
* The body of the <cloned> method of oo::object.
|
||
*/
|
||
|
||
static const char *clonedBody =
|
||
"foreach p [info procs [info object namespace $originObject]::*] {"
|
||
" set args [info args $p];"
|
||
" set idx -1;"
|
||
" foreach a $args {"
|
||
" lset args [incr idx] "
|
||
" [if {[info default $p $a d]} {list $a $d} {list $a}]"
|
||
" };"
|
||
" set b [info body $p];"
|
||
" set p [namespace tail $p];"
|
||
" proc $p $args $b;"
|
||
"};"
|
||
"foreach v [info vars [info object namespace $originObject]::*] {"
|
||
" upvar 0 $v vOrigin;"
|
||
" namespace upvar [namespace current] [namespace tail $v] vNew;"
|
||
" if {[info exists vOrigin]} {"
|
||
" if {[array exists vOrigin]} {"
|
||
" array set vNew [array get vOrigin];"
|
||
" } else {"
|
||
" set vNew $vOrigin;"
|
||
" }"
|
||
" }"
|
||
"}";
|
||
|
||
/*
|
||
* The actual definition of the variable holding the TclOO stub table.
|
||
*/
|
||
|
||
MODULE_SCOPE const TclOOStubs tclOOStubs;
|
||
|
||
/*
|
||
* Convenience macro for getting the foundation from an interpreter.
|
||
*/
|
||
|
||
#define GetFoundation(interp) \
|
||
((Foundation *)((Interp *)(interp))->objectFoundation)
|
||
|
||
/*
|
||
* Macros to make inspecting into the guts of an object cleaner.
|
||
*
|
||
* The ocPtr parameter (only in these macros) is assumed to work fine with
|
||
* either an oPtr or a classPtr. Note that the roots oo::object and oo::class
|
||
* have _both_ their object and class flags tagged with ROOT_OBJECT and
|
||
* ROOT_CLASS respectively.
|
||
*/
|
||
|
||
#define Destructing(oPtr) ((oPtr)->flags & OBJECT_DESTRUCTING)
|
||
#define IsRootObject(ocPtr) ((ocPtr)->flags & ROOT_OBJECT)
|
||
#define IsRootClass(ocPtr) ((ocPtr)->flags & ROOT_CLASS)
|
||
#define IsRoot(ocPtr) ((ocPtr)->flags & (ROOT_OBJECT|ROOT_CLASS))
|
||
|
||
#define RemoveItem(type, lst, i) \
|
||
do { \
|
||
Remove ## type ((lst).list, (lst).num, i); \
|
||
(lst).num--; \
|
||
} while (0)
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOInit --
|
||
*
|
||
* Called to initialise the OO system within an interpreter.
|
||
*
|
||
* Result:
|
||
* TCL_OK if the setup succeeded. Currently assumed to always work.
|
||
*
|
||
* Side effects:
|
||
* Creates namespaces, commands, several classes and a number of
|
||
* callbacks. Upon return, the OO system is ready for use.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOOInit(
|
||
Tcl_Interp *interp) /* The interpreter to install into. */
|
||
{
|
||
/*
|
||
* Build the core of the OO system.
|
||
*/
|
||
|
||
if (InitFoundation(interp) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Run our initialization script and, if that works, declare the package
|
||
* to be fully provided.
|
||
*/
|
||
|
||
if (Tcl_Eval(interp, initScript) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
return Tcl_PkgProvideEx(interp, "TclOO", TCLOO_PATCHLEVEL,
|
||
(ClientData) &tclOOStubs);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOGetFoundation --
|
||
*
|
||
* Get a reference to the OO core class system.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Foundation *
|
||
TclOOGetFoundation(
|
||
Tcl_Interp *interp)
|
||
{
|
||
return GetFoundation(interp);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* InitFoundation --
|
||
*
|
||
* Set up the core of the OO core class system. This is a structure
|
||
* holding references to the magical bits that need to be known about in
|
||
* other places, plus the oo::object and oo::class classes.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
InitFoundation(
|
||
Tcl_Interp *interp)
|
||
{
|
||
static Tcl_ThreadDataKey tsdKey;
|
||
ThreadLocalData *tsdPtr =
|
||
Tcl_GetThreadData(&tsdKey, sizeof(ThreadLocalData));
|
||
Foundation *fPtr = ckalloc(sizeof(Foundation));
|
||
Tcl_Obj *namePtr, *argsPtr, *bodyPtr;
|
||
|
||
Class fakeCls;
|
||
Object fakeObject;
|
||
|
||
Tcl_DString buffer;
|
||
Command *cmdPtr;
|
||
int i;
|
||
|
||
/*
|
||
* Initialize the structure that holds the OO system core. This is
|
||
* attached to the interpreter via an assocData entry; not very efficient,
|
||
* but the best we can do without hacking the core more.
|
||
*/
|
||
|
||
memset(fPtr, 0, sizeof(Foundation));
|
||
((Interp *) interp)->objectFoundation = fPtr;
|
||
fPtr->interp = interp;
|
||
fPtr->ooNs = Tcl_CreateNamespace(interp, "::oo", fPtr, NULL);
|
||
Tcl_Export(interp, fPtr->ooNs, "[a-z]*", 1);
|
||
fPtr->defineNs = Tcl_CreateNamespace(interp, "::oo::define", fPtr,
|
||
DeletedDefineNamespace);
|
||
fPtr->objdefNs = Tcl_CreateNamespace(interp, "::oo::objdefine", fPtr,
|
||
DeletedObjdefNamespace);
|
||
fPtr->helpersNs = Tcl_CreateNamespace(interp, "::oo::Helpers", fPtr,
|
||
DeletedHelpersNamespace);
|
||
fPtr->epoch = 0;
|
||
fPtr->tsdPtr = tsdPtr;
|
||
TclNewLiteralStringObj(fPtr->unknownMethodNameObj, "unknown");
|
||
TclNewLiteralStringObj(fPtr->constructorName, "<constructor>");
|
||
TclNewLiteralStringObj(fPtr->destructorName, "<destructor>");
|
||
TclNewLiteralStringObj(fPtr->clonedName, "<cloned>");
|
||
TclNewLiteralStringObj(fPtr->defineName, "::oo::define");
|
||
Tcl_IncrRefCount(fPtr->unknownMethodNameObj);
|
||
Tcl_IncrRefCount(fPtr->constructorName);
|
||
Tcl_IncrRefCount(fPtr->destructorName);
|
||
Tcl_IncrRefCount(fPtr->clonedName);
|
||
Tcl_IncrRefCount(fPtr->defineName);
|
||
Tcl_CreateObjCommand(interp, "::oo::UnknownDefinition",
|
||
TclOOUnknownDefinition, NULL, NULL);
|
||
TclNewLiteralStringObj(namePtr, "::oo::UnknownDefinition");
|
||
Tcl_SetNamespaceUnknownHandler(interp, fPtr->defineNs, namePtr);
|
||
Tcl_SetNamespaceUnknownHandler(interp, fPtr->objdefNs, namePtr);
|
||
|
||
/*
|
||
* Create the subcommands in the oo::define and oo::objdefine spaces.
|
||
*/
|
||
|
||
Tcl_DStringInit(&buffer);
|
||
for (i = 0 ; defineCmds[i].name ; i++) {
|
||
TclDStringAppendLiteral(&buffer, "::oo::define::");
|
||
Tcl_DStringAppend(&buffer, defineCmds[i].name, -1);
|
||
Tcl_CreateObjCommand(interp, Tcl_DStringValue(&buffer),
|
||
defineCmds[i].objProc, INT2PTR(defineCmds[i].flag), NULL);
|
||
Tcl_DStringFree(&buffer);
|
||
}
|
||
for (i = 0 ; objdefCmds[i].name ; i++) {
|
||
TclDStringAppendLiteral(&buffer, "::oo::objdefine::");
|
||
Tcl_DStringAppend(&buffer, objdefCmds[i].name, -1);
|
||
Tcl_CreateObjCommand(interp, Tcl_DStringValue(&buffer),
|
||
objdefCmds[i].objProc, INT2PTR(objdefCmds[i].flag), NULL);
|
||
Tcl_DStringFree(&buffer);
|
||
}
|
||
|
||
Tcl_CallWhenDeleted(interp, KillFoundation, NULL);
|
||
|
||
/*
|
||
* Create the objects at the core of the object system. These need to be
|
||
* spliced manually.
|
||
*/
|
||
|
||
/*
|
||
* Stand up a phony class for bootstrapping.
|
||
*/
|
||
|
||
fPtr->objectCls = &fakeCls;
|
||
|
||
/*
|
||
* Referenced in TclOOAllocClass to increment the refCount.
|
||
*/
|
||
|
||
fakeCls.thisPtr = &fakeObject;
|
||
|
||
fPtr->objectCls = TclOOAllocClass(interp,
|
||
AllocObject(interp, "object", (Namespace *)fPtr->ooNs, NULL));
|
||
/*
|
||
* Corresponding TclOODecrRefCount in KillFoudation.
|
||
*/
|
||
|
||
AddRef(fPtr->objectCls->thisPtr);
|
||
|
||
/*
|
||
* This is why it is unnecessary in this routine to replace the
|
||
* incremented reference count of fPtr->objectCls that was swallowed by
|
||
* fakeObject.
|
||
*/
|
||
|
||
fPtr->objectCls->superclasses.num = 0;
|
||
ckfree(fPtr->objectCls->superclasses.list);
|
||
fPtr->objectCls->superclasses.list = NULL;
|
||
|
||
/*
|
||
* Special initialization for the primordial objects.
|
||
*/
|
||
|
||
fPtr->objectCls->thisPtr->flags |= ROOT_OBJECT;
|
||
fPtr->objectCls->flags |= ROOT_OBJECT;
|
||
|
||
fPtr->classCls = TclOOAllocClass(interp,
|
||
AllocObject(interp, "class", (Namespace *)fPtr->ooNs, NULL));
|
||
|
||
/*
|
||
* Corresponding TclOODecrRefCount in KillFoudation.
|
||
*/
|
||
|
||
AddRef(fPtr->classCls->thisPtr);
|
||
|
||
/*
|
||
* Increment reference counts for each reference because these
|
||
* relationships can be dynamically changed.
|
||
*
|
||
* Corresponding TclOODecrRefCount for all incremented refcounts is in
|
||
* KillFoundation.
|
||
*/
|
||
|
||
/*
|
||
* Rewire bootstrapped objects.
|
||
*/
|
||
|
||
fPtr->objectCls->thisPtr->selfCls = fPtr->classCls;
|
||
AddRef(fPtr->classCls->thisPtr);
|
||
TclOOAddToInstances(fPtr->objectCls->thisPtr, fPtr->classCls);
|
||
|
||
fPtr->classCls->thisPtr->selfCls = fPtr->classCls;
|
||
AddRef(fPtr->classCls->thisPtr);
|
||
TclOOAddToInstances(fPtr->classCls->thisPtr, fPtr->classCls);
|
||
|
||
fPtr->classCls->thisPtr->flags |= ROOT_CLASS;
|
||
fPtr->classCls->flags |= ROOT_CLASS;
|
||
|
||
/*
|
||
* Standard initialization for new Objects.
|
||
*/
|
||
|
||
TclOOAddToSubclasses(fPtr->classCls, fPtr->objectCls);
|
||
|
||
/*
|
||
* Basic method declarations for the core classes.
|
||
*/
|
||
|
||
for (i = 0 ; objMethods[i].name ; i++) {
|
||
TclOONewBasicMethod(interp, fPtr->objectCls, &objMethods[i]);
|
||
}
|
||
for (i = 0 ; clsMethods[i].name ; i++) {
|
||
TclOONewBasicMethod(interp, fPtr->classCls, &clsMethods[i]);
|
||
}
|
||
|
||
/*
|
||
* Create the default <cloned> method implementation, used when 'oo::copy'
|
||
* is called to finish the copying of one object to another.
|
||
*/
|
||
|
||
TclNewLiteralStringObj(argsPtr, "originObject");
|
||
Tcl_IncrRefCount(argsPtr);
|
||
bodyPtr = Tcl_NewStringObj(clonedBody, -1);
|
||
TclOONewProcMethod(interp, fPtr->objectCls, 0, fPtr->clonedName, argsPtr,
|
||
bodyPtr, NULL);
|
||
TclDecrRefCount(argsPtr);
|
||
|
||
/*
|
||
* Finish setting up the class of classes by marking the 'new' method as
|
||
* private; classes, unlike general objects, must have explicit names. We
|
||
* also need to create the constructor for classes.
|
||
*/
|
||
|
||
TclNewLiteralStringObj(namePtr, "new");
|
||
Tcl_NewInstanceMethod(interp, (Tcl_Object) fPtr->classCls->thisPtr,
|
||
namePtr /* keeps ref */, 0 /* private */, NULL, NULL);
|
||
fPtr->classCls->constructorPtr = (Method *) Tcl_NewMethod(interp,
|
||
(Tcl_Class) fPtr->classCls, NULL, 0, &classConstructor, NULL);
|
||
|
||
/*
|
||
* Create non-object commands and plug ourselves into the Tcl [info]
|
||
* ensemble.
|
||
*/
|
||
|
||
cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "::oo::Helpers::next",
|
||
NULL, TclOONextObjCmd, NULL, NULL);
|
||
cmdPtr->compileProc = TclCompileObjectNextCmd;
|
||
cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "::oo::Helpers::nextto",
|
||
NULL, TclOONextToObjCmd, NULL, NULL);
|
||
cmdPtr->compileProc = TclCompileObjectNextToCmd;
|
||
cmdPtr = (Command *) Tcl_CreateObjCommand(interp, "::oo::Helpers::self",
|
||
TclOOSelfObjCmd, NULL, NULL);
|
||
cmdPtr->compileProc = TclCompileObjectSelfCmd;
|
||
Tcl_CreateObjCommand(interp, "::oo::define", TclOODefineObjCmd, NULL,
|
||
NULL);
|
||
Tcl_CreateObjCommand(interp, "::oo::objdefine", TclOOObjDefObjCmd, NULL,
|
||
NULL);
|
||
Tcl_CreateObjCommand(interp, "::oo::copy", TclOOCopyObjectCmd, NULL,NULL);
|
||
TclOOInitInfo(interp);
|
||
|
||
/*
|
||
* Now make the class of slots.
|
||
*/
|
||
|
||
if (TclOODefineSlots(fPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
return Tcl_Eval(interp, slotScript);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* DeletedDefineNamespace, DeletedObjdefNamespace, DeletedHelpersNamespace --
|
||
*
|
||
* Simple helpers used to clear fields of the foundation when they no
|
||
* longer hold useful information.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
DeletedDefineNamespace(
|
||
ClientData clientData)
|
||
{
|
||
Foundation *fPtr = clientData;
|
||
|
||
fPtr->defineNs = NULL;
|
||
}
|
||
|
||
static void
|
||
DeletedObjdefNamespace(
|
||
ClientData clientData)
|
||
{
|
||
Foundation *fPtr = clientData;
|
||
|
||
fPtr->objdefNs = NULL;
|
||
}
|
||
|
||
static void
|
||
DeletedHelpersNamespace(
|
||
ClientData clientData)
|
||
{
|
||
Foundation *fPtr = clientData;
|
||
|
||
fPtr->helpersNs = NULL;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* KillFoundation --
|
||
*
|
||
* Delete those parts of the OO core that are not deleted automatically
|
||
* when the objects and classes themselves are destroyed.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
KillFoundation(
|
||
ClientData clientData, /* Pointer to the OO system foundation
|
||
* structure. */
|
||
Tcl_Interp *interp) /* The interpreter containing the OO system
|
||
* foundation. */
|
||
{
|
||
Foundation *fPtr = GetFoundation(interp);
|
||
|
||
TclDecrRefCount(fPtr->unknownMethodNameObj);
|
||
TclDecrRefCount(fPtr->constructorName);
|
||
TclDecrRefCount(fPtr->destructorName);
|
||
TclDecrRefCount(fPtr->clonedName);
|
||
TclDecrRefCount(fPtr->defineName);
|
||
TclOODecrRefCount(fPtr->objectCls->thisPtr);
|
||
TclOODecrRefCount(fPtr->classCls->thisPtr);
|
||
|
||
ckfree(fPtr);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* AllocObject --
|
||
*
|
||
* Allocate an object of basic type. Does not splice the object into its
|
||
* class's instance list. The caller must set the classPtr on the object
|
||
* to either a class or NULL, call TclOOAddToInstances to add the object
|
||
* to the class's instance list, and if the object itself is a class, use
|
||
* call TclOOAddToSubclasses() to add it to the right class's list of
|
||
* subclasses.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static Object *
|
||
AllocObject(
|
||
Tcl_Interp *interp, /* Interpreter within which to create the
|
||
* object. */
|
||
const char *nameStr, /* The name of the object to create, or NULL
|
||
* if the OO system should pick the object
|
||
* name itself (equal to the namespace
|
||
* name). */
|
||
Namespace *nsPtr, /* The namespace to create the object in,
|
||
or NULL if *nameStr is NULL */
|
||
const char *nsNameStr) /* The name of the namespace to create, or
|
||
* NULL if the OO system should pick a unique
|
||
* name itself. If this is non-NULL but names
|
||
* a namespace that already exists, the effect
|
||
* will be the same as if this was NULL. */
|
||
{
|
||
Foundation *fPtr = GetFoundation(interp);
|
||
Object *oPtr;
|
||
Command *cmdPtr;
|
||
CommandTrace *tracePtr;
|
||
int creationEpoch;
|
||
|
||
oPtr = ckalloc(sizeof(Object));
|
||
memset(oPtr, 0, sizeof(Object));
|
||
|
||
/*
|
||
* Every object has a namespace; make one. Note that this also normally
|
||
* computes the creation epoch value for the object, a sequence number
|
||
* that is unique to the object (and which allows us to manage method
|
||
* caching without comparing pointers).
|
||
*
|
||
* When creating a namespace, we first check to see if the caller
|
||
* specified the name for the namespace. If not, we generate namespace
|
||
* names using the epoch until such time as a new namespace is actually
|
||
* created.
|
||
*/
|
||
|
||
if (nsNameStr != NULL) {
|
||
oPtr->namespacePtr = Tcl_CreateNamespace(interp, nsNameStr, oPtr, NULL);
|
||
if (oPtr->namespacePtr != NULL) {
|
||
creationEpoch = ++fPtr->tsdPtr->nsCount;
|
||
goto configNamespace;
|
||
}
|
||
Tcl_ResetResult(interp);
|
||
}
|
||
|
||
while (1) {
|
||
char objName[10 + TCL_INTEGER_SPACE];
|
||
|
||
sprintf(objName, "::oo::Obj%d", ++fPtr->tsdPtr->nsCount);
|
||
oPtr->namespacePtr = Tcl_CreateNamespace(interp, objName, oPtr, NULL);
|
||
if (oPtr->namespacePtr != NULL) {
|
||
creationEpoch = fPtr->tsdPtr->nsCount;
|
||
break;
|
||
}
|
||
|
||
/*
|
||
* Could not make that namespace, so we make another. But first we
|
||
* have to get rid of the error message from Tcl_CreateNamespace,
|
||
* since that's something that should not be exposed to the user.
|
||
*/
|
||
|
||
Tcl_ResetResult(interp);
|
||
}
|
||
|
||
configNamespace:
|
||
((Namespace *) oPtr->namespacePtr)->refCount++;
|
||
|
||
/*
|
||
* Make the namespace know about the helper commands. This grants access
|
||
* to the [self] and [next] commands.
|
||
*/
|
||
|
||
if (fPtr->helpersNs != NULL) {
|
||
TclSetNsPath((Namespace *) oPtr->namespacePtr, 1, &fPtr->helpersNs);
|
||
}
|
||
TclOOSetupVariableResolver(oPtr->namespacePtr);
|
||
|
||
/*
|
||
* Suppress use of compiled versions of the commands in this object's
|
||
* namespace and its children; causes wrong behaviour without expensive
|
||
* recompilation. [Bug 2037727]
|
||
*/
|
||
|
||
((Namespace *) oPtr->namespacePtr)->flags |= NS_SUPPRESS_COMPILATION;
|
||
|
||
/*
|
||
* Set up a callback to get notification of the deletion of a namespace
|
||
* when enough of the namespace still remains to execute commands and
|
||
* access variables in it. [Bug 2950259]
|
||
*/
|
||
|
||
((Namespace *) oPtr->namespacePtr)->earlyDeleteProc = ObjectNamespaceDeleted;
|
||
|
||
/*
|
||
* Fill in the rest of the non-zero/NULL parts of the structure.
|
||
*/
|
||
|
||
oPtr->fPtr = fPtr;
|
||
oPtr->creationEpoch = creationEpoch;
|
||
|
||
/*
|
||
* An object starts life with a refCount of 2 to mark the two stages of
|
||
* destruction it occur: A call to ObjectRenamedTrace(), and a call to
|
||
* ObjectNamespaceDeleted().
|
||
*/
|
||
oPtr->refCount = 2;
|
||
|
||
oPtr->flags = USE_CLASS_CACHE;
|
||
|
||
/*
|
||
* Finally, create the object commands and initialize the trace on the
|
||
* public command (so that the object structures are deleted when the
|
||
* command is deleted).
|
||
*/
|
||
|
||
if (!nameStr) {
|
||
nameStr = oPtr->namespacePtr->name;
|
||
nsPtr = (Namespace *)oPtr->namespacePtr;
|
||
if (nsPtr->parentPtr != NULL) {
|
||
nsPtr = nsPtr->parentPtr;
|
||
}
|
||
|
||
}
|
||
oPtr->command = TclCreateObjCommandInNs(interp, nameStr,
|
||
(Tcl_Namespace *)nsPtr, PublicObjectCmd, oPtr, NULL);
|
||
|
||
/*
|
||
* Add the NRE command and trace directly. While this breaks a number of
|
||
* abstractions, it is faster and we're inside Tcl here so we're allowed.
|
||
*/
|
||
|
||
cmdPtr = (Command *) oPtr->command;
|
||
cmdPtr->nreProc = PublicNRObjectCmd;
|
||
cmdPtr->tracePtr = tracePtr = ckalloc(sizeof(CommandTrace));
|
||
tracePtr->traceProc = ObjectRenamedTrace;
|
||
tracePtr->clientData = oPtr;
|
||
tracePtr->flags = TCL_TRACE_RENAME|TCL_TRACE_DELETE;
|
||
tracePtr->nextPtr = NULL;
|
||
tracePtr->refCount = 1;
|
||
|
||
oPtr->myCommand = TclNRCreateCommandInNs(interp, "my", oPtr->namespacePtr,
|
||
PrivateObjectCmd, PrivateNRObjectCmd, oPtr, MyDeleted);
|
||
return oPtr;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* SquelchCachedName --
|
||
*
|
||
* Encapsulates how to throw away a cached object name. Called from
|
||
* object rename traces and at object destruction.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static inline void
|
||
SquelchCachedName(
|
||
Object *oPtr)
|
||
{
|
||
if (oPtr->cachedNameObj) {
|
||
Tcl_DecrRefCount(oPtr->cachedNameObj);
|
||
oPtr->cachedNameObj = NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* MyDeleted --
|
||
*
|
||
* This callback is triggered when the object's [my] command is deleted
|
||
* by any mechanism. It just marks the object as not having a [my]
|
||
* command, and so prevents cleanup of that when the object itself is
|
||
* deleted.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
MyDeleted(
|
||
ClientData clientData) /* Reference to the object whose [my] has been
|
||
* squelched. */
|
||
{
|
||
Object *oPtr = clientData;
|
||
|
||
oPtr->myCommand = NULL;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* ObjectRenamedTrace --
|
||
*
|
||
* This callback is triggered when the object is deleted by any
|
||
* mechanism. It runs the destructors and arranges for the actual cleanup
|
||
* of the object's namespace, which in turn triggers cleansing of the
|
||
* object data structures.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
ObjectRenamedTrace(
|
||
ClientData clientData, /* The object being deleted. */
|
||
Tcl_Interp *interp, /* The interpreter containing the object. */
|
||
const char *oldName, /* What the object was (last) called. */
|
||
const char *newName, /* What it's getting renamed to. (unused) */
|
||
int flags) /* Why was the object deleted? */
|
||
{
|
||
Object *oPtr = clientData;
|
||
/*
|
||
* If this is a rename and not a delete of the object, we just flush the
|
||
* cache of the object name.
|
||
*/
|
||
|
||
if (flags & TCL_TRACE_RENAME) {
|
||
SquelchCachedName(oPtr);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* The namespace is only deleted if it hasn't already been deleted. [Bug
|
||
* 2950259].
|
||
*/
|
||
|
||
if (!Destructing(oPtr)) {
|
||
Tcl_DeleteNamespace(oPtr->namespacePtr);
|
||
}
|
||
oPtr->command = NULL;
|
||
TclOODecrRefCount(oPtr);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOODeleteDescendants --
|
||
*
|
||
* Delete all descendants of a particular class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclOODeleteDescendants(
|
||
Tcl_Interp *interp, /* The interpreter containing the class. */
|
||
Object *oPtr) /* The object representing the class. */
|
||
{
|
||
Class *clsPtr = oPtr->classPtr, *subclassPtr, *mixinSubclassPtr;
|
||
Object *instancePtr;
|
||
|
||
/*
|
||
* Squelch classes that this class has been mixed into.
|
||
*/
|
||
|
||
if (clsPtr->mixinSubs.num > 0) {
|
||
while (clsPtr->mixinSubs.num > 0) {
|
||
mixinSubclassPtr =
|
||
clsPtr->mixinSubs.list[clsPtr->mixinSubs.num - 1];
|
||
|
||
/*
|
||
* This condition also covers the case where mixinSubclassPtr ==
|
||
* clsPtr
|
||
*/
|
||
|
||
if (!Destructing(mixinSubclassPtr->thisPtr)
|
||
&& !(mixinSubclassPtr->thisPtr->flags & DONT_DELETE)) {
|
||
Tcl_DeleteCommandFromToken(interp,
|
||
mixinSubclassPtr->thisPtr->command);
|
||
}
|
||
TclOORemoveFromMixinSubs(mixinSubclassPtr, clsPtr);
|
||
}
|
||
}
|
||
if (clsPtr->mixinSubs.size > 0) {
|
||
ckfree(clsPtr->mixinSubs.list);
|
||
clsPtr->mixinSubs.size = 0;
|
||
}
|
||
/*
|
||
* Squelch subclasses of this class.
|
||
*/
|
||
|
||
if (clsPtr->subclasses.num > 0) {
|
||
while (clsPtr->subclasses.num > 0) {
|
||
subclassPtr = clsPtr->subclasses.list[clsPtr->subclasses.num - 1];
|
||
if (!Destructing(subclassPtr->thisPtr) && !IsRoot(subclassPtr)
|
||
&& !(subclassPtr->thisPtr->flags & DONT_DELETE)) {
|
||
Tcl_DeleteCommandFromToken(interp,
|
||
subclassPtr->thisPtr->command);
|
||
}
|
||
TclOORemoveFromSubclasses(subclassPtr, clsPtr);
|
||
}
|
||
}
|
||
if (clsPtr->subclasses.size > 0) {
|
||
ckfree(clsPtr->subclasses.list);
|
||
clsPtr->subclasses.list = NULL;
|
||
clsPtr->subclasses.size = 0;
|
||
}
|
||
|
||
/*
|
||
* Squelch instances of this class (includes objects we're mixed into).
|
||
*/
|
||
|
||
if (clsPtr->instances.num > 0) {
|
||
while (clsPtr->instances.num > 0) {
|
||
instancePtr = clsPtr->instances.list[clsPtr->instances.num - 1];
|
||
|
||
/*
|
||
* This condition also covers the case where instancePtr == oPtr
|
||
*/
|
||
|
||
if (!Destructing(instancePtr) && !IsRoot(instancePtr) &&
|
||
!(instancePtr->flags & DONT_DELETE)) {
|
||
Tcl_DeleteCommandFromToken(interp, instancePtr->command);
|
||
}
|
||
TclOORemoveFromInstances(instancePtr, clsPtr);
|
||
}
|
||
}
|
||
if (clsPtr->instances.size > 0) {
|
||
ckfree(clsPtr->instances.list);
|
||
clsPtr->instances.list = NULL;
|
||
clsPtr->instances.size = 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOReleaseClassContents --
|
||
*
|
||
* Tear down the special class data structure, including deleting all
|
||
* dependent classes and objects.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclOOReleaseClassContents(
|
||
Tcl_Interp *interp, /* The interpreter containing the class. */
|
||
Object *oPtr) /* The object representing the class. */
|
||
{
|
||
FOREACH_HASH_DECLS;
|
||
int i;
|
||
Class *clsPtr = oPtr->classPtr, *tmpClsPtr;
|
||
Method *mPtr;
|
||
Foundation *fPtr = oPtr->fPtr;
|
||
Tcl_Obj *variableObj;
|
||
|
||
/*
|
||
* Sanity check!
|
||
*/
|
||
|
||
if (!Destructing(oPtr)) {
|
||
if (IsRootClass(oPtr)) {
|
||
Tcl_Panic("deleting class structure for non-deleted %s",
|
||
"::oo::class");
|
||
} else if (IsRootObject(oPtr)) {
|
||
Tcl_Panic("deleting class structure for non-deleted %s",
|
||
"::oo::object");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Squelch method implementation chain caches.
|
||
*/
|
||
|
||
if (clsPtr->constructorChainPtr) {
|
||
TclOODeleteChain(clsPtr->constructorChainPtr);
|
||
clsPtr->constructorChainPtr = NULL;
|
||
}
|
||
if (clsPtr->destructorChainPtr) {
|
||
TclOODeleteChain(clsPtr->destructorChainPtr);
|
||
clsPtr->destructorChainPtr = NULL;
|
||
}
|
||
if (clsPtr->classChainCache) {
|
||
CallChain *callPtr;
|
||
|
||
FOREACH_HASH_VALUE(callPtr, clsPtr->classChainCache) {
|
||
TclOODeleteChain(callPtr);
|
||
}
|
||
Tcl_DeleteHashTable(clsPtr->classChainCache);
|
||
ckfree(clsPtr->classChainCache);
|
||
clsPtr->classChainCache = NULL;
|
||
}
|
||
|
||
/*
|
||
* Squelch our filter list.
|
||
*/
|
||
|
||
if (clsPtr->filters.num) {
|
||
Tcl_Obj *filterObj;
|
||
|
||
FOREACH(filterObj, clsPtr->filters) {
|
||
TclDecrRefCount(filterObj);
|
||
}
|
||
ckfree(clsPtr->filters.list);
|
||
clsPtr->filters.list = NULL;
|
||
clsPtr->filters.num = 0;
|
||
}
|
||
|
||
/*
|
||
* Squelch our metadata.
|
||
*/
|
||
|
||
if (clsPtr->metadataPtr != NULL) {
|
||
Tcl_ObjectMetadataType *metadataTypePtr;
|
||
ClientData value;
|
||
|
||
FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) {
|
||
metadataTypePtr->deleteProc(value);
|
||
}
|
||
Tcl_DeleteHashTable(clsPtr->metadataPtr);
|
||
ckfree(clsPtr->metadataPtr);
|
||
clsPtr->metadataPtr = NULL;
|
||
}
|
||
|
||
if (clsPtr->mixins.num) {
|
||
FOREACH(tmpClsPtr, clsPtr->mixins) {
|
||
TclOORemoveFromMixinSubs(clsPtr, tmpClsPtr);
|
||
TclOODecrRefCount(tmpClsPtr->thisPtr);
|
||
}
|
||
ckfree(clsPtr->mixins.list);
|
||
clsPtr->mixins.list = NULL;
|
||
clsPtr->mixins.num = 0;
|
||
}
|
||
|
||
if (clsPtr->superclasses.num > 0) {
|
||
FOREACH(tmpClsPtr, clsPtr->superclasses) {
|
||
TclOORemoveFromSubclasses(clsPtr, tmpClsPtr);
|
||
TclOODecrRefCount(tmpClsPtr->thisPtr);
|
||
}
|
||
ckfree(clsPtr->superclasses.list);
|
||
clsPtr->superclasses.num = 0;
|
||
clsPtr->superclasses.list = NULL;
|
||
}
|
||
|
||
FOREACH_HASH_VALUE(mPtr, &clsPtr->classMethods) {
|
||
TclOODelMethodRef(mPtr);
|
||
}
|
||
Tcl_DeleteHashTable(&clsPtr->classMethods);
|
||
TclOODelMethodRef(clsPtr->constructorPtr);
|
||
TclOODelMethodRef(clsPtr->destructorPtr);
|
||
|
||
FOREACH(variableObj, clsPtr->variables) {
|
||
TclDecrRefCount(variableObj);
|
||
}
|
||
if (i) {
|
||
ckfree(clsPtr->variables.list);
|
||
}
|
||
|
||
if (IsRootClass(oPtr) && !Destructing(fPtr->objectCls->thisPtr)) {
|
||
Tcl_DeleteCommandFromToken(interp, fPtr->objectCls->thisPtr->command);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* ObjectNamespaceDeleted --
|
||
*
|
||
* Callback when the object's namespace is deleted. Used to clean up the
|
||
* data structures associated with the object. The complicated bit is
|
||
* that this can sometimes happen before the object's command is deleted
|
||
* (interpreter teardown is complex!)
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
ObjectNamespaceDeleted(
|
||
ClientData clientData) /* Pointer to the class whose namespace is
|
||
* being deleted. */
|
||
{
|
||
Object *oPtr = clientData;
|
||
Foundation *fPtr = oPtr->fPtr;
|
||
FOREACH_HASH_DECLS;
|
||
Class *mixinPtr;
|
||
Method *mPtr;
|
||
Tcl_Obj *filterObj, *variableObj;
|
||
Tcl_Interp *interp = oPtr->fPtr->interp;
|
||
int i;
|
||
|
||
if (Destructing(oPtr)) {
|
||
/*
|
||
* TODO: Can ObjectNamespaceDeleted ever be called twice? If not,
|
||
* this guard could be removed.
|
||
*/
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* One rule for the teardown routines is that if an object is in the
|
||
* process of being deleted, nothing else may modify its bookeeping
|
||
* records. This is the flag that
|
||
*/
|
||
oPtr->flags |= OBJECT_DESTRUCTING;
|
||
|
||
/*
|
||
* Let the dominoes fall!
|
||
*/
|
||
|
||
if (oPtr->classPtr) {
|
||
TclOODeleteDescendants(interp, oPtr);
|
||
}
|
||
|
||
/*
|
||
* We do not run destructors on the core class objects when the
|
||
* interpreter is being deleted; their incestuous nature causes problems
|
||
* in that case when the destructor is partially deleted before the uses
|
||
* of it have gone. [Bug 2949397]
|
||
*/
|
||
|
||
if (!Tcl_InterpDeleted(interp) && !(oPtr->flags & DESTRUCTOR_CALLED)) {
|
||
CallContext *contextPtr =
|
||
TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL);
|
||
int result;
|
||
Tcl_InterpState state;
|
||
|
||
oPtr->flags |= DESTRUCTOR_CALLED;
|
||
|
||
if (contextPtr != NULL) {
|
||
contextPtr->callPtr->flags |= DESTRUCTOR;
|
||
contextPtr->skip = 0;
|
||
state = Tcl_SaveInterpState(interp, TCL_OK);
|
||
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext,
|
||
contextPtr, 0, NULL);
|
||
if (result != TCL_OK) {
|
||
Tcl_BackgroundException(interp, result);
|
||
}
|
||
Tcl_RestoreInterpState(interp, state);
|
||
TclOODeleteContext(contextPtr);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Instruct everyone to no longer use any allocated fields of the object.
|
||
* Also delete the command that refers to the object at this point (if it
|
||
* still exists) because otherwise its pointer to the object points into
|
||
* freed memory.
|
||
*/
|
||
|
||
if (((Command *) oPtr->command)->flags && CMD_IS_DELETED) {
|
||
/*
|
||
* Something has already started the command deletion process. We can
|
||
* go ahead and clean up the the namespace,
|
||
*/
|
||
} else {
|
||
/*
|
||
* The namespace must have been deleted directly. Delete the command
|
||
* as well.
|
||
*/
|
||
|
||
Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->command);
|
||
}
|
||
|
||
if (oPtr->myCommand) {
|
||
Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->myCommand);
|
||
}
|
||
|
||
/*
|
||
* Splice the object out of its context. After this, we must *not* call
|
||
* methods on the object.
|
||
*/
|
||
|
||
/* TODO: Should this be protected with a !IsRoot() condition? */
|
||
TclOORemoveFromInstances(oPtr, oPtr->selfCls);
|
||
|
||
if (oPtr->mixins.num > 0) {
|
||
FOREACH(mixinPtr, oPtr->mixins) {
|
||
TclOORemoveFromInstances(oPtr, mixinPtr);
|
||
TclOODecrRefCount(mixinPtr->thisPtr);
|
||
}
|
||
if (oPtr->mixins.list != NULL) {
|
||
ckfree(oPtr->mixins.list);
|
||
}
|
||
}
|
||
|
||
FOREACH(filterObj, oPtr->filters) {
|
||
TclDecrRefCount(filterObj);
|
||
}
|
||
if (i) {
|
||
ckfree(oPtr->filters.list);
|
||
}
|
||
|
||
if (oPtr->methodsPtr) {
|
||
FOREACH_HASH_VALUE(mPtr, oPtr->methodsPtr) {
|
||
TclOODelMethodRef(mPtr);
|
||
}
|
||
Tcl_DeleteHashTable(oPtr->methodsPtr);
|
||
ckfree(oPtr->methodsPtr);
|
||
}
|
||
|
||
FOREACH(variableObj, oPtr->variables) {
|
||
TclDecrRefCount(variableObj);
|
||
}
|
||
if (i) {
|
||
ckfree(oPtr->variables.list);
|
||
}
|
||
|
||
if (oPtr->chainCache) {
|
||
TclOODeleteChainCache(oPtr->chainCache);
|
||
}
|
||
|
||
SquelchCachedName(oPtr);
|
||
|
||
if (oPtr->metadataPtr != NULL) {
|
||
Tcl_ObjectMetadataType *metadataTypePtr;
|
||
ClientData value;
|
||
|
||
FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) {
|
||
metadataTypePtr->deleteProc(value);
|
||
}
|
||
Tcl_DeleteHashTable(oPtr->metadataPtr);
|
||
ckfree(oPtr->metadataPtr);
|
||
oPtr->metadataPtr = NULL;
|
||
}
|
||
|
||
/*
|
||
* Because an object can be a class that is an instance of itself, the
|
||
* class object's class structure should only be cleaned after most of
|
||
* the cleanup on the object is done.
|
||
*
|
||
* The class of objects needs some special care; if it is deleted (and
|
||
* we're not killing the whole interpreter) we force the delete of the
|
||
* class of classes now as well. Due to the incestuous nature of those two
|
||
* classes, if one goes the other must too and yet the tangle can
|
||
* sometimes not go away automatically; we force it here. [Bug 2962664]
|
||
*/
|
||
|
||
if (IsRootObject(oPtr) && !Destructing(fPtr->classCls->thisPtr)
|
||
&& !Tcl_InterpDeleted(interp)) {
|
||
|
||
Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command);
|
||
}
|
||
|
||
if (oPtr->classPtr != NULL) {
|
||
TclOOReleaseClassContents(interp, oPtr);
|
||
}
|
||
|
||
/*
|
||
* Delete the object structure itself.
|
||
*/
|
||
|
||
TclNsDecrRefCount((Namespace *)oPtr->namespacePtr);
|
||
oPtr->namespacePtr = NULL;
|
||
TclOODecrRefCount(oPtr->selfCls->thisPtr);
|
||
oPtr->selfCls = NULL;
|
||
TclOODecrRefCount(oPtr);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOODecrRef --
|
||
*
|
||
* Decrement the refcount of an object and deallocate storage then object
|
||
* is no longer referenced. Returns 1 if storage was deallocated, and 0
|
||
* otherwise.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
int TclOODecrRefCount(Object *oPtr) {
|
||
if (oPtr->refCount-- <= 1) {
|
||
if (oPtr->classPtr != NULL) {
|
||
ckfree(oPtr->classPtr);
|
||
}
|
||
ckfree(oPtr);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOObjectDestroyed --
|
||
*
|
||
* Returns TCL_OK if an object is entirely deleted, i.e. the destruction
|
||
* sequence has completed.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
int TclOOObjectDestroyed(Object *oPtr) {
|
||
return (oPtr->namespacePtr == NULL);
|
||
}
|
||
|
||
/*
|
||
* Setting the "empty" location to NULL makes debugging a little easier.
|
||
*/
|
||
|
||
#define REMOVEBODY { \
|
||
for (; idx < num - 1; idx++) { \
|
||
list[idx] = list[idx + 1]; \
|
||
} \
|
||
list[idx] = NULL; \
|
||
return; \
|
||
}
|
||
void RemoveClass(Class **list, int num, int idx) REMOVEBODY
|
||
|
||
void RemoveObject(Object **list, int num, int idx) REMOVEBODY
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOORemoveFromInstances --
|
||
*
|
||
* Utility function to remove an object from the list of instances within
|
||
* a class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOORemoveFromInstances(
|
||
Object *oPtr, /* The instance to remove. */
|
||
Class *clsPtr) /* The class (possibly) containing the
|
||
* reference to the instance. */
|
||
{
|
||
int i, res = 0;
|
||
Object *instPtr;
|
||
|
||
FOREACH(instPtr, clsPtr->instances) {
|
||
if (oPtr == instPtr) {
|
||
RemoveItem(Object, clsPtr->instances, i);
|
||
TclOODecrRefCount(oPtr);
|
||
res++;
|
||
break;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOAddToInstances --
|
||
*
|
||
* Utility function to add an object to the list of instances within a
|
||
* class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclOOAddToInstances(
|
||
Object *oPtr, /* The instance to add. */
|
||
Class *clsPtr) /* The class to add the instance to. It is
|
||
* assumed that the class is not already
|
||
* present as an instance in the class. */
|
||
{
|
||
if (clsPtr->instances.num >= clsPtr->instances.size) {
|
||
clsPtr->instances.size += ALLOC_CHUNK;
|
||
if (clsPtr->instances.size == ALLOC_CHUNK) {
|
||
clsPtr->instances.list = ckalloc(sizeof(Object *) * ALLOC_CHUNK);
|
||
} else {
|
||
clsPtr->instances.list = ckrealloc(clsPtr->instances.list,
|
||
sizeof(Object *) * clsPtr->instances.size);
|
||
}
|
||
}
|
||
clsPtr->instances.list[clsPtr->instances.num++] = oPtr;
|
||
AddRef(oPtr);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOORemoveFromMixins --
|
||
*
|
||
* Utility function to remove a class from the list of mixins within an
|
||
* object.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOORemoveFromMixins(
|
||
Class *mixinPtr, /* The mixin to remove. */
|
||
Object *oPtr) /* The object (possibly) containing the
|
||
* reference to the mixin. */
|
||
{
|
||
int i, res = 0;
|
||
Class *mixPtr;
|
||
|
||
FOREACH(mixPtr, oPtr->mixins) {
|
||
if (mixinPtr == mixPtr) {
|
||
RemoveItem(Class, oPtr->mixins, i);
|
||
TclOODecrRefCount(mixPtr->thisPtr);
|
||
res++;
|
||
break;
|
||
}
|
||
}
|
||
if (oPtr->mixins.num == 0) {
|
||
ckfree(oPtr->mixins.list);
|
||
oPtr->mixins.list = NULL;
|
||
}
|
||
return res;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOORemoveFromSubclasses --
|
||
*
|
||
* Utility function to remove a class from the list of subclasses within
|
||
* another class. Returns the number of removals performed.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOORemoveFromSubclasses(
|
||
Class *subPtr, /* The subclass to remove. */
|
||
Class *superPtr) /* The superclass to possibly remove the
|
||
* subclass reference from. */
|
||
{
|
||
int i, res = 0;
|
||
Class *subclsPtr;
|
||
|
||
FOREACH(subclsPtr, superPtr->subclasses) {
|
||
if (subPtr == subclsPtr) {
|
||
RemoveItem(Class, superPtr->subclasses, i);
|
||
TclOODecrRefCount(subPtr->thisPtr);
|
||
res++;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOAddToSubclasses --
|
||
*
|
||
* Utility function to add a class to the list of subclasses within
|
||
* another class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclOOAddToSubclasses(
|
||
Class *subPtr, /* The subclass to add. */
|
||
Class *superPtr) /* The superclass to add the subclass to. It
|
||
* is assumed that the class is not already
|
||
* present as a subclass in the superclass. */
|
||
{
|
||
if (Destructing(superPtr->thisPtr)) {
|
||
return;
|
||
}
|
||
if (superPtr->subclasses.num >= superPtr->subclasses.size) {
|
||
superPtr->subclasses.size += ALLOC_CHUNK;
|
||
if (superPtr->subclasses.size == ALLOC_CHUNK) {
|
||
superPtr->subclasses.list = ckalloc(sizeof(Class *) * ALLOC_CHUNK);
|
||
} else {
|
||
superPtr->subclasses.list = ckrealloc(superPtr->subclasses.list,
|
||
sizeof(Class *) * superPtr->subclasses.size);
|
||
}
|
||
}
|
||
superPtr->subclasses.list[superPtr->subclasses.num++] = subPtr;
|
||
AddRef(subPtr->thisPtr);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOORemoveFromMixinSubs --
|
||
*
|
||
* Utility function to remove a class from the list of mixinSubs within
|
||
* another class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOORemoveFromMixinSubs(
|
||
Class *subPtr, /* The subclass to remove. */
|
||
Class *superPtr) /* The superclass to possibly remove the
|
||
* subclass reference from. */
|
||
{
|
||
int i, res = 0;
|
||
Class *subclsPtr;
|
||
|
||
FOREACH(subclsPtr, superPtr->mixinSubs) {
|
||
if (subPtr == subclsPtr) {
|
||
RemoveItem(Class, superPtr->mixinSubs, i);
|
||
TclOODecrRefCount(subPtr->thisPtr);
|
||
res++;
|
||
break;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOAddToMixinSubs --
|
||
*
|
||
* Utility function to add a class to the list of mixinSubs within
|
||
* another class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclOOAddToMixinSubs(
|
||
Class *subPtr, /* The subclass to add. */
|
||
Class *superPtr) /* The superclass to add the subclass to. It
|
||
* is assumed that the class is not already
|
||
* present as a subclass in the superclass. */
|
||
{
|
||
if (Destructing(superPtr->thisPtr)) {
|
||
return;
|
||
}
|
||
if (superPtr->mixinSubs.num >= superPtr->mixinSubs.size) {
|
||
superPtr->mixinSubs.size += ALLOC_CHUNK;
|
||
if (superPtr->mixinSubs.size == ALLOC_CHUNK) {
|
||
superPtr->mixinSubs.list = ckalloc(sizeof(Class *) * ALLOC_CHUNK);
|
||
} else {
|
||
superPtr->mixinSubs.list = ckrealloc(superPtr->mixinSubs.list,
|
||
sizeof(Class *) * superPtr->mixinSubs.size);
|
||
}
|
||
}
|
||
superPtr->mixinSubs.list[superPtr->mixinSubs.num++] = subPtr;
|
||
AddRef(subPtr->thisPtr);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOAllocClass --
|
||
*
|
||
* Allocate a basic class. Does not add class to its class's instance
|
||
* list.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Class *
|
||
TclOOAllocClass(
|
||
Tcl_Interp *interp, /* Interpreter within which to allocate the
|
||
* class. */
|
||
Object *useThisObj) /* Object that is to act as the class
|
||
* representation. */
|
||
{
|
||
Foundation *fPtr = GetFoundation(interp);
|
||
Class *clsPtr = ckalloc(sizeof(Class));
|
||
|
||
memset(clsPtr, 0, sizeof(Class));
|
||
clsPtr->thisPtr = useThisObj;
|
||
|
||
/*
|
||
* Configure the namespace path for the class's object.
|
||
*/
|
||
initClassPath(interp, clsPtr);
|
||
|
||
/*
|
||
* Classes are subclasses of oo::object, i.e. the objects they create are
|
||
* objects.
|
||
*/
|
||
|
||
clsPtr->superclasses.num = 1;
|
||
clsPtr->superclasses.list = ckalloc(sizeof(Class *));
|
||
clsPtr->superclasses.list[0] = fPtr->objectCls;
|
||
AddRef(fPtr->objectCls->thisPtr);
|
||
|
||
/*
|
||
* Finish connecting the class structure to the object structure.
|
||
*/
|
||
|
||
clsPtr->thisPtr->classPtr = clsPtr;
|
||
|
||
/*
|
||
* That's the complicated bit. Now fill in the rest of the non-zero/NULL
|
||
* fields.
|
||
*/
|
||
|
||
Tcl_InitObjHashTable(&clsPtr->classMethods);
|
||
return clsPtr;
|
||
}
|
||
static void
|
||
initClassPath(Tcl_Interp *interp, Class *clsPtr) {
|
||
Foundation *fPtr = GetFoundation(interp);
|
||
if (fPtr->helpersNs != NULL) {
|
||
Tcl_Namespace *path[2];
|
||
path[0] = fPtr->helpersNs;
|
||
path[1] = fPtr->ooNs;
|
||
TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 2, path);
|
||
} else {
|
||
TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 1,
|
||
&fPtr->ooNs);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* Tcl_NewObjectInstance --
|
||
*
|
||
* Allocate a new instance of an object.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
Tcl_Object
|
||
Tcl_NewObjectInstance(
|
||
Tcl_Interp *interp, /* Interpreter context. */
|
||
Tcl_Class cls, /* Class to create an instance of. */
|
||
const char *nameStr, /* Name of object to create, or NULL to ask
|
||
* the code to pick its own unique name. */
|
||
const char *nsNameStr, /* Name of namespace to create inside object,
|
||
* or NULL to ask the code to pick its own
|
||
* unique name. */
|
||
int objc, /* Number of arguments. Negative value means
|
||
* do not call constructor. */
|
||
Tcl_Obj *const *objv, /* Argument list. */
|
||
int skip) /* Number of arguments to _not_ pass to the
|
||
* constructor. */
|
||
{
|
||
Class *classPtr = (Class *) cls;
|
||
Object *oPtr;
|
||
ClientData clientData[4];
|
||
|
||
oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr);
|
||
if (oPtr == NULL) {return NULL;}
|
||
|
||
/*
|
||
* Run constructors, except when objc < 0, which is a special flag case
|
||
* used for object cloning only.
|
||
*/
|
||
|
||
if (objc >= 0) {
|
||
CallContext *contextPtr =
|
||
TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL);
|
||
|
||
if (contextPtr != NULL) {
|
||
int isRoot, result;
|
||
Tcl_InterpState state;
|
||
|
||
state = Tcl_SaveInterpState(interp, TCL_OK);
|
||
contextPtr->callPtr->flags |= CONSTRUCTOR;
|
||
contextPtr->skip = skip;
|
||
|
||
/*
|
||
* Adjust the ensemble tracking record if necessary. [Bug 3514761]
|
||
*/
|
||
|
||
isRoot = TclInitRewriteEnsemble(interp, skip, skip, objv);
|
||
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr,
|
||
objc, objv);
|
||
|
||
if (isRoot) {
|
||
TclResetRewriteEnsemble(interp, 1);
|
||
}
|
||
|
||
clientData[0] = contextPtr;
|
||
clientData[1] = oPtr;
|
||
clientData[2] = state;
|
||
clientData[3] = &oPtr;
|
||
|
||
result = FinalizeAlloc(clientData, interp, result);
|
||
if (result != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
return (Tcl_Object) oPtr;
|
||
}
|
||
|
||
int
|
||
TclNRNewObjectInstance(
|
||
Tcl_Interp *interp, /* Interpreter context. */
|
||
Tcl_Class cls, /* Class to create an instance of. */
|
||
const char *nameStr, /* Name of object to create, or NULL to ask
|
||
* the code to pick its own unique name. */
|
||
const char *nsNameStr, /* Name of namespace to create inside object,
|
||
* or NULL to ask the code to pick its own
|
||
* unique name. */
|
||
int objc, /* Number of arguments. Negative value means
|
||
* do not call constructor. */
|
||
Tcl_Obj *const *objv, /* Argument list. */
|
||
int skip, /* Number of arguments to _not_ pass to the
|
||
* constructor. */
|
||
Tcl_Object *objectPtr) /* Place to write the object reference upon
|
||
* successful allocation. */
|
||
{
|
||
Class *classPtr = (Class *) cls;
|
||
CallContext *contextPtr;
|
||
Tcl_InterpState state;
|
||
Object *oPtr;
|
||
|
||
oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr);
|
||
if (oPtr == NULL) {return TCL_ERROR;}
|
||
|
||
/*
|
||
* Run constructors, except when objc < 0 (a special flag case used for
|
||
* object cloning only). If there aren't any constructors, we do nothing.
|
||
*/
|
||
|
||
if (objc < 0) {
|
||
*objectPtr = (Tcl_Object) oPtr;
|
||
return TCL_OK;
|
||
}
|
||
contextPtr = TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL);
|
||
if (contextPtr == NULL) {
|
||
*objectPtr = (Tcl_Object) oPtr;
|
||
return TCL_OK;
|
||
}
|
||
|
||
state = Tcl_SaveInterpState(interp, TCL_OK);
|
||
contextPtr->callPtr->flags |= CONSTRUCTOR;
|
||
contextPtr->skip = skip;
|
||
|
||
/*
|
||
* Adjust the ensemble tracking record if necessary. [Bug 3514761]
|
||
*/
|
||
|
||
if (TclInitRewriteEnsemble(interp, skip, skip, objv)) {
|
||
TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL);
|
||
}
|
||
|
||
/*
|
||
* Fire off the constructors non-recursively.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, FinalizeAlloc, contextPtr, oPtr, state,
|
||
objectPtr);
|
||
TclPushTailcallPoint(interp);
|
||
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
||
}
|
||
|
||
Object *
|
||
TclNewObjectInstanceCommon(
|
||
Tcl_Interp *interp,
|
||
Class *classPtr,
|
||
const char *nameStr,
|
||
const char *nsNameStr)
|
||
{
|
||
Tcl_HashEntry *hPtr;
|
||
Foundation *fPtr = GetFoundation(interp);
|
||
Object *oPtr;
|
||
const char *simpleName = NULL;
|
||
Namespace *nsPtr = NULL, *dummy,
|
||
*inNsPtr = (Namespace *)TclGetCurrentNamespace(interp);
|
||
|
||
if (nameStr) {
|
||
TclGetNamespaceForQualName(interp, nameStr, inNsPtr,
|
||
TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy, &dummy, &simpleName);
|
||
|
||
/*
|
||
* Disallow creation of an object over an existing command.
|
||
*/
|
||
|
||
hPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simpleName);
|
||
if (hPtr) {
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"can't create object \"%s\": command already exists with"
|
||
" that name", nameStr));
|
||
Tcl_SetErrorCode(interp, "TCL", "OO", "OVERWRITE_OBJECT", NULL);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Create the object.
|
||
*/
|
||
|
||
oPtr = AllocObject(interp, simpleName, nsPtr, nsNameStr);
|
||
oPtr->selfCls = classPtr;
|
||
AddRef(classPtr->thisPtr);
|
||
TclOOAddToInstances(oPtr, classPtr);
|
||
|
||
/*
|
||
* Check to see if we're really creating a class. If so, allocate the
|
||
* class structure as well.
|
||
*/
|
||
|
||
if (TclOOIsReachable(fPtr->classCls, classPtr)) {
|
||
/*
|
||
* Is a class, so attach a class structure. Note that the
|
||
* TclOOAllocClass function splices the structure into the object, so
|
||
* we don't have to. Once that's done, we need to repatch the object
|
||
* to have the right class since TclOOAllocClass interferes with that.
|
||
*/
|
||
|
||
TclOOAllocClass(interp, oPtr);
|
||
TclOOAddToSubclasses(oPtr->classPtr, fPtr->objectCls);
|
||
} else {
|
||
oPtr->classPtr = NULL;
|
||
}
|
||
return oPtr;
|
||
}
|
||
|
||
static int
|
||
FinalizeAlloc(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
CallContext *contextPtr = data[0];
|
||
Object *oPtr = data[1];
|
||
Tcl_InterpState state = data[2];
|
||
Tcl_Object *objectPtr = data[3];
|
||
|
||
/*
|
||
* Ensure an error if the object was deleted in the constructor.
|
||
* Don't want to lose errors by accident. [Bug 2903011]
|
||
*/
|
||
|
||
if (result != TCL_ERROR && Destructing(oPtr)) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"object deleted in constructor", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "OO", "STILLBORN", NULL);
|
||
result = TCL_ERROR;
|
||
}
|
||
if (result != TCL_OK) {
|
||
Tcl_DiscardInterpState(state);
|
||
|
||
/*
|
||
* Take care to not delete a deleted object; that would be bad. [Bug
|
||
* 2903011] Also take care to make sure that we have the name of the
|
||
* command before we delete it. [Bug 9dd1bd7a74]
|
||
*/
|
||
|
||
if (!Destructing(oPtr)) {
|
||
(void) TclOOObjectName(interp, oPtr);
|
||
Tcl_DeleteCommandFromToken(interp, oPtr->command);
|
||
}
|
||
|
||
/*
|
||
* This decrements the refcount of oPtr.
|
||
*/
|
||
|
||
TclOODeleteContext(contextPtr);
|
||
return TCL_ERROR;
|
||
}
|
||
Tcl_RestoreInterpState(interp, state);
|
||
*objectPtr = (Tcl_Object) oPtr;
|
||
|
||
/*
|
||
* This decrements the refcount of oPtr.
|
||
*/
|
||
|
||
TclOODeleteContext(contextPtr);
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* Tcl_CopyObjectInstance --
|
||
*
|
||
* Creates a copy of an object. Does not copy the backing namespace,
|
||
* since the correct way to do that (e.g., shallow/deep) depends on the
|
||
* object/class's own policies.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Object
|
||
Tcl_CopyObjectInstance(
|
||
Tcl_Interp *interp,
|
||
Tcl_Object sourceObject,
|
||
const char *targetName,
|
||
const char *targetNamespaceName)
|
||
{
|
||
Object *oPtr = (Object *) sourceObject, *o2Ptr;
|
||
FOREACH_HASH_DECLS;
|
||
Method *mPtr;
|
||
Class *mixinPtr;
|
||
CallContext *contextPtr;
|
||
Tcl_Obj *keyPtr, *filterObj, *variableObj, *args[3];
|
||
int i, result;
|
||
|
||
/*
|
||
* Sanity check.
|
||
*/
|
||
|
||
if (IsRootClass(oPtr)) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"may not clone the class of classes", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "OO", "CLONING_CLASS", NULL);
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* Build the instance. Note that this does not run any constructors.
|
||
*/
|
||
|
||
o2Ptr = (Object *) Tcl_NewObjectInstance(interp,
|
||
(Tcl_Class) oPtr->selfCls, targetName, targetNamespaceName, -1,
|
||
NULL, -1);
|
||
if (o2Ptr == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* Copy the object-local methods to the new object.
|
||
*/
|
||
|
||
if (oPtr->methodsPtr) {
|
||
FOREACH_HASH(keyPtr, mPtr, oPtr->methodsPtr) {
|
||
if (CloneObjectMethod(interp, o2Ptr, mPtr, keyPtr) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Copy the object's mixin references to the new object.
|
||
*/
|
||
|
||
if (o2Ptr->mixins.num != 0) {
|
||
FOREACH(mixinPtr, o2Ptr->mixins) {
|
||
if (mixinPtr && mixinPtr != o2Ptr->selfCls) {
|
||
TclOORemoveFromInstances(o2Ptr, mixinPtr);
|
||
}
|
||
TclOODecrRefCount(mixinPtr->thisPtr);
|
||
}
|
||
ckfree(o2Ptr->mixins.list);
|
||
}
|
||
DUPLICATE(o2Ptr->mixins, oPtr->mixins, Class *);
|
||
FOREACH(mixinPtr, o2Ptr->mixins) {
|
||
if (mixinPtr && mixinPtr != o2Ptr->selfCls) {
|
||
TclOOAddToInstances(o2Ptr, mixinPtr);
|
||
}
|
||
|
||
/*
|
||
* For the reference just created in DUPLICATE.
|
||
*/
|
||
|
||
AddRef(mixinPtr->thisPtr);
|
||
}
|
||
|
||
/*
|
||
* Copy the object's filter list to the new object.
|
||
*/
|
||
|
||
DUPLICATE(o2Ptr->filters, oPtr->filters, Tcl_Obj *);
|
||
FOREACH(filterObj, o2Ptr->filters) {
|
||
Tcl_IncrRefCount(filterObj);
|
||
}
|
||
|
||
/*
|
||
* Copy the object's variable resolution list to the new object.
|
||
*/
|
||
|
||
DUPLICATE(o2Ptr->variables, oPtr->variables, Tcl_Obj *);
|
||
FOREACH(variableObj, o2Ptr->variables) {
|
||
Tcl_IncrRefCount(variableObj);
|
||
}
|
||
|
||
/*
|
||
* Copy the object's flags to the new object, clearing those that must be
|
||
* kept object-local. The duplicate is never deleted at this point, nor is
|
||
* it the root of the object system or in the midst of processing a filter
|
||
* call.
|
||
*/
|
||
|
||
o2Ptr->flags = oPtr->flags & ~(
|
||
OBJECT_DESTRUCTING | ROOT_OBJECT | ROOT_CLASS | FILTER_HANDLING);
|
||
|
||
/*
|
||
* Copy the object's metadata.
|
||
*/
|
||
|
||
if (oPtr->metadataPtr != NULL) {
|
||
Tcl_ObjectMetadataType *metadataTypePtr;
|
||
ClientData value, duplicate;
|
||
|
||
FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) {
|
||
if (metadataTypePtr->cloneProc == NULL) {
|
||
duplicate = value;
|
||
} else {
|
||
if (metadataTypePtr->cloneProc(interp, value,
|
||
&duplicate) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
if (duplicate != NULL) {
|
||
Tcl_ObjectSetMetadata((Tcl_Object) o2Ptr, metadataTypePtr,
|
||
duplicate);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Copy the class, if present. Note that if there is a class present in
|
||
* the source object, there must also be one in the copy.
|
||
*/
|
||
|
||
if (oPtr->classPtr != NULL) {
|
||
Class *clsPtr = oPtr->classPtr;
|
||
Class *cls2Ptr = o2Ptr->classPtr;
|
||
Class *superPtr;
|
||
|
||
/*
|
||
* Copy the class flags across.
|
||
*/
|
||
|
||
cls2Ptr->flags = clsPtr->flags;
|
||
|
||
/*
|
||
* Ensure that the new class's superclass structure is the same as the
|
||
* old class's.
|
||
*/
|
||
|
||
FOREACH(superPtr, cls2Ptr->superclasses) {
|
||
TclOORemoveFromSubclasses(cls2Ptr, superPtr);
|
||
TclOODecrRefCount(superPtr->thisPtr);
|
||
}
|
||
if (cls2Ptr->superclasses.num) {
|
||
cls2Ptr->superclasses.list = ckrealloc(cls2Ptr->superclasses.list,
|
||
sizeof(Class *) * clsPtr->superclasses.num);
|
||
} else {
|
||
cls2Ptr->superclasses.list =
|
||
ckalloc(sizeof(Class *) * clsPtr->superclasses.num);
|
||
}
|
||
memcpy(cls2Ptr->superclasses.list, clsPtr->superclasses.list,
|
||
sizeof(Class *) * clsPtr->superclasses.num);
|
||
cls2Ptr->superclasses.num = clsPtr->superclasses.num;
|
||
FOREACH(superPtr, cls2Ptr->superclasses) {
|
||
TclOOAddToSubclasses(cls2Ptr, superPtr);
|
||
|
||
/*
|
||
* For the new item in cls2Ptr->superclasses that memcpy just
|
||
* created.
|
||
*/
|
||
|
||
AddRef(superPtr->thisPtr);
|
||
}
|
||
|
||
/*
|
||
* Duplicate the source class's filters.
|
||
*/
|
||
|
||
DUPLICATE(cls2Ptr->filters, clsPtr->filters, Tcl_Obj *);
|
||
FOREACH(filterObj, cls2Ptr->filters) {
|
||
Tcl_IncrRefCount(filterObj);
|
||
}
|
||
|
||
/*
|
||
* Copy the source class's variable resolution list.
|
||
*/
|
||
|
||
DUPLICATE(cls2Ptr->variables, clsPtr->variables, Tcl_Obj *);
|
||
FOREACH(variableObj, cls2Ptr->variables) {
|
||
Tcl_IncrRefCount(variableObj);
|
||
}
|
||
|
||
/*
|
||
* Duplicate the source class's mixins (which cannot be circular
|
||
* references to the duplicate).
|
||
*/
|
||
|
||
if (cls2Ptr->mixins.num != 0) {
|
||
FOREACH(mixinPtr, cls2Ptr->mixins) {
|
||
TclOORemoveFromMixinSubs(cls2Ptr, mixinPtr);
|
||
TclOODecrRefCount(mixinPtr->thisPtr);
|
||
}
|
||
ckfree(clsPtr->mixins.list);
|
||
}
|
||
DUPLICATE(cls2Ptr->mixins, clsPtr->mixins, Class *);
|
||
FOREACH(mixinPtr, cls2Ptr->mixins) {
|
||
TclOOAddToMixinSubs(cls2Ptr, mixinPtr);
|
||
|
||
/*
|
||
* For the copy just created in DUPLICATE.
|
||
*/
|
||
|
||
AddRef(mixinPtr->thisPtr);
|
||
}
|
||
|
||
/*
|
||
* Duplicate the source class's methods, constructor and destructor.
|
||
*/
|
||
|
||
FOREACH_HASH(keyPtr, mPtr, &clsPtr->classMethods) {
|
||
if (CloneClassMethod(interp, cls2Ptr, mPtr, keyPtr,
|
||
NULL) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
if (clsPtr->constructorPtr) {
|
||
if (CloneClassMethod(interp, cls2Ptr, clsPtr->constructorPtr,
|
||
NULL, &cls2Ptr->constructorPtr) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
if (clsPtr->destructorPtr) {
|
||
if (CloneClassMethod(interp, cls2Ptr, clsPtr->destructorPtr, NULL,
|
||
&cls2Ptr->destructorPtr) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Duplicate the class's metadata.
|
||
*/
|
||
|
||
if (clsPtr->metadataPtr != NULL) {
|
||
Tcl_ObjectMetadataType *metadataTypePtr;
|
||
ClientData value, duplicate;
|
||
|
||
FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) {
|
||
if (metadataTypePtr->cloneProc == NULL) {
|
||
duplicate = value;
|
||
} else {
|
||
if (metadataTypePtr->cloneProc(interp, value,
|
||
&duplicate) != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
if (duplicate != NULL) {
|
||
Tcl_ClassSetMetadata((Tcl_Class) cls2Ptr, metadataTypePtr,
|
||
duplicate);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
TclResetRewriteEnsemble(interp, 1);
|
||
contextPtr = TclOOGetCallContext(o2Ptr, oPtr->fPtr->clonedName, 0, NULL);
|
||
if (contextPtr) {
|
||
args[0] = TclOOObjectName(interp, o2Ptr);
|
||
args[1] = oPtr->fPtr->clonedName;
|
||
args[2] = TclOOObjectName(interp, oPtr);
|
||
Tcl_IncrRefCount(args[0]);
|
||
Tcl_IncrRefCount(args[1]);
|
||
Tcl_IncrRefCount(args[2]);
|
||
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, 3,
|
||
args);
|
||
TclDecrRefCount(args[0]);
|
||
TclDecrRefCount(args[1]);
|
||
TclDecrRefCount(args[2]);
|
||
TclOODeleteContext(contextPtr);
|
||
if (result == TCL_ERROR) {
|
||
Tcl_AddErrorInfo(interp,
|
||
"\n (while performing post-copy callback)");
|
||
}
|
||
if (result != TCL_OK) {
|
||
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
return (Tcl_Object) o2Ptr;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* CloneObjectMethod, CloneClassMethod --
|
||
*
|
||
* Helper functions used for cloning methods. They work identically to
|
||
* each other, except for the difference between them in how they
|
||
* register the cloned method on a successful clone.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
CloneObjectMethod(
|
||
Tcl_Interp *interp,
|
||
Object *oPtr,
|
||
Method *mPtr,
|
||
Tcl_Obj *namePtr)
|
||
{
|
||
if (mPtr->typePtr == NULL) {
|
||
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
||
mPtr->flags & PUBLIC_METHOD, NULL, NULL);
|
||
} else if (mPtr->typePtr->cloneProc) {
|
||
ClientData newClientData;
|
||
|
||
if (mPtr->typePtr->cloneProc(interp, mPtr->clientData,
|
||
&newClientData) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
||
mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, newClientData);
|
||
} else {
|
||
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
||
mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, mPtr->clientData);
|
||
}
|
||
return TCL_OK;
|
||
}
|
||
|
||
static int
|
||
CloneClassMethod(
|
||
Tcl_Interp *interp,
|
||
Class *clsPtr,
|
||
Method *mPtr,
|
||
Tcl_Obj *namePtr,
|
||
Method **m2PtrPtr)
|
||
{
|
||
Method *m2Ptr;
|
||
|
||
if (mPtr->typePtr == NULL) {
|
||
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
||
namePtr, mPtr->flags & PUBLIC_METHOD, NULL, NULL);
|
||
} else if (mPtr->typePtr->cloneProc) {
|
||
ClientData newClientData;
|
||
|
||
if (mPtr->typePtr->cloneProc(interp, mPtr->clientData,
|
||
&newClientData) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
||
namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr,
|
||
newClientData);
|
||
} else {
|
||
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
||
namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr,
|
||
mPtr->clientData);
|
||
}
|
||
if (m2PtrPtr != NULL) {
|
||
*m2PtrPtr = m2Ptr;
|
||
}
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* Tcl_ClassGetMetadata, Tcl_ClassSetMetadata, Tcl_ObjectGetMetadata,
|
||
* Tcl_ObjectSetMetadata --
|
||
*
|
||
* Metadata management API. The metadata system allows code in extensions
|
||
* to attach arbitrary non-NULL pointers to objects and classes without
|
||
* the different things that might be interested being able to interfere
|
||
* with each other. Apart from non-NULL-ness, these routines attach no
|
||
* interpretation to the meaning of the metadata pointers.
|
||
*
|
||
* The Tcl_*GetMetadata routines get the metadata pointer attached that
|
||
* has been related with a particular type, or NULL if no metadata
|
||
* associated with the given type has been attached.
|
||
*
|
||
* The Tcl_*SetMetadata routines set or delete the metadata pointer that
|
||
* is related to a particular type. The value associated with the type is
|
||
* deleted (if present; no-op otherwise) if the value is NULL, and
|
||
* attached (replacing the previous value, which is deleted if present)
|
||
* otherwise. This means it is impossible to attach a NULL value for any
|
||
* metadata type.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
ClientData
|
||
Tcl_ClassGetMetadata(
|
||
Tcl_Class clazz,
|
||
const Tcl_ObjectMetadataType *typePtr)
|
||
{
|
||
Class *clsPtr = (Class *) clazz;
|
||
Tcl_HashEntry *hPtr;
|
||
|
||
/*
|
||
* If there's no metadata store attached, the type in question has
|
||
* definitely not been attached either!
|
||
*/
|
||
|
||
if (clsPtr->metadataPtr == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* There is a metadata store, so look in it for the given type.
|
||
*/
|
||
|
||
hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, (char *) typePtr);
|
||
|
||
/*
|
||
* Return the metadata value if we found it, otherwise NULL.
|
||
*/
|
||
|
||
if (hPtr == NULL) {
|
||
return NULL;
|
||
}
|
||
return Tcl_GetHashValue(hPtr);
|
||
}
|
||
|
||
void
|
||
Tcl_ClassSetMetadata(
|
||
Tcl_Class clazz,
|
||
const Tcl_ObjectMetadataType *typePtr,
|
||
ClientData metadata)
|
||
{
|
||
Class *clsPtr = (Class *) clazz;
|
||
Tcl_HashEntry *hPtr;
|
||
int isNew;
|
||
|
||
/*
|
||
* Attach the metadata store if not done already.
|
||
*/
|
||
|
||
if (clsPtr->metadataPtr == NULL) {
|
||
if (metadata == NULL) {
|
||
return;
|
||
}
|
||
clsPtr->metadataPtr = ckalloc(sizeof(Tcl_HashTable));
|
||
Tcl_InitHashTable(clsPtr->metadataPtr, TCL_ONE_WORD_KEYS);
|
||
}
|
||
|
||
/*
|
||
* If the metadata is NULL, we're deleting the metadata for the type.
|
||
*/
|
||
|
||
if (metadata == NULL) {
|
||
hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, (char *) typePtr);
|
||
if (hPtr != NULL) {
|
||
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
||
Tcl_DeleteHashEntry(hPtr);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Otherwise we're attaching the metadata. Note that if there was already
|
||
* some metadata attached of this type, we delete that first.
|
||
*/
|
||
|
||
hPtr = Tcl_CreateHashEntry(clsPtr->metadataPtr, (char *) typePtr, &isNew);
|
||
if (!isNew) {
|
||
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
||
}
|
||
Tcl_SetHashValue(hPtr, metadata);
|
||
}
|
||
|
||
ClientData
|
||
Tcl_ObjectGetMetadata(
|
||
Tcl_Object object,
|
||
const Tcl_ObjectMetadataType *typePtr)
|
||
{
|
||
Object *oPtr = (Object *) object;
|
||
Tcl_HashEntry *hPtr;
|
||
|
||
/*
|
||
* If there's no metadata store attached, the type in question has
|
||
* definitely not been attached either!
|
||
*/
|
||
|
||
if (oPtr->metadataPtr == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* There is a metadata store, so look in it for the given type.
|
||
*/
|
||
|
||
hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, (char *) typePtr);
|
||
|
||
/*
|
||
* Return the metadata value if we found it, otherwise NULL.
|
||
*/
|
||
|
||
if (hPtr == NULL) {
|
||
return NULL;
|
||
}
|
||
return Tcl_GetHashValue(hPtr);
|
||
}
|
||
|
||
void
|
||
Tcl_ObjectSetMetadata(
|
||
Tcl_Object object,
|
||
const Tcl_ObjectMetadataType *typePtr,
|
||
ClientData metadata)
|
||
{
|
||
Object *oPtr = (Object *) object;
|
||
Tcl_HashEntry *hPtr;
|
||
int isNew;
|
||
|
||
/*
|
||
* Attach the metadata store if not done already.
|
||
*/
|
||
|
||
if (oPtr->metadataPtr == NULL) {
|
||
if (metadata == NULL) {
|
||
return;
|
||
}
|
||
oPtr->metadataPtr = ckalloc(sizeof(Tcl_HashTable));
|
||
Tcl_InitHashTable(oPtr->metadataPtr, TCL_ONE_WORD_KEYS);
|
||
}
|
||
|
||
/*
|
||
* If the metadata is NULL, we're deleting the metadata for the type.
|
||
*/
|
||
|
||
if (metadata == NULL) {
|
||
hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, (char *) typePtr);
|
||
if (hPtr != NULL) {
|
||
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
||
Tcl_DeleteHashEntry(hPtr);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Otherwise we're attaching the metadata. Note that if there was already
|
||
* some metadata attached of this type, we delete that first.
|
||
*/
|
||
|
||
hPtr = Tcl_CreateHashEntry(oPtr->metadataPtr, (char *) typePtr, &isNew);
|
||
if (!isNew) {
|
||
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
||
}
|
||
Tcl_SetHashValue(hPtr, metadata);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* PublicObjectCmd, PrivateObjectCmd, TclOOInvokeObject --
|
||
*
|
||
* Main entry point for object invocations. The Public* and Private*
|
||
* wrapper functions (implementations of both object instance commands
|
||
* and [my]) are just thin wrappers round the main TclOOObjectCmdCore
|
||
* function. Note that the core is function is NRE-aware.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
PublicObjectCmd(
|
||
ClientData clientData,
|
||
Tcl_Interp *interp,
|
||
int objc,
|
||
Tcl_Obj *const *objv)
|
||
{
|
||
return Tcl_NRCallObjProc(interp, PublicNRObjectCmd, clientData,objc,objv);
|
||
}
|
||
|
||
static int
|
||
PublicNRObjectCmd(
|
||
ClientData clientData,
|
||
Tcl_Interp *interp,
|
||
int objc,
|
||
Tcl_Obj *const *objv)
|
||
{
|
||
return TclOOObjectCmdCore(clientData, interp, objc, objv, PUBLIC_METHOD,
|
||
NULL);
|
||
}
|
||
|
||
static int
|
||
PrivateObjectCmd(
|
||
ClientData clientData,
|
||
Tcl_Interp *interp,
|
||
int objc,
|
||
Tcl_Obj *const *objv)
|
||
{
|
||
return Tcl_NRCallObjProc(interp, PrivateNRObjectCmd,clientData,objc,objv);
|
||
}
|
||
|
||
static int
|
||
PrivateNRObjectCmd(
|
||
ClientData clientData,
|
||
Tcl_Interp *interp,
|
||
int objc,
|
||
Tcl_Obj *const *objv)
|
||
{
|
||
return TclOOObjectCmdCore(clientData, interp, objc, objv, 0, NULL);
|
||
}
|
||
|
||
int
|
||
TclOOInvokeObject(
|
||
Tcl_Interp *interp, /* Interpreter for commands, variables,
|
||
* results, error reporting, etc. */
|
||
Tcl_Object object, /* The object to invoke. */
|
||
Tcl_Class startCls, /* Where in the class chain to start the
|
||
* invoke from, or NULL to traverse the whole
|
||
* chain including filters. */
|
||
int publicPrivate, /* Whether this is an invoke from a public
|
||
* context (PUBLIC_METHOD), a private context
|
||
* (PRIVATE_METHOD), or a *really* private
|
||
* context (any other value; conventionally
|
||
* 0). */
|
||
int objc, /* Number of arguments. */
|
||
Tcl_Obj *const *objv) /* Array of argument objects. It is assumed
|
||
* that the name of the method to invoke will
|
||
* be at index 1. */
|
||
{
|
||
switch (publicPrivate) {
|
||
case PUBLIC_METHOD:
|
||
return TclOOObjectCmdCore((Object *) object, interp, objc, objv,
|
||
PUBLIC_METHOD, (Class *) startCls);
|
||
case PRIVATE_METHOD:
|
||
return TclOOObjectCmdCore((Object *) object, interp, objc, objv,
|
||
PRIVATE_METHOD, (Class *) startCls);
|
||
default:
|
||
return TclOOObjectCmdCore((Object *) object, interp, objc, objv, 0,
|
||
(Class *) startCls);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOObjectCmdCore, FinalizeObjectCall --
|
||
*
|
||
* Main function for object invocations. Does call chain creation,
|
||
* management and invocation. The function FinalizeObjectCall exists to
|
||
* clean up after the non-recursive processing of TclOOObjectCmdCore.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOOObjectCmdCore(
|
||
Object *oPtr, /* The object being invoked. */
|
||
Tcl_Interp *interp, /* The interpreter containing the object. */
|
||
int objc, /* How many arguments are being passed in. */
|
||
Tcl_Obj *const *objv, /* The array of arguments. */
|
||
int flags, /* Whether this is an invocation through the
|
||
* public or the private command interface. */
|
||
Class *startCls) /* Where to start in the call chain, or NULL
|
||
* if we are to start at the front with
|
||
* filters and the object's methods (which is
|
||
* the normal case). */
|
||
{
|
||
CallContext *contextPtr;
|
||
Tcl_Obj *methodNamePtr;
|
||
int result;
|
||
|
||
/*
|
||
* If we've no method name, throw this directly into the unknown
|
||
* processing.
|
||
*/
|
||
|
||
if (objc < 2) {
|
||
flags |= FORCE_UNKNOWN;
|
||
methodNamePtr = NULL;
|
||
goto noMapping;
|
||
}
|
||
|
||
/*
|
||
* Give plugged in code a chance to remap the method name.
|
||
*/
|
||
|
||
methodNamePtr = objv[1];
|
||
if (oPtr->mapMethodNameProc != NULL) {
|
||
Class **startClsPtr = &startCls;
|
||
Tcl_Obj *mappedMethodName = Tcl_DuplicateObj(methodNamePtr);
|
||
|
||
result = oPtr->mapMethodNameProc(interp, (Tcl_Object) oPtr,
|
||
(Tcl_Class *) startClsPtr, mappedMethodName);
|
||
if (result != TCL_OK) {
|
||
TclDecrRefCount(mappedMethodName);
|
||
if (result == TCL_BREAK) {
|
||
goto noMapping;
|
||
} else if (result == TCL_ERROR) {
|
||
Tcl_AddErrorInfo(interp, "\n (while mapping method name)");
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
* Get the call chain for the remapped name.
|
||
*/
|
||
|
||
Tcl_IncrRefCount(mappedMethodName);
|
||
contextPtr = TclOOGetCallContext(oPtr, mappedMethodName,
|
||
flags | (oPtr->flags & FILTER_HANDLING), methodNamePtr);
|
||
TclDecrRefCount(mappedMethodName);
|
||
if (contextPtr == NULL) {
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"impossible to invoke method \"%s\": no defined method or"
|
||
" unknown method", TclGetString(methodNamePtr)));
|
||
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD_MAPPED",
|
||
TclGetString(methodNamePtr), NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
} else {
|
||
/*
|
||
* Get the call chain.
|
||
*/
|
||
|
||
noMapping:
|
||
contextPtr = TclOOGetCallContext(oPtr, methodNamePtr,
|
||
flags | (oPtr->flags & FILTER_HANDLING), NULL);
|
||
if (contextPtr == NULL) {
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"impossible to invoke method \"%s\": no defined method or"
|
||
" unknown method", TclGetString(methodNamePtr)));
|
||
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD",
|
||
TclGetString(methodNamePtr), NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Check to see if we need to apply magical tricks to start part way
|
||
* through the call chain.
|
||
*/
|
||
|
||
if (startCls != NULL) {
|
||
for (; contextPtr->index < contextPtr->callPtr->numChain;
|
||
contextPtr->index++) {
|
||
struct MInvoke *miPtr =
|
||
&contextPtr->callPtr->chain[contextPtr->index];
|
||
|
||
if (miPtr->isFilter) {
|
||
continue;
|
||
}
|
||
if (miPtr->mPtr->declaringClassPtr == startCls) {
|
||
break;
|
||
}
|
||
}
|
||
if (contextPtr->index >= contextPtr->callPtr->numChain) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"no valid method implementation", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD",
|
||
TclGetString(methodNamePtr), NULL);
|
||
TclOODeleteContext(contextPtr);
|
||
return TCL_ERROR;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Invoke the call chain, locking the object structure against deletion
|
||
* for the duration.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, FinalizeObjectCall, contextPtr, NULL,NULL,NULL);
|
||
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
||
}
|
||
|
||
static int
|
||
FinalizeObjectCall(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
/*
|
||
* Dispose of the call chain, which drops the lock on the object's
|
||
* structure.
|
||
*/
|
||
|
||
TclOODeleteContext(data[0]);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* Tcl_ObjectContextInvokeNext, TclNRObjectContextInvokeNext, FinalizeNext --
|
||
*
|
||
* Invokes the next stage of the call chain described in an object
|
||
* context. This is the core of the implementation of the [next] command.
|
||
* Does not do management of the call-frame stack. Available in public
|
||
* (standard API) and private (NRE-aware) forms. FinalizeNext is a
|
||
* private function used to clean up in the NRE case.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
Tcl_ObjectContextInvokeNext(
|
||
Tcl_Interp *interp,
|
||
Tcl_ObjectContext context,
|
||
int objc,
|
||
Tcl_Obj *const *objv,
|
||
int skip)
|
||
{
|
||
CallContext *contextPtr = (CallContext *) context;
|
||
int savedIndex = contextPtr->index;
|
||
int savedSkip = contextPtr->skip;
|
||
int result;
|
||
|
||
if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) {
|
||
/*
|
||
* We're at the end of the chain; generate an error message unless the
|
||
* interpreter is being torn down, in which case we might be getting
|
||
* here because of methods/destructors doing a [next] (or equivalent)
|
||
* unexpectedly.
|
||
*/
|
||
|
||
const char *methodType;
|
||
|
||
if (Tcl_InterpDeleted(interp)) {
|
||
return TCL_OK;
|
||
}
|
||
|
||
if (contextPtr->callPtr->flags & CONSTRUCTOR) {
|
||
methodType = "constructor";
|
||
} else if (contextPtr->callPtr->flags & DESTRUCTOR) {
|
||
methodType = "destructor";
|
||
} else {
|
||
methodType = "method";
|
||
}
|
||
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"no next %s implementation", methodType));
|
||
Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Advance to the next method implementation in the chain in the method
|
||
* call context while we process the body. However, need to adjust the
|
||
* argument-skip control because we're guaranteed to have a single prefix
|
||
* arg (i.e., 'next') and not the variable amount that can happen because
|
||
* method invocations (i.e., '$obj meth' and 'my meth'), constructors
|
||
* (i.e., '$cls new' and '$cls create obj') and destructors (no args at
|
||
* all) come through the same code.
|
||
*/
|
||
|
||
contextPtr->index++;
|
||
contextPtr->skip = skip;
|
||
|
||
/*
|
||
* Invoke the (advanced) method call context in the caller context.
|
||
*/
|
||
|
||
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, objc,
|
||
objv);
|
||
|
||
/*
|
||
* Restore the call chain context index as we've finished the inner invoke
|
||
* and want to operate in the outer context again.
|
||
*/
|
||
|
||
contextPtr->index = savedIndex;
|
||
contextPtr->skip = savedSkip;
|
||
|
||
return result;
|
||
}
|
||
|
||
int
|
||
TclNRObjectContextInvokeNext(
|
||
Tcl_Interp *interp,
|
||
Tcl_ObjectContext context,
|
||
int objc,
|
||
Tcl_Obj *const *objv,
|
||
int skip)
|
||
{
|
||
CallContext *contextPtr = (CallContext *) context;
|
||
|
||
if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) {
|
||
/*
|
||
* We're at the end of the chain; generate an error message unless the
|
||
* interpreter is being torn down, in which case we might be getting
|
||
* here because of methods/destructors doing a [next] (or equivalent)
|
||
* unexpectedly.
|
||
*/
|
||
|
||
const char *methodType;
|
||
|
||
if (Tcl_InterpDeleted(interp)) {
|
||
return TCL_OK;
|
||
}
|
||
|
||
if (contextPtr->callPtr->flags & CONSTRUCTOR) {
|
||
methodType = "constructor";
|
||
} else if (contextPtr->callPtr->flags & DESTRUCTOR) {
|
||
methodType = "destructor";
|
||
} else {
|
||
methodType = "method";
|
||
}
|
||
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"no next %s implementation", methodType));
|
||
Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Advance to the next method implementation in the chain in the method
|
||
* call context while we process the body. However, need to adjust the
|
||
* argument-skip control because we're guaranteed to have a single prefix
|
||
* arg (i.e., 'next') and not the variable amount that can happen because
|
||
* method invocations (i.e., '$obj meth' and 'my meth'), constructors
|
||
* (i.e., '$cls new' and '$cls create obj') and destructors (no args at
|
||
* all) come through the same code.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, FinalizeNext, contextPtr,
|
||
INT2PTR(contextPtr->index), INT2PTR(contextPtr->skip), NULL);
|
||
contextPtr->index++;
|
||
contextPtr->skip = skip;
|
||
|
||
/*
|
||
* Invoke the (advanced) method call context in the caller context.
|
||
*/
|
||
|
||
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
||
}
|
||
|
||
static int
|
||
FinalizeNext(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
CallContext *contextPtr = data[0];
|
||
|
||
/*
|
||
* Restore the call chain context index as we've finished the inner invoke
|
||
* and want to operate in the outer context again.
|
||
*/
|
||
|
||
contextPtr->index = PTR2INT(data[1]);
|
||
contextPtr->skip = PTR2INT(data[2]);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* Tcl_GetObjectFromObj --
|
||
*
|
||
* Utility function to get an object from a Tcl_Obj containing its name.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Object
|
||
Tcl_GetObjectFromObj(
|
||
Tcl_Interp *interp, /* Interpreter in which to locate the object.
|
||
* Will have an error message placed in it if
|
||
* the name does not refer to an object. */
|
||
Tcl_Obj *objPtr) /* The name of the object to look up, which is
|
||
* exactly the name of its public command. */
|
||
{
|
||
Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr);
|
||
|
||
if (cmdPtr == NULL) {
|
||
goto notAnObject;
|
||
}
|
||
if (cmdPtr->objProc != PublicObjectCmd) {
|
||
cmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr);
|
||
if (cmdPtr == NULL || cmdPtr->objProc != PublicObjectCmd) {
|
||
goto notAnObject;
|
||
}
|
||
}
|
||
return cmdPtr->objClientData;
|
||
|
||
notAnObject:
|
||
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"%s does not refer to an object", TclGetString(objPtr)));
|
||
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "OBJECT", TclGetString(objPtr),
|
||
NULL);
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOIsReachable --
|
||
*
|
||
* Utility function that tests whether a class is a subclass (whether
|
||
* directly or indirectly) of another class.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclOOIsReachable(
|
||
Class *targetPtr,
|
||
Class *startPtr)
|
||
{
|
||
int i;
|
||
Class *superPtr;
|
||
|
||
tailRecurse:
|
||
if (startPtr == targetPtr) {
|
||
return 1;
|
||
}
|
||
if (startPtr->superclasses.num == 1 && startPtr->mixins.num == 0) {
|
||
startPtr = startPtr->superclasses.list[0];
|
||
goto tailRecurse;
|
||
}
|
||
FOREACH(superPtr, startPtr->superclasses) {
|
||
if (TclOOIsReachable(targetPtr, superPtr)) {
|
||
return 1;
|
||
}
|
||
}
|
||
FOREACH(superPtr, startPtr->mixins) {
|
||
if (TclOOIsReachable(targetPtr, superPtr)) {
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* TclOOObjectName, Tcl_GetObjectName --
|
||
*
|
||
* Utility function that returns the name of the object. Note that this
|
||
* simplifies cache management by keeping the code to do it in one place
|
||
* and not sprayed all over. The value returned always has a reference
|
||
* count of at least one.
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Obj *
|
||
TclOOObjectName(
|
||
Tcl_Interp *interp,
|
||
Object *oPtr)
|
||
{
|
||
Tcl_Obj *namePtr;
|
||
|
||
if (oPtr->cachedNameObj) {
|
||
return oPtr->cachedNameObj;
|
||
}
|
||
namePtr = Tcl_NewObj();
|
||
Tcl_GetCommandFullName(interp, oPtr->command, namePtr);
|
||
Tcl_IncrRefCount(namePtr);
|
||
oPtr->cachedNameObj = namePtr;
|
||
return namePtr;
|
||
}
|
||
|
||
Tcl_Obj *
|
||
Tcl_GetObjectName(
|
||
Tcl_Interp *interp,
|
||
Tcl_Object object)
|
||
{
|
||
return TclOOObjectName(interp, (Object *) object);
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------
|
||
*
|
||
* assorted trivial 'getter' functions
|
||
*
|
||
* ----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Method
|
||
Tcl_ObjectContextMethod(
|
||
Tcl_ObjectContext context)
|
||
{
|
||
CallContext *contextPtr = (CallContext *) context;
|
||
return (Tcl_Method) contextPtr->callPtr->chain[contextPtr->index].mPtr;
|
||
}
|
||
|
||
int
|
||
Tcl_ObjectContextIsFiltering(
|
||
Tcl_ObjectContext context)
|
||
{
|
||
CallContext *contextPtr = (CallContext *) context;
|
||
return contextPtr->callPtr->chain[contextPtr->index].isFilter;
|
||
}
|
||
|
||
Tcl_Object
|
||
Tcl_ObjectContextObject(
|
||
Tcl_ObjectContext context)
|
||
{
|
||
return (Tcl_Object) ((CallContext *)context)->oPtr;
|
||
}
|
||
|
||
int
|
||
Tcl_ObjectContextSkippedArgs(
|
||
Tcl_ObjectContext context)
|
||
{
|
||
return ((CallContext *)context)->skip;
|
||
}
|
||
|
||
Tcl_Namespace *
|
||
Tcl_GetObjectNamespace(
|
||
Tcl_Object object)
|
||
{
|
||
return ((Object *)object)->namespacePtr;
|
||
}
|
||
|
||
Tcl_Command
|
||
Tcl_GetObjectCommand(
|
||
Tcl_Object object)
|
||
{
|
||
return ((Object *)object)->command;
|
||
}
|
||
|
||
Tcl_Class
|
||
Tcl_GetObjectAsClass(
|
||
Tcl_Object object)
|
||
{
|
||
return (Tcl_Class) ((Object *)object)->classPtr;
|
||
}
|
||
|
||
int
|
||
Tcl_ObjectDeleted(
|
||
Tcl_Object object)
|
||
{
|
||
return ((Object *)object)->command == NULL;
|
||
}
|
||
|
||
Tcl_Object
|
||
Tcl_GetClassAsObject(
|
||
Tcl_Class clazz)
|
||
{
|
||
return (Tcl_Object) ((Class *)clazz)->thisPtr;
|
||
}
|
||
|
||
Tcl_ObjectMapMethodNameProc *
|
||
Tcl_ObjectGetMethodNameMapper(
|
||
Tcl_Object object)
|
||
{
|
||
return ((Object *) object)->mapMethodNameProc;
|
||
}
|
||
|
||
void
|
||
Tcl_ObjectSetMethodNameMapper(
|
||
Tcl_Object object,
|
||
Tcl_ObjectMapMethodNameProc *mapMethodNameProc)
|
||
{
|
||
((Object *) object)->mapMethodNameProc = mapMethodNameProc;
|
||
}
|
||
|
||
/*
|
||
* Local Variables:
|
||
* mode: c
|
||
* c-basic-offset: 4
|
||
* fill-column: 78
|
||
* End:
|
||
*/
|