зеркало из https://github.com/mozilla/gecko-dev.git
Bug 990290 - Store class objects in a weak map off the XBL global. r=bz
Note that we simultaneously rip out all of the crazy lifetime management for the dynamic JSClasses here (it would be nice to do that in a separate patch, but it's all kind of tied up together). With this patch, we simply have one dynamic JSClass per class object, which is deleted in the finalizer. In the next patch, we remove dynamic JSClasses entirely.
This commit is contained in:
Родитель
a75c841246
Коммит
2f39bb33a1
|
@ -77,7 +77,7 @@ XBLFinalize(JSFreeOp *fop, JSObject *obj)
|
|||
nsContentUtils::DeferredFinalize(docInfo);
|
||||
|
||||
nsXBLJSClass* c = nsXBLJSClass::fromJSClass(::JS_GetClass(obj));
|
||||
c->Drop();
|
||||
delete c;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -90,12 +90,7 @@ XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
|
|||
return protoBinding->ResolveAllFields(cx, obj);
|
||||
}
|
||||
|
||||
uint64_t nsXBLJSClass::sIdCount = 0;
|
||||
|
||||
nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName,
|
||||
const nsCString& aKey)
|
||||
: mRefCnt(0)
|
||||
, mKey(aKey)
|
||||
nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName)
|
||||
{
|
||||
memset(static_cast<JSClass*>(this), 0, sizeof(JSClass));
|
||||
name = ToNewCString(aClassName);
|
||||
|
@ -121,19 +116,9 @@ nsXBLJSClass::IsXBLJSClass(const JSClass* aClass)
|
|||
|
||||
nsXBLJSClass::~nsXBLJSClass()
|
||||
{
|
||||
if (nsXBLService::gClassTable) {
|
||||
nsXBLService::gClassTable->Remove(mKey);
|
||||
mKey.Truncate();
|
||||
}
|
||||
nsMemory::Free((void*) name);
|
||||
}
|
||||
|
||||
nsXBLJSClass*
|
||||
nsXBLService::getClass(const nsCString& k)
|
||||
{
|
||||
return nsXBLService::gClassTable->Get(k);
|
||||
}
|
||||
|
||||
// Implementation /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Constructors/Destructors
|
||||
|
@ -909,6 +894,98 @@ nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
|
|||
|
||||
// Internal helper methods ////////////////////////////////////////////////////////////////
|
||||
|
||||
// Get or create a WeakMap object on a given XBL-hosting global.
|
||||
//
|
||||
// The scheme is as follows. XBL-hosting globals (either privileged content
|
||||
// Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
|
||||
// WeakMap is keyed by the grand-proto - i.e. the original prototype of the
|
||||
// content before it was bound, and the prototype of the class object that we
|
||||
// splice in. The values in the WeakMap are simple dictionary-style objects,
|
||||
// mapping from XBL class names to class objects.
|
||||
static JSObject*
|
||||
GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
|
||||
{
|
||||
AssertSameCompartment(cx, scope);
|
||||
MOZ_ASSERT(JS_IsGlobalObject(scope));
|
||||
MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
|
||||
|
||||
// First, see if the map is already defined.
|
||||
JS::Rooted<JSPropertyDescriptor> desc(cx);
|
||||
if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, 0, &desc)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (desc.object() && desc.value().isObject() &&
|
||||
JS::IsWeakMapObject(&desc.value().toObject())) {
|
||||
return &desc.value().toObject();
|
||||
}
|
||||
|
||||
// It's not there. Create and define it.
|
||||
JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
|
||||
if (!map || !JS_DefineProperty(cx, scope, mapName,
|
||||
JS::ObjectValue(*map),
|
||||
JS_PropertyStub, JS_StrictPropertyStub,
|
||||
JSPROP_PERMANENT | JSPROP_READONLY))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static JSObject*
|
||||
GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
|
||||
{
|
||||
AssertSameCompartment(cx, proto);
|
||||
// We want to hang our class objects off the XBL scope. But since we also
|
||||
// hoist anonymous content into the XBL scope, this creates the potential for
|
||||
// tricky collisions, since we can simultaneously have a bound in-content
|
||||
// node with grand-proto HTMLDivElement and a bound anonymous node whose
|
||||
// grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
|
||||
// Since we have to wrap the WeakMap keys into its scope, this distinction
|
||||
// would be lost if we don't do something about it.
|
||||
//
|
||||
// So we define two maps - one class objects that live in content (prototyped
|
||||
// to content prototypes), and the other for class objects that live in the
|
||||
// XBL scope (prototyped to cross-compartment-wrapped content prototypes).
|
||||
const char* name = xpc::IsInXBLScope(proto) ? "__ContentClassObjectMap__"
|
||||
: "__XBLClassObjectMap__";
|
||||
|
||||
// Now, enter the XBL scope, since that's where we need to operate, and wrap
|
||||
// the proto accordingly.
|
||||
JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
|
||||
JS::Rooted<JSObject*> wrappedProto(cx, proto);
|
||||
JSAutoCompartment ac(cx, scope);
|
||||
if (!JS_WrapObject(cx, &wrappedProto)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Grab the appropriate WeakMap.
|
||||
JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
|
||||
if (!map) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// See if we already have a map entry for that prototype.
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (val.isObject()) {
|
||||
return &val.toObject();
|
||||
}
|
||||
|
||||
// We don't have an entry. Create one and stick it in the map.
|
||||
JS::Rooted<JSObject*> entry(cx);
|
||||
entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope);
|
||||
if (!entry) {
|
||||
return nullptr;
|
||||
}
|
||||
JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
|
||||
if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
nsXBLBinding::DoInitJSClass(JSContext *cx,
|
||||
|
@ -920,93 +997,54 @@ nsXBLBinding::DoInitJSClass(JSContext *cx,
|
|||
{
|
||||
MOZ_ASSERT(obj);
|
||||
|
||||
// First ensure our JS class is initialized.
|
||||
nsAutoCString className(aClassName);
|
||||
nsAutoCString xblKey(aClassName);
|
||||
|
||||
// Note that, now that NAC reflectors are created in the XBL scope, the
|
||||
// reflector is not necessarily same-compartment with the document. So we'll
|
||||
// end up creating a separate instance of the oddly-named XBL class object
|
||||
// and defining it as a property on the XBL scope's global. This works fine,
|
||||
// but we need to make sure never to assume that the the reflector and
|
||||
// prototype are same-compartment with the bound document.
|
||||
JS::RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(obj));
|
||||
JSAutoCompartment ac(cx, global);
|
||||
JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
|
||||
JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
|
||||
|
||||
JS::Rooted<JSObject*> parent_proto(cx, nullptr);
|
||||
nsXBLJSClass* c = nullptr;
|
||||
|
||||
// Retrieve the current prototype of obj.
|
||||
JS::Rooted<JSObject*> parent_proto(cx);
|
||||
if (!JS_GetPrototype(cx, obj, &parent_proto)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Get the map entry for the parent prototype. In the one-off case that the
|
||||
// parent prototype is null, we somewhat hackily just use the WeakMap itself
|
||||
// as a property holder.
|
||||
JS::Rooted<JSObject*> holder(cx);
|
||||
if (parent_proto) {
|
||||
// We need to create a unique classname based on aClassName and
|
||||
// id. Append a space (an invalid URI character) to ensure that
|
||||
// we don't have accidental collisions with the case when parent_proto is
|
||||
// null and aClassName ends in some bizarre numbers (yeah, it's unlikely).
|
||||
JS::Rooted<jsid> parent_proto_id(cx);
|
||||
if (!::JS_GetObjectId(cx, parent_proto, &parent_proto_id)) {
|
||||
// Probably OOM
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// One space, maybe "0x", at most 16 chars (on a 64-bit system) of long,
|
||||
// and a null-terminator (which PR_snprintf ensures is there even if the
|
||||
// string representation of what we're printing does not fit in the buffer
|
||||
// provided).
|
||||
char buf[20];
|
||||
if (sizeof(jsid) == 4) {
|
||||
PR_snprintf(buf, sizeof(buf), " %lx", parent_proto_id.get());
|
||||
} else {
|
||||
MOZ_ASSERT(sizeof(jsid) == 8);
|
||||
PR_snprintf(buf, sizeof(buf), " %llx", parent_proto_id.get());
|
||||
}
|
||||
xblKey.Append(buf);
|
||||
|
||||
c = nsXBLService::getClass(xblKey);
|
||||
if (c) {
|
||||
className.Assign(c->name);
|
||||
} else {
|
||||
char buf[20];
|
||||
PR_snprintf(buf, sizeof(buf), " %llx", nsXBLJSClass::NewId());
|
||||
className.Append(buf);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> proto(cx);
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
|
||||
if (!::JS_LookupPropertyWithFlags(cx, global, className.get(), 0, &val))
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
if (val.isObject() && nsXBLJSClass::IsXBLJSClass(JS_GetClass(&val.toObject()))) {
|
||||
*aNew = false;
|
||||
proto = &val.toObject();
|
||||
holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
|
||||
} else {
|
||||
// We need to initialize the class.
|
||||
*aNew = true;
|
||||
JSAutoCompartment innerAC(cx, xblScope);
|
||||
holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
|
||||
}
|
||||
js::AssertSameCompartment(holder, xblScope);
|
||||
JSAutoCompartment ac(cx, holder);
|
||||
|
||||
if (!c) {
|
||||
c = nsXBLService::getClass(xblKey);
|
||||
}
|
||||
if (!c) {
|
||||
// We need to create a struct for this class.
|
||||
c = new nsXBLJSClass(className, xblKey);
|
||||
|
||||
// Add c to our table.
|
||||
nsXBLService::gClassTable->Put(xblKey, c);
|
||||
}
|
||||
|
||||
// The prototype holds a strong reference to its class struct.
|
||||
c->Hold();
|
||||
// Look up the class on the property holder. The only properties on the
|
||||
// holder should be class objects. If we don't find the class object, we need
|
||||
// to create and define it.
|
||||
JS::Rooted<JSObject*> proto(cx);
|
||||
JS::Rooted<JSPropertyDescriptor> desc(cx);
|
||||
if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), 0, &desc)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
*aNew = !desc.object();
|
||||
if (desc.object()) {
|
||||
proto = &desc.value().toObject();
|
||||
MOZ_ASSERT(nsXBLJSClass::IsXBLJSClass(JS_GetClass(js::UncheckedUnwrap(proto))));
|
||||
} else {
|
||||
|
||||
// We need to create the prototype. First, enter the compartment where it's
|
||||
// going to live, and create it.
|
||||
JSAutoCompartment ac2(cx, global);
|
||||
nsXBLJSClass* c = new nsXBLJSClass(aClassName);
|
||||
proto = JS_NewObjectWithGivenProto(cx, c, parent_proto, global);
|
||||
if (!proto || !JS_DefineProperty(cx, global, c->name, JS::ObjectValue(*proto),
|
||||
JS_PropertyStub, JS_StrictPropertyStub, 0))
|
||||
{
|
||||
nsXBLService::gClassTable->Remove(xblKey);
|
||||
c->Drop();
|
||||
if (!proto) {
|
||||
delete c;
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
|
@ -1019,19 +1057,27 @@ nsXBLBinding::DoInitJSClass(JSContext *cx,
|
|||
nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
|
||||
::JS_SetPrivate(proto, docInfo);
|
||||
NS_ADDREF(docInfo);
|
||||
JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
|
||||
|
||||
::JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
|
||||
}
|
||||
|
||||
aClassObject.set(proto);
|
||||
|
||||
if (obj) {
|
||||
// Set the prototype of our object to be the new class.
|
||||
if (!::JS_SetPrototype(cx, obj, proto)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
// Next, enter the compartment of the property holder, wrap the proto, and
|
||||
// stick it on.
|
||||
JSAutoCompartment ac3(cx, holder);
|
||||
if (!JS_WrapObject(cx, &proto) ||
|
||||
!JS_DefineProperty(cx, holder, aClassName.get(), JS::ObjectValue(*proto),
|
||||
JS_PropertyStub, JS_StrictPropertyStub,
|
||||
JSPROP_READONLY | JSPROP_PERMANENT))
|
||||
{
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
// Whew. We have the proto. Wrap it back into the compartment of |obj|,
|
||||
// splice it in, and return it.
|
||||
JSAutoCompartment ac4(cx, obj);
|
||||
if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aClassObject.set(proto);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1119,9 +1165,10 @@ nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
|
|||
JS::MutableHandle<JSPropertyDescriptor> aDesc,
|
||||
JS::Handle<JSObject*> aXBLScope)
|
||||
{
|
||||
// First, see if we have a JSClass. If we don't, it means that this binding
|
||||
// doesn't have a class object, and thus doesn't have any members. Skip it.
|
||||
if (!mJSClass) {
|
||||
// First, see if we have an implementation. If we don't, it means that this
|
||||
// binding doesn't have a class object, and thus doesn't have any members.
|
||||
// Skip it.
|
||||
if (!PrototypeBinding()->HasImplementation()) {
|
||||
if (!mNextBinding) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1132,7 +1179,8 @@ nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
|
|||
// Find our class object. It's in a protected scope and permanent just in case,
|
||||
// so should be there no matter what.
|
||||
JS::Rooted<JS::Value> classObject(aCx);
|
||||
if (!JS_GetProperty(aCx, aXBLScope, mJSClass->name, &classObject)) {
|
||||
if (!JS_GetProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
|
||||
&classObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,11 +68,6 @@ public:
|
|||
nsIContent* GetBoundElement() { return mBoundElement; }
|
||||
void SetBoundElement(nsIContent *aElement);
|
||||
|
||||
void SetJSClass(nsXBLJSClass *aClass) {
|
||||
MOZ_ASSERT(!mJSClass && aClass);
|
||||
mJSClass = aClass;
|
||||
}
|
||||
|
||||
/*
|
||||
* Does a lookup for a method or attribute provided by one of the bindings'
|
||||
* prototype implementation. If found, |desc| will be set up appropriately,
|
||||
|
@ -170,9 +165,6 @@ protected:
|
|||
nsXBLPrototypeBinding* mPrototypeBinding; // Weak, but we're holding a ref to the docinfo
|
||||
nsCOMPtr<nsIContent> mContent; // Strong. Our anonymous content stays around with us.
|
||||
nsRefPtr<nsXBLBinding> mNextBinding; // Strong. The derived binding owns the base class bindings.
|
||||
nsRefPtr<nsXBLJSClass> mJSClass; // Strong. The class object also holds a strong reference,
|
||||
// which might be somewhat redundant, but be safe to avoid
|
||||
// worrying about edge cases.
|
||||
|
||||
nsIContent* mBoundElement; // [WEAK] We have a reference, but we don't own it.
|
||||
|
||||
|
|
|
@ -62,9 +62,6 @@ nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
|
|||
NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
|
||||
MOZ_ASSERT(targetClassObject);
|
||||
|
||||
// Stash a strong reference to the JSClass in the binding.
|
||||
aBinding->SetJSClass(nsXBLJSClass::fromJSClass(JS_GetClass(targetClassObject)));
|
||||
|
||||
// If the prototype already existed, we don't need to install anything. return early.
|
||||
if (!targetObjectIsNew)
|
||||
return NS_OK;
|
||||
|
@ -183,6 +180,7 @@ nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JS::Rooted<JSObject*> value(cx, &v.toObject());
|
||||
JSAutoCompartment ac2(cx, value);
|
||||
|
||||
// All of the above code was just obtaining the bound element's script object and its immediate
|
||||
// concrete base class. We need to alter the object so that our concrete class is interposed
|
||||
|
|
|
@ -368,8 +368,6 @@ nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
|
|||
// Static member variable initialization
|
||||
bool nsXBLService::gAllowDataURIs = false;
|
||||
|
||||
nsXBLService::ClassTable* nsXBLService::gClassTable = nullptr;
|
||||
|
||||
// Implement our nsISupports methods
|
||||
NS_IMPL_ISUPPORTS1(nsXBLService, nsISupportsWeakReference)
|
||||
|
||||
|
@ -383,17 +381,11 @@ nsXBLService::Init()
|
|||
// Constructors/Destructors
|
||||
nsXBLService::nsXBLService(void)
|
||||
{
|
||||
gClassTable = new ClassTable();
|
||||
|
||||
Preferences::AddBoolVarCache(&gAllowDataURIs, "layout.debug.enable_data_xbl");
|
||||
}
|
||||
|
||||
nsXBLService::~nsXBLService(void)
|
||||
{
|
||||
// At this point, the only hash table entries should be for referenced
|
||||
// XBL class structs held by unfinalized JS binding objects.
|
||||
delete gClassTable;
|
||||
gClassTable = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
|
@ -114,39 +114,17 @@ protected:
|
|||
// MEMBER VARIABLES
|
||||
public:
|
||||
static bool gDisableChromeCache;
|
||||
|
||||
typedef nsDataHashtable<nsCStringHashKey, nsXBLJSClass*> ClassTable;
|
||||
static ClassTable* gClassTable; // A table of nsXBLJSClass objects.
|
||||
|
||||
static bool gAllowDataURIs; // Whether we should allow data
|
||||
// urls in -moz-binding. Needed for
|
||||
// testing.
|
||||
|
||||
// Look up the class by key in gClassTable.
|
||||
static nsXBLJSClass *getClass(const nsCString &key);
|
||||
};
|
||||
|
||||
class nsXBLJSClass : public JSClass
|
||||
{
|
||||
private:
|
||||
nsrefcnt mRefCnt;
|
||||
nsCString mKey;
|
||||
static uint64_t sIdCount;
|
||||
|
||||
public:
|
||||
nsXBLJSClass(const nsAFlatCString& aClassName, const nsCString& aKey);
|
||||
nsXBLJSClass(const nsAFlatCString& aClassName);
|
||||
~nsXBLJSClass();
|
||||
|
||||
static uint64_t NewId() { return ++sIdCount; }
|
||||
|
||||
nsCString& Key() { return mKey; }
|
||||
void SetKey(const nsCString& aKey) { mKey = aKey; }
|
||||
|
||||
nsrefcnt Hold() { return ++mRefCnt; }
|
||||
nsrefcnt Drop() { nsrefcnt curr = --mRefCnt; if (!curr) delete this; return curr; }
|
||||
nsrefcnt AddRef() { return Hold(); }
|
||||
nsrefcnt Release() { return Drop(); }
|
||||
|
||||
static bool IsXBLJSClass(const JSClass* aClass);
|
||||
|
||||
// Downcast from a pointer to const JSClass to a pointer to non-const
|
||||
|
@ -163,7 +141,6 @@ public:
|
|||
{
|
||||
MOZ_ASSERT(IsXBLJSClass(c));
|
||||
nsXBLJSClass* x = const_cast<nsXBLJSClass*>(static_cast<const nsXBLJSClass*>(c));
|
||||
MOZ_ASSERT(nsXBLService::getClass(x->mKey) == x);
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -198,18 +198,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=821850
|
|||
checkMayTamper(proto, 'prop', "XBL Proto Prop");
|
||||
checkMayTamper(proto, 'primitiveField', "XBL Field Accessor");
|
||||
|
||||
// As above, check that content can do what it wants with the prototype's
|
||||
// property on the global.
|
||||
var protoName, count = 0;
|
||||
for (var k of Object.getOwnPropertyNames(window)) {
|
||||
if (!/testBinding/.exec(k))
|
||||
continue;
|
||||
count++;
|
||||
protoName = k;
|
||||
}
|
||||
is(count, 1, "Should be exactly one prototype object");
|
||||
checkMayTamper(window, protoName, "XBL prototype prop on window");
|
||||
|
||||
// Tamper with the derived object. This doesn't affect the XBL scope thanks
|
||||
// to Xrays.
|
||||
bound.method = function() { return "heh"; };
|
||||
|
|
Загрузка…
Ссылка в новой задаче