From: stsoe on
Hi all,

I am using tcl8.5.6 and have run into an issue when my extension is
calling Tcl_InvalidateStringRep with a shared Tcl_Obj that happens to
be stored in Tcl's literal table. When Tcl later looks up in the
literal table for an object that matches the string rep of the earlier
invalidated object, it can happen that it reaches the object that was
earlier processed by Tcl_InvalidateStringRep. This object correctly
has bytes==0x0 but the length was not reset and remains the original
length, hence matches the length of the requested string literal. A
crash occurs when TclCreateLiteral then deferences the null bytes ptr.

Shouldn't Tcl_InvalidateStringRep remove the objPtr from the literal
table if it is there? Or at the very least set the length to 0 at the
same time it deletes and nulls the bytes ptr ?

--Soren

From: stsoe on
On Apr 7, 6:28 pm, miguel sofer <mso...(a)users.sf.net> wrote:
> stsoe wrote:
> > Hi all,
>
> > I am using tcl8.5.6 and have run into an issue when my extension is
> > calling Tcl_InvalidateStringRep with a shared Tcl_Obj that happens to
> > be stored in Tcl's literal table. When Tcl later looks up in the
> > literal table for an object that matches the string rep of the earlier
> > invalidated object, it can happen that it reaches the object that was
> > earlier processed by Tcl_InvalidateStringRep. This object correctly
> > has bytes==0x0 but the length was not reset and remains the original
> > length, hence matches the length of the requested string literal. A
> > crash occurs when TclCreateLiteral then deferences the null bytes ptr.
>
> > Shouldn't Tcl_InvalidateStringRep remove the objPtr from the literal
> > table if it is there? Or at the very least set the length to 0 at the
> > same time it deletes and nulls the bytes ptr ?
>
> Remove from the literal table: no. Depending on which literal table you
> mean, this may even be disastrous: bytecodes index into this table.
>
> Set the length to 0: not really, irrelevant. There are Tcl_Objs with 0
> length anyway (the empty string), so length==0 is not a good signal for
> 'invalid string rep'. That is signalled by bytes==NULL.
>
> The REAL question is: why are you invalidating the string rep of a
> shared object?
>
> The literal code assumes that this is never done: it doesn't make a lot
> of sense (or else we're missing something). Remember EIAS and the COW
> principle: a shared object is read-only, and any changes (shimmering?)
> that you do to it have to preserve the string rep (which carries the
> real identity of the object).
>
> Miguel
>
> EIAS: Everything Is A String
> COW : Copy On Write

In my extension a Tcl_Obj wraps native C++ objects (using
internalRep).

% set x [create_object -name foo]

The Tcl_Obj for the 'foo' argument is being 'shimmered' by the command
and gets an internal representation for the corresponding native C++
object. The relationship between the C++ object and the Tcl_Obj is
recorded by the extension.

Some time later, another extension command may invoke C++ code that
ends up deleting the C++ object and as part of this
Tcl_InvalidateStringRep is called on the corresponding Tcl_Obj. Any
following

% puts $x

will force Tcl to recompute the string representation, which could for
example be "null" to indicate the object is no longer valid. The
code that calls Tcl_InvalidateStringRep() cannot use Tcl_IsShared() to
prevent the call, because the object legally may be shared, for
example

% set y $x

Miguel, your answer makes me realize that I probably shouldn't be
shimmering a shared object (the one that Tcl inserted in its literal
table) if I later on may have to change its string representation.
Preventing this does indeed fix the crash.

Thanks for your answer.

--Soren
From: Alexandre Ferrieux on
On Apr 8, 6:22 am, stsoe <st...(a)gonsoe.com> wrote:
>
> Miguel, your answer makes me realize that I probably shouldn't be
> shimmering a shared object (the one that Tcl inserted in its literal
> table) if I later on may have to change its string representation.
> Preventing this does indeed fix the crash.

Warning, shimmering and invalidating the string rep are two different
things. It turns out that shimmering on a shared object happens
routinely, and does no harm:

% namespace path ::tcl::unsupported
% set x 13
13
% rep $x
value is a pure string with a refcount of 6, object pointer at
0x8374a88, string representation "13".
% expr {$x+2}
15
% rep $x
value is a int with a refcount of 6, object pointer at 0x8374a88,
internal representation 0xd:0x83c5f90, string representation "13".
% lindex $x 0
13
% rep $x
value is a list with a refcount of 6, object pointer at 0x8374a88,
internal representation 0x839e340:(nil), string representation "13".

The reason is that, as Miguel points out, COW means copy when you
_modify_ the object in the sense of its script-accessible semantics,
which is its string rep in our EIAS universe. In pure shimmering
(above example) the string rep is not touched, hence the object keeps
its identity (and high refcount), never being duplicated in the
process.

Now touching the string rep is somthing more radical: the power of
EIAS is that you can reason entirely at the string level. Touch the
string rep and you get a new value, and COW commands to duplicate.

Then, we can look again at your problem globally as one of
"transparent values vs. handles".
EIAS (again) means that true Tcl value are strictly equivalent with
their string representation, meaning that any internal state playing a
role in their semantics can be _regenerated_ from the string rep
alone. Those are "transparent values". On the other hand,
"handles" (like file handles given by [open]) are only keys into an
external "bag of state" (here the IO subsystem). Those _cannot_ be
carelessly refcounted like transparent values without violating EIAS.

Hence, to be clean you (1) mustn't touch the string rep and (2)
prepare to detect "stale accesses" if someone uses that "handle" again
after the C++ object's death.

-Alex