mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 898347 - Generalize the newScript field on type objects into an "addendum" (Part 4/6) r=bhackett
This commit is contained in:
parent
d57113c33b
commit
e088a95129
@ -1128,9 +1128,13 @@ ScanTypeObject(GCMarker *gcmarker, types::TypeObject *type)
|
||||
if (type->singleton && !type->lazy())
|
||||
PushMarkStack(gcmarker, type->singleton);
|
||||
|
||||
if (type->newScript) {
|
||||
PushMarkStack(gcmarker, type->newScript->fun);
|
||||
PushMarkStack(gcmarker, type->newScript->shape.get());
|
||||
if (type->addendum) {
|
||||
switch (type->addendum->kind) {
|
||||
case types::TypeObjectAddendum::NewScript:
|
||||
PushMarkStack(gcmarker, type->newScript()->fun);
|
||||
PushMarkStack(gcmarker, type->newScript()->shape.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type->interpretedFunction)
|
||||
@ -1153,9 +1157,13 @@ gc::MarkChildren(JSTracer *trc, types::TypeObject *type)
|
||||
if (type->singleton && !type->lazy())
|
||||
MarkObject(trc, &type->singleton, "type_singleton");
|
||||
|
||||
if (type->newScript) {
|
||||
MarkObject(trc, &type->newScript->fun, "type_new_function");
|
||||
MarkShape(trc, &type->newScript->shape, "type_new_shape");
|
||||
if (type->addendum) {
|
||||
switch (type->addendum->kind) {
|
||||
case types::TypeObjectAddendum::NewScript:
|
||||
MarkObject(trc, &type->newScript()->fun, "type_new_function");
|
||||
MarkShape(trc, &type->newScript()->shape, "type_new_shape");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type->interpretedFunction)
|
||||
|
@ -4589,7 +4589,7 @@ IonBuilder::createThisScriptedSingleton(HandleFunction target, MDefinition *call
|
||||
return NULL;
|
||||
|
||||
// Trigger recompilation if the templateObject changes.
|
||||
if (templateObject->type()->newScript)
|
||||
if (templateObject->type()->hasNewScript())
|
||||
types::HeapTypeSet::WatchObjectStateChange(cx, templateObject->type());
|
||||
|
||||
MCreateThisWithTemplate *createThis = MCreateThisWithTemplate::New(templateObject);
|
||||
|
@ -1921,12 +1921,12 @@ HeapTypeSet::isOwnProperty(JSContext *cx, TypeObject *object, bool configurable)
|
||||
* definite properties be invalidated.
|
||||
*/
|
||||
if (object->flags & OBJECT_FLAG_NEW_SCRIPT_REGENERATE) {
|
||||
if (object->newScript) {
|
||||
if (object->hasNewScript()) {
|
||||
Rooted<TypeObject*> typeObj(cx, object);
|
||||
RootedFunction fun(cx, object->newScript->fun);
|
||||
RootedFunction fun(cx, object->newScript()->fun);
|
||||
CheckNewScriptProperties(cx, typeObj, fun);
|
||||
} else {
|
||||
JS_ASSERT(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED);
|
||||
JS_ASSERT(object->flags & OBJECT_FLAG_ADDENDUM_CLEARED);
|
||||
object->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE;
|
||||
}
|
||||
}
|
||||
@ -3679,8 +3679,8 @@ TypeObject::markUnknown(ExclusiveContext *cx)
|
||||
JS_ASSERT(cx->compartment()->activeAnalysis);
|
||||
JS_ASSERT(!unknownProperties());
|
||||
|
||||
if (!(flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED))
|
||||
clearNewScript(cx);
|
||||
if (!(flags & OBJECT_FLAG_ADDENDUM_CLEARED))
|
||||
clearAddendum(cx);
|
||||
|
||||
InferSpew(ISpewOps, "UnknownProperties: %s", TypeObjectString(this));
|
||||
|
||||
@ -3706,22 +3706,43 @@ TypeObject::markUnknown(ExclusiveContext *cx)
|
||||
}
|
||||
|
||||
void
|
||||
TypeObject::clearNewScript(ExclusiveContext *cx)
|
||||
TypeObject::clearAddendum(ExclusiveContext *cx)
|
||||
{
|
||||
JS_ASSERT(!(flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED));
|
||||
flags |= OBJECT_FLAG_NEW_SCRIPT_CLEARED;
|
||||
JS_ASSERT(!(flags & OBJECT_FLAG_ADDENDUM_CLEARED));
|
||||
flags |= OBJECT_FLAG_ADDENDUM_CLEARED;
|
||||
|
||||
/*
|
||||
* It is possible for the object to not have a new script yet but to have
|
||||
* one added in the future. When analyzing properties of new scripts we mix
|
||||
* in adding constraints to trigger clearNewScript with changes to the
|
||||
* type sets themselves (from breakTypeBarriers). It is possible that we
|
||||
* could trigger one of these constraints before AnalyzeNewScriptProperties
|
||||
* has finished, in which case we want to make sure that call fails.
|
||||
* It is possible for the object to not have a new script or other
|
||||
* addendum yet, but to have one added in the future. When
|
||||
* analyzing properties of new scripts we mix in adding
|
||||
* constraints to trigger clearNewScript with changes to the type
|
||||
* sets themselves (from breakTypeBarriers). It is possible that
|
||||
* we could trigger one of these constraints before
|
||||
* AnalyzeNewScriptProperties has finished, in which case we want
|
||||
* to make sure that call fails.
|
||||
*/
|
||||
if (!newScript)
|
||||
if (!addendum)
|
||||
return;
|
||||
|
||||
switch (addendum->kind) {
|
||||
case TypeObjectAddendum::NewScript:
|
||||
return clearNewScriptAddendum(cx);
|
||||
}
|
||||
|
||||
|
||||
/* We NULL out addendum *before* freeing it so the write barrier works. */
|
||||
TypeObjectAddendum *savedAddendum = addendum;
|
||||
addendum = NULL;
|
||||
js_free(savedAddendum);
|
||||
|
||||
markStateChange(cx);
|
||||
|
||||
MOZ_ASSUME_UNREACHABLE("Invalid addendum kind");
|
||||
}
|
||||
|
||||
void
|
||||
TypeObject::clearNewScriptAddendum(ExclusiveContext *cx)
|
||||
{
|
||||
AutoEnterAnalysis enter(cx);
|
||||
|
||||
/*
|
||||
@ -3754,7 +3775,7 @@ TypeObject::clearNewScript(ExclusiveContext *cx)
|
||||
for (ScriptFrameIter iter(cx->asJSContext()); !iter.done(); ++iter) {
|
||||
pcOffsets.append(uint32_t(iter.pc() - iter.script()->code));
|
||||
if (iter.isConstructing() &&
|
||||
iter.callee() == newScript->fun &&
|
||||
iter.callee() == newScript()->fun &&
|
||||
iter.thisv().isObject() &&
|
||||
!iter.thisv().toObject().hasLazyType() &&
|
||||
iter.thisv().toObject().type() == this)
|
||||
@ -3775,7 +3796,7 @@ TypeObject::clearNewScript(ExclusiveContext *cx)
|
||||
size_t callDepth = pcOffsets.length() - 1;
|
||||
uint32_t offset = pcOffsets[callDepth];
|
||||
|
||||
for (TypeNewScript::Initializer *init = newScript->initializerList;; init++) {
|
||||
for (TypeNewScript::Initializer *init = newScript()->initializerList;; init++) {
|
||||
if (init->kind == TypeNewScript::Initializer::SETPROP) {
|
||||
if (!depth && init->offset > offset) {
|
||||
/* Advanced past all properties which have been initialized. */
|
||||
@ -3818,13 +3839,6 @@ TypeObject::clearNewScript(ExclusiveContext *cx)
|
||||
// Threads with an ExclusiveContext are not allowed to run scripts.
|
||||
JS_ASSERT(!cx->perThreadData->activation());
|
||||
}
|
||||
|
||||
/* We NULL out newScript *before* freeing it so the write barrier works. */
|
||||
TypeNewScript *savedNewScript = newScript;
|
||||
newScript = NULL;
|
||||
js_free(savedNewScript);
|
||||
|
||||
markStateChange(cx);
|
||||
}
|
||||
|
||||
void
|
||||
@ -4747,7 +4761,7 @@ class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint
|
||||
|
||||
void newPropertyState(JSContext *cx, TypeSet *source)
|
||||
{
|
||||
if (!object->newScript)
|
||||
if (!object->hasNewScript())
|
||||
return;
|
||||
/*
|
||||
* Clear out the newScript shape and definite property information from
|
||||
@ -4755,8 +4769,8 @@ class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint
|
||||
* non-writable, both of which are indicated by the source type set
|
||||
* being marked as configured.
|
||||
*/
|
||||
if (!(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED) && source->ownProperty(true))
|
||||
object->clearNewScript(cx);
|
||||
if (!(object->flags & OBJECT_FLAG_ADDENDUM_CLEARED) && source->ownProperty(true))
|
||||
object->clearAddendum(cx);
|
||||
}
|
||||
|
||||
void newType(JSContext *cx, TypeSet *source, Type type) {}
|
||||
@ -4800,11 +4814,11 @@ class TypeConstraintClearDefiniteSingle : public TypeConstraint
|
||||
const char *kind() { return "clearDefiniteSingle"; }
|
||||
|
||||
void newType(JSContext *cx, TypeSet *source, Type type) {
|
||||
if (object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED)
|
||||
if (object->flags & OBJECT_FLAG_ADDENDUM_CLEARED)
|
||||
return;
|
||||
|
||||
if (source->baseFlags() || source->getObjectCount() > 1)
|
||||
object->clearNewScript(cx);
|
||||
object->clearAddendum(cx);
|
||||
}
|
||||
};
|
||||
|
||||
@ -5150,8 +5164,8 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
/* Strawman object to add properties to and watch for duplicates. */
|
||||
state.baseobj = NewBuiltinClassInstance(cx, &JSObject::class_, gc::FINALIZE_OBJECT16);
|
||||
if (!state.baseobj) {
|
||||
if (type->newScript)
|
||||
type->clearNewScript(cx);
|
||||
if (type->addendum)
|
||||
type->clearAddendum(cx);
|
||||
cx->compartment()->types.setPendingNukeTypes(cx);
|
||||
return;
|
||||
}
|
||||
@ -5159,10 +5173,10 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
AnalyzeNewScriptProperties(cx, type, fun, state);
|
||||
if (!state.baseobj ||
|
||||
state.baseobj->slotSpan() == 0 ||
|
||||
!!(type->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED))
|
||||
!!(type->flags & OBJECT_FLAG_ADDENDUM_CLEARED))
|
||||
{
|
||||
if (type->newScript)
|
||||
type->clearNewScript(cx);
|
||||
if (type->addendum)
|
||||
type->clearAddendum(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5171,11 +5185,12 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
* constraints and don't need to make another TypeNewScript. Make sure that
|
||||
* the properties added to baseobj match the type's definite properties.
|
||||
*/
|
||||
if (type->newScript) {
|
||||
if (type->hasNewScript()) {
|
||||
if (!type->matchDefiniteProperties(state.baseobj))
|
||||
type->clearNewScript(cx);
|
||||
type->clearAddendum(cx);
|
||||
return;
|
||||
}
|
||||
JS_ASSERT(!type->addendum);
|
||||
|
||||
gc::AllocKind kind = gc::GetGCObjectKind(state.baseobj->slotSpan());
|
||||
|
||||
@ -5200,29 +5215,31 @@ CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fu
|
||||
|
||||
size_t numBytes = sizeof(TypeNewScript)
|
||||
+ (state.initializerList.length() * sizeof(TypeNewScript::Initializer));
|
||||
TypeNewScript *newScript;
|
||||
#ifdef JSGC_ROOT_ANALYSIS
|
||||
// calloc can legitimately return a pointer that appears to be poisoned.
|
||||
void *p;
|
||||
do {
|
||||
p = cx->calloc_(numBytes);
|
||||
} while (IsPoisonedPtr(p));
|
||||
type->newScript = (TypeNewScript *) p;
|
||||
newScript = (TypeNewScript *) p;
|
||||
#else
|
||||
type->newScript = (TypeNewScript *) cx->calloc_(numBytes);
|
||||
newScript = (TypeNewScript *) cx->calloc_(numBytes);
|
||||
#endif
|
||||
type->addendum = newScript;
|
||||
|
||||
if (!type->newScript) {
|
||||
if (!newScript) {
|
||||
cx->compartment()->types.setPendingNukeTypes(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
type->newScript->fun = fun;
|
||||
type->newScript->allocKind = kind;
|
||||
type->newScript->shape = state.baseobj->lastProperty();
|
||||
newScript->fun = fun;
|
||||
newScript->allocKind = kind;
|
||||
newScript->shape = state.baseobj->lastProperty();
|
||||
|
||||
type->newScript->initializerList = (TypeNewScript::Initializer *)
|
||||
((char *) type->newScript.get() + sizeof(TypeNewScript));
|
||||
PodCopy(type->newScript->initializerList,
|
||||
newScript->initializerList = (TypeNewScript::Initializer *)
|
||||
((char *) newScript + sizeof(TypeNewScript));
|
||||
PodCopy(newScript->initializerList,
|
||||
state.initializerList.begin(),
|
||||
state.initializerList.length());
|
||||
}
|
||||
@ -6058,8 +6075,8 @@ ExclusiveContext::getNewType(Class *clasp, TaggedProto proto_, JSFunction *fun_)
|
||||
* Object.create is called with a prototype object that is also the
|
||||
* 'prototype' property of some scripted function.
|
||||
*/
|
||||
if (type->newScript && type->newScript->fun != fun_)
|
||||
type->clearNewScript(this);
|
||||
if (type->hasNewScript() && type->newScript()->fun != fun_)
|
||||
type->clearAddendum(this);
|
||||
|
||||
return type;
|
||||
}
|
||||
@ -6235,7 +6252,7 @@ inline void
|
||||
TypeObject::sweep(FreeOp *fop)
|
||||
{
|
||||
if (singleton) {
|
||||
JS_ASSERT(!newScript);
|
||||
JS_ASSERT(!hasNewScript());
|
||||
|
||||
/*
|
||||
* All properties can be discarded. We will regenerate them as needed
|
||||
@ -6247,8 +6264,8 @@ TypeObject::sweep(FreeOp *fop)
|
||||
}
|
||||
|
||||
if (!isMarked()) {
|
||||
if (newScript)
|
||||
fop->free_(newScript);
|
||||
if (addendum)
|
||||
fop->free_(addendum);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -6313,7 +6330,7 @@ TypeObject::sweep(FreeOp *fop)
|
||||
* newScript information, these constraints will need to be regenerated
|
||||
* the next time we compile code which depends on this info.
|
||||
*/
|
||||
if (newScript)
|
||||
if (hasNewScript())
|
||||
flags |= OBJECT_FLAG_NEW_SCRIPT_REGENERATE;
|
||||
}
|
||||
|
||||
@ -6665,11 +6682,11 @@ TypeObject::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
|
||||
* every GC. The type object is normally destroyed too, but we don't
|
||||
* charge this to 'temporary' as this is not for GC heap values.
|
||||
*/
|
||||
JS_ASSERT(!newScript);
|
||||
JS_ASSERT(!hasNewScript());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mallocSizeOf(newScript);
|
||||
return mallocSizeOf(addendum);
|
||||
}
|
||||
|
||||
TypeZone::TypeZone(Zone *zone)
|
||||
|
@ -355,8 +355,8 @@ enum {
|
||||
/* Objects with this type are functions. */
|
||||
OBJECT_FLAG_FUNCTION = 0x1,
|
||||
|
||||
/* If set, newScript information should not be installed on this object. */
|
||||
OBJECT_FLAG_NEW_SCRIPT_CLEARED = 0x2,
|
||||
/* If set, addendum information should not be installed on this object. */
|
||||
OBJECT_FLAG_ADDENDUM_CLEARED = 0x2,
|
||||
|
||||
/*
|
||||
* If set, type constraints covering the correctness of the newScript
|
||||
@ -866,6 +866,29 @@ struct Property
|
||||
static jsid getKey(Property *p) { return p->id; }
|
||||
};
|
||||
|
||||
struct TypeNewScript;
|
||||
|
||||
struct TypeObjectAddendum
|
||||
{
|
||||
enum Kind {
|
||||
NewScript
|
||||
};
|
||||
|
||||
Kind kind;
|
||||
|
||||
bool isNewScript() {
|
||||
return kind == NewScript;
|
||||
}
|
||||
|
||||
TypeNewScript *asNewScript() {
|
||||
JS_ASSERT(isNewScript());
|
||||
return (TypeNewScript*) this;
|
||||
}
|
||||
|
||||
static inline void writeBarrierPre(TypeObjectAddendum *newScript);
|
||||
static void writeBarrierPost(TypeObjectAddendum *newScript, void *addr) {}
|
||||
};
|
||||
|
||||
/*
|
||||
* Information attached to a TypeObject if it is always constructed using 'new'
|
||||
* on a particular script. This is used to manage state related to the definite
|
||||
@ -876,7 +899,7 @@ struct Property
|
||||
* remove the definite property information and repair the JS stack if the
|
||||
* constraints are violated.
|
||||
*/
|
||||
struct TypeNewScript
|
||||
struct TypeNewScript : public TypeObjectAddendum
|
||||
{
|
||||
HeapPtrFunction fun;
|
||||
|
||||
@ -912,7 +935,6 @@ struct TypeNewScript
|
||||
Initializer *initializerList;
|
||||
|
||||
static inline void writeBarrierPre(TypeNewScript *newScript);
|
||||
static void writeBarrierPost(TypeNewScript *newScript, void *addr) {}
|
||||
};
|
||||
|
||||
/*
|
||||
@ -970,11 +992,24 @@ struct TypeObject : gc::Cell
|
||||
static inline size_t offsetOfFlags() { return offsetof(TypeObject, flags); }
|
||||
|
||||
/*
|
||||
* If non-NULL, objects of this type have always been constructed using
|
||||
* 'new' on the specified script, which adds some number of properties to
|
||||
* the object in a definite order before the object escapes.
|
||||
* This field allows various special classes of objects to attach
|
||||
* additional information to a type object:
|
||||
*
|
||||
* - `TypeNewScript`: If addendum is a `TypeNewScript`, it
|
||||
* indicates that objects of this type have always been
|
||||
* constructed using 'new' on the specified script, which adds
|
||||
* some number of properties to the object in a definite order
|
||||
* before the object escapes.
|
||||
*/
|
||||
HeapPtr<TypeNewScript> newScript;
|
||||
HeapPtr<TypeObjectAddendum> addendum;
|
||||
|
||||
bool hasNewScript() {
|
||||
return addendum && addendum->isNewScript();
|
||||
}
|
||||
|
||||
TypeNewScript *newScript() {
|
||||
return addendum->asNewScript();
|
||||
}
|
||||
|
||||
/*
|
||||
* Properties of this object. This may contain JSID_VOID, representing the
|
||||
@ -1068,7 +1103,8 @@ struct TypeObject : gc::Cell
|
||||
void markStateChange(ExclusiveContext *cx);
|
||||
void setFlags(ExclusiveContext *cx, TypeObjectFlags flags);
|
||||
void markUnknown(ExclusiveContext *cx);
|
||||
void clearNewScript(ExclusiveContext *cx);
|
||||
void clearAddendum(ExclusiveContext *cx);
|
||||
void clearNewScriptAddendum(ExclusiveContext *cx);
|
||||
void getFromPrototypes(JSContext *cx, jsid id, TypeSet *types, bool force = false);
|
||||
|
||||
void print();
|
||||
|
@ -1583,6 +1583,20 @@ TypeObject::readBarrier(TypeObject *type)
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
TypeObjectAddendum::writeBarrierPre(TypeObjectAddendum *type)
|
||||
{
|
||||
#ifdef JSGC_INCREMENTAL
|
||||
if (!type)
|
||||
return;
|
||||
|
||||
switch (type->kind) {
|
||||
case NewScript:
|
||||
return TypeNewScript::writeBarrierPre(type->asNewScript());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
TypeNewScript::writeBarrierPre(TypeNewScript *newScript)
|
||||
{
|
||||
|
@ -1543,18 +1543,18 @@ static inline JSObject *
|
||||
CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent,
|
||||
NewObjectKind newKind)
|
||||
{
|
||||
if (type->newScript) {
|
||||
if (type->hasNewScript()) {
|
||||
/*
|
||||
* Make an object with the type's associated finalize kind and shape,
|
||||
* which reflects any properties that will definitely be added to the
|
||||
* object before it is read from.
|
||||
*/
|
||||
gc::AllocKind kind = type->newScript->allocKind;
|
||||
gc::AllocKind kind = type->newScript()->allocKind;
|
||||
RootedObject res(cx, NewObjectWithType(cx, type, parent, kind, newKind));
|
||||
if (!res)
|
||||
return NULL;
|
||||
RootedObject metadata(cx, res->getMetadata());
|
||||
RootedShape shape(cx, type->newScript->shape);
|
||||
RootedShape shape(cx, type->newScript()->shape);
|
||||
JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, res, shape));
|
||||
if (metadata && !JSObject::setMetadata(cx, res, metadata))
|
||||
return NULL;
|
||||
@ -2538,8 +2538,8 @@ JSObject::growSlots(ExclusiveContext *cx, HandleObject obj, uint32_t oldCount, u
|
||||
* type to give these objects a larger number of fixed slots when future
|
||||
* objects are constructed.
|
||||
*/
|
||||
if (!obj->hasLazyType() && !oldCount && obj->type()->newScript) {
|
||||
gc::AllocKind kind = obj->type()->newScript->allocKind;
|
||||
if (!obj->hasLazyType() && !oldCount && obj->type()->hasNewScript()) {
|
||||
gc::AllocKind kind = obj->type()->newScript()->allocKind;
|
||||
uint32_t newScriptSlots = gc::GetGCKindSlots(kind);
|
||||
if (newScriptSlots == obj->numFixedSlots() &&
|
||||
gc::TryIncrementAllocKind(&kind) &&
|
||||
@ -2549,13 +2549,13 @@ JSObject::growSlots(ExclusiveContext *cx, HandleObject obj, uint32_t oldCount, u
|
||||
AutoEnterAnalysis enter(ncx);
|
||||
|
||||
Rooted<TypeObject*> typeObj(cx, obj->type());
|
||||
RootedShape shape(cx, typeObj->newScript->shape);
|
||||
RootedShape shape(cx, typeObj->newScript()->shape);
|
||||
JSObject *reshapedObj = NewReshapedObject(ncx, typeObj, obj->getParent(), kind, shape);
|
||||
if (!reshapedObj)
|
||||
return false;
|
||||
|
||||
typeObj->newScript->allocKind = kind;
|
||||
typeObj->newScript->shape = reshapedObj->lastProperty();
|
||||
typeObj->newScript()->allocKind = kind;
|
||||
typeObj->newScript()->shape = reshapedObj->lastProperty();
|
||||
typeObj->markStateChange(ncx);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user