Bug 1618038 - Implement support for wrapper-class comparisons in the innermost namespace containing each wrapper class, as ADL intended. Also simplify the operator implementations in certain minor ways. r=sfink

Differential Revision: https://phabricator.services.mozilla.com/D64242

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jeff Walden 2020-03-09 20:05:10 +00:00
Родитель 0186cbe565
Коммит 4ab1c168ae
5 изменённых файлов: 373 добавлений и 227 удалений

Просмотреть файл

@ -0,0 +1,197 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Support comparison operations on wrapper types -- e.g. |JS::Rooted<T>|,
* |JS::Handle<T>|, and so on -- against raw |T| values, against pointers or
* |nullptr| if the wrapper is a pointer wrapper, and against other wrappers
* around compatible types.
*/
#ifndef js_ComparisonOperators_h
#define js_ComparisonOperators_h
#include <type_traits> // std::false_type, std::true_type, std::enable_if_t, std::is_pointer_v, std::remove_pointer_t
// To define |operator==| and |operator!=| for a wrapper class |W| (which may
// or may not be a template class) that contains a |T|:
//
// * Specialize |JS::detail::DefineComparisonOps| for |W|:
// - Make it inherit from |std::true_type|.
// - Include within your specialization a |static get(const W& v)| function
// that returns the value (which may be an lvalue reference) of the |T| in
// |W|.
// * If needed, add |using JS::detail::wrapper_comparison::operator==;| and
// |using JS::detail::wrapper_comparison::operator!=;| to the namespace
// directly containing |W| at the end of this header. (If you are not in
// SpiderMonkey code and have questionably decided to define your own
// wrapper class, add these to its namespace somewhere in your code.)
//
// The first step opts the wrapper class into comparison support and defines a
// generic means of extracting a comparable |T| out of an instance.
//
// The second step ensures that symmetric |operator==| and |operator!=| are
// exposed for the wrapper, accepting two wrappers or a wrapper and a suitable
// raw value.
//
// Failure to perform *both* steps will likely result in errors like
// 'invalid operands to binary expression' or 'no match for operator=='
// when comparing an instance of your wrapper.
namespace JS {
namespace detail {
// By default, comparison ops are not supported for types.
template <typename T>
struct DefineComparisonOps : std::false_type {};
// Define functions for the core equality operations, that the actual operators
// can all invoke.
// Compare two wrapper types. Assumes both wrapper types support comparison
// operators.
template <typename W, typename OW>
inline bool WrapperEqualsWrapper(const W& wrapper, const OW& other) {
return JS::detail::DefineComparisonOps<W>::get(wrapper) ==
JS::detail::DefineComparisonOps<OW>::get(other);
}
// Compare a wrapper against a value of its unwrapped element type (or against a
// value that implicitly converts to that unwrapped element type). Assumes its
// wrapper argument supports comparison operators.
template <typename W>
inline bool WrapperEqualsUnwrapped(const W& wrapper,
const typename W::ElementType& value) {
return JS::detail::DefineComparisonOps<W>::get(wrapper) == value;
}
// Compare a wrapper containing a pointer against a pointer to const element
// type. Assumes its wrapper argument supports comparison operators.
template <typename W>
inline bool WrapperEqualsPointer(
const W& wrapper,
const typename std::remove_pointer_t<typename W::ElementType>* ptr) {
return JS::detail::DefineComparisonOps<W>::get(wrapper) == ptr;
}
// It is idiomatic C++ to define operators on user-defined types in the
// namespace of their operands' types (not at global scope, which isn't examined
// if at point of operator use another operator definition shadows the global
// definition). But our wrappers live in *multiple* namespaces (|namespace js|
// and |namespace JS| in SpiderMonkey), so we can't literally do that without
// defining ambiguous overloads.
//
// Instead, we define the operators *once* in a namespace containing nothing
// else at all. Then we |using| the operators into each namespace containing
// a wrapper type. |using| creates *aliases*, so two |using|s of the same
// operator contribute only one overload to overload resolution.
namespace wrapper_comparison {
// Comparisons between potentially-differing wrappers.
template <typename W, typename OW>
inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value &&
JS::detail::DefineComparisonOps<OW>::value,
bool>
operator==(const W& wrapper, const OW& other) {
return JS::detail::WrapperEqualsWrapper(wrapper, other);
}
template <typename W, typename OW>
inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value &&
JS::detail::DefineComparisonOps<OW>::value,
bool>
operator!=(const W& wrapper, const OW& other) {
return !JS::detail::WrapperEqualsWrapper(wrapper, other);
}
// Comparisons between a wrapper and its unwrapped element type.
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool>
operator==(const W& wrapper, const typename W::ElementType& value) {
return WrapperEqualsUnwrapped(wrapper, value);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool>
operator!=(const W& wrapper, const typename W::ElementType& value) {
return !WrapperEqualsUnwrapped(wrapper, value);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool>
operator==(const typename W::ElementType& value, const W& wrapper) {
return WrapperEqualsUnwrapped(wrapper, value);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool>
operator!=(const typename W::ElementType& value, const W& wrapper) {
return !WrapperEqualsUnwrapped(wrapper, value);
}
// For wrappers around a pointer type, comparisons between a wrapper object
// and a const element pointer (including against |nullptr|).
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value &&
std::is_pointer_v<typename W::ElementType>,
bool>
operator==(const W& wrapper,
const typename std::remove_pointer_t<typename W::ElementType>* ptr) {
return WrapperEqualsPointer(wrapper, ptr);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value &&
std::is_pointer_v<typename W::ElementType>,
bool>
operator!=(const W& wrapper,
const typename std::remove_pointer_t<typename W::ElementType>* ptr) {
return !WrapperEqualsPointer(wrapper, ptr);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value &&
std::is_pointer_v<typename W::ElementType>,
bool>
operator==(const typename std::remove_pointer_t<typename W::ElementType>* ptr,
const W& wrapper) {
return WrapperEqualsPointer(wrapper, ptr);
}
template <typename W>
inline typename std::enable_if_t<DefineComparisonOps<W>::value &&
std::is_pointer_v<typename W::ElementType>,
bool>
operator!=(const typename std::remove_pointer_t<typename W::ElementType>* ptr,
const W& wrapper) {
return !WrapperEqualsPointer(wrapper, ptr);
}
} // namespace wrapper_comparison
} // namespace detail
} // namespace JS
// Expose wrapper-supporting |operator==| and |operator!=| in the namespaces of
// all SpiderMonkey's wrapper classes that support comparisons.
namespace JS {
using JS::detail::wrapper_comparison::operator==;
using JS::detail::wrapper_comparison::operator!=;
} // namespace JS
namespace js {
using JS::detail::wrapper_comparison::operator==;
using JS::detail::wrapper_comparison::operator!=;
} // namespace js
#endif // js_ComparisonOperators_h

Просмотреть файл

@ -18,6 +18,7 @@
#include "jspubtd.h"
#include "js/ComparisonOperators.h" // JS::detail::DefineComparisonOps
#include "js/GCAnnotations.h"
#include "js/GCPolicyAPI.h"
#include "js/GCTypeMacros.h" // JS_FOR_EACH_PUBLIC_{,TAGGED_}GC_POINTER_TYPE
@ -199,11 +200,6 @@ struct PersistentRootedMarker;
namespace JS {
template <typename T>
class Rooted;
template <typename T>
class PersistentRooted;
JS_FRIEND_API void HeapObjectPostWriteBarrier(JSObject** objp, JSObject* prev,
JSObject* next);
JS_FRIEND_API void HeapStringPostWriteBarrier(JSString** objp, JSString* prev,
@ -372,6 +368,15 @@ class MOZ_NON_MEMMOVABLE Heap : public js::HeapBase<T, Heap<T>> {
T ptr;
};
namespace detail {
template <typename T>
struct DefineComparisonOps<Heap<T>> : std::true_type {
static const T& get(const Heap<T>& v) { return v.unbarrieredGet(); }
};
} // namespace detail
static MOZ_ALWAYS_INLINE bool ObjectIsTenured(JSObject* obj) {
return !js::gc::IsInsideNursery(reinterpret_cast<js::gc::Cell*>(obj));
}
@ -525,6 +530,15 @@ class TenuredHeap : public js::HeapBase<T, TenuredHeap<T>> {
uintptr_t bits;
};
namespace detail {
template <typename T>
struct DefineComparisonOps<TenuredHeap<T>> : std::true_type {
static const T get(const TenuredHeap<T>& v) { return v.unbarrieredGetPtr(); }
};
} // namespace detail
// std::swap uses a stack temporary, which prevents classes like Heap<T>
// from being declared MOZ_HEAP_CLASS.
template <typename T>
@ -546,6 +560,13 @@ static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray(
return ObjectIsMarkedGray(obj.unbarrieredGetPtr());
}
template <typename T>
class MutableHandle;
template <typename T>
class Rooted;
template <typename T>
class PersistentRooted;
/**
* Reference to a T that has been rooted elsewhere. This is most useful
* as a parameter type, which guarantees that the T lvalue is properly
@ -556,7 +577,7 @@ static MOZ_ALWAYS_INLINE bool ObjectIsMarkedGray(
*/
template <typename T>
class MOZ_NONHEAP_CLASS Handle : public js::HandleBase<T, Handle<T>> {
friend class JS::MutableHandle<T>;
friend class MutableHandle<T>;
public:
using ElementType = T;
@ -638,6 +659,15 @@ class MOZ_NONHEAP_CLASS Handle : public js::HandleBase<T, Handle<T>> {
const T* ptr;
};
namespace detail {
template <typename T>
struct DefineComparisonOps<Handle<T>> : std::true_type {
static const T& get(const Handle<T>& v) { return v.get(); }
};
} // namespace detail
/**
* Similar to a handle, but the underlying storage can be changed. This is
* useful for outparams.
@ -693,6 +723,15 @@ class MOZ_STACK_CLASS MutableHandle
T* ptr;
};
namespace detail {
template <typename T>
struct DefineComparisonOps<MutableHandle<T>> : std::true_type {
static const T& get(const MutableHandle<T>& v) { return v.get(); }
};
} // namespace detail
} /* namespace JS */
namespace js {
@ -1116,6 +1155,15 @@ class MOZ_RAII Rooted : public js::RootedBase<T, Rooted<T>> {
Rooted(const Rooted&) = delete;
} JS_HAZ_ROOTED;
namespace detail {
template <typename T>
struct DefineComparisonOps<Rooted<T>> : std::true_type {
static const T& get(const Rooted<T>& v) { return v.get(); }
};
} // namespace detail
} /* namespace JS */
namespace js {
@ -1389,6 +1437,15 @@ class PersistentRooted
detail::MaybeWrapped<T> ptr;
} JS_HAZ_ROOTED;
namespace detail {
template <typename T>
struct DefineComparisonOps<PersistentRooted<T>> : std::true_type {
static const T& get(const PersistentRooted<T>& v) { return v.get(); }
};
} // namespace detail
} /* namespace JS */
namespace js {
@ -1435,192 +1492,6 @@ void CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks,
} /* namespace gc */
namespace detail {
// DefineComparisonOps is a trait which selects which wrapper classes to define
// operator== and operator!= for. It supplies a getter function to extract the
// value to compare. This is used to avoid triggering the automatic read
// barriers where appropriate.
//
// If DefineComparisonOps is not specialized for a particular wrapper you may
// get errors such as 'invalid operands to binary expression' or 'no match for
// operator==' when trying to compare against instances of the wrapper.
template <typename T>
struct DefineComparisonOps : mozilla::FalseType {};
template <typename T>
struct DefineComparisonOps<JS::Heap<T>> : mozilla::TrueType {
static const T& get(const JS::Heap<T>& v) { return v.unbarrieredGet(); }
};
template <typename T>
struct DefineComparisonOps<JS::TenuredHeap<T>> : mozilla::TrueType {
static const T get(const JS::TenuredHeap<T>& v) {
return v.unbarrieredGetPtr();
}
};
template <typename T>
struct DefineComparisonOps<JS::Rooted<T>> : mozilla::TrueType {
static const T& get(const JS::Rooted<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<JS::Handle<T>> : mozilla::TrueType {
static const T& get(const JS::Handle<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<JS::MutableHandle<T>> : mozilla::TrueType {
static const T& get(const JS::MutableHandle<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<JS::PersistentRooted<T>> : mozilla::TrueType {
static const T& get(const JS::PersistentRooted<T>& v) { return v.get(); }
};
} /* namespace detail */
} /* namespace js */
// Overload operator== and operator!= for all types with the DefineComparisonOps
// trait using the supplied getter.
//
// There are four cases:
// Case 1: comparison between two wrapper objects.
template <typename T, typename U>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value &&
js::detail::DefineComparisonOps<U>::value,
bool>::Type
operator==(const T& a, const U& b) {
return js::detail::DefineComparisonOps<T>::get(a) ==
js::detail::DefineComparisonOps<U>::get(b);
}
template <typename T, typename U>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value &&
js::detail::DefineComparisonOps<U>::value,
bool>::Type
operator!=(const T& a, const U& b) {
return !(a == b);
}
// Case 2: comparison between a wrapper object and its unwrapped element type.
template <typename T>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value,
bool>::Type
operator==(const T& a, const typename T::ElementType& b) {
return js::detail::DefineComparisonOps<T>::get(a) == b;
}
template <typename T>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value,
bool>::Type
operator!=(const T& a, const typename T::ElementType& b) {
return !(a == b);
}
template <typename T>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value,
bool>::Type
operator==(const typename T::ElementType& a, const T& b) {
return a == js::detail::DefineComparisonOps<T>::get(b);
}
template <typename T>
typename mozilla::EnableIf<js::detail::DefineComparisonOps<T>::value,
bool>::Type
operator!=(const typename T::ElementType& a, const T& b) {
return !(a == b);
}
// Case 3: For pointer wrappers, comparison between the wrapper and a const
// element pointer.
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator==(
const typename mozilla::RemovePointer<typename T::ElementType>::Type* a,
const T& b) {
return a == js::detail::DefineComparisonOps<T>::get(b);
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator!=(
const typename mozilla::RemovePointer<typename T::ElementType>::Type* a,
const T& b) {
return !(a == b);
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator==(
const T& a,
const typename mozilla::RemovePointer<typename T::ElementType>::Type* b) {
return js::detail::DefineComparisonOps<T>::get(a) == b;
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator!=(
const T& a,
const typename mozilla::RemovePointer<typename T::ElementType>::Type* b) {
return !(a == b);
}
// Case 4: For pointer wrappers, comparison between the wrapper and nullptr.
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator==(std::nullptr_t a, const T& b) {
return a == js::detail::DefineComparisonOps<T>::get(b);
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator!=(std::nullptr_t a, const T& b) {
return !(a == b);
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator==(const T& a, std::nullptr_t b) {
return js::detail::DefineComparisonOps<T>::get(a) == b;
}
template <typename T>
typename mozilla::EnableIf<
js::detail::DefineComparisonOps<T>::value &&
mozilla::IsPointer<typename T::ElementType>::value,
bool>::Type
operator!=(const T& a, std::nullptr_t b) {
return !(a == b);
}
#endif /* js_RootingAPI_h */

Просмотреть файл

@ -7,10 +7,13 @@
#ifndef gc_Barrier_h
#define gc_Barrier_h
#include <type_traits> // std::true_type
#include "NamespaceImports.h"
#include "gc/Cell.h"
#include "gc/StoreBuffer.h"
#include "js/ComparisonOperators.h" // JS::detail::DefineComparisonOps
#include "js/HeapAPI.h"
#include "js/Id.h"
#include "js/RootingAPI.h"
@ -516,6 +519,23 @@ class PreBarriered : public WriteBarriered<T> {
}
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<js::PreBarriered<T>> : std::true_type {
static const T& get(const js::PreBarriered<T>& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
/*
* A pre- and post-barriered heap pointer, for use inside the JS engine.
*
@ -591,6 +611,23 @@ class GCPtr : public WriteBarriered<T> {
GCPtr<T>& operator=(GCPtr<T>&&) = delete;
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<js::GCPtr<T>> : std::true_type {
static const T& get(const js::GCPtr<T>& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
/*
* A pre- and post-barriered heap pointer, for use inside the JS engine. These
* heap pointers can be stored in C++ containers like GCVector and GCHashMap.
@ -673,6 +710,23 @@ class HeapPtr : public WriteBarriered<T> {
}
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<js::HeapPtr<T>> : std::true_type {
static const T& get(const js::HeapPtr<T>& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
// Base class for barriered pointer types that intercept reads and writes.
template <typename T>
class ReadBarriered : public BarrieredBase<T> {
@ -766,6 +820,25 @@ class WeakHeapPtr : public ReadBarriered<T>,
}
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<js::WeakHeapPtr<T>> : std::true_type {
static const T& get(const js::WeakHeapPtr<T>& v) {
return v.unbarrieredGet();
}
};
} // namespace detail
} // namespace JS
namespace js {
// A WeakRef pointer does not hold its target live and is automatically nulled
// out when the GC discovers that it is not reachable from any other path.
template <typename T>
@ -816,6 +889,23 @@ class HeapSlot : public WriteBarriered<Value> {
}
};
} // namespace js
namespace JS {
namespace detail {
template <>
struct DefineComparisonOps<js::HeapSlot> : std::true_type {
static const Value& get(const js::HeapSlot& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
class HeapSlotArray {
HeapSlot* array;
@ -1096,35 +1186,6 @@ using HeapPtrObject = HeapPtr<JSObject*>;
using HeapPtrRegExpShared = HeapPtr<RegExpShared*>;
using HeapPtrValue = HeapPtr<Value>;
namespace detail {
template <typename T>
struct DefineComparisonOps<PreBarriered<T>> : mozilla::TrueType {
static const T& get(const PreBarriered<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<GCPtr<T>> : mozilla::TrueType {
static const T& get(const GCPtr<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<HeapPtr<T>> : mozilla::TrueType {
static const T& get(const HeapPtr<T>& v) { return v.get(); }
};
template <typename T>
struct DefineComparisonOps<WeakHeapPtr<T>> : mozilla::TrueType {
static const T& get(const WeakHeapPtr<T>& v) { return v.unbarrieredGet(); }
};
template <>
struct DefineComparisonOps<HeapSlot> : mozilla::TrueType {
static const Value& get(const HeapSlot& v) { return v.get(); }
};
} /* namespace detail */
} /* namespace js */
#endif /* gc_Barrier_h */

Просмотреть файл

@ -13,13 +13,13 @@
#ifndef gc_MaybeRooted_h
#define gc_MaybeRooted_h
#include "mozilla/Assertions.h" // MOZ_CRASH
#include "mozilla/Attributes.h" // MOZ_IMPLICIT, MOZ_RAII
#include <type_traits> // std::true_type
#include "gc/Allocator.h" // js::AllowGC, js::CanGC, js::NoGC
#include "js/RootingAPI.h" // js::{Rooted,MutableHandle}Base, js::detail::DefineComparisonOps, JS::SafelyInitialized, DECLARE_POINTER_{CONSTREF,ASSIGN}_OPS, DECLARE_NONPOINTER_{,MUTABLE_}ACCESSOR_METHODS, JS::Rooted, JS::{,Mutable}Handle
#include "js/ComparisonOperators.h" // JS::detail::DefineComparisonOps
#include "js/RootingAPI.h" // js::{Rooted,MutableHandle}Base, JS::SafelyInitialized, DECLARE_POINTER_{CONSTREF,ASSIGN}_OPS, DECLARE_NONPOINTER_{,MUTABLE_}ACCESSOR_METHODS, JS::Rooted, JS::{,Mutable}Handle
namespace js {
@ -51,15 +51,23 @@ class MOZ_RAII FakeRooted : public RootedBase<T, FakeRooted<T>> {
FakeRooted(const FakeRooted&) = delete;
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<FakeRooted<T>> : std::true_type {
static const T& get(const FakeRooted<T>& v) { return v.get(); }
struct DefineComparisonOps<js::FakeRooted<T>> : std::true_type {
static const T& get(const js::FakeRooted<T>& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
/**
* Interface substitute for MutableHandle<T> which is not required to point to
* rooted memory.
@ -87,15 +95,23 @@ class FakeMutableHandle
T* ptr;
};
} // namespace js
namespace JS {
namespace detail {
template <typename T>
struct DefineComparisonOps<FakeMutableHandle<T>> : std::true_type {
static const T& get(const FakeMutableHandle<T>& v) { return v.get(); }
struct DefineComparisonOps<js::FakeMutableHandle<T>> : std::true_type {
static const T& get(const js::FakeMutableHandle<T>& v) { return v.get(); }
};
} // namespace detail
} // namespace JS
namespace js {
/**
* Types for a variable that either should or shouldn't be rooted, depending on
* the template parameter allowGC. Used for implementing functions that can

Просмотреть файл

@ -132,6 +132,7 @@ EXPORTS.js += [
'../public/CallNonGenericMethod.h',
'../public/CharacterEncoding.h',
'../public/Class.h',
'../public/ComparisonOperators.h',
'../public/CompilationAndEvaluation.h',
'../public/CompileOptions.h',
'../public/ContextOptions.h',