/// <summary> CleanupVar -> cleanupVar /// /// This procedure is called when it looks like it may be OK /// to free up the variable's record and hash table entry, and /// those of its containing parent. It's called, for example, /// when a trace on a variable deletes the variable. /// /// </summary> /// <param name="var">variable that may be a candidate for being expunged. /// </param> /// <param name="array">Array that contains the variable, or NULL if this /// variable isn't an array element. /// </param> static protected internal void cleanupVar( Var var, Var array ) { if ( var.isVarUndefined() && ( var.refCount == 0 ) && ( var.traces == null ) && ( ( var.flags & VarFlags.IN_HASHTABLE ) != 0 ) ) { if ( var.table != null ) { SupportClass.HashtableRemove( var.table, var.hashKey ); var.table = null; var.hashKey = null; } } if ( array != null ) { if ( array.isVarUndefined() && ( array.refCount == 0 ) && ( array.traces == null ) && ( ( array.flags & VarFlags.IN_HASHTABLE ) != 0 ) ) { if ( array.table != null ) { SupportClass.HashtableRemove( array.table, array.hashKey ); array.table = null; array.hashKey = null; } } } }
/// <summary> TCL.Tcl_UnsetVar2 -> unsetVar /// /// Unset a variable, given a two-part name consisting of array /// name and element within array. /// /// </summary> /// <param name="part1">1st part of the variable name. /// </param> /// <param name="part2">2nd part of the variable name. /// </param> /// <param name="flags">misc flags that control the actions of this method. /// /// If part1 and part2 indicate a local or global variable in interp, /// it is deleted. If part1 is an array name and part2 is null, then /// the whole array is deleted. /// /// </param> internal static void unsetVar( Interp interp, string part1, string part2, TCL.VarFlag flags ) { Var dummyVar; Var var; Var array; //ActiveVarTrace active; TclObject obj; TCL.CompletionCode result; // FIXME : what about the null return vs exception thing here? Var[] lookup_result = lookupVar( interp, part1, part2, flags, "unset", false, false ); if ( lookup_result == null ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) throw new TclRuntimeError( "unexpected null reference" ); else return; } var = lookup_result[0]; array = lookup_result[1]; result = ( var.isVarUndefined() ? TCL.CompletionCode.ERROR : TCL.CompletionCode.OK ); if ( ( array != null ) && ( array.sidVec != null ) ) { deleteSearches( array ); } // The code below is tricky, because of the possibility that // a trace procedure might try to access a variable being // deleted. To handle this situation gracefully, do things // in three steps: // 1. Copy the contents of the variable to a dummy variable // structure, and mark the original Var structure as undefined. // 2. Invoke traces and clean up the variable, using the dummy copy. // 3. If at the end of this the original variable is still // undefined and has no outstanding references, then delete // it (but it could have gotten recreated by a trace). dummyVar = new Var(); //FIXME: Var class really should implement clone to make a bit copy. dummyVar.value = var.value; dummyVar.traces = var.traces; dummyVar.flags = var.flags; dummyVar.hashKey = var.hashKey; dummyVar.table = var.table; dummyVar.refCount = var.refCount; dummyVar.ns = var.ns; var.setVarUndefined(); var.setVarScalar(); var.value = null; // dummyVar points to any value object var.traces = null; var.sidVec = null; // Call trace procedures for the variable being deleted. Then delete // its traces. Be sure to abort any other traces for the variable // that are still pending. Special tricks: // 1. We need to increment var's refCount around this: CallTraces // will use dummyVar so it won't increment var's refCount itself. // 2. Turn off the TRACE_ACTIVE flag in dummyVar: we want to // call unset traces even if other traces are pending. if ( ( dummyVar.traces != null ) || ( ( array != null ) && ( array.traces != null ) ) ) { var.refCount++; dummyVar.flags &= ~VarFlags.TRACE_ACTIVE; callTraces( interp, array, dummyVar, part1, part2, ( flags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) | TCL.VarFlag.TRACE_UNSETS ); dummyVar.traces = null; // Active trace stuff is not part of Jacl's interp var.refCount--; } // If the variable is an array, delete all of its elements. This must be // done after calling the traces on the array, above (that's the way // traces are defined). If it is a scalar, "discard" its object // (decrement the ref count of its object, if any). if ( dummyVar.isVarArray() && !dummyVar.isVarUndefined() ) { deleteArray( interp, part1, dummyVar, ( flags & ( TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.NAMESPACE_ONLY ) ) | TCL.VarFlag.TRACE_UNSETS ); } if ( dummyVar.isVarScalar() && ( dummyVar.value != null ) ) { obj = (TclObject)dummyVar.value; obj.release(); dummyVar.value = null; } // If the variable was a namespace variable, decrement its reference count. if ( ( var.flags & VarFlags.NAMESPACE_VAR ) != 0 ) { var.flags &= ~VarFlags.NAMESPACE_VAR; var.refCount--; } // Finally, if the variable is truly not in use then free up its Var // structure and remove it from its hash table, if any. The ref count of // its value object, if any, was decremented above. cleanupVar( var, array ); // It's an error to unset an undefined variable. if ( result != TCL.CompletionCode.OK ) { if ( ( flags & TCL.VarFlag.LEAVE_ERR_MSG ) != 0 ) { throw new TclVarException( interp, part1, part2, "unset", ( ( array == null ) ? noSuchVar : noSuchElement ) ); } } }