зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
Коммит
7d1f9f312f
|
@ -132,6 +132,7 @@ var whitelist = [
|
|||
{file: "chrome://marionette/content/test_anonymous_content.xul"},
|
||||
{file: "chrome://marionette/content/test_dialog.properties"},
|
||||
{file: "chrome://marionette/content/test_dialog.xul"},
|
||||
{file: "chrome://marionette/content/PerTestCoverageUtils.jsm"},
|
||||
// Bug 1348533
|
||||
{file: "chrome://mozapps/skin/downloads/buttons.png", platforms: ["macosx"]},
|
||||
{file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
|
||||
|
|
|
@ -7769,8 +7769,8 @@ nsContentUtils::IPCTransferableToTransferable(const IPCDataTransfer& aDataTransf
|
|||
|
||||
// The buffer contains the terminating null.
|
||||
Shmem itemData = item.data().get_Shmem();
|
||||
const nsDependentCString text(itemData.get<char>(),
|
||||
itemData.Size<char>());
|
||||
const nsDependentCSubstring text(itemData.get<char>(),
|
||||
itemData.Size<char>());
|
||||
rv = dataWrapper->SetData(text);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
@ -7951,7 +7951,7 @@ ConvertToShmem(mozilla::dom::nsIContentChild* aChild,
|
|||
: static_cast<IShmemAllocator*>(aParent);
|
||||
|
||||
Shmem result;
|
||||
if (!allocator->AllocShmem(aInput.Length() + 1,
|
||||
if (!allocator->AllocShmem(aInput.Length(),
|
||||
SharedMemory::TYPE_BASIC,
|
||||
&result)) {
|
||||
return result;
|
||||
|
@ -7959,7 +7959,7 @@ ConvertToShmem(mozilla::dom::nsIContentChild* aChild,
|
|||
|
||||
memcpy(result.get<char>(),
|
||||
aInput.BeginReading(),
|
||||
aInput.Length() + 1);
|
||||
aInput.Length());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -2843,7 +2843,7 @@ nsIDocument::InitCSP(nsIChannel* aChannel)
|
|||
{
|
||||
MOZ_ASSERT(!mScriptGlobalObject,
|
||||
"CSP must be initialized before mScriptGlobalObject is set!");
|
||||
if (!CSPService::sCSPEnabled) {
|
||||
if (!StaticPrefs::security_csp_enable()) {
|
||||
MOZ_LOG(gCspPRLog, LogLevel::Debug,
|
||||
("CSP is disabled, skipping CSP init for document %p", this));
|
||||
return NS_OK;
|
||||
|
@ -12517,8 +12517,9 @@ nsIDocument::MaybeAllowStorageForOpener()
|
|||
return;
|
||||
}
|
||||
|
||||
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin,
|
||||
openerInner);
|
||||
// We don't care when the asynchronous work finishes here.
|
||||
Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin,
|
||||
openerInner);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -7149,7 +7149,9 @@ nsGlobalWindowOuter::MaybeAllowStorageForOpenedWindow(nsIURI* aURI)
|
|||
return;
|
||||
}
|
||||
|
||||
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin, inner);
|
||||
// We don't care when the asynchronous work finishes here.
|
||||
Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin,
|
||||
inner);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
|
|
|
@ -94,7 +94,8 @@ HTMLMetaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
nsContentUtils::ProcessViewportInfo(aDocument, content);
|
||||
}
|
||||
|
||||
if (CSPService::sCSPEnabled && aDocument && !aDocument->IsLoadedAsData() &&
|
||||
if (StaticPrefs::security_csp_enable() && aDocument &&
|
||||
!aDocument->IsLoadedAsData() &&
|
||||
AttrValueIs(kNameSpaceID_None, nsGkAtoms::httpEquiv, nsGkAtoms::headerCSP, eIgnoreCase)) {
|
||||
|
||||
// only accept <meta http-equiv="Content-Security-Policy" content=""> if it appears
|
||||
|
|
|
@ -3345,7 +3345,7 @@ ContentChild::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
|
|||
variant->SetAsAString(data);
|
||||
} else if (item.data().type() == IPCDataTransferData::TShmem) {
|
||||
Shmem data = item.data().get_Shmem();
|
||||
variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>()));
|
||||
variant->SetAsACString(nsDependentCSubstring(data.get<char>(), data.Size<char>()));
|
||||
Unused << DeallocShmem(data);
|
||||
} else if (item.data().type() == IPCDataTransferData::TIPCBlob) {
|
||||
RefPtr<BlobImpl> blobImpl =
|
||||
|
|
|
@ -5787,10 +5787,12 @@ ContentParent::RecvBHRThreadHang(const HangDetails& aDetails)
|
|||
mozilla::ipc::IPCResult
|
||||
ContentParent::RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
|
||||
const nsCString& aTrackingOrigin,
|
||||
const nsCString& aGrantedOrigin)
|
||||
const nsCString& aGrantedOrigin,
|
||||
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
|
||||
{
|
||||
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(aParentPrincipal,
|
||||
aTrackingOrigin,
|
||||
aGrantedOrigin);
|
||||
aGrantedOrigin,
|
||||
std::move(aResolver));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
|
|
@ -1228,7 +1228,8 @@ public:
|
|||
virtual mozilla::ipc::IPCResult
|
||||
RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
|
||||
const nsCString& aTrackingOrigin,
|
||||
const nsCString& aGrantedOrigin) override;
|
||||
const nsCString& aGrantedOrigin,
|
||||
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver) override;
|
||||
|
||||
// Notify the ContentChild to enable the input event prioritization when
|
||||
// initializing.
|
||||
|
|
|
@ -1165,7 +1165,8 @@ parent:
|
|||
*/
|
||||
async FirstPartyStorageAccessGrantedForOrigin(Principal aParentPrincipal,
|
||||
nsCString aTrackingOrigin,
|
||||
nsCString aGrantedOrigin);
|
||||
nsCString aGrantedOrigin)
|
||||
returns (bool unused);
|
||||
|
||||
both:
|
||||
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
|
||||
|
|
|
@ -3402,7 +3402,7 @@ TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer,
|
|||
variant->SetAsISupports(imageContainer);
|
||||
} else {
|
||||
Shmem data = item.data().get_Shmem();
|
||||
variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>()));
|
||||
variant->SetAsACString(nsDependentCSubstring(data.get<char>(), data.Size<char>()));
|
||||
}
|
||||
|
||||
mozilla::Unused << DeallocShmem(item.data().get_Shmem());
|
||||
|
|
|
@ -320,25 +320,12 @@ NS_IMPL_ISUPPORTS_CI(nsCSPContext,
|
|||
nsIContentSecurityPolicy,
|
||||
nsISerializable)
|
||||
|
||||
int32_t nsCSPContext::sScriptSampleMaxLength;
|
||||
bool nsCSPContext::sViolationEventsEnabled = false;
|
||||
|
||||
nsCSPContext::nsCSPContext()
|
||||
: mInnerWindowID(0)
|
||||
, mLoadingContext(nullptr)
|
||||
, mLoadingPrincipal(nullptr)
|
||||
, mQueueUpMessages(true)
|
||||
{
|
||||
static bool sInitialized = false;
|
||||
if (!sInitialized) {
|
||||
Preferences::AddIntVarCache(&sScriptSampleMaxLength,
|
||||
"security.csp.reporting.script-sample.max-length",
|
||||
40);
|
||||
Preferences::AddBoolVarCache(&sViolationEventsEnabled,
|
||||
"security.csp.enable_violation_events");
|
||||
sInitialized = true;
|
||||
}
|
||||
|
||||
CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
|
||||
}
|
||||
|
||||
|
@ -1201,7 +1188,7 @@ nsCSPContext::FireViolationEvent(
|
|||
Element* aTriggeringElement,
|
||||
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
|
||||
{
|
||||
if (!sViolationEventsEnabled) {
|
||||
if (!StaticPrefs::security_csp_enable_violation_events()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "mozilla/dom/nsCSPUtils.h"
|
||||
#include "mozilla/dom/SecurityPolicyViolationEvent.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIChannelEventSink.h"
|
||||
|
@ -140,7 +141,9 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
|||
|
||||
static uint32_t ScriptSampleMaxLength()
|
||||
{
|
||||
return std::max(sScriptSampleMaxLength, 0);
|
||||
return std::max(
|
||||
mozilla::StaticPrefs::security_csp_reporting_script_sample_max_length(),
|
||||
0);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -165,10 +168,6 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
|||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber);
|
||||
|
||||
static int32_t sScriptSampleMaxLength;
|
||||
|
||||
static bool sViolationEventsEnabled;
|
||||
|
||||
nsString mReferrer;
|
||||
uint64_t mInnerWindowID; // used for web console logging
|
||||
nsTArray<nsCSPPolicy*> mPolicies;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCSPParser.h"
|
||||
|
@ -61,8 +62,6 @@ static const char* const kStyle = "style";
|
|||
static const char* const kScript = "script";
|
||||
|
||||
/* ===== nsCSPParser ==================== */
|
||||
bool nsCSPParser::sCSPExperimentalEnabled = false;
|
||||
bool nsCSPParser::sStrictDynamicEnabled = false;
|
||||
|
||||
nsCSPParser::nsCSPParser(policyTokens& aTokens,
|
||||
nsIURI* aSelfURI,
|
||||
|
@ -84,12 +83,6 @@ nsCSPParser::nsCSPParser(policyTokens& aTokens,
|
|||
, mCSPContext(aCSPContext)
|
||||
, mDeliveredViaMetaTag(aDeliveredViaMetaTag)
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
Preferences::AddBoolVarCache(&sCSPExperimentalEnabled, "security.csp.experimentalEnabled");
|
||||
Preferences::AddBoolVarCache(&sStrictDynamicEnabled, "security.csp.enableStrictDynamic");
|
||||
}
|
||||
CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
|
||||
}
|
||||
|
||||
|
@ -488,7 +481,7 @@ nsCSPParser::keywordSource()
|
|||
|
||||
if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) {
|
||||
// make sure strict dynamic is enabled
|
||||
if (!sStrictDynamicEnabled) {
|
||||
if (!StaticPrefs::security_csp_enableStrictDynamic()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) {
|
||||
|
@ -968,7 +961,7 @@ nsCSPParser::directiveName()
|
|||
|
||||
// Check if it is a valid directive
|
||||
if (!CSP_IsValidDirective(mCurToken) ||
|
||||
(!sCSPExperimentalEnabled &&
|
||||
(!StaticPrefs::security_csp_experimentalEnabled() &&
|
||||
CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR))) {
|
||||
const char16_t* params[] = { mCurToken.get() };
|
||||
logWarningErrorToConsole(nsIScriptError::warningFlag, "couldNotProcessUnknownDirective",
|
||||
|
|
|
@ -33,9 +33,6 @@ class nsCSPParser {
|
|||
nsCSPContext* aCSPContext,
|
||||
bool aDeliveredViaMetaTag);
|
||||
|
||||
static bool sCSPExperimentalEnabled;
|
||||
static bool sStrictDynamicEnabled;
|
||||
|
||||
~nsCSPParser();
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "nsString.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIURI.h"
|
||||
|
@ -16,21 +18,16 @@
|
|||
#include "nsError.h"
|
||||
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||
#include "nsAsyncRedirectVerifyHelper.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsContentPolicyUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
/* Keeps track of whether or not CSP is enabled */
|
||||
bool CSPService::sCSPEnabled = true;
|
||||
|
||||
static LazyLogModule gCspPRLog("CSP");
|
||||
|
||||
CSPService::CSPService()
|
||||
{
|
||||
Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable");
|
||||
}
|
||||
|
||||
CSPService::~CSPService()
|
||||
|
@ -152,7 +149,8 @@ CSPService::ShouldLoad(nsIURI *aContentLocation,
|
|||
// Please note, the correct way to opt-out of CSP using a custom
|
||||
// protocolHandler is to set one of the nsIProtocolHandler flags
|
||||
// that are whitelistet in subjectToCSP()
|
||||
if (!sCSPEnabled || !subjectToCSP(aContentLocation, contentType)) {
|
||||
if (!StaticPrefs::security_csp_enable() ||
|
||||
!subjectToCSP(aContentLocation, contentType)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -282,7 +280,8 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
|
|||
// protocolHandler is to set one of the nsIProtocolHandler flags
|
||||
// that are whitelistet in subjectToCSP()
|
||||
nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
|
||||
if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) {
|
||||
if (!StaticPrefs::security_csp_enable() ||
|
||||
!subjectToCSP(newUri, policyType)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ public:
|
|||
NS_DECL_NSICHANNELEVENTSINK
|
||||
|
||||
CSPService();
|
||||
static bool sCSPEnabled;
|
||||
|
||||
protected:
|
||||
virtual ~CSPService();
|
||||
|
|
|
@ -1259,7 +1259,7 @@ private:
|
|||
nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerPrivate->GetCSP();
|
||||
// We did inherit CSP in bug 1223647. If we do not already have a CSP, we
|
||||
// should get it from the HTTP headers on the worker script.
|
||||
if (CSPService::sCSPEnabled) {
|
||||
if (StaticPrefs::security_csp_enable()) {
|
||||
if (!csp) {
|
||||
rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
|
||||
tCspROHeaderValue);
|
||||
|
|
|
@ -30,7 +30,12 @@ namespace {
|
|||
|
||||
// The C stack size. We use the same stack size on all platforms for
|
||||
// consistency.
|
||||
const uint32_t kWorkerStackSize = 256 * sizeof(size_t) * 1024;
|
||||
//
|
||||
// Note: Our typical equation of 256 machine words works out to 2MB on 64-bit
|
||||
// platforms. Since that works out to the size of a VM huge page, that can
|
||||
// sometimes lead to an OS allocating an entire huge page for the stack at once.
|
||||
// To avoid this, we subtract the size of 2 pages, to be safe.
|
||||
const uint32_t kWorkerStackSize = 256 * sizeof(size_t) * 1024 - 8192;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "frontend/ForOfLoopControl.h"
|
||||
#include "frontend/IfEmitter.h"
|
||||
#include "frontend/Parser.h"
|
||||
#include "frontend/SwitchEmitter.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "frontend/TryEmitter.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
|
@ -1484,6 +1485,20 @@ BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...)
|
|||
va_end(args);
|
||||
}
|
||||
|
||||
void
|
||||
BytecodeEmitter::reportError(const Maybe<uint32_t>& maybeOffset, unsigned errorNumber, ...)
|
||||
{
|
||||
MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet);
|
||||
uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset;
|
||||
|
||||
va_list args;
|
||||
va_start(args, errorNumber);
|
||||
|
||||
parser->errorReporter().errorAtVA(offset, errorNumber, &args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...)
|
||||
{
|
||||
|
@ -1499,6 +1514,22 @@ BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::reportExtraWarning(const Maybe<uint32_t>& maybeOffset,
|
||||
unsigned errorNumber, ...)
|
||||
{
|
||||
MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet);
|
||||
uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset;
|
||||
|
||||
va_list args;
|
||||
va_start(args, errorNumber);
|
||||
|
||||
bool result = parser->errorReporter().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, &args);
|
||||
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::emitNewInit(JSProtoKey key)
|
||||
{
|
||||
|
@ -2311,38 +2342,27 @@ BytecodeEmitter::emitNumberOp(double dval)
|
|||
MOZ_NEVER_INLINE bool
|
||||
BytecodeEmitter::emitSwitch(ParseNode* pn)
|
||||
{
|
||||
ParseNode* cases = pn->pn_right;
|
||||
MOZ_ASSERT(cases->isKind(ParseNodeKind::LexicalScope) ||
|
||||
cases->isKind(ParseNodeKind::StatementList));
|
||||
ParseNode* lexical = pn->pn_right;
|
||||
MOZ_ASSERT(lexical->isKind(ParseNodeKind::LexicalScope));
|
||||
ParseNode* cases = lexical->scopeBody();
|
||||
MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
|
||||
|
||||
// Ensure that the column of the switch statement is set properly.
|
||||
if (!updateSourceCoordNotes(pn->pn_pos.begin))
|
||||
SwitchEmitter se(this);
|
||||
if (!se.emitDiscriminant(Some(pn->pn_pos.begin)))
|
||||
return false;
|
||||
|
||||
// Emit code for the discriminant.
|
||||
if (!emitTree(pn->pn_left))
|
||||
return false;
|
||||
|
||||
// Enter the scope before pushing the switch BreakableControl since all
|
||||
// breaks are under this scope.
|
||||
Maybe<TDZCheckCache> tdzCache;
|
||||
Maybe<EmitterScope> emitterScope;
|
||||
if (cases->isKind(ParseNodeKind::LexicalScope)) {
|
||||
if (!cases->isEmptyScope()) {
|
||||
tdzCache.emplace(this);
|
||||
emitterScope.emplace(this);
|
||||
if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advance |cases| to refer to the switch case list.
|
||||
cases = cases->scopeBody();
|
||||
if (!lexical->isEmptyScope()) {
|
||||
if (!se.emitLexical(lexical->scopeBindings()))
|
||||
return false;
|
||||
|
||||
// A switch statement may contain hoisted functions inside its
|
||||
// cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
|
||||
// bodies of the cases to the case list.
|
||||
if (cases->pn_xflags & PNX_FUNCDEFS) {
|
||||
MOZ_ASSERT(emitterScope);
|
||||
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
|
||||
if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
|
||||
if (!emitHoistedFunctionsInList(caseNode->pn_right))
|
||||
|
@ -2350,305 +2370,104 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After entering the scope, push the switch control.
|
||||
BreakableControl controlInfo(this, StatementKind::Switch);
|
||||
|
||||
ptrdiff_t top = offset();
|
||||
|
||||
// Switch bytecodes run from here till end of final case.
|
||||
uint32_t caseCount = cases->pn_count;
|
||||
if (caseCount > JS_BIT(16)) {
|
||||
reportError(pn, JSMSG_TOO_MANY_CASES);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try for most optimal, fall back if not dense ints.
|
||||
JSOp switchOp = JSOP_TABLESWITCH;
|
||||
uint32_t tableLength = 0;
|
||||
int32_t low, high;
|
||||
bool hasDefault = false;
|
||||
CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
|
||||
if (caseCount == 0 ||
|
||||
(caseCount == 1 && (hasDefault = firstCase->isDefault())))
|
||||
{
|
||||
low = 0;
|
||||
high = -1;
|
||||
} else {
|
||||
Vector<size_t, 128, SystemAllocPolicy> intmap;
|
||||
int32_t intmapBitLength = 0;
|
||||
|
||||
low = JSVAL_INT_MAX;
|
||||
high = JSVAL_INT_MIN;
|
||||
MOZ_ASSERT(!(cases->pn_xflags & PNX_FUNCDEFS));
|
||||
}
|
||||
|
||||
SwitchEmitter::TableGenerator tableGen(this);
|
||||
uint32_t caseCount = cases->pn_count;
|
||||
CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
|
||||
if (caseCount == 0) {
|
||||
tableGen.finish(0);
|
||||
} else if (caseCount == 1 && firstCase->isDefault()) {
|
||||
caseCount = 0;
|
||||
tableGen.finish(0);
|
||||
} else {
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (caseNode->isDefault()) {
|
||||
hasDefault = true;
|
||||
caseCount--; // one of the "cases" was the default
|
||||
continue;
|
||||
}
|
||||
|
||||
if (switchOp == JSOP_CONDSWITCH)
|
||||
if (tableGen.isInvalid())
|
||||
continue;
|
||||
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
|
||||
if (caseValue->getKind() != ParseNodeKind::Number) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
tableGen.setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t i;
|
||||
if (!NumberEqualsInt32(caseValue->pn_dval, &i)) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
tableGen.setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
continue;
|
||||
}
|
||||
if (i < low)
|
||||
low = i;
|
||||
if (i > high)
|
||||
high = i;
|
||||
|
||||
// Check for duplicates, which require a JSOP_CONDSWITCH.
|
||||
// We bias i by 65536 if it's negative, and hope that's a rare
|
||||
// case (because it requires a malloc'd bitmap).
|
||||
if (i < 0)
|
||||
i += JS_BIT(16);
|
||||
if (i >= intmapBitLength) {
|
||||
size_t newLength = NumWordsForBitArrayOfLength(i + 1);
|
||||
if (!intmap.resize(newLength)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
intmapBitLength = newLength * BitArrayElementBits;
|
||||
}
|
||||
if (IsBitArrayElementSet(intmap.begin(), intmap.length(), i)) {
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
continue;
|
||||
}
|
||||
SetBitArrayElement(intmap.begin(), intmap.length(), i);
|
||||
if (!tableGen.addNumber(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute table length and select condswitch instead if overlarge or
|
||||
// more than half-sparse.
|
||||
if (switchOp == JSOP_TABLESWITCH) {
|
||||
tableLength = uint32_t(high - low + 1);
|
||||
if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount)
|
||||
switchOp = JSOP_CONDSWITCH;
|
||||
}
|
||||
tableGen.finish(caseCount);
|
||||
}
|
||||
|
||||
// The note has one or two offsets: first tells total switch code length;
|
||||
// second (if condswitch) tells offset to first JSOP_CASE.
|
||||
unsigned noteIndex;
|
||||
size_t switchSize;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
// 0 bytes of immediate for unoptimized switch.
|
||||
switchSize = 0;
|
||||
if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex))
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
// 3 offsets (len, low, high) before the table, 1 per entry.
|
||||
switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength));
|
||||
if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Emit switchOp followed by switchSize bytes of jump or lookup table.
|
||||
MOZ_ASSERT(top == offset());
|
||||
if (!emitN(switchOp, switchSize))
|
||||
if (!se.validateCaseCount(caseCount))
|
||||
return false;
|
||||
|
||||
Vector<CaseClause*, 32, SystemAllocPolicy> table;
|
||||
|
||||
JumpList condSwitchDefaultOff;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
unsigned caseNoteIndex;
|
||||
bool beforeCases = true;
|
||||
ptrdiff_t lastCaseOffset = -1;
|
||||
|
||||
// The case conditions need their own TDZ cache since they might not
|
||||
// all execute.
|
||||
TDZCheckCache tdzCache(this);
|
||||
bool isTableSwitch = tableGen.isValid();
|
||||
if (isTableSwitch) {
|
||||
if (!se.emitTable(tableGen))
|
||||
return false;
|
||||
} else {
|
||||
if (!se.emitCond())
|
||||
return false;
|
||||
|
||||
// Emit code for evaluating cases and jumping to case statements.
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
if (caseNode->isDefault())
|
||||
continue;
|
||||
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
// If the expression is a literal, suppress line number emission so
|
||||
// that debugging works more naturally.
|
||||
if (caseValue) {
|
||||
if (!emitTree(caseValue, ValueUsage::WantValue,
|
||||
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!beforeCases) {
|
||||
// prevCase is the previous JSOP_CASE's bytecode offset.
|
||||
if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
|
||||
return false;
|
||||
}
|
||||
if (!caseValue) {
|
||||
// This is the default clause.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex))
|
||||
return false;
|
||||
|
||||
// The case clauses are produced before any of the case body. The
|
||||
// JumpList is saved on the parsed tree, then later restored and
|
||||
// patched when generating the cases body.
|
||||
JumpList caseJump;
|
||||
if (!emitJump(JSOP_CASE, &caseJump))
|
||||
return false;
|
||||
caseNode->setOffset(caseJump.offset);
|
||||
lastCaseOffset = caseJump.offset;
|
||||
|
||||
if (beforeCases) {
|
||||
// Switch note's second offset is to first JSOP_CASE.
|
||||
unsigned noteCount = notes().length();
|
||||
if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top))
|
||||
return false;
|
||||
unsigned noteCountDelta = notes().length() - noteCount;
|
||||
if (noteCountDelta != 0)
|
||||
caseNoteIndex += noteCountDelta;
|
||||
beforeCases = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't have an explicit default (which could fall in between
|
||||
// cases, preventing us from fusing this setSrcNoteOffset with the call
|
||||
// in the loop above), link the last case to the implicit default for
|
||||
// the benefit of IonBuilder.
|
||||
if (!hasDefault &&
|
||||
!beforeCases &&
|
||||
!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Emit default even if no explicit default statement.
|
||||
if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff))
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
|
||||
// skip default offset.
|
||||
jsbytecode* pc = code(top + JUMP_OFFSET_LEN);
|
||||
|
||||
// Fill in switch bounds, which we know fit in 16-bit offsets.
|
||||
SET_JUMP_OFFSET(pc, low);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
SET_JUMP_OFFSET(pc, high);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
|
||||
if (tableLength != 0) {
|
||||
if (!table.growBy(tableLength)) {
|
||||
ReportOutOfMemory(cx);
|
||||
if (!emitTree(caseValue, ValueUsage::WantValue,
|
||||
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (ParseNode* caseValue = caseNode->caseExpression()) {
|
||||
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
|
||||
|
||||
int32_t i = int32_t(caseValue->pn_dval);
|
||||
MOZ_ASSERT(double(i) == caseValue->pn_dval);
|
||||
|
||||
i -= low;
|
||||
MOZ_ASSERT(uint32_t(i) < tableLength);
|
||||
MOZ_ASSERT(!table[i]);
|
||||
table[i] = caseNode;
|
||||
}
|
||||
}
|
||||
if (!se.emitCaseJump())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JumpTarget defaultOffset{ -1 };
|
||||
|
||||
// Emit code for each case's statements.
|
||||
for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
|
||||
if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) {
|
||||
// The case offset got saved in the caseNode structure after
|
||||
// emitting the JSOP_CASE jump instruction above.
|
||||
JumpList caseCond;
|
||||
caseCond.offset = caseNode->offset();
|
||||
if (!emitJumpTargetAndPatch(caseCond))
|
||||
if (caseNode->isDefault()) {
|
||||
if (!se.emitDefaultBody())
|
||||
return false;
|
||||
} else {
|
||||
if (isTableSwitch) {
|
||||
ParseNode* caseValue = caseNode->caseExpression();
|
||||
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
|
||||
|
||||
int32_t i = int32_t(caseValue->pn_dval);
|
||||
MOZ_ASSERT(double(i) == caseValue->pn_dval);
|
||||
|
||||
if (!se.emitCaseBody(i, tableGen))
|
||||
return false;
|
||||
} else {
|
||||
if (!se.emitCaseBody())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JumpTarget here;
|
||||
if (!emitJumpTarget(&here))
|
||||
return false;
|
||||
if (caseNode->isDefault())
|
||||
defaultOffset = here;
|
||||
|
||||
// If this is emitted as a TABLESWITCH, we'll need to know this case's
|
||||
// offset later when emitting the table. Store it in the node's
|
||||
// pn_offset (giving the field a different meaning vs. how we used it
|
||||
// on the immediately preceding line of code).
|
||||
caseNode->setOffset(here.offset);
|
||||
|
||||
TDZCheckCache tdzCache(this);
|
||||
|
||||
if (!emitTree(caseNode->statementList()))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasDefault) {
|
||||
// If no default case, offset for default is to end of switch.
|
||||
if (!emitJumpTarget(&defaultOffset))
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(defaultOffset.offset != -1);
|
||||
|
||||
// Set the default offset (to end of switch if no default).
|
||||
jsbytecode* pc;
|
||||
if (switchOp == JSOP_CONDSWITCH) {
|
||||
pc = nullptr;
|
||||
patchJumpsToTarget(condSwitchDefaultOff, defaultOffset);
|
||||
} else {
|
||||
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
|
||||
pc = code(top);
|
||||
SET_JUMP_OFFSET(pc, defaultOffset.offset - top);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
|
||||
// Set the SRC_SWITCH note's offset operand to tell end of switch.
|
||||
if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top))
|
||||
return false;
|
||||
|
||||
if (switchOp == JSOP_TABLESWITCH) {
|
||||
// Skip over the already-initialized switch bounds.
|
||||
pc += 2 * JUMP_OFFSET_LEN;
|
||||
|
||||
// Fill in the jump table, if there is one.
|
||||
for (uint32_t i = 0; i < tableLength; i++) {
|
||||
CaseClause* caseNode = table[i];
|
||||
ptrdiff_t off = caseNode ? caseNode->offset() - top : 0;
|
||||
SET_JUMP_OFFSET(pc, off);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Patch breaks before leaving the scope, as all breaks are under the
|
||||
// lexical scope if it exists.
|
||||
if (!controlInfo.patchBreaks(this))
|
||||
return false;
|
||||
|
||||
if (emitterScope && !emitterScope->leave(this))
|
||||
if (!se.emitEnd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -419,7 +419,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
}
|
||||
|
||||
void reportError(ParseNode* pn, unsigned errorNumber, ...);
|
||||
void reportError(const mozilla::Maybe<uint32_t>& maybeOffset,
|
||||
unsigned errorNumber, ...);
|
||||
bool reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...);
|
||||
bool reportExtraWarning(const mozilla::Maybe<uint32_t>& maybeOffset,
|
||||
unsigned errorNumber, ...);
|
||||
|
||||
// If pn contains a useful expression, return true with *answer set to true.
|
||||
// If pn contains a useless expression, return true with *answer set to
|
||||
|
|
|
@ -255,16 +255,13 @@ IsTypeofKind(ParseNodeKind kind)
|
|||
* StatementList list pn_head: list of pn_count statements
|
||||
* If ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null.
|
||||
* Switch binary pn_left: discriminant
|
||||
* pn_right: list of Case nodes, with at most one
|
||||
* default node, or if there are let bindings
|
||||
* in the top level of the switch body's cases, a
|
||||
* LexicalScope node that contains the list of
|
||||
* Case nodes.
|
||||
* pn_right: LexicalScope node that contains the list
|
||||
* of Case nodes, with at most one
|
||||
* default node.
|
||||
* Case binary pn_left: case-expression if CaseClause, or
|
||||
* null if DefaultClause
|
||||
* pn_right: StatementList node for this case's
|
||||
* statements
|
||||
* pn_u.binary.offset: scratch space for the emitter
|
||||
* While binary pn_left: cond, pn_right: body
|
||||
* DoWhile binary pn_left: body, pn_right: cond
|
||||
* For binary pn_left: either ForIn (for-in statement),
|
||||
|
@ -558,7 +555,6 @@ class ParseNode
|
|||
union {
|
||||
unsigned iflags; /* JSITER_* flags for ParseNodeKind::For node */
|
||||
bool isStatic; /* only for ParseNodeKind::ClassMethod */
|
||||
uint32_t offset; /* for the emitter's use on ParseNodeKind::Case nodes */
|
||||
};
|
||||
} binary;
|
||||
struct { /* one kid if unary */
|
||||
|
@ -1022,10 +1018,6 @@ class CaseClause : public BinaryNode
|
|||
// The next CaseClause in the same switch statement.
|
||||
CaseClause* next() const { return pn_next ? &pn_next->as<CaseClause>() : nullptr; }
|
||||
|
||||
// Scratch space used by the emitter.
|
||||
uint32_t offset() const { return pn_u.binary.offset; }
|
||||
void setOffset(uint32_t u) { pn_u.binary.offset = u; }
|
||||
|
||||
static bool test(const ParseNode& node) {
|
||||
bool match = node.isKind(ParseNodeKind::Case);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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/. */
|
||||
|
||||
#include "frontend/SwitchEmitter.h"
|
||||
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "vm/BytecodeUtil.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
bool
|
||||
SwitchEmitter::TableGenerator::addNumber(int32_t caseValue)
|
||||
{
|
||||
if (isInvalid())
|
||||
return true;
|
||||
|
||||
if (unsigned(caseValue + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (intmap_.isNothing())
|
||||
intmap_.emplace();
|
||||
|
||||
low_ = std::min(low_, caseValue);
|
||||
high_ = std::max(high_, caseValue);
|
||||
|
||||
// Check for duplicates, which require a JSOP_CONDSWITCH.
|
||||
// We bias caseValue by 65536 if it's negative, and hope that's a rare case
|
||||
// (because it requires a malloc'd bitmap).
|
||||
if (caseValue < 0)
|
||||
caseValue += JS_BIT(16);
|
||||
if (caseValue >= intmapBitLength_) {
|
||||
size_t newLength = NumWordsForBitArrayOfLength(caseValue + 1);
|
||||
if (!intmap_->resize(newLength)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
intmapBitLength_ = newLength * BitArrayElementBits;
|
||||
}
|
||||
if (IsBitArrayElementSet(intmap_->begin(), intmap_->length(), caseValue)) {
|
||||
// Duplicate entry is not supported in table switch.
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
SetBitArrayElement(intmap_->begin(), intmap_->length(), caseValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SwitchEmitter::TableGenerator::finish(uint32_t caseCount)
|
||||
{
|
||||
intmap_.reset();
|
||||
|
||||
#ifdef DEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
|
||||
if (isInvalid())
|
||||
return;
|
||||
|
||||
if (caseCount == 0) {
|
||||
low_ = 0;
|
||||
high_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute table length and select condswitch instead if overlarge
|
||||
// or more than half-sparse.
|
||||
tableLength_ = uint32_t(high_ - low_ + 1);
|
||||
if (tableLength_ >= JS_BIT(16) || tableLength_ > 2 * caseCount)
|
||||
setInvalid();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::toCaseIndex(int32_t caseValue) const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
uint32_t caseIndex = uint32_t(caseValue - low_);
|
||||
MOZ_ASSERT(caseIndex < tableLength_);
|
||||
return caseIndex;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::tableLength() const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
return tableLength_;
|
||||
}
|
||||
|
||||
SwitchEmitter::SwitchEmitter(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDiscriminant(const Maybe<uint32_t>& switchPos)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
switchPos_ = switchPos;
|
||||
|
||||
if (switchPos_) {
|
||||
// Ensure that the column of the switch statement is set properly.
|
||||
if (!bce_->updateSourceCoordNotes(*switchPos_))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = State::Discriminant;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitLexical(Handle<LexicalScope::Data*> bindings)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant);
|
||||
MOZ_ASSERT(bindings);
|
||||
|
||||
tdzCacheLexical_.emplace(bce_);
|
||||
emitterScope_.emplace(bce_);
|
||||
if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings))
|
||||
return false;
|
||||
|
||||
state_ = State::Lexical;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::validateCaseCount(uint32_t caseCount)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant || state_ == State::Lexical);
|
||||
if (caseCount > JS_BIT(16)) {
|
||||
bce_->reportError(switchPos_, JSMSG_TOO_MANY_CASES);
|
||||
return false;
|
||||
}
|
||||
caseCount_ = caseCount;
|
||||
|
||||
state_ = State::CaseCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCond()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
|
||||
kind_ = Kind::Cond;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
if (!caseOffsets_.resize(caseCount_)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The note has two offsets: first tells total switch code length;
|
||||
// second tells offset to first JSOP_CASE.
|
||||
if (!bce_->newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_CONDSWITCH, 0))
|
||||
return false;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::Cond;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitTable(const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
kind_ = Kind::Table;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
// The note has one offset that tells total switch code length.
|
||||
|
||||
// 3 offsets (len, low, high) before the table, 1 per entry.
|
||||
size_t switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableGen.tableLength()));
|
||||
if (!bce_->newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
if (!caseOffsets_.resize(tableGen.tableLength())) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_TABLESWITCH, switchSize))
|
||||
return false;
|
||||
|
||||
// Skip default offset.
|
||||
jsbytecode* pc = bce_->code(top_ + JUMP_OFFSET_LEN);
|
||||
|
||||
// Fill in switch bounds, which we know fit in 16-bit offsets.
|
||||
SET_JUMP_OFFSET(pc, tableGen.low());
|
||||
SET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN, tableGen.high());
|
||||
|
||||
state_ = State::Table;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
|
||||
if (state_ == State::Case) {
|
||||
// Link the last JSOP_CASE's SRC_NEXTCASE to current JSOP_CASE or
|
||||
// JSOP_DEFAULT for the benefit of IonBuilder.
|
||||
if (!bce_->setSrcNoteOffset(caseNoteIndex_, 0, bce_->offset() - lastCaseOffset_))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDefault) {
|
||||
if (!bce_->emitJump(JSOP_DEFAULT, &condSwitchDefaultOffset_))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bce_->newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex_))
|
||||
return false;
|
||||
|
||||
JumpList caseJump;
|
||||
if (!bce_->emitJump(JSOP_CASE, &caseJump))
|
||||
return false;
|
||||
caseOffsets_[caseIndex] = caseJump.offset;
|
||||
lastCaseOffset_ = caseJump.offset;
|
||||
|
||||
if (state_ == State::Cond) {
|
||||
// Switch note's second offset is to first JSOP_CASE.
|
||||
unsigned noteCount = bce_->notes().length();
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 1, lastCaseOffset_ - top_))
|
||||
return false;
|
||||
unsigned noteCountDelta = bce_->notes().length() - noteCount;
|
||||
if (noteCountDelta != 0)
|
||||
caseNoteIndex_ += noteCountDelta;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseJump()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(caseIndex_, false))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
state_ = State::Case;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitImplicitDefault()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(0, true))
|
||||
return false;
|
||||
|
||||
caseIndex_ = 0;
|
||||
|
||||
// No internal state after emitting default jump.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
|
||||
JumpList caseJump;
|
||||
caseJump.offset = caseOffsets_[caseIndex_];
|
||||
if (!bce_->emitJumpTargetAndPatch(caseJump))
|
||||
return false;
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody(int32_t caseValue, const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Table);
|
||||
MOZ_ASSERT(state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseOffsets_[tableGen.toCaseIndex(caseValue)] = here.offset;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDefaultBody()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::Case ||
|
||||
state_ == State::CaseBody);
|
||||
MOZ_ASSERT(!hasDefault_);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
defaultJumpTargetOffset_ = here;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
hasDefault_ = true;
|
||||
state_ = State::DefaultBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (!hasDefault_) {
|
||||
// If no default case, offset for default is to end of switch.
|
||||
if (!bce_->emitJumpTarget(&defaultJumpTargetOffset_))
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(defaultJumpTargetOffset_.offset != -1);
|
||||
|
||||
// Set the default offset (to end of switch if no default).
|
||||
jsbytecode* pc;
|
||||
if (kind_ == Kind::Cond) {
|
||||
pc = nullptr;
|
||||
bce_->patchJumpsToTarget(condSwitchDefaultOffset_, defaultJumpTargetOffset_);
|
||||
} else {
|
||||
// Fill in the default jump target.
|
||||
pc = bce_->code(top_);
|
||||
SET_JUMP_OFFSET(pc, defaultJumpTargetOffset_.offset - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
|
||||
// Set the SRC_SWITCH note's offset operand to tell end of switch.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->lastNonJumpTargetOffset() - top_))
|
||||
return false;
|
||||
|
||||
if (kind_ == Kind::Table) {
|
||||
// Skip over the already-initialized switch bounds.
|
||||
pc += 2 * JUMP_OFFSET_LEN;
|
||||
|
||||
// Fill in the jump table, if there is one.
|
||||
for (uint32_t i = 0, length = caseOffsets_.length(); i < length; i++) {
|
||||
ptrdiff_t off = caseOffsets_[i];
|
||||
SET_JUMP_OFFSET(pc, off == 0 ? 0 : off - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Patch breaks before leaving the scope, as all breaks are under the
|
||||
// lexical scope if it exists.
|
||||
if (!controlInfo_->patchBreaks(bce_))
|
||||
return false;
|
||||
|
||||
if (emitterScope_ && !emitterScope_->leave(bce_))
|
||||
return false;
|
||||
|
||||
emitterScope_.reset();
|
||||
tdzCacheLexical_.reset();
|
||||
|
||||
controlInfo_.reset();
|
||||
|
||||
state_ = State::End;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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/. */
|
||||
|
||||
#ifndef frontend_SwitchEmitter_h
|
||||
#define frontend_SwitchEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/BytecodeControlStructures.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "js/AllocPolicy.h"
|
||||
#include "js/Value.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/Scope.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for switch-case-default block.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// emit(c2_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(this);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(bce);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains lexical bindings
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains hosted functions
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
// emit(hosted functions);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
class MOZ_STACK_CLASS SwitchEmitter
|
||||
{
|
||||
// Bytecode for each case.
|
||||
//
|
||||
// Cond Switch
|
||||
// {discriminant}
|
||||
// JSOP_CONDSWITCH
|
||||
//
|
||||
// {c1_expr}
|
||||
// JSOP_CASE c1
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_expr}
|
||||
// JSOP_CASE c2
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// JSOP_DEFAULT default
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// default:
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
//
|
||||
// Table Switch
|
||||
// {discriminant}
|
||||
// JSOP_TABLESWITCH c1, c2, ...
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
|
||||
public:
|
||||
enum class Kind {
|
||||
Table,
|
||||
Cond
|
||||
};
|
||||
|
||||
// Class for generating optimized table switch data.
|
||||
class MOZ_STACK_CLASS TableGenerator
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// Bit array for given numbers.
|
||||
mozilla::Maybe<js::Vector<size_t, 128, SystemAllocPolicy>> intmap_;
|
||||
|
||||
// The length of the intmap_.
|
||||
int32_t intmapBitLength_ = 0;
|
||||
|
||||
// The length of the table.
|
||||
uint32_t tableLength_ = 0;
|
||||
|
||||
// The lower and higher bounds of the table.
|
||||
int32_t low_ = JSVAL_INT_MAX, high_ = JSVAL_INT_MIN;
|
||||
|
||||
// Whether the table is still valid.
|
||||
bool valid_= true;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit TableGenerator(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
void setInvalid() {
|
||||
valid_ = false;
|
||||
}
|
||||
MOZ_MUST_USE bool isValid() const {
|
||||
return valid_;
|
||||
}
|
||||
MOZ_MUST_USE bool isInvalid() const {
|
||||
return !valid_;
|
||||
}
|
||||
|
||||
// Add the given number to the table. The number is the value of
|
||||
// `expr` for `case expr:` syntax.
|
||||
MOZ_MUST_USE bool addNumber(int32_t caseValue);
|
||||
|
||||
// Finish generating the table.
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
void finish(uint32_t caseCount);
|
||||
|
||||
private:
|
||||
friend SwitchEmitter;
|
||||
|
||||
// The following methods can be used only after calling `finish`.
|
||||
|
||||
// Returns the lower bound of the added numbers.
|
||||
int32_t low() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return low_;
|
||||
}
|
||||
|
||||
// Returns the higher bound of the numbers.
|
||||
int32_t high() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return high_;
|
||||
}
|
||||
|
||||
// Returns the index in SwitchEmitter.caseOffsets_ for table switch.
|
||||
uint32_t toCaseIndex(int32_t caseValue) const;
|
||||
|
||||
// Returns the length of the table.
|
||||
// This method can be called only if `isValid()` is true.
|
||||
uint32_t tableLength() const;
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// `kind_` should be set to the correct value in emitCond/emitTable.
|
||||
Kind kind_ = Kind::Cond;
|
||||
|
||||
// True if there's explicit default case.
|
||||
bool hasDefault_ = false;
|
||||
|
||||
// The source note index for SRC_CONDSWITCH.
|
||||
unsigned noteIndex_ = 0;
|
||||
|
||||
// Source note index of the previous SRC_NEXTCASE.
|
||||
unsigned caseNoteIndex_ = 0;
|
||||
|
||||
// The number of cases in the switch statement, excluding the default case.
|
||||
uint32_t caseCount_ = 0;
|
||||
|
||||
// Internal index for case jump and case body, used by cond switch.
|
||||
uint32_t caseIndex_ = 0;
|
||||
|
||||
// Bytecode offset after emitting `discriminant`.
|
||||
ptrdiff_t top_ = 0;
|
||||
|
||||
// Bytecode offset of the previous JSOP_CASE.
|
||||
ptrdiff_t lastCaseOffset_ = 0;
|
||||
|
||||
// Bytecode offset of the JSOP_JUMPTARGET for default body.
|
||||
JumpTarget defaultJumpTargetOffset_ = { -1 };
|
||||
|
||||
// Bytecode offset of the JSOP_DEFAULT.
|
||||
JumpList condSwitchDefaultOffset_;
|
||||
|
||||
// Instantiated when there's lexical scope for entire switch.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheLexical_;
|
||||
mozilla::Maybe<EmitterScope> emitterScope_;
|
||||
|
||||
// Instantiated while emitting case expression and case/default body.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheCaseAndBody_;
|
||||
|
||||
// Control for switch.
|
||||
mozilla::Maybe<BreakableControl> controlInfo_;
|
||||
|
||||
mozilla::Maybe<uint32_t> switchPos_;
|
||||
|
||||
// Cond Switch:
|
||||
// Offset of each JSOP_CASE.
|
||||
// Table Switch:
|
||||
// Offset of each JSOP_JUMPTARGET for case.
|
||||
js::Vector<ptrdiff_t, 32, SystemAllocPolicy> caseOffsets_;
|
||||
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ emitDiscriminant +--------------+
|
||||
// | Start |----------------->| Discriminant |-+
|
||||
// +-------+ +--------------+ |
|
||||
// |
|
||||
// +-------------------------------------------+
|
||||
// |
|
||||
// | validateCaseCount +-----------+
|
||||
// +->+------------------------>+------------------>| CaseCount |-+
|
||||
// | ^ +-----------+ |
|
||||
// | emitLexical +---------+ | |
|
||||
// +------------>| Lexical |-+ |
|
||||
// +---------+ |
|
||||
// |
|
||||
// +--------------------------------------------------------------+
|
||||
// |
|
||||
// | emitTable +-------+
|
||||
// +---------->| Table |---------------------------->+-+
|
||||
// | +-------+ ^ |
|
||||
// | | |
|
||||
// | emitCond +------+ | |
|
||||
// +---------->| Cond |-+------------------------>+->+ |
|
||||
// +------+ | ^ |
|
||||
// | | |
|
||||
// | emitCase +------+ | |
|
||||
// +->+--------->| Case |->+-+ |
|
||||
// ^ +------+ | |
|
||||
// | | |
|
||||
// +--------------------+ |
|
||||
// |
|
||||
// +---------------------------------------------------+
|
||||
// |
|
||||
// | emitEnd +-----+
|
||||
// +-+----------------------------------------->+-------->| End |
|
||||
// | ^ +-----+
|
||||
// | emitCaseBody +----------+ |
|
||||
// +->+-+---------------->| CaseBody |--->+-+-+
|
||||
// ^ | +----------+ ^ |
|
||||
// | | | |
|
||||
// | | emitDefaultBody +-------------+ | |
|
||||
// | +---------------->| DefaultBody |-+ |
|
||||
// | +-------------+ |
|
||||
// | |
|
||||
// +-------------------------------------+
|
||||
//
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitDiscriminant.
|
||||
Discriminant,
|
||||
|
||||
// After calling validateCaseCount.
|
||||
CaseCount,
|
||||
|
||||
// After calling emitLexical.
|
||||
Lexical,
|
||||
|
||||
// After calling emitCond.
|
||||
Cond,
|
||||
|
||||
// After calling emitTable.
|
||||
Table,
|
||||
|
||||
// After calling emitCase.
|
||||
Case,
|
||||
|
||||
// After calling emitCaseBody.
|
||||
CaseBody,
|
||||
|
||||
// After calling emitDefaultBody.
|
||||
DefaultBody,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_ = State::Start;
|
||||
|
||||
public:
|
||||
explicit SwitchEmitter(BytecodeEmitter* bce);
|
||||
|
||||
// `switchPos` is the offset in the source code for the character below:
|
||||
//
|
||||
// switch ( cond ) { ... }
|
||||
// ^
|
||||
// |
|
||||
// switchPos
|
||||
//
|
||||
// Can be Nothing() if not available.
|
||||
MOZ_MUST_USE bool emitDiscriminant(const mozilla::Maybe<uint32_t>& switchPos);
|
||||
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
MOZ_MUST_USE bool validateCaseCount(uint32_t caseCount);
|
||||
|
||||
// `bindings` is a lexical scope for the entire switch, in case there's
|
||||
// let/const effectively directly under case or default blocks.
|
||||
MOZ_MUST_USE bool emitLexical(Handle<LexicalScope::Data*> bindings);
|
||||
|
||||
MOZ_MUST_USE bool emitCond();
|
||||
MOZ_MUST_USE bool emitTable(const TableGenerator& tableGen);
|
||||
|
||||
MOZ_MUST_USE bool emitCaseJump();
|
||||
|
||||
MOZ_MUST_USE bool emitCaseBody();
|
||||
MOZ_MUST_USE bool emitCaseBody(int32_t caseValue, const TableGenerator& tableGen);
|
||||
MOZ_MUST_USE bool emitDefaultBody();
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault);
|
||||
MOZ_MUST_USE bool emitImplicitDefault();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_SwitchEmitter_h */
|
|
@ -440,6 +440,32 @@ TokenStreamCharsBase<CharT>::TokenStreamCharsBase(JSContext* cx, const CharT* ch
|
|||
sourceUnits(chars, length, startOffset)
|
||||
{}
|
||||
|
||||
template<>
|
||||
MOZ_MUST_USE bool
|
||||
TokenStreamCharsBase<char16_t>::fillCharBufferWithTemplateStringContents(const char16_t* cur,
|
||||
const char16_t* end)
|
||||
{
|
||||
MOZ_ASSERT(this->charBuffer.length() == 0);
|
||||
|
||||
while (cur < end) {
|
||||
// Template literals normalize only '\r' and "\r\n" to '\n'. The
|
||||
// Unicode separators need no special handling here.
|
||||
// https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv
|
||||
char16_t ch = *cur++;
|
||||
if (ch == '\r') {
|
||||
ch = '\n';
|
||||
if (cur < end && *cur == '\n')
|
||||
cur++;
|
||||
}
|
||||
|
||||
if (!this->charBuffer.append(ch))
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(cur == end);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename CharT, class AnyCharsAccess>
|
||||
TokenStreamSpecific<CharT, AnyCharsAccess>::TokenStreamSpecific(JSContext* cx,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
|
@ -487,9 +513,6 @@ TokenStreamAnyChars::undoInternalUpdateLineInfoForEOL()
|
|||
lineno--;
|
||||
}
|
||||
|
||||
// This gets a full code point, starting from an already-consumed leading code
|
||||
// unit, normalizing EOL sequences to '\n', also updating line/column info as
|
||||
// needed.
|
||||
template<class AnyCharsAccess>
|
||||
bool
|
||||
TokenStreamChars<char16_t, AnyCharsAccess>::getCodePoint(int32_t* cp)
|
||||
|
@ -510,10 +533,7 @@ TokenStreamChars<char16_t, AnyCharsAccess>::getCodePoint(int32_t* cp)
|
|||
break;
|
||||
|
||||
if (MOZ_UNLIKELY(c == '\r')) {
|
||||
// If it's a \r\n sequence: treat as a single EOL, skip over the \n.
|
||||
if (MOZ_LIKELY(!this->sourceUnits.atEnd()))
|
||||
this->sourceUnits.matchCodeUnit('\n');
|
||||
|
||||
matchLineTerminator('\n');
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -601,21 +621,6 @@ TokenStreamChars<char16_t, AnyCharsAccess>::ungetCodePointIgnoreEOL(uint32_t cod
|
|||
ungetCodeUnit(units[numUnits]);
|
||||
}
|
||||
|
||||
template<class AnyCharsAccess>
|
||||
void
|
||||
TokenStreamChars<char16_t, AnyCharsAccess>::ungetLineTerminator()
|
||||
{
|
||||
this->sourceUnits.ungetCodeUnit();
|
||||
|
||||
char16_t last = this->sourceUnits.peekCodeUnit();
|
||||
MOZ_ASSERT(SourceUnits::isRawEOLChar(last));
|
||||
|
||||
if (last == '\n')
|
||||
this->sourceUnits.ungetOptionalCRBeforeLF();
|
||||
|
||||
anyCharsAccess().undoInternalUpdateLineInfoForEOL();
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
size_t
|
||||
SourceUnits<CharT>::findEOLMax(size_t start, size_t max)
|
||||
|
@ -1329,20 +1334,15 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::putIdentInCharBuffer(const CharT* id
|
|||
if (unit != '\\' || !matchUnicodeEscapeIdent(&codePoint))
|
||||
break;
|
||||
} else {
|
||||
int32_t cp;
|
||||
if (!getNonAsciiCodePoint(unit, &cp))
|
||||
// |restoreNextRawCharAddress| undoes all gets, and this function
|
||||
// doesn't update line/column info.
|
||||
char32_t cp;
|
||||
if (!getNonAsciiCodePointDontNormalize(unit, &cp))
|
||||
return false;
|
||||
|
||||
codePoint = AssertedCast<uint32_t>(cp);
|
||||
|
||||
if (!unicode::IsIdentifierPart(codePoint)) {
|
||||
if (MOZ_UNLIKELY(codePoint == '\n')) {
|
||||
// |restoreNextRawCharAddress| will undo all gets, but we
|
||||
// have to revert a line/column update manually.
|
||||
anyCharsAccess().undoInternalUpdateLineInfoForEOL();
|
||||
}
|
||||
codePoint = cp;
|
||||
if (!unicode::IsIdentifierPart(codePoint))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!appendCodePointToCharBuffer(codePoint))
|
||||
|
@ -1511,16 +1511,16 @@ static const uint8_t firstCharKinds[] = {
|
|||
static_assert(LastCharKind < (1 << (sizeof(firstCharKinds[0]) * 8)),
|
||||
"Elements of firstCharKinds[] are too small");
|
||||
|
||||
template<typename CharT, class AnyCharsAccess>
|
||||
void
|
||||
GeneralTokenStreamChars<CharT, AnyCharsAccess>::consumeRestOfSingleLineComment()
|
||||
SpecializedTokenStreamCharsBase<char16_t>::infallibleConsumeRestOfSingleLineComment()
|
||||
{
|
||||
int32_t c;
|
||||
do {
|
||||
c = getCodeUnit();
|
||||
} while (c != EOF && !SourceUnits::isRawEOLChar(c));
|
||||
while (MOZ_LIKELY(!this->sourceUnits.atEnd())) {
|
||||
char16_t unit = this->sourceUnits.peekCodeUnit();
|
||||
if (SourceUnits::isRawEOLChar(unit))
|
||||
return;
|
||||
|
||||
ungetCodeUnit(c);
|
||||
this->sourceUnits.consumeKnownCodeUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CharT, class AnyCharsAccess>
|
||||
|
@ -1628,13 +1628,16 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::regexpLiteral(TokenStart start, Toke
|
|||
|
||||
auto ProcessNonAsciiCodePoint = [this](int32_t lead) {
|
||||
MOZ_ASSERT(lead != EOF);
|
||||
MOZ_ASSERT(!this->isAsciiCodePoint(lead));
|
||||
|
||||
int32_t codePoint;
|
||||
if (!this->getNonAsciiCodePoint(lead, &codePoint))
|
||||
char32_t codePoint;
|
||||
if (!this->getNonAsciiCodePointDontNormalize(lead, &codePoint))
|
||||
return false;
|
||||
|
||||
if (codePoint == '\n') {
|
||||
this->ungetLineTerminator();
|
||||
if (MOZ_UNLIKELY(codePoint == unicode::LINE_SEPARATOR ||
|
||||
codePoint == unicode::PARA_SEPARATOR))
|
||||
{
|
||||
this->sourceUnits.ungetLineOrParagraphSeparator();
|
||||
this->reportError(JSMSG_UNTERMINATED_REGEXP);
|
||||
return false;
|
||||
}
|
||||
|
@ -1655,45 +1658,53 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::regexpLiteral(TokenStart start, Toke
|
|||
return badToken();
|
||||
}
|
||||
|
||||
if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
|
||||
if (unit == '\\') {
|
||||
if (!this->charBuffer.append(unit))
|
||||
return badToken();
|
||||
if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) {
|
||||
if (!ProcessNonAsciiCodePoint(unit))
|
||||
return badToken();
|
||||
|
||||
unit = getCodeUnit();
|
||||
if (unit == EOF) {
|
||||
ReportUnterminatedRegExp(unit);
|
||||
return badToken();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallthrough only handles ASCII code points, so
|
||||
// deal with non-ASCII and skip everything else.
|
||||
if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) {
|
||||
if (!ProcessNonAsciiCodePoint(unit))
|
||||
return badToken();
|
||||
if (unit == '\\') {
|
||||
if (!this->charBuffer.append(unit))
|
||||
return badToken();
|
||||
|
||||
continue;
|
||||
}
|
||||
} else if (unit == '[') {
|
||||
inCharClass = true;
|
||||
} else if (unit == ']') {
|
||||
inCharClass = false;
|
||||
} else if (unit == '/' && !inCharClass) {
|
||||
// For IE compat, allow unescaped / in char classes.
|
||||
break;
|
||||
}
|
||||
|
||||
if (unit == '\r' || unit == '\n') {
|
||||
unit = getCodeUnit();
|
||||
if (unit == EOF) {
|
||||
ReportUnterminatedRegExp(unit);
|
||||
return badToken();
|
||||
}
|
||||
|
||||
if (!this->charBuffer.append(unit))
|
||||
return badToken();
|
||||
} else {
|
||||
if (!ProcessNonAsciiCodePoint(unit))
|
||||
return badToken();
|
||||
// Fallthrough only handles ASCII code points, so
|
||||
// deal with non-ASCII and skip everything else.
|
||||
if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) {
|
||||
if (!ProcessNonAsciiCodePoint(unit))
|
||||
return badToken();
|
||||
|
||||
continue;
|
||||
}
|
||||
} else if (unit == '[') {
|
||||
inCharClass = true;
|
||||
} else if (unit == ']') {
|
||||
inCharClass = false;
|
||||
} else if (unit == '/' && !inCharClass) {
|
||||
// For IE compat, allow unescaped / in char classes.
|
||||
break;
|
||||
}
|
||||
|
||||
if (unit == '\r' || unit == '\n') {
|
||||
ReportUnterminatedRegExp(unit);
|
||||
return badToken();
|
||||
}
|
||||
|
||||
// We're accumulating regular expression *source* text here: source
|
||||
// text matching a line break will appear as U+005C REVERSE SOLIDUS
|
||||
// U+006E LATIN SMALL LETTER N, and |unit| here would be the latter
|
||||
// code point.
|
||||
MOZ_ASSERT(!SourceUnits::isRawEOLChar(unit));
|
||||
|
||||
if (!this->charBuffer.append(unit))
|
||||
return badToken();
|
||||
} while (true);
|
||||
|
||||
int32_t unit;
|
||||
|
@ -1855,9 +1866,8 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getTokenInternal(TokenKind* const tt
|
|||
// Skip over EOL chars, updating line state along the way.
|
||||
//
|
||||
if (c1kind == EOL) {
|
||||
// If it's a \r\n sequence, consume it as a single EOL.
|
||||
if (unit == '\r' && !this->sourceUnits.atEnd())
|
||||
this->sourceUnits.matchCodeUnit('\n');
|
||||
if (unit == '\r')
|
||||
matchLineTerminator('\n');
|
||||
|
||||
if (!updateLineInfoForEOL())
|
||||
return badToken();
|
||||
|
@ -2097,7 +2107,9 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getTokenInternal(TokenKind* const tt
|
|||
if (matchCodeUnit('!')) {
|
||||
if (matchCodeUnit('-')) {
|
||||
if (matchCodeUnit('-')) {
|
||||
consumeRestOfSingleLineComment();
|
||||
if (!consumeRestOfSingleLineComment())
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
ungetCodeUnit('-');
|
||||
|
@ -2142,7 +2154,9 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getTokenInternal(TokenKind* const tt
|
|||
ungetCodeUnit(unit);
|
||||
}
|
||||
|
||||
consumeRestOfSingleLineComment();
|
||||
if (!consumeRestOfSingleLineComment())
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2199,7 +2213,9 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getTokenInternal(TokenKind* const tt
|
|||
!anyCharsAccess().flags.isDirtyLine)
|
||||
{
|
||||
if (matchCodeUnit('>')) {
|
||||
consumeRestOfSingleLineComment();
|
||||
if (!consumeRestOfSingleLineComment())
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -2342,8 +2358,7 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getStringOrTemplateToken(char untilC
|
|||
case 'v': unit = '\v'; break;
|
||||
|
||||
case '\r':
|
||||
if (MOZ_LIKELY(!this->sourceUnits.atEnd()))
|
||||
this->sourceUnits.matchCodeUnit('\n');
|
||||
matchLineTerminator('\n');
|
||||
MOZ_FALLTHROUGH;
|
||||
case '\n': {
|
||||
// LineContinuation represents no code points. We're manually
|
||||
|
@ -2549,10 +2564,7 @@ TokenStreamSpecific<CharT, AnyCharsAccess>::getStringOrTemplateToken(char untilC
|
|||
|
||||
if (unit == '\r') {
|
||||
unit = '\n';
|
||||
|
||||
// If it's a \r\n sequence: treat as a single EOL, skip over the \n.
|
||||
if (!this->sourceUnits.atEnd())
|
||||
this->sourceUnits.matchCodeUnit('\n');
|
||||
matchLineTerminator('\n');
|
||||
}
|
||||
|
||||
if (!updateLineInfoForEOL())
|
||||
|
@ -2615,21 +2627,21 @@ TokenKindToString(TokenKind tt)
|
|||
}
|
||||
#endif
|
||||
|
||||
template class frontend::TokenStreamCharsBase<Utf8Unit>;
|
||||
template class frontend::TokenStreamCharsBase<char16_t>;
|
||||
template class TokenStreamCharsBase<Utf8Unit>;
|
||||
template class TokenStreamCharsBase<char16_t>;
|
||||
|
||||
template class frontend::TokenStreamChars<char16_t, frontend::TokenStreamAnyCharsAccess>;
|
||||
template class frontend::TokenStreamSpecific<char16_t, frontend::TokenStreamAnyCharsAccess>;
|
||||
template class TokenStreamChars<char16_t, TokenStreamAnyCharsAccess>;
|
||||
template class TokenStreamSpecific<char16_t, TokenStreamAnyCharsAccess>;
|
||||
|
||||
template class
|
||||
frontend::TokenStreamChars<char16_t, frontend::ParserAnyCharsAccess<frontend::GeneralParser<frontend::FullParseHandler, char16_t>>>;
|
||||
TokenStreamChars<char16_t, ParserAnyCharsAccess<GeneralParser<FullParseHandler, char16_t>>>;
|
||||
template class
|
||||
frontend::TokenStreamChars<char16_t, frontend::ParserAnyCharsAccess<frontend::GeneralParser<frontend::SyntaxParseHandler, char16_t>>>;
|
||||
TokenStreamChars<char16_t, ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, char16_t>>>;
|
||||
|
||||
template class
|
||||
frontend::TokenStreamSpecific<char16_t, frontend::ParserAnyCharsAccess<frontend::GeneralParser<frontend::FullParseHandler, char16_t>>>;
|
||||
TokenStreamSpecific<char16_t, ParserAnyCharsAccess<GeneralParser<FullParseHandler, char16_t>>>;
|
||||
template class
|
||||
frontend::TokenStreamSpecific<char16_t, frontend::ParserAnyCharsAccess<frontend::GeneralParser<frontend::SyntaxParseHandler, char16_t>>>;
|
||||
TokenStreamSpecific<char16_t, ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, char16_t>>>;
|
||||
|
||||
} // namespace frontend
|
||||
|
||||
|
|
|
@ -60,23 +60,41 @@
|
|||
* for it. (ParserBase isn't the only instance of this, but it's certainly the
|
||||
* biggest case of it.) Ergo, TokenStreamAnyChars.
|
||||
*
|
||||
* == TokenStreamCharsBase<CharT> → ∅ ==
|
||||
* == TokenStreamCharsShared → ∅ ==
|
||||
*
|
||||
* Certain data structures in tokenizing are character-type-specific:
|
||||
* Some functionality has meaning independent of character type, yet has no use
|
||||
* *unless* you know the character type in actual use. It *could* live in
|
||||
* TokenStreamAnyChars, but it makes more sense to live in a separate class
|
||||
* that character-aware token information can simply inherit.
|
||||
*
|
||||
* This class currently exists only to contain a char16_t buffer, transiently
|
||||
* used to accumulate strings in tricky cases that can't just be read directly
|
||||
* from source text. It's not used outside character-aware tokenizing, so it
|
||||
* doesn't make sense in TokenStreamAnyChars.
|
||||
*
|
||||
* == TokenStreamCharsBase<CharT> → TokenStreamCharsShared ==
|
||||
*
|
||||
* Certain data structures in tokenizing are character-type-specific: namely,
|
||||
* the various pointers identifying the source text (including current offset
|
||||
* and end) , and the temporary vector into which characters are read/written
|
||||
* in certain cases (think writing out the actual codepoints identified by an
|
||||
* identifier containing a Unicode escape, to create the atom for the
|
||||
* identifier: |a\u0062c| versus |abc|, for example).
|
||||
* and end).
|
||||
*
|
||||
* Additionally, some functions operating on this data are defined the same way
|
||||
* no matter what character type you have -- the offset being |offset - start|
|
||||
* no matter whether those two variables are single- or double-byte pointers.
|
||||
* no matter what character type you have (e.g. current offset in code units
|
||||
* into the source text) or share a common interface regardless of character
|
||||
* type (e.g. consume the next code unit if it has a given value).
|
||||
*
|
||||
* All such functionality lives in TokenStreamCharsBase<CharT>.
|
||||
*
|
||||
* == SpecializedTokenStreamCharsBase<CharT> → TokenStreamCharsBase<CharT> ==
|
||||
*
|
||||
* Certain tokenizing functionality is specific to a single character type.
|
||||
* For example, JS's UTF-16 encoding recognizes no coding errors, because lone
|
||||
* surrogates are not an error; but a UTF-8 encoding must recognize a variety
|
||||
* of validation errors. Such functionality is defined only in the appropriate
|
||||
* SpecializedTokenStreamCharsBase specialization.
|
||||
*
|
||||
* == GeneralTokenStreamChars<CharT, AnyCharsAccess> →
|
||||
* TokenStreamCharsBase<CharT> ==
|
||||
* SpecializedTokenStreamCharsBase<CharT> ==
|
||||
*
|
||||
* Some functionality operates differently on different character types, just
|
||||
* as for TokenStreamCharsBase, but additionally requires access to character-
|
||||
|
@ -928,6 +946,9 @@ CodeUnitValue(mozilla::Utf8Unit unit)
|
|||
return unit.toUint8();
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
class TokenStreamCharsBase;
|
||||
|
||||
// This is the low-level interface to the JS source code buffer. It just gets
|
||||
// raw Unicode code units -- 16-bit char16_t units of source text that are not
|
||||
// (always) full code points, and 8-bit units of UTF-8 source text soon.
|
||||
|
@ -1050,14 +1071,25 @@ class SourceUnits
|
|||
ptr -= n;
|
||||
}
|
||||
|
||||
bool matchCodeUnit(CharT c) {
|
||||
if (*ptr == c) { // this will nullptr-crash if poisoned
|
||||
private:
|
||||
friend class TokenStreamCharsBase<CharT>;
|
||||
|
||||
bool internalMatchCodeUnit(CharT c) {
|
||||
MOZ_ASSERT(ptr, "shouldn't use poisoned SourceUnits");
|
||||
if (MOZ_LIKELY(!atEnd()) && *ptr == c) {
|
||||
ptr++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
void consumeKnownCodeUnit(CharT c) {
|
||||
MOZ_ASSERT(ptr, "shouldn't use poisoned SourceUnits");
|
||||
MOZ_ASSERT(*ptr == c, "consuming the wrong code unit");
|
||||
ptr++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unget the '\n' (CR) that precedes a '\n' (LF), when ungetting a line
|
||||
* terminator that's a full "\r\n" sequence. If the prior code unit isn't
|
||||
|
@ -1073,6 +1105,9 @@ class SourceUnits
|
|||
ptr--;
|
||||
}
|
||||
|
||||
/** Unget U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR. */
|
||||
inline void ungetLineOrParagraphSeparator();
|
||||
|
||||
void ungetCodeUnit() {
|
||||
MOZ_ASSERT(!atStart(), "can't unget if currently at start");
|
||||
MOZ_ASSERT(ptr); // make sure it hasn't been poisoned
|
||||
|
@ -1122,6 +1157,33 @@ class SourceUnits
|
|||
const CharT* ptr;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline void
|
||||
SourceUnits<char16_t>::ungetLineOrParagraphSeparator()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
char16_t prev = previousCodeUnit();
|
||||
#endif
|
||||
MOZ_ASSERT(prev == unicode::LINE_SEPARATOR || prev == unicode::PARA_SEPARATOR);
|
||||
|
||||
ungetCodeUnit();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
SourceUnits<mozilla::Utf8Unit>::ungetLineOrParagraphSeparator()
|
||||
{
|
||||
unskipCodeUnits(3);
|
||||
|
||||
MOZ_ASSERT(ptr[0].toUint8() == 0xE2);
|
||||
MOZ_ASSERT(ptr[1].toUint8() == 0x80);
|
||||
|
||||
#ifdef DEBUG
|
||||
uint8_t last = ptr[2].toUint8();
|
||||
#endif
|
||||
MOZ_ASSERT(last == 0xA8 || last == 0xA9);
|
||||
}
|
||||
|
||||
class TokenStreamCharsShared
|
||||
{
|
||||
// Using char16_t (not CharT) is a simplifying decision that hopefully
|
||||
|
@ -1171,6 +1233,8 @@ class TokenStreamCharsBase
|
|||
: public TokenStreamCharsShared
|
||||
{
|
||||
protected:
|
||||
TokenStreamCharsBase(JSContext* cx, const CharT* chars, size_t length, size_t startOffset);
|
||||
|
||||
/**
|
||||
* Convert a non-EOF code unit returned by |getCodeUnit()| or
|
||||
* |peekCodeUnit()| to a CharT code unit.
|
||||
|
@ -1184,35 +1248,54 @@ class TokenStreamCharsBase
|
|||
sourceUnits.ungetCodeUnit();
|
||||
}
|
||||
|
||||
public:
|
||||
TokenStreamCharsBase(JSContext* cx, const CharT* chars, size_t length, size_t startOffset);
|
||||
|
||||
static MOZ_ALWAYS_INLINE JSAtom*
|
||||
atomizeSourceChars(JSContext* cx, const CharT* chars, size_t length);
|
||||
|
||||
using SourceUnits = frontend::SourceUnits<CharT>;
|
||||
|
||||
/** Match a non-EOL, non-EOF code unit; return true iff it was matched. */
|
||||
inline bool matchCodeUnit(int32_t expect);
|
||||
/**
|
||||
* Try to match a non-LineTerminator ASCII code point. Return true iff it
|
||||
* was matched.
|
||||
*/
|
||||
bool matchCodeUnit(char expect) {
|
||||
MOZ_ASSERT(mozilla::IsAscii(expect));
|
||||
MOZ_ASSERT(expect != '\r');
|
||||
MOZ_ASSERT(expect != '\n');
|
||||
return this->sourceUnits.internalMatchCodeUnit(CharT(expect));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to match an ASCII LineTerminator code point. Return true iff it was
|
||||
* matched.
|
||||
*/
|
||||
bool matchLineTerminator(char expect) {
|
||||
MOZ_ASSERT(expect == '\r' || expect == '\n');
|
||||
return this->sourceUnits.internalMatchCodeUnit(CharT(expect));
|
||||
}
|
||||
|
||||
template<typename T> bool matchCodeUnit(T) = delete;
|
||||
template<typename T> bool matchLineTerminator(T) = delete;
|
||||
|
||||
protected:
|
||||
int32_t peekCodeUnit() {
|
||||
return MOZ_LIKELY(!sourceUnits.atEnd()) ? CodeUnitValue(sourceUnits.peekCodeUnit()) : EOF;
|
||||
}
|
||||
|
||||
void consumeKnownCodeUnit(int32_t unit) {
|
||||
MOZ_ASSERT(unit != EOF, "shouldn't be matching EOF");
|
||||
MOZ_ASSERT(!sourceUnits.atEnd(), "must have units to consume");
|
||||
#ifdef DEBUG
|
||||
CharT next =
|
||||
#endif
|
||||
sourceUnits.getCodeUnit();
|
||||
MOZ_ASSERT(CodeUnitValue(next) == unit,
|
||||
"must be consuming the correct unit");
|
||||
}
|
||||
/** Consume a known, non-EOF code unit. */
|
||||
inline void consumeKnownCodeUnit(int32_t unit);
|
||||
|
||||
MOZ_MUST_USE inline bool
|
||||
fillCharBufferWithTemplateStringContents(const CharT* cur, const CharT* end);
|
||||
// Forbid accidental calls to consumeKnownCodeUnit *not* with the single
|
||||
// unit-or-EOF type. CharT should use SourceUnits::consumeKnownCodeUnit;
|
||||
// CodeUnitValue() results should go through toCharT(), or better yet just
|
||||
// use the original CharT.
|
||||
template<typename T> inline void consumeKnownCodeUnit(T) = delete;
|
||||
|
||||
/**
|
||||
* Accumulate the provided range of already-validated (i.e. valid UTF-8, or
|
||||
* anything if CharT is char16_t because JS permits lone and mispaired
|
||||
* surrogates) raw template literal text (i.e. containing no escapes or
|
||||
* substitutions) into |charBuffer|.
|
||||
*/
|
||||
MOZ_MUST_USE bool fillCharBufferWithTemplateStringContents(const CharT* cur, const CharT* end);
|
||||
|
||||
protected:
|
||||
/** Code units in the source code being tokenized. */
|
||||
|
@ -1235,6 +1318,13 @@ TokenStreamCharsBase<mozilla::Utf8Unit>::toCharT(int32_t value)
|
|||
return mozilla::Utf8Unit(static_cast<unsigned char>(value));
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
inline void
|
||||
TokenStreamCharsBase<CharT>::consumeKnownCodeUnit(int32_t unit)
|
||||
{
|
||||
sourceUnits.consumeKnownCodeUnit(toCharT(unit));
|
||||
}
|
||||
|
||||
template<>
|
||||
/* static */ MOZ_ALWAYS_INLINE JSAtom*
|
||||
TokenStreamCharsBase<char16_t>::atomizeSourceChars(JSContext* cx, const char16_t* chars,
|
||||
|
@ -1244,40 +1334,77 @@ TokenStreamCharsBase<char16_t>::atomizeSourceChars(JSContext* cx, const char16_t
|
|||
}
|
||||
|
||||
template<typename CharT>
|
||||
inline bool
|
||||
TokenStreamCharsBase<CharT>::matchCodeUnit(int32_t expect)
|
||||
{
|
||||
MOZ_ASSERT(expect != EOF, "shouldn't be matching EOFs");
|
||||
MOZ_ASSERT(!SourceUnits::isRawEOLChar(expect));
|
||||
return MOZ_LIKELY(!this->sourceUnits.atEnd()) &&
|
||||
this->sourceUnits.matchCodeUnit(toCharT(expect));
|
||||
}
|
||||
class SpecializedTokenStreamCharsBase;
|
||||
|
||||
template<>
|
||||
MOZ_MUST_USE inline bool
|
||||
TokenStreamCharsBase<char16_t>::fillCharBufferWithTemplateStringContents(const char16_t* cur,
|
||||
const char16_t* end)
|
||||
class SpecializedTokenStreamCharsBase<char16_t>
|
||||
: public TokenStreamCharsBase<char16_t>
|
||||
{
|
||||
MOZ_ASSERT(this->charBuffer.length() == 0);
|
||||
using CharsBase = TokenStreamCharsBase<char16_t>;
|
||||
|
||||
while (cur < end) {
|
||||
// U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are
|
||||
// interpreted literally inside template literal contents; only
|
||||
// literal CRLF sequences are normalized to '\n'. See
|
||||
// <https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv>.
|
||||
char16_t ch = *cur++;
|
||||
if (ch == '\r') {
|
||||
ch = '\n';
|
||||
if (cur < end && *cur == '\n')
|
||||
cur++;
|
||||
protected:
|
||||
using TokenStreamCharsShared::isAsciiCodePoint;
|
||||
// Deliberately don't |using| |sourceUnits| because of bug 1472569. :-(
|
||||
|
||||
using typename CharsBase::SourceUnits;
|
||||
|
||||
protected:
|
||||
// These APIs are only usable by UTF-16-specific code.
|
||||
|
||||
/**
|
||||
* Consume the rest of a single-line comment (but not the EOL/EOF that
|
||||
* terminates it) -- infallibly because no 16-bit code unit sequence in a
|
||||
* comment is an error.
|
||||
*/
|
||||
void infallibleConsumeRestOfSingleLineComment();
|
||||
|
||||
/**
|
||||
* Given |lead| already consumed, consume and return the code point encoded
|
||||
* starting from it. Infallible because lone surrogates in JS encode a
|
||||
* "code point" of the same value.
|
||||
*/
|
||||
char32_t infallibleGetNonAsciiCodePointDontNormalize(char16_t lead) {
|
||||
MOZ_ASSERT(!isAsciiCodePoint(lead));
|
||||
MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == lead);
|
||||
|
||||
// Handle single-unit code points and lone trailing surrogates.
|
||||
if (MOZ_LIKELY(!unicode::IsLeadSurrogate(lead)) ||
|
||||
// Or handle lead surrogates not paired with trailing surrogates.
|
||||
MOZ_UNLIKELY(this->sourceUnits.atEnd() ||
|
||||
!unicode::IsTrailSurrogate(this->sourceUnits.peekCodeUnit())))
|
||||
{
|
||||
return lead;
|
||||
}
|
||||
|
||||
if (!this->charBuffer.append(ch))
|
||||
return false;
|
||||
// Otherwise it's a multi-unit code point.
|
||||
return unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
protected:
|
||||
// These APIs are in both SpecializedTokenStreamCharsBase specializations
|
||||
// and so are usable in subclasses no matter what CharT is.
|
||||
|
||||
using CharsBase::CharsBase;
|
||||
};
|
||||
|
||||
template<>
|
||||
class SpecializedTokenStreamCharsBase<mozilla::Utf8Unit>
|
||||
: public TokenStreamCharsBase<mozilla::Utf8Unit>
|
||||
{
|
||||
using CharsBase = TokenStreamCharsBase<mozilla::Utf8Unit>;
|
||||
|
||||
protected:
|
||||
// Deliberately don't |using| |sourceUnits| because of bug 1472569. :-(
|
||||
|
||||
protected:
|
||||
// These APIs are only usable by UTF-8-specific code.
|
||||
|
||||
protected:
|
||||
// These APIs are in both SpecializedTokenStreamCharsBase specializations
|
||||
// and so are usable in subclasses no matter what CharT is.
|
||||
|
||||
using CharsBase::CharsBase;
|
||||
};
|
||||
|
||||
/** A small class encapsulating computation of the start-offset of a Token. */
|
||||
class TokenStart
|
||||
|
@ -1302,9 +1429,10 @@ class TokenStart
|
|||
|
||||
template<typename CharT, class AnyCharsAccess>
|
||||
class GeneralTokenStreamChars
|
||||
: public TokenStreamCharsBase<CharT>
|
||||
: public SpecializedTokenStreamCharsBase<CharT>
|
||||
{
|
||||
using CharsBase = TokenStreamCharsBase<CharT>;
|
||||
using SpecializedCharsBase = SpecializedTokenStreamCharsBase<CharT>;
|
||||
|
||||
Token* newTokenInternal(TokenKind kind, TokenStart start, TokenKind* out);
|
||||
|
||||
|
@ -1332,10 +1460,13 @@ class GeneralTokenStreamChars
|
|||
uint32_t matchExtendedUnicodeEscape(uint32_t* codePoint);
|
||||
|
||||
protected:
|
||||
using TokenStreamCharsShared::drainCharBufferIntoAtom;
|
||||
using CharsBase::fillCharBufferWithTemplateStringContents;
|
||||
|
||||
using typename CharsBase::SourceUnits;
|
||||
|
||||
protected:
|
||||
using CharsBase::CharsBase;
|
||||
using SpecializedCharsBase::SpecializedCharsBase;
|
||||
|
||||
TokenStreamAnyChars& anyCharsAccess() {
|
||||
return AnyCharsAccess::anyChars(this);
|
||||
|
@ -1417,18 +1548,34 @@ class GeneralTokenStreamChars
|
|||
CharsBase::ungetCodeUnit(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume code units til EOL/EOF following the start of a single-line
|
||||
* comment, without consuming the EOL/EOF.
|
||||
*/
|
||||
void consumeRestOfSingleLineComment();
|
||||
|
||||
MOZ_MUST_USE MOZ_ALWAYS_INLINE bool updateLineInfoForEOL() {
|
||||
return anyCharsAccess().internalUpdateLineInfoForEOL(this->sourceUnits.offset());
|
||||
}
|
||||
|
||||
uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint);
|
||||
bool matchUnicodeEscapeIdent(uint32_t* codePoint);
|
||||
|
||||
public:
|
||||
JSAtom* getRawTemplateStringAtom() {
|
||||
TokenStreamAnyChars& anyChars = anyCharsAccess();
|
||||
|
||||
MOZ_ASSERT(anyChars.currentToken().type == TokenKind::TemplateHead ||
|
||||
anyChars.currentToken().type == TokenKind::NoSubsTemplate);
|
||||
const CharT* cur = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1);
|
||||
const CharT* end;
|
||||
if (anyChars.currentToken().type == TokenKind::TemplateHead) {
|
||||
// Of the form |`...${| or |}...${|
|
||||
end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2);
|
||||
} else {
|
||||
// NO_SUBS_TEMPLATE is of the form |`...`| or |}...`|
|
||||
end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1);
|
||||
}
|
||||
|
||||
if (!fillCharBufferWithTemplateStringContents(cur, end))
|
||||
return nullptr;
|
||||
|
||||
return drainCharBufferIntoAtom(anyChars.cx);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename CharT, class AnyCharsAccess> class TokenStreamChars;
|
||||
|
@ -1439,6 +1586,7 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
{
|
||||
private:
|
||||
using CharsBase = TokenStreamCharsBase<char16_t>;
|
||||
using SpecializedCharsBase = SpecializedTokenStreamCharsBase<char16_t>;
|
||||
using GeneralCharsBase = GeneralTokenStreamChars<char16_t, AnyCharsAccess>;
|
||||
using Self = TokenStreamChars<char16_t, AnyCharsAccess>;
|
||||
|
||||
|
@ -1449,7 +1597,10 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
protected:
|
||||
using GeneralCharsBase::anyCharsAccess;
|
||||
using GeneralCharsBase::getCodeUnit;
|
||||
using SpecializedCharsBase::infallibleConsumeRestOfSingleLineComment;
|
||||
using SpecializedCharsBase::infallibleGetNonAsciiCodePointDontNormalize;
|
||||
using TokenStreamCharsShared::isAsciiCodePoint;
|
||||
using CharsBase::matchLineTerminator;
|
||||
// Deliberately don't |using| |sourceUnits| because of bug 1472569. :-(
|
||||
using GeneralCharsBase::ungetCodeUnit;
|
||||
using GeneralCharsBase::updateLineInfoForEOL;
|
||||
|
@ -1459,10 +1610,24 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
protected:
|
||||
using GeneralCharsBase::GeneralCharsBase;
|
||||
|
||||
// Try to get the next code point, normalizing '\r', '\r\n', '\n', and the
|
||||
// Unicode line/paragraph separators into '\n'. Also updates internal
|
||||
// line-counter state. Return true on success and store the code point in
|
||||
// |*c|. Return false and leave |*c| undefined on failure.
|
||||
/**
|
||||
* Given the non-ASCII |lead| code unit just consumed, consume and return a
|
||||
* complete non-ASCII code point. Line/column updates are not performed,
|
||||
* and line breaks are returned as-is without normalization.
|
||||
*/
|
||||
MOZ_MUST_USE bool getNonAsciiCodePointDontNormalize(char16_t lead, char32_t* codePoint) {
|
||||
// There are no encoding errors in 16-bit JS, so implement this so that
|
||||
// the compiler knows it, too.
|
||||
*codePoint = infallibleGetNonAsciiCodePointDontNormalize(lead);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next code point, converting LineTerminatorSequences to '\n' and
|
||||
* updating internal line-counter state if needed. Return true on success
|
||||
* and store the code point in |*c|. Return false and leave |*c| undefined
|
||||
* on failure.
|
||||
*/
|
||||
MOZ_MUST_USE bool getCodePoint(int32_t* cp);
|
||||
|
||||
/**
|
||||
|
@ -1483,9 +1648,7 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
"getFullAsciiCodePoint called incorrectly");
|
||||
|
||||
if (MOZ_UNLIKELY(lead == '\r')) {
|
||||
// NOTE: |this->|-qualify to avoid a gcc bug: see bug 1472569.
|
||||
if (MOZ_LIKELY(!this->sourceUnits.atEnd()))
|
||||
this->sourceUnits.matchCodeUnit('\n');
|
||||
matchLineTerminator('\n');
|
||||
} else if (MOZ_LIKELY(lead != '\n')) {
|
||||
*codePoint = lead;
|
||||
return true;
|
||||
|
@ -1503,16 +1666,17 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a just-consumed non-ASCII code unit (and maybe point) |lead|,
|
||||
* consume a full code point or LineTerminatorSequence (normalizing it to
|
||||
* '\n') and store it in |*codePoint|. Return true on success, otherwise
|
||||
* return false and leave |*codePoint| undefined on failure.
|
||||
* Given a just-consumed non-ASCII code unit |lead| (which may also be a
|
||||
* full code point, for UTF-16), consume a full code point or
|
||||
* LineTerminatorSequence (normalizing it to '\n') and store it in
|
||||
* |*codePoint|. Return true on success, otherwise return false and leave
|
||||
* |*codePoint| undefined on failure.
|
||||
*
|
||||
* If a LineTerminatorSequence was consumed, also update line/column info.
|
||||
*
|
||||
* This may change the current |sourceUnits| offset.
|
||||
*/
|
||||
MOZ_MUST_USE bool getNonAsciiCodePoint(int32_t lead, int32_t* cp);
|
||||
MOZ_MUST_USE bool getNonAsciiCodePoint(int32_t lead, int32_t* codePoint);
|
||||
|
||||
/**
|
||||
* Unget a full code point (ASCII or not) without altering line/column
|
||||
|
@ -1545,11 +1709,15 @@ class TokenStreamChars<char16_t, AnyCharsAccess>
|
|||
}
|
||||
|
||||
/**
|
||||
* Unget a just-gotten LineTerminator sequence: '\r', '\n', '\r\n', or
|
||||
* a Unicode line/paragraph separator, also undoing line/column information
|
||||
* changes reflecting that LineTerminator.
|
||||
* Consume code points til EOL/EOF following the start of a single-line
|
||||
* comment, without consuming the EOL/EOF.
|
||||
*/
|
||||
void ungetLineTerminator();
|
||||
MOZ_MUST_USE bool consumeRestOfSingleLineComment() {
|
||||
// This operation is infallible for UTF-16 -- and this implementation
|
||||
// approach lets the compiler boil away call-side fallibility handling.
|
||||
infallibleConsumeRestOfSingleLineComment();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// TokenStream is the lexical scanner for JavaScript source text.
|
||||
|
@ -1601,8 +1769,9 @@ class MOZ_STACK_CLASS TokenStreamSpecific
|
|||
{
|
||||
public:
|
||||
using CharsBase = TokenStreamCharsBase<CharT>;
|
||||
using SpecializedCharsBase = SpecializedTokenStreamCharsBase<CharT>;
|
||||
using GeneralCharsBase = GeneralTokenStreamChars<CharT, AnyCharsAccess>;
|
||||
using SpecializedCharsBase = TokenStreamChars<CharT, AnyCharsAccess>;
|
||||
using SpecializedChars = TokenStreamChars<CharT, AnyCharsAccess>;
|
||||
|
||||
using Position = TokenStreamPosition<CharT>;
|
||||
|
||||
|
@ -1629,16 +1798,18 @@ class MOZ_STACK_CLASS TokenStreamSpecific
|
|||
using GeneralCharsBase::badToken;
|
||||
// Deliberately don't |using| |charBuffer| because of bug 1472569. :-(
|
||||
using CharsBase::consumeKnownCodeUnit;
|
||||
using GeneralCharsBase::consumeRestOfSingleLineComment;
|
||||
using SpecializedChars::consumeRestOfSingleLineComment;
|
||||
using TokenStreamCharsShared::copyCharBufferTo;
|
||||
using TokenStreamCharsShared::drainCharBufferIntoAtom;
|
||||
using CharsBase::fillCharBufferWithTemplateStringContents;
|
||||
using SpecializedCharsBase::getCodePoint;
|
||||
using SpecializedChars::getCodePoint;
|
||||
using GeneralCharsBase::getCodeUnit;
|
||||
using SpecializedCharsBase::getFullAsciiCodePoint;
|
||||
using SpecializedCharsBase::getNonAsciiCodePoint;
|
||||
using SpecializedChars::getFullAsciiCodePoint;
|
||||
using SpecializedChars::getNonAsciiCodePoint;
|
||||
using SpecializedChars::getNonAsciiCodePointDontNormalize;
|
||||
using TokenStreamCharsShared::isAsciiCodePoint;
|
||||
using CharsBase::matchCodeUnit;
|
||||
using CharsBase::matchLineTerminator;
|
||||
using GeneralCharsBase::matchUnicodeEscapeIdent;
|
||||
using GeneralCharsBase::matchUnicodeEscapeIdStart;
|
||||
using GeneralCharsBase::newAtomToken;
|
||||
|
@ -1648,9 +1819,9 @@ class MOZ_STACK_CLASS TokenStreamSpecific
|
|||
using GeneralCharsBase::newSimpleToken;
|
||||
using CharsBase::peekCodeUnit;
|
||||
// Deliberately don't |using| |sourceUnits| because of bug 1472569. :-(
|
||||
using SpecializedCharsBase::ungetCodePointIgnoreEOL;
|
||||
using SpecializedChars::ungetCodePointIgnoreEOL;
|
||||
using GeneralCharsBase::ungetCodeUnit;
|
||||
using SpecializedCharsBase::ungetNonAsciiNormalizedCodePoint;
|
||||
using SpecializedChars::ungetNonAsciiNormalizedCodePoint;
|
||||
using GeneralCharsBase::updateLineInfoForEOL;
|
||||
|
||||
template<typename CharU> friend class TokenStreamPosition;
|
||||
|
@ -1738,27 +1909,6 @@ class MOZ_STACK_CLASS TokenStreamSpecific
|
|||
bool reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
|
||||
unsigned errorNumber, va_list* args);
|
||||
|
||||
JSAtom* getRawTemplateStringAtom() {
|
||||
TokenStreamAnyChars& anyChars = anyCharsAccess();
|
||||
|
||||
MOZ_ASSERT(anyChars.currentToken().type == TokenKind::TemplateHead ||
|
||||
anyChars.currentToken().type == TokenKind::NoSubsTemplate);
|
||||
const CharT* cur = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1);
|
||||
const CharT* end;
|
||||
if (anyChars.currentToken().type == TokenKind::TemplateHead) {
|
||||
// Of the form |`...${| or |}...${|
|
||||
end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2);
|
||||
} else {
|
||||
// NO_SUBS_TEMPLATE is of the form |`...`| or |}...`|
|
||||
end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1);
|
||||
}
|
||||
|
||||
if (!fillCharBufferWithTemplateStringContents(cur, end))
|
||||
return nullptr;
|
||||
|
||||
return drainCharBufferIntoAtom(anyChars.cx);
|
||||
}
|
||||
|
||||
private:
|
||||
// This is private because it should only be called by the tokenizer while
|
||||
// tokenizing not by, for example, BytecodeEmitter.
|
||||
|
|
|
@ -241,7 +241,7 @@ class FreeLists
|
|||
|
||||
inline void unmarkPreMarkedFreeCells(AllocKind kind);
|
||||
|
||||
const void* addressOfFreeList(AllocKind thingKind) const {
|
||||
FreeSpan** addressOfFreeList(AllocKind thingKind) {
|
||||
return &freeLists_[thingKind];
|
||||
}
|
||||
};
|
||||
|
@ -298,7 +298,7 @@ class ArenaLists
|
|||
FreeLists& freeLists() { return freeLists_.ref(); }
|
||||
const FreeLists& freeLists() const { return freeLists_.ref(); }
|
||||
|
||||
const void* addressOfFreeList(AllocKind thingKind) const {
|
||||
FreeSpan** addressOfFreeList(AllocKind thingKind) {
|
||||
return freeLists_.refNoCheck().addressOfFreeList(thingKind);
|
||||
}
|
||||
|
||||
|
|
|
@ -293,7 +293,9 @@ class GCRuntime
|
|||
void onOutOfMallocMemory(const AutoLockGC& lock);
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
const void* addressOfZealModeBits() { return &zealModeBits; }
|
||||
const uint32_t* addressOfZealModeBits() {
|
||||
return &zealModeBits.refNoCheck();
|
||||
}
|
||||
void getZealBits(uint32_t* zealBits, uint32_t* frequency, uint32_t* nextScheduled);
|
||||
void setZeal(uint8_t zeal, uint32_t frequency);
|
||||
bool parseAndSetZeal(const char* str);
|
||||
|
@ -1005,7 +1007,7 @@ class GCRuntime
|
|||
// after minor GC.
|
||||
MainThreadData<LifoAlloc> blocksToFreeAfterMinorGC;
|
||||
|
||||
const void* addressOfNurseryPosition() {
|
||||
void* addressOfNurseryPosition() {
|
||||
return nursery_.refNoCheck().addressOfPosition();
|
||||
}
|
||||
const void* addressOfNurseryCurrentEnd() {
|
||||
|
|
|
@ -324,9 +324,11 @@ class Nursery
|
|||
/* Print total profile times on shutdown. */
|
||||
void printTotalProfileTimes();
|
||||
|
||||
void* addressOfCurrentEnd() const { return (void*)¤tEnd_; }
|
||||
void* addressOfPosition() const { return (void*)&position_; }
|
||||
void* addressOfCurrentStringEnd() const { return (void*)¤tStringEnd_; }
|
||||
void* addressOfPosition() const { return (void**)&position_; }
|
||||
const void* addressOfCurrentEnd() const { return (void**)¤tEnd_; }
|
||||
const void* addressOfCurrentStringEnd() const {
|
||||
return (void*)¤tStringEnd_;
|
||||
}
|
||||
|
||||
void requestMinorGC(JS::gcreason::Reason reason) const;
|
||||
|
||||
|
|
|
@ -4049,8 +4049,9 @@ CodeGenerator::maybeEmitGlobalBarrierCheck(const LAllocation* maybeGlobal, OutOf
|
|||
if (gen->realm->maybeGlobal() != obj)
|
||||
return;
|
||||
|
||||
auto addr = AbsoluteAddress(gen->realm->addressOfGlobalWriteBarriered());
|
||||
masm.branch32(Assembler::NotEqual, addr, Imm32(0), ool->rejoin());
|
||||
const uint32_t* addr = gen->realm->addressOfGlobalWriteBarriered();
|
||||
masm.branch32(Assembler::NotEqual, AbsoluteAddress(addr), Imm32(0),
|
||||
ool->rejoin());
|
||||
}
|
||||
|
||||
template <class LPostBarrierType, MIRType nurseryType>
|
||||
|
@ -13286,7 +13287,8 @@ CodeGenerator::visitRandom(LRandom* ins)
|
|||
Register64 s1Reg(ToRegister(ins->temp3()), ToRegister(ins->temp4()));
|
||||
#endif
|
||||
|
||||
const void* rng = gen->realm->addressOfRandomNumberGenerator();
|
||||
const XorShift128PlusRNG* rng =
|
||||
gen->realm->addressOfRandomNumberGenerator();
|
||||
masm.movePtr(ImmPtr(rng), tempReg);
|
||||
|
||||
static_assert(sizeof(XorShift128PlusRNG) == 2 * sizeof(uint64_t),
|
||||
|
|
|
@ -28,7 +28,7 @@ CompileRuntime::get(JSRuntime* rt)
|
|||
}
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
const void*
|
||||
const uint32_t*
|
||||
CompileRuntime::addressOfGCZealModeBits()
|
||||
{
|
||||
return runtime()->gc.addressOfZealModeBits();
|
||||
|
@ -171,25 +171,25 @@ CompileZone::addressOfIonBailAfter()
|
|||
}
|
||||
#endif
|
||||
|
||||
const void*
|
||||
const uint32_t*
|
||||
CompileZone::addressOfNeedsIncrementalBarrier()
|
||||
{
|
||||
return zone()->addressOfNeedsIncrementalBarrier();
|
||||
}
|
||||
|
||||
const void*
|
||||
gc::FreeSpan**
|
||||
CompileZone::addressOfFreeList(gc::AllocKind allocKind)
|
||||
{
|
||||
return zone()->arenas.addressOfFreeList(allocKind);
|
||||
}
|
||||
|
||||
const void*
|
||||
void*
|
||||
CompileZone::addressOfNurseryPosition()
|
||||
{
|
||||
return zone()->runtimeFromAnyThread()->gc.addressOfNurseryPosition();
|
||||
}
|
||||
|
||||
const void*
|
||||
void*
|
||||
CompileZone::addressOfStringNurseryPosition()
|
||||
{
|
||||
// Objects and strings share a nursery, for now at least.
|
||||
|
@ -205,6 +205,12 @@ CompileZone::addressOfNurseryCurrentEnd()
|
|||
const void*
|
||||
CompileZone::addressOfStringNurseryCurrentEnd()
|
||||
{
|
||||
// Although objects and strings share a nursery (and this may change)
|
||||
// there is still a separate string end address. The only time it
|
||||
// is different from the regular end address, is when nursery strings are
|
||||
// disabled (it will be NULL).
|
||||
//
|
||||
// This function returns _a pointer to_ that end address.
|
||||
return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd();
|
||||
}
|
||||
|
||||
|
@ -254,7 +260,7 @@ CompileRealm::runtime()
|
|||
return CompileRuntime::get(realm()->runtimeFromAnyThread());
|
||||
}
|
||||
|
||||
const void*
|
||||
const mozilla::non_crypto::XorShift128PlusRNG*
|
||||
CompileRealm::addressOfRandomNumberGenerator()
|
||||
{
|
||||
return realm()->addressOfRandomNumberGenerator();
|
||||
|
|
|
@ -28,7 +28,7 @@ class CompileRuntime
|
|||
static CompileRuntime* get(JSRuntime* rt);
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
const void* addressOfGCZealModeBits();
|
||||
const uint32_t* addressOfGCZealModeBits();
|
||||
#endif
|
||||
|
||||
const JitRuntime* jitRuntime();
|
||||
|
@ -75,10 +75,10 @@ class CompileZone
|
|||
const void* addressOfIonBailAfter();
|
||||
#endif
|
||||
|
||||
const void* addressOfNeedsIncrementalBarrier();
|
||||
const void* addressOfFreeList(gc::AllocKind allocKind);
|
||||
const void* addressOfNurseryPosition();
|
||||
const void* addressOfStringNurseryPosition();
|
||||
const uint32_t* addressOfNeedsIncrementalBarrier();
|
||||
gc::FreeSpan** addressOfFreeList(gc::AllocKind allocKind);
|
||||
void* addressOfNurseryPosition();
|
||||
void* addressOfStringNurseryPosition();
|
||||
const void* addressOfNurseryCurrentEnd();
|
||||
const void* addressOfStringNurseryCurrentEnd();
|
||||
|
||||
|
@ -103,7 +103,8 @@ class CompileRealm
|
|||
return realm();
|
||||
}
|
||||
|
||||
const void* addressOfRandomNumberGenerator();
|
||||
const mozilla::non_crypto::XorShift128PlusRNG*
|
||||
addressOfRandomNumberGenerator();
|
||||
|
||||
const JitRealm* jitRealm();
|
||||
|
||||
|
|
|
@ -637,8 +637,8 @@ MacroAssembler::branchTestNeedsIncrementalBarrier(Condition cond, Label* label)
|
|||
{
|
||||
MOZ_ASSERT(cond == Zero || cond == NonZero);
|
||||
CompileZone* zone = GetJitContext()->realm->zone();
|
||||
AbsoluteAddress needsBarrierAddr(zone->addressOfNeedsIncrementalBarrier());
|
||||
branchTest32(cond, needsBarrierAddr, Imm32(0x1), label);
|
||||
const uint32_t* needsBarrierAddr = zone->addressOfNeedsIncrementalBarrier();
|
||||
branchTest32(cond, AbsoluteAddress(needsBarrierAddr), Imm32(0x1), label);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -818,9 +818,10 @@ MacroAssembler::checkAllocatorState(Label* fail)
|
|||
|
||||
#ifdef JS_GC_ZEAL
|
||||
// Don't execute the inline path if gc zeal or tracing are active.
|
||||
branch32(Assembler::NotEqual,
|
||||
AbsoluteAddress(GetJitContext()->runtime->addressOfGCZealModeBits()), Imm32(0),
|
||||
fail);
|
||||
const uint32_t *ptrZealModeBits =
|
||||
GetJitContext()->runtime->addressOfGCZealModeBits();
|
||||
branch32(Assembler::NotEqual, AbsoluteAddress(ptrZealModeBits), Imm32(0),
|
||||
fail);
|
||||
#endif
|
||||
|
||||
// Don't execute the inline path if the realm has an object metadata callback,
|
||||
|
@ -863,10 +864,13 @@ MacroAssembler::nurseryAllocateObject(Register result, Register temp, gc::AllocK
|
|||
int thingSize = int(gc::Arena::thingSize(allocKind));
|
||||
int totalSize = thingSize + nDynamicSlots * sizeof(HeapSlot);
|
||||
MOZ_ASSERT(totalSize % gc::CellAlignBytes == 0);
|
||||
loadPtr(AbsoluteAddress(zone->addressOfNurseryPosition()), result);
|
||||
void *ptrNurseryPosition = zone->addressOfNurseryPosition();
|
||||
loadPtr(AbsoluteAddress(ptrNurseryPosition), result);
|
||||
computeEffectiveAddress(Address(result, totalSize), temp);
|
||||
branchPtr(Assembler::Below, AbsoluteAddress(zone->addressOfNurseryCurrentEnd()), temp, fail);
|
||||
storePtr(temp, AbsoluteAddress(zone->addressOfNurseryPosition()));
|
||||
const void *ptrNurseryCurrentEnd = zone->addressOfNurseryCurrentEnd();
|
||||
branchPtr(Assembler::Below, AbsoluteAddress(ptrNurseryCurrentEnd), temp,
|
||||
fail);
|
||||
storePtr(temp, AbsoluteAddress(ptrNurseryPosition));
|
||||
|
||||
if (nDynamicSlots) {
|
||||
computeEffectiveAddress(Address(result, thingSize), temp);
|
||||
|
@ -886,14 +890,15 @@ MacroAssembler::freeListAllocate(Register result, Register temp, gc::AllocKind a
|
|||
|
||||
// Load the first and last offsets of |zone|'s free list for |allocKind|.
|
||||
// If there is no room remaining in the span, fall back to get the next one.
|
||||
loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp);
|
||||
gc::FreeSpan **ptrFreeList = zone->addressOfFreeList(allocKind);
|
||||
loadPtr(AbsoluteAddress(ptrFreeList), temp);
|
||||
load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfFirst()), result);
|
||||
load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfLast()), temp);
|
||||
branch32(Assembler::AboveOrEqual, result, temp, &fallback);
|
||||
|
||||
// Bump the offset for the next allocation.
|
||||
add32(Imm32(thingSize), result);
|
||||
loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp);
|
||||
loadPtr(AbsoluteAddress(ptrFreeList), temp);
|
||||
store16(result, Address(temp, js::gc::FreeSpan::offsetOfFirst()));
|
||||
sub32(Imm32(thingSize), result);
|
||||
addPtr(temp, result); // Turn the offset into a pointer.
|
||||
|
@ -904,7 +909,7 @@ MacroAssembler::freeListAllocate(Register result, Register temp, gc::AllocKind a
|
|||
// interpreter will call the GC allocator to set up a new arena to allocate
|
||||
// from, after which we can resume allocating in the jit.
|
||||
branchTest32(Assembler::Zero, result, result, fail);
|
||||
loadPtr(AbsoluteAddress(zone->addressOfFreeList(allocKind)), temp);
|
||||
loadPtr(AbsoluteAddress(ptrFreeList), temp);
|
||||
addPtr(temp, result); // Turn the offset into a pointer.
|
||||
Push(result);
|
||||
// Update the free list to point to the next span (which may be empty).
|
||||
|
|
|
@ -60,11 +60,11 @@
|
|||
// - If the declaration is "inline", then the method definition(s) would be in
|
||||
// the "-inl.h" variant of the same file(s).
|
||||
//
|
||||
// The script check_macroassembler_style.py (check-masm target of the Makefile)
|
||||
// is used to verify that method definitions are matching the annotation added
|
||||
// to the method declarations. If there is any difference, then you either
|
||||
// forgot to define the method in one of the macro assembler, or you forgot to
|
||||
// update the annotation of the macro assembler declaration.
|
||||
// The script check_macroassembler_style.py (which runs on every build) is
|
||||
// used to verify that method definitions match the annotation on the method
|
||||
// declarations. If there is any difference, then you either forgot to define
|
||||
// the method in one of the macro assembler, or you forgot to update the
|
||||
// annotation of the macro assembler declaration.
|
||||
//
|
||||
// Some convenient short-cuts are used to avoid repeating the same list of
|
||||
// architectures on each method declaration, such as PER_ARCH and
|
||||
|
@ -79,7 +79,7 @@
|
|||
// inline uint32_t framePushed() const OOL_IN_HEADER;
|
||||
//
|
||||
// Such functions should then be defined immediately after MacroAssembler's
|
||||
// definition, for example like so:
|
||||
// definition, for example:
|
||||
//
|
||||
// //{{{ check_macroassembler_style
|
||||
// inline uint32_t
|
||||
|
@ -100,10 +100,10 @@
|
|||
//
|
||||
// For each architecture, we have a macro named DEFINED_ON_arch. This macro is
|
||||
// empty if this is not the current architecture. Otherwise it must be either
|
||||
// set to "define" or "crash" (only use for the none target so-far).
|
||||
// set to "define" or "crash" (only used for the none target so far).
|
||||
//
|
||||
// The DEFINED_ON macro maps the list of architecture names given as argument to
|
||||
// a list of macro names. For example,
|
||||
// The DEFINED_ON macro maps the list of architecture names given as arguments
|
||||
// to a list of macro names. For example,
|
||||
//
|
||||
// DEFINED_ON(arm, x86_shared)
|
||||
//
|
||||
|
@ -126,7 +126,7 @@
|
|||
// architecture if it is listed in the arguments of DEFINED_ON.
|
||||
//
|
||||
// This result is appended to DEFINED_ON_RESULT_ before expanding the macro,
|
||||
// which result is either no annotation, a MOZ_CRASH(), or a "= delete"
|
||||
// which results in either no annotation, a MOZ_CRASH(), or a "= delete"
|
||||
// annotation on the method declaration.
|
||||
|
||||
# define DEFINED_ON_x86
|
||||
|
|
|
@ -219,6 +219,7 @@ UNIFIED_SOURCES += [
|
|||
'frontend/JumpList.cpp',
|
||||
'frontend/NameFunctions.cpp',
|
||||
'frontend/ParseNode.cpp',
|
||||
'frontend/SwitchEmitter.cpp',
|
||||
'frontend/TDZCheckCache.cpp',
|
||||
'frontend/TokenStream.cpp',
|
||||
'frontend/TryEmitter.cpp',
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
if (!this.oomTest) {
|
||||
this.reportCompare && reportCompare(true, true);
|
||||
quit(0);
|
||||
}
|
||||
|
||||
let lfPreamble = `
|
||||
`;
|
||||
oomTest(new Function(`var TOTAL_MEMORY = 50 * 1024 * 1024;
|
||||
HEAP = IHEAP = new Int32Array(TOTAL_MEMORY);
|
||||
function __Z9makeFastaI10RandomizedEvPKcS2_jRT_(\$id, \$desc, \$N, \$output)
|
||||
{
|
||||
}
|
||||
`));
|
||||
|
||||
this.reportCompare && reportCompare(true, true);
|
|
@ -177,7 +177,7 @@ struct JSContext : public JS::RootingContext,
|
|||
*/
|
||||
template <typename T>
|
||||
T* pod_callocCanGC(size_t numElems, arena_id_t arena = js::MallocArena) {
|
||||
T* p = pod_calloc<T>(numElems, arena);
|
||||
T* p = maybe_pod_calloc<T>(numElems, arena);
|
||||
if (MOZ_LIKELY(!!p))
|
||||
return p;
|
||||
size_t bytes;
|
||||
|
|
|
@ -812,7 +812,8 @@ class JS::Realm : public JS::shadow::Realm
|
|||
// Initializes randomNumberGenerator if needed.
|
||||
mozilla::non_crypto::XorShift128PlusRNG& getOrCreateRandomNumberGenerator();
|
||||
|
||||
const void* addressOfRandomNumberGenerator() const {
|
||||
const mozilla::non_crypto::XorShift128PlusRNG*
|
||||
addressOfRandomNumberGenerator() const {
|
||||
return randomNumberGenerator_.ptr();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* 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/. */
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/TextUtils.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/Utf8.h"
|
||||
|
||||
|
@ -14,66 +16,24 @@ MFBT_API bool
|
|||
mozilla::IsValidUtf8(const void* aCodeUnits, size_t aCount)
|
||||
{
|
||||
const auto* s = static_cast<const unsigned char*>(aCodeUnits);
|
||||
const auto* limit = s + aCount;
|
||||
const auto* const limit = s + aCount;
|
||||
|
||||
while (s < limit) {
|
||||
uint32_t n = *s++;
|
||||
unsigned char c = *s++;
|
||||
|
||||
// If the first byte is ASCII, it's the only one in the code point. Have a
|
||||
// fast path that avoids all the rest of the work and looping in that case.
|
||||
if ((n & 0x80) == 0) {
|
||||
if (IsAscii(c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The leading code unit determines the length of the next code point and
|
||||
// the number of bits of the leading code unit that contribute to the code
|
||||
// point's value.
|
||||
uint_fast8_t remaining;
|
||||
uint32_t min;
|
||||
if ((n & 0xE0) == 0xC0) {
|
||||
remaining = 1;
|
||||
min = 0x80;
|
||||
n &= 0x1F;
|
||||
} else if ((n & 0xF0) == 0xE0) {
|
||||
remaining = 2;
|
||||
min = 0x800;
|
||||
n &= 0x0F;
|
||||
} else if ((n & 0xF8) == 0xF0) {
|
||||
remaining = 3;
|
||||
min = 0x10000;
|
||||
n &= 0x07;
|
||||
} else {
|
||||
// UTF-8 used to have a hyper-long encoding form, but it's been removed
|
||||
// for years now. So in this case, the string is not valid UTF-8.
|
||||
Maybe<char32_t> maybeCodePoint =
|
||||
DecodeOneUtf8CodePoint(Utf8Unit(c), &s, limit);
|
||||
if (maybeCodePoint.isNothing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the code point would require more code units than remain, the encoding
|
||||
// is invalid.
|
||||
if (s + remaining > limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint_fast8_t i = 0; i < remaining; i++) {
|
||||
// Every non-leading code unit in properly encoded UTF-8 has its high bit
|
||||
// set and the next-highest bit unset.
|
||||
if ((s[i] & 0xC0) != 0x80) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The code point being encoded is the concatenation of all the
|
||||
// unconstrained bits.
|
||||
n = (n << 6) | (s[i] & 0x3F);
|
||||
}
|
||||
|
||||
// Don't consider code points that are overlong, UTF-16 surrogates, or
|
||||
// exceed the maximum code point to be valid.
|
||||
if (n < min || (0xD800 <= n && n < 0xE000) || n >= 0x110000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s += remaining;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(s == limit);
|
||||
return true;
|
||||
}
|
||||
|
|
215
mfbt/Utf8.h
215
mfbt/Utf8.h
|
@ -12,6 +12,10 @@
|
|||
#ifndef mozilla_Utf8_h
|
||||
#define mozilla_Utf8_h
|
||||
|
||||
#include "mozilla/Casting.h" // for mozilla::AssertedCast
|
||||
#include "mozilla/Likely.h" // for MOZ_UNLIKELY
|
||||
#include "mozilla/Maybe.h" // for mozilla::Maybe
|
||||
#include "mozilla/TextUtils.h" // for mozilla::IsAscii
|
||||
#include "mozilla/Types.h" // for MFBT_API
|
||||
|
||||
#include <limits.h> // for CHAR_BIT
|
||||
|
@ -205,6 +209,217 @@ public:
|
|||
extern MFBT_API bool
|
||||
IsValidUtf8(const void* aCodeUnits, size_t aCount);
|
||||
|
||||
/**
|
||||
* Given |aLeadUnit| that is a non-ASCII code unit, a pointer to an |Iter aIter|
|
||||
* that (initially) itself points one unit past |aLeadUnit|, and
|
||||
* |const EndIter aEnd| that denotes the end of the UTF-8 data when compared
|
||||
* against |*aIter| using |aEnd - *aIter|:
|
||||
*
|
||||
* If |aLeadUnit| and subsequent code units computed using |*aIter| (up to
|
||||
* |aEnd|) encode a valid code point -- not exceeding Unicode's range, not a
|
||||
* surrogate, in shortest form -- then return Some(that code point) and advance
|
||||
* |*aIter| past those code units.
|
||||
*
|
||||
* Otherwise decrement |*aIter| (so that it points at |aLeadUnit|) and return
|
||||
* Nothing().
|
||||
*
|
||||
* |Iter| and |EndIter| are generalized concepts most easily understood as if
|
||||
* they were |const char*|, |const unsigned char*|, or |const Utf8Unit*|:
|
||||
* iterators that when dereferenced can be used to construct a |Utf8Unit| and
|
||||
* that can be compared and modified in certain limited ways. (Carefully note
|
||||
* that this function mutates |*aIter|.) |Iter| and |EndIter| are template
|
||||
* parameters to support more-complicated adaptor iterators.
|
||||
*
|
||||
* The template parameters after |Iter| allow users to implement custom handling
|
||||
* for various forms of invalid UTF-8. A version of this function that defaults
|
||||
* all such handling to no-ops is defined below this function. To learn how to
|
||||
* define your own custom handling, consult the implementation of that function,
|
||||
* which documents exactly how custom handler functors are invoked.
|
||||
*
|
||||
* This function is MOZ_ALWAYS_INLINE: if you don't need that, use the version
|
||||
* of this function without the "Inline" suffix on the name.
|
||||
*/
|
||||
template<typename Iter,
|
||||
typename EndIter,
|
||||
class OnBadLeadUnit,
|
||||
class OnNotEnoughUnits,
|
||||
class OnBadTrailingUnit,
|
||||
class OnBadCodePoint,
|
||||
class OnNotShortestForm>
|
||||
MOZ_ALWAYS_INLINE Maybe<char32_t>
|
||||
DecodeOneUtf8CodePointInline(const Utf8Unit aLeadUnit,
|
||||
Iter* aIter, const EndIter aEnd,
|
||||
OnBadLeadUnit aOnBadLeadUnit,
|
||||
OnNotEnoughUnits aOnNotEnoughUnits,
|
||||
OnBadTrailingUnit aOnBadTrailingUnit,
|
||||
OnBadCodePoint aOnBadCodePoint,
|
||||
OnNotShortestForm aOnNotShortestForm)
|
||||
{
|
||||
MOZ_ASSERT(Utf8Unit((*aIter)[-1]) == aLeadUnit);
|
||||
|
||||
char32_t n = aLeadUnit.toUint8();
|
||||
MOZ_ASSERT(!IsAscii(n));
|
||||
|
||||
// |aLeadUnit| determines the number of trailing code units in the code point
|
||||
// and the bits of |aLeadUnit| that contribute to the code point's value.
|
||||
uint8_t remaining;
|
||||
uint32_t min;
|
||||
if ((n & 0b1110'0000) == 0b1100'0000) {
|
||||
remaining = 1;
|
||||
min = 0x80;
|
||||
n &= 0b0001'1111;
|
||||
} else if ((n & 0b1111'0000) == 0b1110'0000) {
|
||||
remaining = 2;
|
||||
min = 0x800;
|
||||
n &= 0b0000'1111;
|
||||
} else if ((n & 0b1111'1000) == 0b1111'0000) {
|
||||
remaining = 3;
|
||||
min = 0x10000;
|
||||
n &= 0b0000'0111;
|
||||
} else {
|
||||
*aIter -= 1;
|
||||
aOnBadLeadUnit();
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// If the code point would require more code units than remain, the encoding
|
||||
// is invalid.
|
||||
auto actual = aEnd - *aIter;
|
||||
if (MOZ_UNLIKELY(actual < remaining)) {
|
||||
*aIter -= 1;
|
||||
aOnNotEnoughUnits(AssertedCast<uint8_t>(actual + 1), remaining + 1);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < remaining; i++) {
|
||||
uint8_t unit = Utf8Unit(*(*aIter)++).toUint8();
|
||||
|
||||
// Every non-leading code unit in properly encoded UTF-8 has its high
|
||||
// bit set and the next-highest bit unset.
|
||||
if (MOZ_UNLIKELY((unit & 0b1100'0000) != 0b1000'0000)) {
|
||||
uint8_t unitsObserved = i + 1 + 1;
|
||||
*aIter -= unitsObserved;
|
||||
aOnBadTrailingUnit(unitsObserved);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// The code point being encoded is the concatenation of all the
|
||||
// unconstrained bits.
|
||||
n = (n << 6) | (unit & 0b0011'1111);
|
||||
}
|
||||
|
||||
// UTF-16 surrogates and values outside the Unicode range are invalid.
|
||||
if (MOZ_UNLIKELY(n > 0x10FFFF || (0xD800 <= n && n <= 0xDFFF))) {
|
||||
uint8_t unitsObserved = remaining + 1;
|
||||
*aIter -= unitsObserved;
|
||||
aOnBadCodePoint(n, unitsObserved);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// Overlong code points are also invalid.
|
||||
if (MOZ_UNLIKELY(n < min)) {
|
||||
uint8_t unitsObserved = remaining + 1;
|
||||
*aIter -= unitsObserved;
|
||||
aOnNotShortestForm(n, unitsObserved);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to the above function, but not forced to be instantiated inline --
|
||||
* the compiler is permitted to common up separate invocations if it chooses.
|
||||
*/
|
||||
template<typename Iter,
|
||||
typename EndIter,
|
||||
class OnBadLeadUnit,
|
||||
class OnNotEnoughUnits,
|
||||
class OnBadTrailingUnit,
|
||||
class OnBadCodePoint,
|
||||
class OnNotShortestForm>
|
||||
inline Maybe<char32_t>
|
||||
DecodeOneUtf8CodePoint(const Utf8Unit aLeadUnit,
|
||||
Iter* aIter, const EndIter aEnd,
|
||||
OnBadLeadUnit aOnBadLeadUnit,
|
||||
OnNotEnoughUnits aOnNotEnoughUnits,
|
||||
OnBadTrailingUnit aOnBadTrailingUnit,
|
||||
OnBadCodePoint aOnBadCodePoint,
|
||||
OnNotShortestForm aOnNotShortestForm)
|
||||
{
|
||||
return DecodeOneUtf8CodePointInline(aLeadUnit, aIter, aEnd,
|
||||
aOnBadLeadUnit, aOnNotEnoughUnits,
|
||||
aOnBadTrailingUnit, aOnBadCodePoint,
|
||||
aOnNotShortestForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the always-inlined function above, but with no-op behavior from all
|
||||
* trailing if-invalid notifier functors.
|
||||
*
|
||||
* This function is MOZ_ALWAYS_INLINE: if you don't need that, use the version
|
||||
* of this function without the "Inline" suffix on the name.
|
||||
*/
|
||||
template<typename Iter, typename EndIter>
|
||||
MOZ_ALWAYS_INLINE Maybe<char32_t>
|
||||
DecodeOneUtf8CodePointInline(const Utf8Unit aLeadUnit,
|
||||
Iter* aIter, const EndIter aEnd)
|
||||
{
|
||||
// aOnBadLeadUnit is called when |aLeadUnit| itself is an invalid lead unit in
|
||||
// a multi-unit code point. It is passed no arguments: the caller already has
|
||||
// |aLeadUnit| on hand, so no need to provide it again.
|
||||
auto onBadLeadUnit = []() {};
|
||||
|
||||
// aOnNotEnoughUnits is called when |aLeadUnit| properly indicates a code
|
||||
// point length, but there aren't enough units from |*aIter| to |aEnd| to
|
||||
// satisfy that length. It is passed the number of code units actually
|
||||
// available (according to |aEnd - *aIter|) and the number of code units that
|
||||
// |aLeadUnit| indicates are needed. Both numbers include the contribution
|
||||
// of |aLeadUnit| itself: so |aUnitsAvailable <= 3|, |aUnitsNeeded <= 4|, and
|
||||
// |aUnitsAvailable < aUnitsNeeded|. As above, it also is not passed the lead
|
||||
// code unit.
|
||||
auto onNotEnoughUnits = [](uint8_t aUnitsAvailable, uint8_t aUnitsNeeded) {};
|
||||
|
||||
// aOnBadTrailingUnit is called when one of the trailing code units implied by
|
||||
// |aLeadUnit| doesn't match the 0b10xx'xxxx bit pattern that all UTF-8
|
||||
// trailing code units must satisfy. It is passed the total count of units
|
||||
// observed (including |aLeadUnit|). The bad trailing code unit will
|
||||
// conceptually be at |(*aIter)[aUnitsObserved - 1]| if this functor is
|
||||
// called, and so |aUnitsObserved <= 4|.
|
||||
auto onBadTrailingUnit = [](uint8_t aUnitsObserved) {};
|
||||
|
||||
// aOnBadCodePoint is called when a structurally-correct code point encoding
|
||||
// is found, but the *value* that is encoded is not a valid code point: either
|
||||
// because it exceeded the U+10FFFF Unicode maximum code point, or because it
|
||||
// was a UTF-16 surrogate. It is passed the non-code point value and the
|
||||
// number of code units used to encode it.
|
||||
auto onBadCodePoint = [](char32_t aBadCodePoint, uint8_t aUnitsObserved) {};
|
||||
|
||||
// aOnNotShortestForm is called when structurally-correct encoding is found,
|
||||
// but the encoded value should have been encoded in fewer code units (e.g.
|
||||
// mis-encoding U+0000 as 0b1100'0000 0b1000'0000 in two code units instead of
|
||||
// as 0b0000'0000). It is passed the mis-encoded code point (which will be
|
||||
// valid and not a surrogate) and the count of code units that mis-encoded it.
|
||||
auto onNotShortestForm = [](char32_t aBadCodePoint, uint8_t aUnitsObserved) {};
|
||||
|
||||
return DecodeOneUtf8CodePointInline(aLeadUnit, aIter, aEnd,
|
||||
onBadLeadUnit, onNotEnoughUnits,
|
||||
onBadTrailingUnit, onBadCodePoint,
|
||||
onNotShortestForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to the above function, but not forced to be instantiated inline --
|
||||
* the compiler/linker are allowed to common up separate invocations.
|
||||
*/
|
||||
template<typename Iter, typename EndIter>
|
||||
inline Maybe<char32_t>
|
||||
DecodeOneUtf8CodePoint(const Utf8Unit aLeadUnit,
|
||||
Iter* aIter, const EndIter aEnd)
|
||||
{
|
||||
return DecodeOneUtf8CodePointInline(aLeadUnit, aIter, aEnd);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_Utf8_h */
|
||||
|
|
|
@ -8,8 +8,15 @@
|
|||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EnumSet.h"
|
||||
#include "mozilla/IntegerRange.h"
|
||||
#include "mozilla/TextUtils.h"
|
||||
|
||||
using mozilla::ArrayLength;
|
||||
using mozilla::DecodeOneUtf8CodePoint;
|
||||
using mozilla::EnumSet;
|
||||
using mozilla::IntegerRange;
|
||||
using mozilla::IsAscii;
|
||||
using mozilla::IsValidUtf8;
|
||||
using mozilla::Utf8Unit;
|
||||
|
||||
|
@ -35,6 +42,242 @@ TestUtf8Unit()
|
|||
MOZ_RELEASE_ASSERT(first == second);
|
||||
}
|
||||
|
||||
template<typename Char>
|
||||
struct ToUtf8Units
|
||||
{
|
||||
public:
|
||||
explicit ToUtf8Units(const Char* aStart, const Char* aEnd)
|
||||
: lead(Utf8Unit(aStart[0]))
|
||||
, iter(aStart + 1)
|
||||
, end(aEnd)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!IsAscii(aStart[0]));
|
||||
}
|
||||
|
||||
const Utf8Unit lead;
|
||||
const Char* iter;
|
||||
const Char* const end;
|
||||
};
|
||||
|
||||
class AssertIfCalled
|
||||
{
|
||||
public:
|
||||
template<typename... Args>
|
||||
void operator()(Args&&... aArgs) {
|
||||
MOZ_RELEASE_ASSERT(false, "AssertIfCalled instance was called");
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: For simplicity in treating |aCharN| identically regardless whether it's
|
||||
// a string literal or a more-generalized array, we require |aCharN| be
|
||||
// null-terminated.
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectValidCodePoint(const Char (&aCharN)[N],
|
||||
char32_t aExpectedCodePoint)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(aCharN[N - 1] == 0,
|
||||
"array must be null-terminated for |aCharN + N - 1| to "
|
||||
"compute the value of |aIter| as altered by "
|
||||
"DecodeOneUtf8CodePoint");
|
||||
|
||||
ToUtf8Units<Char> simpleUnit(aCharN, aCharN + N - 1);
|
||||
auto simple =
|
||||
DecodeOneUtf8CodePoint(simpleUnit.lead, &simpleUnit.iter, simpleUnit.end);
|
||||
MOZ_RELEASE_ASSERT(simple.isSome());
|
||||
MOZ_RELEASE_ASSERT(*simple == aExpectedCodePoint);
|
||||
MOZ_RELEASE_ASSERT(simpleUnit.iter == simpleUnit.end);
|
||||
|
||||
ToUtf8Units<Char> complexUnit(aCharN, aCharN + N - 1);
|
||||
auto complex =
|
||||
DecodeOneUtf8CodePoint(complexUnit.lead, &complexUnit.iter, complexUnit.end,
|
||||
AssertIfCalled(),
|
||||
AssertIfCalled(),
|
||||
AssertIfCalled(),
|
||||
AssertIfCalled(),
|
||||
AssertIfCalled());
|
||||
MOZ_RELEASE_ASSERT(complex.isSome());
|
||||
MOZ_RELEASE_ASSERT(*complex == aExpectedCodePoint);
|
||||
MOZ_RELEASE_ASSERT(complexUnit.iter == complexUnit.end);
|
||||
}
|
||||
|
||||
enum class InvalidUtf8Reason
|
||||
{
|
||||
BadLeadUnit,
|
||||
NotEnoughUnits,
|
||||
BadTrailingUnit,
|
||||
BadCodePoint,
|
||||
NotShortestForm,
|
||||
};
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectInvalidCodePointHelper(const Char (&aCharN)[N],
|
||||
InvalidUtf8Reason aExpectedReason,
|
||||
uint8_t aExpectedUnitsAvailable,
|
||||
uint8_t aExpectedUnitsNeeded,
|
||||
char32_t aExpectedBadCodePoint,
|
||||
uint8_t aExpectedUnitsObserved)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(aCharN[N - 1] == 0,
|
||||
"array must be null-terminated for |aCharN + N - 1| to "
|
||||
"compute the value of |aIter| as altered by "
|
||||
"DecodeOneUtf8CodePoint");
|
||||
|
||||
ToUtf8Units<Char> simpleUnit(aCharN, aCharN + N - 1);
|
||||
auto simple =
|
||||
DecodeOneUtf8CodePoint(simpleUnit.lead, &simpleUnit.iter, simpleUnit.end);
|
||||
MOZ_RELEASE_ASSERT(simple.isNothing());
|
||||
MOZ_RELEASE_ASSERT(static_cast<const void*>(simpleUnit.iter) == aCharN);
|
||||
|
||||
EnumSet<InvalidUtf8Reason> reasons;
|
||||
uint8_t unitsAvailable;
|
||||
uint8_t unitsNeeded;
|
||||
char32_t badCodePoint;
|
||||
uint8_t unitsObserved;
|
||||
|
||||
struct OnNotShortestForm
|
||||
{
|
||||
EnumSet<InvalidUtf8Reason>& reasons;
|
||||
char32_t& badCodePoint;
|
||||
uint8_t& unitsObserved;
|
||||
|
||||
void operator()(char32_t aBadCodePoint, uint8_t aUnitsObserved) {
|
||||
reasons += InvalidUtf8Reason::NotShortestForm;
|
||||
badCodePoint = aBadCodePoint;
|
||||
unitsObserved = aUnitsObserved;
|
||||
}
|
||||
};
|
||||
|
||||
ToUtf8Units<Char> complexUnit(aCharN, aCharN + N - 1);
|
||||
auto complex =
|
||||
DecodeOneUtf8CodePoint(complexUnit.lead, &complexUnit.iter, complexUnit.end,
|
||||
[&reasons]() {
|
||||
reasons += InvalidUtf8Reason::BadLeadUnit;
|
||||
},
|
||||
[&reasons, &unitsAvailable, &unitsNeeded](uint8_t aUnitsAvailable,
|
||||
uint8_t aUnitsNeeded)
|
||||
{
|
||||
reasons += InvalidUtf8Reason::NotEnoughUnits;
|
||||
unitsAvailable = aUnitsAvailable;
|
||||
unitsNeeded = aUnitsNeeded;
|
||||
},
|
||||
[&reasons, &unitsObserved](uint8_t aUnitsObserved)
|
||||
{
|
||||
reasons += InvalidUtf8Reason::BadTrailingUnit;
|
||||
unitsObserved = aUnitsObserved;
|
||||
},
|
||||
[&reasons, &badCodePoint, &unitsObserved](char32_t aBadCodePoint,
|
||||
uint8_t aUnitsObserved)
|
||||
{
|
||||
reasons += InvalidUtf8Reason::BadCodePoint;
|
||||
badCodePoint = aBadCodePoint;
|
||||
unitsObserved = aUnitsObserved;
|
||||
},
|
||||
[&reasons, &badCodePoint, &unitsObserved](char32_t aBadCodePoint,
|
||||
uint8_t aUnitsObserved)
|
||||
{
|
||||
reasons += InvalidUtf8Reason::NotShortestForm;
|
||||
badCodePoint = aBadCodePoint;
|
||||
unitsObserved = aUnitsObserved;
|
||||
});
|
||||
MOZ_RELEASE_ASSERT(complex.isNothing());
|
||||
MOZ_RELEASE_ASSERT(static_cast<const void*>(complexUnit.iter) == aCharN);
|
||||
|
||||
bool alreadyIterated = false;
|
||||
for (InvalidUtf8Reason reason : reasons) {
|
||||
MOZ_RELEASE_ASSERT(!alreadyIterated);
|
||||
alreadyIterated = true;
|
||||
|
||||
switch (reason) {
|
||||
case InvalidUtf8Reason::BadLeadUnit:
|
||||
break;
|
||||
|
||||
case InvalidUtf8Reason::NotEnoughUnits:
|
||||
MOZ_RELEASE_ASSERT(unitsAvailable == aExpectedUnitsAvailable);
|
||||
MOZ_RELEASE_ASSERT(unitsNeeded == aExpectedUnitsNeeded);
|
||||
break;
|
||||
|
||||
case InvalidUtf8Reason::BadTrailingUnit:
|
||||
MOZ_RELEASE_ASSERT(unitsObserved == aExpectedUnitsObserved);
|
||||
break;
|
||||
|
||||
case InvalidUtf8Reason::BadCodePoint:
|
||||
MOZ_RELEASE_ASSERT(badCodePoint == aExpectedBadCodePoint);
|
||||
MOZ_RELEASE_ASSERT(unitsObserved == aExpectedUnitsObserved);
|
||||
break;
|
||||
|
||||
case InvalidUtf8Reason::NotShortestForm:
|
||||
MOZ_RELEASE_ASSERT(badCodePoint == aExpectedBadCodePoint);
|
||||
MOZ_RELEASE_ASSERT(unitsObserved == aExpectedUnitsObserved);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: For simplicity in treating |aCharN| identically regardless whether it's
|
||||
// a string literal or a more-generalized array, we require |aCharN| be
|
||||
// null-terminated in all these functions.
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectBadLeadUnit(const Char (&aCharN)[N])
|
||||
{
|
||||
ExpectInvalidCodePointHelper(aCharN,
|
||||
InvalidUtf8Reason::BadLeadUnit,
|
||||
0xFF, 0xFF, 0xFFFFFFFF, 0xFF);
|
||||
}
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectNotEnoughUnits(const Char (&aCharN)[N],
|
||||
uint8_t aExpectedUnitsAvailable,
|
||||
uint8_t aExpectedUnitsNeeded)
|
||||
{
|
||||
ExpectInvalidCodePointHelper(aCharN,
|
||||
InvalidUtf8Reason::NotEnoughUnits,
|
||||
aExpectedUnitsAvailable, aExpectedUnitsNeeded,
|
||||
0xFFFFFFFF, 0xFF);
|
||||
}
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectBadTrailingUnit(const Char (&aCharN)[N],
|
||||
uint8_t aExpectedUnitsObserved)
|
||||
{
|
||||
ExpectInvalidCodePointHelper(aCharN,
|
||||
InvalidUtf8Reason::BadTrailingUnit,
|
||||
0xFF, 0xFF, 0xFFFFFFFF,
|
||||
aExpectedUnitsObserved);
|
||||
}
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectNotShortestForm(const Char (&aCharN)[N],
|
||||
char32_t aExpectedBadCodePoint,
|
||||
uint8_t aExpectedUnitsObserved)
|
||||
{
|
||||
ExpectInvalidCodePointHelper(aCharN,
|
||||
InvalidUtf8Reason::NotShortestForm,
|
||||
0xFF, 0xFF,
|
||||
aExpectedBadCodePoint,
|
||||
aExpectedUnitsObserved);
|
||||
}
|
||||
|
||||
template<typename Char, size_t N>
|
||||
static void
|
||||
ExpectBadCodePoint(const Char (&aCharN)[N],
|
||||
char32_t aExpectedBadCodePoint,
|
||||
uint8_t aExpectedUnitsObserved)
|
||||
{
|
||||
ExpectInvalidCodePointHelper(aCharN,
|
||||
InvalidUtf8Reason::BadCodePoint,
|
||||
0xFF, 0xFF,
|
||||
aExpectedBadCodePoint,
|
||||
aExpectedUnitsObserved);
|
||||
}
|
||||
|
||||
static void
|
||||
TestIsValidUtf8()
|
||||
{
|
||||
|
@ -62,48 +305,469 @@ TestIsValidUtf8()
|
|||
static_assert(twoBytesLen == 3, "U+0606 in two bytes plus nul");
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(twoBytes, twoBytesLen));
|
||||
|
||||
ExpectValidCodePoint(twoBytes, 0x0606);
|
||||
|
||||
// 3
|
||||
static const char threeBytes[] = u8"᨞"; // U+1A1E BUGINESE PALLAWA
|
||||
constexpr size_t threeBytesLen = ArrayLength(threeBytes);
|
||||
static_assert(threeBytesLen == 4, "U+1A1E in three bytes plus nul");
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(threeBytes, threeBytesLen));
|
||||
|
||||
ExpectValidCodePoint(threeBytes, 0x1A1E);
|
||||
|
||||
// 4
|
||||
static const char fourBytes[] = u8"🁡"; // U+1F061 DOMINO TILE HORIZONTAL-06-06
|
||||
constexpr size_t fourBytesLen = ArrayLength(fourBytes);
|
||||
static_assert(fourBytesLen == 5, "U+1F061 in four bytes plus nul");
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(fourBytes, fourBytesLen));
|
||||
|
||||
ExpectValidCodePoint(fourBytes, 0x1F061);
|
||||
|
||||
// Max code point
|
||||
static const char maxCodePoint[] = u8""; // U+10FFFF
|
||||
constexpr size_t maxCodePointLen = ArrayLength(maxCodePoint);
|
||||
static_assert(maxCodePointLen == 5, "U+10FFFF in four bytes plus nul");
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(maxCodePoint, maxCodePointLen));
|
||||
|
||||
ExpectValidCodePoint(maxCodePoint, 0x10FFFF);
|
||||
|
||||
// One past max code point
|
||||
static unsigned const char onePastMaxCodePoint[] = { 0xF4, 0x90, 0x80, 0x80 };
|
||||
static const unsigned char onePastMaxCodePoint[] = { 0xF4, 0x90, 0x80, 0x80, 0x0 };
|
||||
constexpr size_t onePastMaxCodePointLen = ArrayLength(onePastMaxCodePoint);
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(onePastMaxCodePoint, onePastMaxCodePointLen));
|
||||
|
||||
ExpectBadCodePoint(onePastMaxCodePoint, 0x110000, 4);
|
||||
|
||||
// Surrogate-related testing
|
||||
|
||||
static const unsigned char justBeforeSurrogates[] = { 0xED, 0x9F, 0xBF };
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(justBeforeSurrogates, ArrayLength(justBeforeSurrogates)));
|
||||
// (Note that the various code unit sequences here are null-terminated to
|
||||
// simplify life for ExpectValidCodePoint, which presumes null termination.)
|
||||
|
||||
static const unsigned char leastSurrogate[] = { 0xED, 0xA0, 0x80 };
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(leastSurrogate, ArrayLength(leastSurrogate)));
|
||||
static const unsigned char justBeforeSurrogates[] = { 0xED, 0x9F, 0xBF, 0x0 };
|
||||
constexpr size_t justBeforeSurrogatesLen = ArrayLength(justBeforeSurrogates) - 1;
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(justBeforeSurrogates, justBeforeSurrogatesLen));
|
||||
|
||||
static const unsigned char arbitraryHighSurrogate[] = { 0xED, 0xA2, 0x87 };
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryHighSurrogate, ArrayLength(arbitraryHighSurrogate)));
|
||||
ExpectValidCodePoint(justBeforeSurrogates, 0xD7FF);
|
||||
|
||||
static const unsigned char arbitraryLowSurrogate[] = { 0xED, 0xB7, 0xAF };
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryLowSurrogate, ArrayLength(arbitraryLowSurrogate)));
|
||||
static const unsigned char leastSurrogate[] = { 0xED, 0xA0, 0x80, 0x0 };
|
||||
constexpr size_t leastSurrogateLen = ArrayLength(leastSurrogate) - 1;
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(leastSurrogate, leastSurrogateLen));
|
||||
|
||||
static const unsigned char greatestSurrogate[] = { 0xED, 0xBF, 0xBF };
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(greatestSurrogate, ArrayLength(greatestSurrogate)));
|
||||
ExpectBadCodePoint(leastSurrogate, 0xD800, 3);
|
||||
|
||||
static const unsigned char justAfterSurrogates[] = { 0xEE, 0x80, 0x80 };
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(justAfterSurrogates, ArrayLength(justAfterSurrogates)));
|
||||
static const unsigned char arbitraryHighSurrogate[] = { 0xED, 0xA2, 0x87, 0x0 };
|
||||
constexpr size_t arbitraryHighSurrogateLen = ArrayLength(arbitraryHighSurrogate) - 1;
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryHighSurrogate, arbitraryHighSurrogateLen));
|
||||
|
||||
ExpectBadCodePoint(arbitraryHighSurrogate, 0xD887, 3);
|
||||
|
||||
static const unsigned char arbitraryLowSurrogate[] = { 0xED, 0xB7, 0xAF, 0x0 };
|
||||
constexpr size_t arbitraryLowSurrogateLen = ArrayLength(arbitraryLowSurrogate) - 1;
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryLowSurrogate, arbitraryLowSurrogateLen));
|
||||
|
||||
ExpectBadCodePoint(arbitraryLowSurrogate, 0xDDEF, 3);
|
||||
|
||||
static const unsigned char greatestSurrogate[] = { 0xED, 0xBF, 0xBF, 0x0 };
|
||||
constexpr size_t greatestSurrogateLen = ArrayLength(greatestSurrogate) - 1;
|
||||
MOZ_RELEASE_ASSERT(!IsValidUtf8(greatestSurrogate, greatestSurrogateLen));
|
||||
|
||||
ExpectBadCodePoint(greatestSurrogate, 0xDFFF, 3);
|
||||
|
||||
static const unsigned char justAfterSurrogates[] = { 0xEE, 0x80, 0x80, 0x0 };
|
||||
constexpr size_t justAfterSurrogatesLen = ArrayLength(justAfterSurrogates) - 1;
|
||||
MOZ_RELEASE_ASSERT(IsValidUtf8(justAfterSurrogates, justAfterSurrogatesLen));
|
||||
|
||||
ExpectValidCodePoint(justAfterSurrogates, 0xE000);
|
||||
}
|
||||
|
||||
static void
|
||||
TestDecodeOneValidUtf8CodePoint()
|
||||
{
|
||||
// NOTE: DecodeOneUtf8CodePoint decodes only *non*-ASCII code points that
|
||||
// consist of multiple code units, so there are no ASCII tests below.
|
||||
|
||||
// Length two.
|
||||
|
||||
ExpectValidCodePoint(u8"", 0x80); // <control>
|
||||
ExpectValidCodePoint(u8"©", 0xA9); // COPYRIGHT SIGN
|
||||
ExpectValidCodePoint(u8"¶", 0xB6); // PILCROW SIGN
|
||||
ExpectValidCodePoint(u8"¾", 0xBE); // VULGAR FRACTION THREE QUARTERS
|
||||
ExpectValidCodePoint(u8"÷", 0xF7); // DIVISION SIGN
|
||||
ExpectValidCodePoint(u8"ÿ", 0xFF); // LATIN SMALL LETTER Y WITH DIAERESIS
|
||||
ExpectValidCodePoint(u8"Ā", 0x100); // LATIN CAPITAL LETTER A WITH MACRON
|
||||
ExpectValidCodePoint(u8"IJ", 0x132); // LATIN CAPITAL LETTER LIGATURE IJ
|
||||
ExpectValidCodePoint(u8"ͼ", 0x37C); // GREEK SMALL DOTTED LUNATE SIGMA SYMBOL
|
||||
ExpectValidCodePoint(u8"Ӝ", 0x4DC); // CYRILLIC CAPITAL LETTER ZHE WITTH DIAERESIS
|
||||
ExpectValidCodePoint(u8"۩", 0x6E9); // ARABIC PLACE OF SAJDAH
|
||||
ExpectValidCodePoint(u8"߿", 0x7FF); // <not assigned>
|
||||
|
||||
// Length three.
|
||||
|
||||
ExpectValidCodePoint(u8"ࠀ", 0x800); // SAMARITAN LETTER ALAF
|
||||
ExpectValidCodePoint(u8"ࡁ", 0x841); // MANDAIC LETTER AB
|
||||
ExpectValidCodePoint(u8"ࣿ", 0x8FF); // ARABIC MARK SIDEWAYS NOON GHUNNA
|
||||
ExpectValidCodePoint(u8"ஆ", 0xB86); // TAMIL LETTER AA
|
||||
ExpectValidCodePoint(u8"༃", 0xF03); // TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA
|
||||
ExpectValidCodePoint(u8"࿉", 0xFC9); // TIBETAN SYMBOL NOR BU (but on my system it really looks like SOFT-SERVE ICE CREAM FROM ABOVE THE PLANE if you ask me)
|
||||
ExpectValidCodePoint(u8"ဪ", 0x102A); // MYANMAR LETTER AU
|
||||
ExpectValidCodePoint(u8"ᚏ", 0x168F); // OGHAM LETTER RUIS
|
||||
ExpectValidCodePoint("\xE2\x80\xA8", 0x2028); // (the hated) LINE SEPARATOR
|
||||
ExpectValidCodePoint("\xE2\x80\xA9", 0x2029); // (the hated) PARAGRAPH SEPARATOR
|
||||
ExpectValidCodePoint(u8"☬", 0x262C); // ADI SHAKTI
|
||||
ExpectValidCodePoint(u8"㊮", 0x32AE); // CIRCLED IDEOGRAPH RESOURCE
|
||||
ExpectValidCodePoint(u8"㏖", 0x33D6); // SQUARE MOL
|
||||
ExpectValidCodePoint(u8"ꔄ", 0xA504); // VAI SYLLABLE WEEN
|
||||
ExpectValidCodePoint(u8"ퟕ", 0xD7D5); // HANGUL JONGSEONG RIEUL-SSANGKIYEOK
|
||||
ExpectValidCodePoint(u8"", 0xD7FF); // <not assigned>
|
||||
ExpectValidCodePoint(u8"", 0xE000); // <Private Use>
|
||||
ExpectValidCodePoint(u8"鱗", 0xF9F2); // CJK COMPATIBILITY IDEOGRAPH-F9F
|
||||
ExpectValidCodePoint(u8"﷽", 0xFDFD); // ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHHHEEEEM
|
||||
ExpectValidCodePoint(u8"", 0xFFFF); // <not assigned>
|
||||
|
||||
// Length four.
|
||||
ExpectValidCodePoint(u8"𐀀", 0x10000); // LINEAR B SYLLABLE B008 A
|
||||
ExpectValidCodePoint(u8"𔑀", 0x14440); // ANATOLIAN HIEROGLYPH A058
|
||||
ExpectValidCodePoint(u8"𝛗", 0x1D6D7); // MATHEMATICAL BOLD SMALL PHI
|
||||
ExpectValidCodePoint(u8"💩", 0x1F4A9); // PILE OF POO
|
||||
ExpectValidCodePoint(u8"🔫", 0x1F52B); // PISTOL
|
||||
ExpectValidCodePoint(u8"🥌", 0x1F94C); // CURLING STONE
|
||||
ExpectValidCodePoint(u8"🥏", 0x1F94F); // FLYING DISC
|
||||
ExpectValidCodePoint(u8"𠍆", 0x20346); // CJK UNIFIED IDEOGRAPH-20346
|
||||
ExpectValidCodePoint(u8"𡠺", 0x2183A); // CJK UNIFIED IDEOGRAPH-2183A
|
||||
ExpectValidCodePoint(u8"", 0x417F6); // <not assigned>
|
||||
ExpectValidCodePoint(u8"", 0x7E836); // <not assigned>
|
||||
ExpectValidCodePoint(u8"", 0xFEF67); // <Plane 15 Private Use>
|
||||
ExpectValidCodePoint(u8"", 0x10FFFF); //
|
||||
}
|
||||
|
||||
static void
|
||||
TestDecodeBadLeadUnit()
|
||||
{
|
||||
// These tests are actually exhaustive.
|
||||
|
||||
unsigned char badLead[] = { '\0', '\0' };
|
||||
|
||||
for (uint8_t lead : IntegerRange(0b1000'0000, 0b1100'0000)) {
|
||||
badLead[0] = lead;
|
||||
ExpectBadLeadUnit(badLead);
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t lead = 0b1111'1000;
|
||||
do {
|
||||
badLead[0] = lead;
|
||||
ExpectBadLeadUnit(badLead);
|
||||
if (lead == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
lead++;
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestTooFewOrBadTrailingUnits()
|
||||
{
|
||||
// Lead unit indicates a two-byte code point.
|
||||
|
||||
char truncatedTwo[] = { '\0', '\0' };
|
||||
char badTrailTwo[] = { '\0', '\0', '\0' };
|
||||
|
||||
for (uint8_t lead : IntegerRange(0b1100'0000, 0b1110'0000)) {
|
||||
truncatedTwo[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedTwo, 1, 2);
|
||||
|
||||
badTrailTwo[0] = lead;
|
||||
for (uint8_t trail : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailTwo[1] = trail;
|
||||
ExpectBadTrailingUnit(badTrailTwo, 2);
|
||||
}
|
||||
|
||||
for (uint8_t trail : IntegerRange(0b1100'0000, 0b1111'1111)) {
|
||||
badTrailTwo[1] = trail;
|
||||
ExpectBadTrailingUnit(badTrailTwo, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Lead unit indicates a three-byte code point.
|
||||
|
||||
char truncatedThreeOne[] = { '\0', '\0' };
|
||||
char truncatedThreeTwo[] = { '\0', '\0', '\0' };
|
||||
unsigned char badTrailThree[] = { '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (uint8_t lead : IntegerRange(0b1110'0000, 0b1111'0000)) {
|
||||
truncatedThreeOne[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedThreeOne, 1, 3);
|
||||
|
||||
truncatedThreeTwo[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedThreeTwo, 2, 3);
|
||||
|
||||
badTrailThree[0] = lead;
|
||||
badTrailThree[2] = 0b1011'1111; // make valid to test overreads
|
||||
for (uint8_t mid : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailThree[1] = mid;
|
||||
ExpectBadTrailingUnit(badTrailThree, 2);
|
||||
}
|
||||
{
|
||||
uint8_t mid = 0b1100'0000;
|
||||
do {
|
||||
badTrailThree[1] = mid;
|
||||
ExpectBadTrailingUnit(badTrailThree, 2);
|
||||
if (mid == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
mid++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
badTrailThree[1] = 0b1011'1111;
|
||||
for (uint8_t last : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailThree[2] = last;
|
||||
ExpectBadTrailingUnit(badTrailThree, 3);
|
||||
}
|
||||
{
|
||||
uint8_t last = 0b1100'0000;
|
||||
do {
|
||||
badTrailThree[2] = last;
|
||||
ExpectBadTrailingUnit(badTrailThree, 3);
|
||||
if (last == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
last++;
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
||||
// Lead unit indicates a four-byte code point.
|
||||
|
||||
char truncatedFourOne[] = { '\0', '\0' };
|
||||
char truncatedFourTwo[] = { '\0', '\0', '\0' };
|
||||
char truncatedFourThree[] = { '\0', '\0', '\0', '\0' };
|
||||
|
||||
unsigned char badTrailFour[] = { '\0', '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (uint8_t lead : IntegerRange(0b1111'0000, 0b1111'1000)) {
|
||||
truncatedFourOne[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedFourOne, 1, 4);
|
||||
|
||||
truncatedFourTwo[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedFourTwo, 2, 4);
|
||||
|
||||
truncatedFourThree[0] = lead;
|
||||
ExpectNotEnoughUnits(truncatedFourThree, 3, 4);
|
||||
|
||||
badTrailFour[0] = lead;
|
||||
badTrailFour[2] = badTrailFour[3] = 0b1011'1111; // test for overreads
|
||||
for (uint8_t second : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailFour[1] = second;
|
||||
ExpectBadTrailingUnit(badTrailFour, 2);
|
||||
}
|
||||
{
|
||||
uint8_t second = 0b1100'0000;
|
||||
do {
|
||||
badTrailFour[1] = second;
|
||||
ExpectBadTrailingUnit(badTrailFour, 2);
|
||||
if (second == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
second++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
badTrailFour[1] = badTrailFour[3] = 0b1011'1111; // test for overreads
|
||||
for (uint8_t third : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailFour[2] = third;
|
||||
ExpectBadTrailingUnit(badTrailFour, 3);
|
||||
}
|
||||
{
|
||||
uint8_t third = 0b1100'0000;
|
||||
do {
|
||||
badTrailFour[2] = third;
|
||||
ExpectBadTrailingUnit(badTrailFour, 3);
|
||||
if (third == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
third++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
badTrailFour[2] = 0b1011'1111;
|
||||
for (uint8_t fourth : IntegerRange(0b0000'0000, 0b1000'0000)) {
|
||||
badTrailFour[3] = fourth;
|
||||
ExpectBadTrailingUnit(badTrailFour, 4);
|
||||
}
|
||||
{
|
||||
uint8_t fourth = 0b1100'0000;
|
||||
do {
|
||||
badTrailFour[3] = fourth;
|
||||
ExpectBadTrailingUnit(badTrailFour, 4);
|
||||
if (fourth == 0b1111'1111) {
|
||||
break;
|
||||
}
|
||||
|
||||
fourth++;
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestBadSurrogate()
|
||||
{
|
||||
// These tests are actually exhaustive.
|
||||
|
||||
ExpectValidCodePoint("\xED\x9F\xBF", 0xD7FF); // last before surrogates
|
||||
ExpectValidCodePoint("\xEE\x80\x80", 0xE000); // first after surrogates
|
||||
|
||||
// First invalid surrogate encoding is { 0xED, 0xA0, 0x80 }. Last invalid
|
||||
// surrogate encoding is { 0xED, 0xBF, 0xBF }.
|
||||
|
||||
char badSurrogate[] = { '\xED', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = 0xD800; c < 0xE000; c++) {
|
||||
badSurrogate[1] = 0b1000'0000 ^ ((c & 0b1111'1100'0000) >> 6);
|
||||
badSurrogate[2] = 0b1000'0000 ^ ((c & 0b0000'0011'1111));
|
||||
|
||||
ExpectBadCodePoint(badSurrogate, c, 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestBadTooBig()
|
||||
{
|
||||
// These tests are actually exhaustive.
|
||||
|
||||
ExpectValidCodePoint("\xF4\x8F\xBF\xBF", 0x10'FFFF); // last code point
|
||||
|
||||
// Four-byte code points are
|
||||
//
|
||||
// 0b1111'0xxx 0b10xx'xxxx 0b10xx'xxxx 0b10xx'xxxx
|
||||
//
|
||||
// with 3 + 6 + 6 + 6 == 21 unconstrained bytes, so the structurally
|
||||
// representable limit (exclusive) is 2**21 - 1 == 2097152.
|
||||
|
||||
char tooLargeCodePoint[] = { '\0', '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = 0x11'0000; c < (1 << 21); c++) {
|
||||
tooLargeCodePoint[0] = 0b1111'0000 ^ ((c & 0b1'1100'0000'0000'0000'0000) >> 18);
|
||||
tooLargeCodePoint[1] = 0b1000'0000 ^ ((c & 0b0'0011'1111'0000'0000'0000) >> 12);
|
||||
tooLargeCodePoint[2] = 0b1000'0000 ^ ((c & 0b0'0000'0000'1111'1100'0000) >> 6);
|
||||
tooLargeCodePoint[3] = 0b1000'0000 ^ ((c & 0b0'0000'0000'0000'0011'1111));
|
||||
|
||||
ExpectBadCodePoint(tooLargeCodePoint, c, 4);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestBadCodePoint()
|
||||
{
|
||||
TestBadSurrogate();
|
||||
TestBadTooBig();
|
||||
}
|
||||
|
||||
static void
|
||||
TestNotShortestForm()
|
||||
{
|
||||
{
|
||||
// One-byte in two-byte.
|
||||
|
||||
char oneInTwo[] = { '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = '\0'; c < 0x80; c++) {
|
||||
oneInTwo[0] = 0b1100'0000 ^ ((c & 0b0111'1100'0000) >> 6);
|
||||
oneInTwo[1] = 0b1000'0000 ^ ((c & 0b0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(oneInTwo, c, 2);
|
||||
}
|
||||
|
||||
// One-byte in three-byte.
|
||||
|
||||
char oneInThree[] = { '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = '\0'; c < 0x80; c++) {
|
||||
oneInThree[0] = 0b1110'0000 ^ ((c & 0b1111'0000'0000'0000) >> 12);
|
||||
oneInThree[1] = 0b1000'0000 ^ ((c & 0b0000'1111'1100'0000) >> 6);
|
||||
oneInThree[2] = 0b1000'0000 ^ ((c & 0b0000'0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(oneInThree, c, 3);
|
||||
}
|
||||
|
||||
// One-byte in four-byte.
|
||||
|
||||
char oneInFour[] = { '\0', '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = '\0'; c < 0x80; c++) {
|
||||
oneInFour[0] = 0b1111'0000 ^ ((c & 0b1'1100'0000'0000'0000'0000) >> 18);
|
||||
oneInFour[1] = 0b1000'0000 ^ ((c & 0b0'0011'1111'0000'0000'0000) >> 12);
|
||||
oneInFour[2] = 0b1000'0000 ^ ((c & 0b0'0000'0000'1111'1100'0000) >> 6);
|
||||
oneInFour[3] = 0b1000'0000 ^ ((c & 0b0'0000'0000'0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(oneInFour, c, 4);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Two-byte in three-byte.
|
||||
|
||||
char twoInThree[] = { '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = 0x80; c < 0x800; c++) {
|
||||
twoInThree[0] = 0b1110'0000 ^ ((c & 0b1111'0000'0000'0000) >> 12);
|
||||
twoInThree[1] = 0b1000'0000 ^ ((c & 0b0000'1111'1100'0000) >> 6);
|
||||
twoInThree[2] = 0b1000'0000 ^ ((c & 0b0000'0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(twoInThree, c, 3);
|
||||
}
|
||||
|
||||
// Two-byte in four-byte.
|
||||
|
||||
char twoInFour[] = { '\0', '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = 0x80; c < 0x800; c++) {
|
||||
twoInFour[0] = 0b1111'0000 ^ ((c & 0b1'1100'0000'0000'0000'0000) >> 18);
|
||||
twoInFour[1] = 0b1000'0000 ^ ((c & 0b0'0011'1111'0000'0000'0000) >> 12);
|
||||
twoInFour[2] = 0b1000'0000 ^ ((c & 0b0'0000'0000'1111'1100'0000) >> 6);
|
||||
twoInFour[3] = 0b1000'0000 ^ ((c & 0b0'0000'0000'0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(twoInFour, c, 4);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Three-byte in four-byte.
|
||||
|
||||
char threeInFour[] = { '\0', '\0', '\0', '\0', '\0' };
|
||||
|
||||
for (char32_t c = 0x800; c < 0x1'0000; c++) {
|
||||
threeInFour[0] = 0b1111'0000 ^ ((c & 0b1'1100'0000'0000'0000'0000) >> 18);
|
||||
threeInFour[1] = 0b1000'0000 ^ ((c & 0b0'0011'1111'0000'0000'0000) >> 12);
|
||||
threeInFour[2] = 0b1000'0000 ^ ((c & 0b0'0000'0000'1111'1100'0000) >> 6);
|
||||
threeInFour[3] = 0b1000'0000 ^ ((c & 0b0'0000'0000'0000'0011'1111));
|
||||
|
||||
ExpectNotShortestForm(threeInFour, c, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TestDecodeOneInvalidUtf8CodePoint()
|
||||
{
|
||||
TestDecodeBadLeadUnit();
|
||||
TestTooFewOrBadTrailingUnits();
|
||||
TestBadCodePoint();
|
||||
TestNotShortestForm();
|
||||
}
|
||||
|
||||
static void
|
||||
TestDecodeOneUtf8CodePoint()
|
||||
{
|
||||
TestDecodeOneValidUtf8CodePoint();
|
||||
TestDecodeOneInvalidUtf8CodePoint();
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -111,5 +775,6 @@ main()
|
|||
{
|
||||
TestUtf8Unit();
|
||||
TestIsValidUtf8();
|
||||
TestDecodeOneUtf8CodePoint();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1141,17 +1141,7 @@ VARCACHE_PREF(
|
|||
PREF("preferences.allow.omt-write", bool, true)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// View source prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
VARCACHE_PREF(
|
||||
"view_source.editor.external",
|
||||
view_source_editor_external,
|
||||
bool, false
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Anti-Tracking prefs
|
||||
// Privacy prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
VARCACHE_PREF(
|
||||
|
@ -1173,6 +1163,56 @@ VARCACHE_PREF(
|
|||
uint32_t, 2592000 // 30 days (in seconds)
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Security prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
VARCACHE_PREF(
|
||||
"security.csp.enable",
|
||||
security_csp_enable,
|
||||
bool, true
|
||||
)
|
||||
|
||||
VARCACHE_PREF(
|
||||
"security.csp.experimentalEnabled",
|
||||
security_csp_experimentalEnabled,
|
||||
bool, false
|
||||
)
|
||||
|
||||
VARCACHE_PREF(
|
||||
"security.csp.enableStrictDynamic",
|
||||
security_csp_enableStrictDynamic,
|
||||
bool, true
|
||||
)
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
# define PREF_VALUE true
|
||||
#else
|
||||
# define PREF_VALUE false
|
||||
#endif
|
||||
VARCACHE_PREF(
|
||||
"security.csp.enable_violation_events",
|
||||
security_csp_enable_violation_events,
|
||||
bool, PREF_VALUE
|
||||
)
|
||||
#undef PREF_VALUE
|
||||
|
||||
VARCACHE_PREF(
|
||||
"security.csp.reporting.script-sample.max-length",
|
||||
security_csp_reporting_script_sample_max_length,
|
||||
int32_t, 40
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// View source prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
VARCACHE_PREF(
|
||||
"view_source.editor.external",
|
||||
view_source_editor_external,
|
||||
bool, false
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// End of prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -2527,21 +2527,11 @@ pref("security.directory", "");
|
|||
pref("security.dialog_enable_delay", 1000);
|
||||
pref("security.notification_enable_delay", 500);
|
||||
|
||||
pref("security.csp.enable", true);
|
||||
pref("security.csp.experimentalEnabled", false);
|
||||
pref("security.csp.enableStrictDynamic", true);
|
||||
|
||||
#if defined(DEBUG) && !defined(ANDROID)
|
||||
// about:welcome has been added until Bug 1448359 is fixed at which time home, newtab, and welcome will all be removed.
|
||||
pref("csp.content_privileged_about_uris_without_csp", "blank,home,newtab,printpreview,srcdoc,welcome");
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("security.csp.enable_violation_events", true);
|
||||
#else
|
||||
pref("security.csp.enable_violation_events", false);
|
||||
#endif
|
||||
|
||||
// Default Content Security Policy to apply to signed contents.
|
||||
pref("security.signed_content.CSP.default", "script-src 'self'; style-src 'self'");
|
||||
|
||||
|
|
|
@ -1135,7 +1135,7 @@ nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(
|
|||
void
|
||||
nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP)
|
||||
{
|
||||
if (!CSPService::sCSPEnabled) {
|
||||
if (!StaticPrefs::security_csp_enable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1000,7 +1000,7 @@ linux64-ccov/opt:
|
|||
tooltool-downloads: public
|
||||
need-xvfb: true
|
||||
toolchains:
|
||||
- linux64-clang
|
||||
- linux64-clang-7
|
||||
- linux64-rust
|
||||
- linux64-gcc
|
||||
- linux64-sccache
|
||||
|
|
|
@ -46,4 +46,5 @@ marionette.jar:
|
|||
content/test_dialog.xul (chrome/test_dialog.xul)
|
||||
content/test_nested_iframe.xul (chrome/test_nested_iframe.xul)
|
||||
content/test.xul (chrome/test.xul)
|
||||
content/PerTestCoverageUtils.jsm (../../tools/code-coverage/PerTestCoverageUtils.jsm)
|
||||
#endif
|
||||
|
|
|
@ -131,6 +131,8 @@ class MochitestFormatter(TbplFormatter):
|
|||
|
||||
def __call__(self, data):
|
||||
output = super(MochitestFormatter, self).__call__(data)
|
||||
if not output:
|
||||
return None
|
||||
log_level = data.get('level', 'info').upper()
|
||||
|
||||
if 'js_source' in data or log_level == 'ERROR':
|
||||
|
|
|
@ -204,16 +204,20 @@ class CodeCoverageMixin(SingleTestMixin):
|
|||
def coverage_args(self):
|
||||
return []
|
||||
|
||||
def set_coverage_env(self, env):
|
||||
def set_coverage_env(self, env, is_baseline_test=False):
|
||||
# Set the GCOV directory.
|
||||
gcov_dir = tempfile.mkdtemp()
|
||||
env['GCOV_PREFIX'] = gcov_dir
|
||||
self.gcov_dir = tempfile.mkdtemp()
|
||||
env['GCOV_PREFIX'] = self.gcov_dir
|
||||
|
||||
# Set the GCOV directory where counters will be dumped in per-test mode.
|
||||
# Resetting/dumping is only available on Linux for the time being
|
||||
# (https://bugzilla.mozilla.org/show_bug.cgi?id=1471576).
|
||||
if self.per_test_coverage and not is_baseline_test and self._is_linux():
|
||||
env['GCOV_RESULTS_DIR'] = tempfile.mkdtemp()
|
||||
|
||||
# Set JSVM directory.
|
||||
jsvm_dir = tempfile.mkdtemp()
|
||||
env['JS_CODE_COVERAGE_OUTPUT_DIR'] = jsvm_dir
|
||||
|
||||
return (gcov_dir, jsvm_dir)
|
||||
self.jsvm_dir = tempfile.mkdtemp()
|
||||
env['JS_CODE_COVERAGE_OUTPUT_DIR'] = self.jsvm_dir
|
||||
|
||||
@PreScriptAction('run-tests')
|
||||
def _set_gcov_prefix(self, action):
|
||||
|
@ -223,7 +227,7 @@ class CodeCoverageMixin(SingleTestMixin):
|
|||
if self.per_test_coverage:
|
||||
return
|
||||
|
||||
self.gcov_dir, self.jsvm_dir = self.set_coverage_env(os.environ)
|
||||
self.set_coverage_env(os.environ)
|
||||
|
||||
def parse_coverage_artifacts(self,
|
||||
gcov_dir,
|
||||
|
@ -286,9 +290,11 @@ class CodeCoverageMixin(SingleTestMixin):
|
|||
else:
|
||||
return grcov_output_file, jsvm_output_file
|
||||
|
||||
def add_per_test_coverage_report(self, gcov_dir, jsvm_dir, suite, test):
|
||||
def add_per_test_coverage_report(self, env, suite, test):
|
||||
gcov_dir = env['GCOV_RESULTS_DIR'] if 'GCOV_RESULTS_DIR' in env else self.gcov_dir
|
||||
|
||||
grcov_file = self.parse_coverage_artifacts(
|
||||
gcov_dir, jsvm_dir, merge=True, output_format='coveralls',
|
||||
gcov_dir, self.jsvm_dir, merge=True, output_format='coveralls',
|
||||
filter_covered=True,
|
||||
)
|
||||
|
||||
|
@ -300,6 +306,11 @@ class CodeCoverageMixin(SingleTestMixin):
|
|||
assert test not in self.per_test_reports[suite]
|
||||
self.per_test_reports[suite][test] = report_file
|
||||
|
||||
if 'GCOV_RESULTS_DIR' in env:
|
||||
# In this case, parse_coverage_artifacts has removed GCOV_RESULTS_DIR
|
||||
# so we need to remove GCOV_PREFIX.
|
||||
shutil.rmtree(self.gcov_dir)
|
||||
|
||||
def is_covered(self, sf):
|
||||
# For C/C++ source files, we can consider a file as being uncovered
|
||||
# when all its source lines are uncovered.
|
||||
|
|
|
@ -15,7 +15,6 @@ import re
|
|||
import sys
|
||||
import copy
|
||||
import shutil
|
||||
import tempfile
|
||||
import glob
|
||||
import imp
|
||||
|
||||
|
@ -889,28 +888,18 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin,
|
|||
final_cmd = copy.copy(cmd)
|
||||
final_cmd.extend(per_test_args)
|
||||
|
||||
final_env = copy.copy(env)
|
||||
|
||||
if self.per_test_coverage:
|
||||
gcov_dir, jsvm_dir = self.set_coverage_env(env)
|
||||
# Per-test reset/dump is only supported on Linux for the time being.
|
||||
if not is_baseline_test and \
|
||||
self._is_linux():
|
||||
env['GCOV_RESULTS_DIR'] = tempfile.mkdtemp()
|
||||
self.set_coverage_env(final_env)
|
||||
|
||||
return_code = self.run_command(final_cmd, cwd=dirs['abs_work_dir'],
|
||||
output_timeout=cmd_timeout,
|
||||
output_parser=parser,
|
||||
env=env)
|
||||
env=final_env)
|
||||
|
||||
if self.per_test_coverage:
|
||||
self.add_per_test_coverage_report(
|
||||
env['GCOV_RESULTS_DIR'] if 'GCOV_RESULTS_DIR' in env else gcov_dir,
|
||||
jsvm_dir,
|
||||
suite,
|
||||
per_test_args[-1]
|
||||
)
|
||||
if 'GCOV_RESULTS_DIR' in env:
|
||||
shutil.rmtree(gcov_dir)
|
||||
del env['GCOV_RESULTS_DIR']
|
||||
self.add_per_test_coverage_report(final_env, suite, per_test_args[-1])
|
||||
|
||||
# mochitest, reftest, and xpcshell suites do not return
|
||||
# appropriate return codes. Therefore, we must parse the output
|
||||
|
|
|
@ -377,17 +377,19 @@ class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin):
|
|||
cmd = self._query_cmd(test_types)
|
||||
cmd.extend(per_test_args)
|
||||
|
||||
final_env = copy.copy(env)
|
||||
|
||||
if self.per_test_coverage:
|
||||
gcov_dir, jsvm_dir = self.set_coverage_env(env)
|
||||
self.set_coverage_env(final_env, is_baseline_test)
|
||||
|
||||
return_code = self.run_command(cmd,
|
||||
cwd=dirs['abs_work_dir'],
|
||||
output_timeout=1000,
|
||||
output_parser=parser,
|
||||
env=env)
|
||||
env=final_env)
|
||||
|
||||
if self.per_test_coverage:
|
||||
self.add_per_test_coverage_report(gcov_dir, jsvm_dir, suite, per_test_args[-1])
|
||||
self.add_per_test_coverage_report(final_env, suite, per_test_args[-1])
|
||||
|
||||
tbpl_status, log_level, summary = parser.evaluate_parser(return_code,
|
||||
previous_summary=summary)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[background-position-001.xht]
|
||||
expected:
|
||||
if os == "linux": FAIL
|
||||
if os == "linux" and not webrender: FAIL
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[background-position-002.xht]
|
||||
expected:
|
||||
if os == "linux": FAIL
|
||||
if os == "linux" and not webrender: FAIL
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
[navigation.sub.html?encoding=x-cp1251]
|
||||
expected:
|
||||
if not debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not asan and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
[follow hyperlink <a href>]
|
||||
expected: FAIL
|
||||
|
@ -25,10 +24,9 @@
|
|||
[navigation.sub.html?encoding=utf8]
|
||||
expected:
|
||||
if not debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not asan and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
|
||||
[hyperlink auditing <a ping>]
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -29,7 +29,8 @@ from .protocol import (AssertsProtocolPart,
|
|||
SelectorProtocolPart,
|
||||
ClickProtocolPart,
|
||||
SendKeysProtocolPart,
|
||||
TestDriverProtocolPart)
|
||||
TestDriverProtocolPart,
|
||||
CoverageProtocolPart)
|
||||
from ..testrunner import Stop
|
||||
from ..webdriver_server import GeckoDriverServer
|
||||
|
||||
|
@ -371,6 +372,53 @@ class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
|
|||
self.parent.base.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
|
||||
|
||||
|
||||
class MarionetteCoverageProtocolPart(CoverageProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
script = """
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
return PerTestCoverageUtils.enabled;
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.is_enabled = self.marionette.execute_script(script)
|
||||
|
||||
def reset(self):
|
||||
script = """
|
||||
var callback = arguments[arguments.length - 1];
|
||||
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
PerTestCoverageUtils.beforeTest().then(callback, callback);
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
try:
|
||||
error = self.marionette.execute_async_script(script)
|
||||
if error is not None:
|
||||
raise Exception('Failure while resetting counters: %s' % json.dumps(error))
|
||||
except (errors.MarionetteException, socket.error):
|
||||
# This usually happens if the process crashed
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
if len(self.marionette.window_handles):
|
||||
handle = self.marionette.window_handles[0]
|
||||
self.marionette.switch_to_window(handle)
|
||||
|
||||
script = """
|
||||
var callback = arguments[arguments.length - 1];
|
||||
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
PerTestCoverageUtils.afterTest().then(callback, callback);
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
try:
|
||||
error = self.marionette.execute_async_script(script)
|
||||
if error is not None:
|
||||
raise Exception('Failure while dumping counters: %s' % json.dumps(error))
|
||||
except (errors.MarionetteException, socket.error):
|
||||
# This usually happens if the process crashed
|
||||
pass
|
||||
|
||||
|
||||
class MarionetteProtocol(Protocol):
|
||||
implements = [MarionetteBaseProtocolPart,
|
||||
MarionetteTestharnessProtocolPart,
|
||||
|
@ -380,7 +428,8 @@ class MarionetteProtocol(Protocol):
|
|||
MarionetteClickProtocolPart,
|
||||
MarionetteSendKeysProtocolPart,
|
||||
MarionetteTestDriverProtocolPart,
|
||||
MarionetteAssertsProtocolPart]
|
||||
MarionetteAssertsProtocolPart,
|
||||
MarionetteCoverageProtocolPart]
|
||||
|
||||
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True):
|
||||
do_delayed_imports()
|
||||
|
@ -599,6 +648,9 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
|||
else:
|
||||
timeout_ms = "null"
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.reset()
|
||||
|
||||
format_map = {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
|
@ -621,6 +673,10 @@ class MarionetteTestharnessExecutor(TestharnessExecutor):
|
|||
done, rv = handler(result)
|
||||
if done:
|
||||
break
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.dump()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
@ -689,8 +745,14 @@ class MarionetteRefTestExecutor(RefTestExecutor):
|
|||
self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = True
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.reset()
|
||||
|
||||
result = self.implementation.run_test(test)
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.dump()
|
||||
|
||||
if self.debug:
|
||||
assertion_count = self.protocol.asserts.get()
|
||||
if "extra" not in result:
|
||||
|
|
|
@ -303,3 +303,20 @@ class AssertsProtocolPart(ProtocolPart):
|
|||
def get(self):
|
||||
"""Get a count of assertions since the last browser start"""
|
||||
pass
|
||||
|
||||
|
||||
class CoverageProtocolPart(ProtocolPart):
|
||||
"""Protocol part for collecting per-test coverage data."""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
name = "coverage"
|
||||
|
||||
@abstractmethod
|
||||
def reset(self):
|
||||
"""Reset coverage counters"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(self):
|
||||
"""Dump coverage counters"""
|
||||
pass
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "AntiTrackingCommon.h"
|
||||
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/ipc/MessageChannel.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
#include "mozIThirdPartyUtil.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
@ -71,14 +73,14 @@ CreatePermissionKey(const nsCString& aTrackingOrigin,
|
|||
|
||||
} // anonymous
|
||||
|
||||
/* static */ void
|
||||
/* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
|
||||
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigin,
|
||||
nsPIDOMWindowInner* aParentWindow)
|
||||
{
|
||||
MOZ_ASSERT(aParentWindow);
|
||||
|
||||
if (!StaticPrefs::privacy_restrict3rdpartystorage_enabled()) {
|
||||
return;
|
||||
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
|
||||
|
@ -88,7 +90,7 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigi
|
|||
nsGlobalWindowOuter* outerParentWindow =
|
||||
nsGlobalWindowOuter::Cast(parentWindow->GetOuterWindow());
|
||||
if (NS_WARN_IF(!outerParentWindow)) {
|
||||
return;
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
}
|
||||
|
||||
// We are a first party resource.
|
||||
|
@ -96,23 +98,27 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigi
|
|||
CopyUTF16toUTF8(aOrigin, trackingOrigin);
|
||||
topLevelStoragePrincipal = parentWindow->GetPrincipal();
|
||||
if (NS_WARN_IF(!topLevelStoragePrincipal)) {
|
||||
return;
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
}
|
||||
|
||||
// We are a 3rd party source.
|
||||
} else if (!GetParentPrincipalAndTrackingOrigin(parentWindow,
|
||||
getter_AddRefs(topLevelStoragePrincipal),
|
||||
trackingOrigin)) {
|
||||
return;
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 grantedOrigin(aOrigin);
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
RefPtr<StorageAccessGrantPromise::Private> p = new StorageAccessGrantPromise::Private(__func__);
|
||||
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
|
||||
trackingOrigin,
|
||||
grantedOrigin);
|
||||
return;
|
||||
grantedOrigin,
|
||||
[p] (bool success) {
|
||||
p->Resolve(success, __func__);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
|
@ -120,25 +126,36 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigi
|
|||
|
||||
// This is not really secure, because here we have the content process sending
|
||||
// the request of storing a permission.
|
||||
Unused << cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
|
||||
trackingOrigin,
|
||||
grantedOrigin);
|
||||
RefPtr<StorageAccessGrantPromise::Private> p = new StorageAccessGrantPromise::Private(__func__);
|
||||
cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
|
||||
trackingOrigin,
|
||||
grantedOrigin)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[p] (bool success) {
|
||||
p->Resolve(success, __func__);
|
||||
}, [p] (ipc::ResponseRejectReason aReason) {
|
||||
p->Reject(false, __func__);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal,
|
||||
const nsCString& aTrackingOrigin,
|
||||
const nsCString& aGrantedOrigin)
|
||||
const nsCString& aGrantedOrigin,
|
||||
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
if (NS_WARN_IF(!aParentPrincipal)) {
|
||||
// The child process is sending something wrong. Let's ignore it.
|
||||
aResolver(false);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
|
||||
if (NS_WARN_IF(!pm)) {
|
||||
aResolver(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -154,6 +171,7 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n
|
|||
nsIPermissionManager::ALLOW_ACTION,
|
||||
nsIPermissionManager::EXPIRE_TIME, when);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
aResolver(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#define mozilla_antitrackingservice_h
|
||||
|
||||
#include "nsString.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
class nsIHttpChannel;
|
||||
class nsIPrincipal;
|
||||
|
@ -19,6 +21,13 @@ namespace mozilla {
|
|||
class AntiTrackingCommon final
|
||||
{
|
||||
public:
|
||||
// Normally we would include PContentParent.h here and use the
|
||||
// ipc::FirstPartyStorageAccessGrantedForOriginResolver type which maps to
|
||||
// the same underlying type, but that results in Windows compilation errors,
|
||||
// so we use the underlying type to avoid the #include here.
|
||||
typedef std::function<void(const bool&)>
|
||||
FirstPartyStorageAccessGrantedForOriginResolver;
|
||||
|
||||
// This method returns true if the URI has first party storage access when
|
||||
// loaded inside the passed 3rd party context tracking resource window.
|
||||
// If the window is first party context, please use
|
||||
|
@ -59,7 +68,8 @@ public:
|
|||
// Ex: example.net import tracker.com/script.js which does opens a popup and
|
||||
// the user interacts with it. tracker.com is allowed when loaded by
|
||||
// example.net.
|
||||
static void
|
||||
typedef MozPromise<bool, bool, false> StorageAccessGrantPromise;
|
||||
static MOZ_MUST_USE RefPtr<StorageAccessGrantPromise>
|
||||
AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigin,
|
||||
nsPIDOMWindowInner* aParentWindow);
|
||||
|
||||
|
@ -67,7 +77,8 @@ public:
|
|||
static void
|
||||
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
|
||||
const nsCString& aParentOrigin,
|
||||
const nsCString& aGrantedOrigin);
|
||||
const nsCString& aGrantedOrigin,
|
||||
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -1124,11 +1124,7 @@ EnvironmentCache.prototype = {
|
|||
_startWatchingPrefs() {
|
||||
this._log.trace("_startWatchingPrefs - " + this._watchedPrefs);
|
||||
|
||||
for (let [pref, options] of this._watchedPrefs) {
|
||||
if (!("requiresRestart" in options) || !options.requiresRestart) {
|
||||
Services.prefs.addObserver(pref, this, true);
|
||||
}
|
||||
}
|
||||
Services.prefs.addObserver("", this, true);
|
||||
},
|
||||
|
||||
_onPrefChanged(aData) {
|
||||
|
@ -1144,11 +1140,7 @@ EnvironmentCache.prototype = {
|
|||
_stopWatchingPrefs() {
|
||||
this._log.trace("_stopWatchingPrefs");
|
||||
|
||||
for (let [pref, options] of this._watchedPrefs) {
|
||||
if (!("requiresRestart" in options) || !options.requiresRestart) {
|
||||
Services.prefs.removeObserver(pref, this);
|
||||
}
|
||||
}
|
||||
Services.prefs.removeObserver("", this);
|
||||
},
|
||||
|
||||
_addObservers() {
|
||||
|
@ -1220,7 +1212,8 @@ EnvironmentCache.prototype = {
|
|||
this._updateDefaultBrowser();
|
||||
break;
|
||||
case PREF_CHANGED_TOPIC:
|
||||
if (this._watchedPrefs.has(aData)) {
|
||||
let options = this._watchedPrefs.get(aData);
|
||||
if (options && !options.requiresRestart) {
|
||||
this._onPrefChanged(aData);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -40,6 +40,9 @@ XPCOMUtils.defineLazyProxy(this, "PopupBlocking", () => {
|
|||
XPCOMUtils.defineLazyProxy(this, "SelectionSourceContent",
|
||||
"resource://gre/modules/SelectionSourceContent.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyProxy(this, "WebChannelContent",
|
||||
"resource://gre/modules/WebChannelContent.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyProxy(this, "DateTimePickerContent", () => {
|
||||
let tmp = {};
|
||||
ChromeUtils.import("resource://gre/modules/DateTimePickerContent.jsm", tmp);
|
||||
|
@ -285,90 +288,9 @@ var FindBar = {
|
|||
};
|
||||
FindBar.init();
|
||||
|
||||
let WebChannelMessageToChromeListener = {
|
||||
// Preference containing the list (space separated) of origins that are
|
||||
// allowed to send non-string values through a WebChannel, mainly for
|
||||
// backwards compatability. See bug 1238128 for more information.
|
||||
URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
|
||||
|
||||
// Cached list of whitelisted principals, we avoid constructing this if the
|
||||
// value in `_lastWhitelistValue` hasn't changed since we constructed it last.
|
||||
_cachedWhitelist: [],
|
||||
_lastWhitelistValue: "",
|
||||
|
||||
init() {
|
||||
addEventListener("WebChannelMessageToChrome", e => {
|
||||
this._onMessageToChrome(e);
|
||||
}, true, true);
|
||||
},
|
||||
|
||||
_getWhitelistedPrincipals() {
|
||||
let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
|
||||
if (whitelist != this._lastWhitelistValue) {
|
||||
let urls = whitelist.split(/\s+/);
|
||||
this._cachedWhitelist = urls.map(origin =>
|
||||
Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
|
||||
}
|
||||
return this._cachedWhitelist;
|
||||
},
|
||||
|
||||
_onMessageToChrome(e) {
|
||||
// If target is window then we want the document principal, otherwise fallback to target itself.
|
||||
let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
|
||||
|
||||
if (e.detail) {
|
||||
if (typeof e.detail != "string") {
|
||||
// Check if the principal is one of the ones that's allowed to send
|
||||
// non-string values for e.detail. They're whitelisted by site origin,
|
||||
// so we compare on originNoSuffix in order to avoid other origin attributes
|
||||
// that are not relevant here, such as containers or private browsing.
|
||||
let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
|
||||
principal.originNoSuffix == whitelisted.originNoSuffix);
|
||||
if (!objectsAllowed) {
|
||||
Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. No message detail.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebChannelMessageToChromeListener.init();
|
||||
|
||||
// This should be kept in sync with /browser/base/content.js.
|
||||
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
|
||||
addMessageListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.data) {
|
||||
// e.objects.eventTarget will be defined if sending a response to
|
||||
// a WebChannelMessageToChrome event. An unsolicited send
|
||||
// may not have an eventTarget defined, in this case send to the
|
||||
// main content window.
|
||||
let eventTarget = e.objects.eventTarget || content;
|
||||
|
||||
// Use nodePrincipal if available, otherwise fallback to document principal.
|
||||
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
|
||||
|
||||
if (e.principal.subsumes(targetPrincipal)) {
|
||||
// If eventTarget is a window, use it as the targetWindow, otherwise
|
||||
// find the window that owns the eventTarget.
|
||||
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerGlobal;
|
||||
|
||||
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
|
||||
detail: Cu.cloneInto({
|
||||
id: e.data.id,
|
||||
message: e.data.message,
|
||||
}, targetWindow),
|
||||
}));
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. Principal mismatch.");
|
||||
}
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. No message data.");
|
||||
}
|
||||
});
|
||||
addEventListener("WebChannelMessageToChrome", WebChannelContent,
|
||||
true, true);
|
||||
addMessageListener("WebChannelMessageToContent", WebChannelContent);
|
||||
|
||||
var AudioPlaybackListener = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/* eslint no-unused-vars: ["error", {args: "none"}] */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["WebChannelContent"];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function getMessageManager(event) {
|
||||
let window = Cu.getGlobalForObject(event.target);
|
||||
|
||||
return window.document.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
}
|
||||
|
||||
var WebChannelContent = {
|
||||
// Preference containing the list (space separated) of origins that are
|
||||
// allowed to send non-string values through a WebChannel, mainly for
|
||||
// backwards compatability. See bug 1238128 for more information.
|
||||
URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
|
||||
|
||||
// Cached list of whitelisted principals, we avoid constructing this if the
|
||||
// value in `_lastWhitelistValue` hasn't changed since we constructed it last.
|
||||
_cachedWhitelist: [],
|
||||
_lastWhitelistValue: "",
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type === "WebChannelMessageToChrome") {
|
||||
return this._onMessageToChrome(event);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
if (msg.name === "WebChannelMessageToContent") {
|
||||
return this._onMessageToContent(msg);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_getWhitelistedPrincipals() {
|
||||
let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
|
||||
if (whitelist != this._lastWhitelistValue) {
|
||||
let urls = whitelist.split(/\s+/);
|
||||
this._cachedWhitelist = urls.map(origin =>
|
||||
Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
|
||||
}
|
||||
return this._cachedWhitelist;
|
||||
},
|
||||
|
||||
_onMessageToChrome(e) {
|
||||
// If target is window then we want the document principal, otherwise fallback to target itself.
|
||||
let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
|
||||
|
||||
if (e.detail) {
|
||||
if (typeof e.detail != "string") {
|
||||
// Check if the principal is one of the ones that's allowed to send
|
||||
// non-string values for e.detail. They're whitelisted by site origin,
|
||||
// so we compare on originNoSuffix in order to avoid other origin attributes
|
||||
// that are not relevant here, such as containers or private browsing.
|
||||
let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
|
||||
principal.originNoSuffix == whitelisted.originNoSuffix);
|
||||
if (!objectsAllowed) {
|
||||
Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mm = getMessageManager(e);
|
||||
|
||||
mm.sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. No message detail.");
|
||||
}
|
||||
},
|
||||
|
||||
_onMessageToContent(msg) {
|
||||
if (msg.data) {
|
||||
// msg.objects.eventTarget will be defined if sending a response to
|
||||
// a WebChannelMessageToChrome event. An unsolicited send
|
||||
// may not have an eventTarget defined, in this case send to the
|
||||
// main content window.
|
||||
let eventTarget = msg.objects.eventTarget || msg.target.content;
|
||||
|
||||
// Use nodePrincipal if available, otherwise fallback to document principal.
|
||||
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
|
||||
|
||||
if (msg.principal.subsumes(targetPrincipal)) {
|
||||
// If eventTarget is a window, use it as the targetWindow, otherwise
|
||||
// find the window that owns the eventTarget.
|
||||
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerGlobal;
|
||||
|
||||
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
|
||||
detail: Cu.cloneInto({
|
||||
id: msg.data.id,
|
||||
message: msg.data.message,
|
||||
}, targetWindow),
|
||||
}));
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. Principal mismatch.");
|
||||
}
|
||||
} else {
|
||||
Cu.reportError("WebChannel message failed. No message data.");
|
||||
}
|
||||
},
|
||||
};
|
|
@ -253,6 +253,7 @@ EXTRA_JS_MODULES += [
|
|||
'Troubleshoot.jsm',
|
||||
'UpdateUtils.jsm',
|
||||
'WebChannel.jsm',
|
||||
'WebChannelContent.jsm',
|
||||
'WindowDraggingUtils.jsm',
|
||||
'ZipUtils.jsm',
|
||||
]
|
||||
|
|
|
@ -613,8 +613,8 @@ nsProfiler::StartGathering(double aSinceTime)
|
|||
for (auto profile : profiles) {
|
||||
profile->Then(GetMainThreadSerialEventTarget(), __func__,
|
||||
[self](const mozilla::ipc::Shmem& aResult) {
|
||||
const nsDependentCString profileString(aResult.get<char>(),
|
||||
aResult.Size<char>());
|
||||
const nsDependentCSubstring profileString(aResult.get<char>(),
|
||||
aResult.Size<char>() - 1);
|
||||
self->GatheredOOPProfile(profileString);
|
||||
},
|
||||
[self](ipc::ResponseRejectReason aReason) {
|
||||
|
|
|
@ -99,7 +99,7 @@ nsClipboardProxy::GetData(nsITransferable *aTransferable, int32_t aWhichClipboar
|
|||
nsCOMPtr<nsIInputStream> stream;
|
||||
|
||||
NS_NewCStringInputStream(getter_AddRefs(stream),
|
||||
nsDependentCString(data.get<char>(), data.Size<char>()));
|
||||
nsDependentCSubstring(data.get<char>(), data.Size<char>()));
|
||||
|
||||
rv = aTransferable->SetTransferData(flavor.get(), stream, sizeof(nsISupports*));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -110,7 +110,7 @@ nsClipboardProxy::GetData(nsITransferable *aTransferable, int32_t aWhichClipboar
|
|||
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = dataWrapper->SetData(nsDependentCString(data.get<char>(), data.Size<char>()));
|
||||
rv = dataWrapper->SetData(nsDependentCSubstring(data.get<char>(), data.Size<char>()));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsXPCOMPrivate.h"
|
||||
#include "nscore.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "prenv.h"
|
||||
|
@ -52,8 +54,6 @@
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "plhash.h"
|
||||
|
||||
#include "prthread.h"
|
||||
|
||||
// We use a spin lock instead of a regular mutex because this lock is usually
|
||||
|
@ -80,10 +80,19 @@ struct MOZ_STACK_CLASS AutoTraceLogLock final
|
|||
~AutoTraceLogLock() { if (doRelease) gTraceLogLocked = 0; }
|
||||
};
|
||||
|
||||
static PLHashTable* gBloatView;
|
||||
static PLHashTable* gTypesToLog;
|
||||
static PLHashTable* gObjectsToLog;
|
||||
static PLHashTable* gSerialNumbers;
|
||||
class BloatEntry;
|
||||
struct SerialNumberRecord;
|
||||
|
||||
using BloatHash = nsClassHashtable<nsDepCharHashKey, BloatEntry>;
|
||||
using CharPtrSet = nsTHashtable<nsCharPtrHashKey>;
|
||||
using IntPtrSet = nsTHashtable<IntPtrHashKey>;
|
||||
using SerialHash = nsClassHashtable<nsVoidPtrHashKey, SerialNumberRecord>;
|
||||
|
||||
static StaticAutoPtr<BloatHash> gBloatView;
|
||||
static StaticAutoPtr<CharPtrSet> gTypesToLog;
|
||||
static StaticAutoPtr<IntPtrSet> gObjectsToLog;
|
||||
static StaticAutoPtr<SerialHash> gSerialNumbers;
|
||||
|
||||
static intptr_t gNextSerialNumber;
|
||||
static bool gDumpedStatistics = false;
|
||||
static bool gLogJSStacks = false;
|
||||
|
@ -205,56 +214,6 @@ AssertActivityIsLegal()
|
|||
# define ASSERT_ACTIVITY_IS_LEGAL do { } while(0)
|
||||
#endif // DEBUG
|
||||
|
||||
// These functions are copied from nsprpub/lib/ds/plhash.c, with changes
|
||||
// to the functions not called Default* to free the SerialNumberRecord or
|
||||
// the BloatEntry.
|
||||
|
||||
static void*
|
||||
DefaultAllocTable(void* aPool, size_t aSize)
|
||||
{
|
||||
return malloc(aSize);
|
||||
}
|
||||
|
||||
static void
|
||||
DefaultFreeTable(void* aPool, void* aItem)
|
||||
{
|
||||
free(aItem);
|
||||
}
|
||||
|
||||
static PLHashEntry*
|
||||
DefaultAllocEntry(void* aPool, const void* aKey)
|
||||
{
|
||||
return (PLHashEntry*) malloc(sizeof(PLHashEntry));
|
||||
}
|
||||
|
||||
static void
|
||||
SerialNumberFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag)
|
||||
{
|
||||
if (aFlag == HT_FREE_ENTRY) {
|
||||
delete static_cast<SerialNumberRecord*>(aHashEntry->value);
|
||||
free(aHashEntry);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
TypesToLogFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag)
|
||||
{
|
||||
if (aFlag == HT_FREE_ENTRY) {
|
||||
free(const_cast<char*>(static_cast<const char*>(aHashEntry->key)));
|
||||
free(aHashEntry);
|
||||
}
|
||||
}
|
||||
|
||||
static const PLHashAllocOps serialNumberHashAllocOps = {
|
||||
DefaultAllocTable, DefaultFreeTable,
|
||||
DefaultAllocEntry, SerialNumberFreeEntry
|
||||
};
|
||||
|
||||
static const PLHashAllocOps typesToLogHashAllocOps = {
|
||||
DefaultAllocTable, DefaultFreeTable,
|
||||
DefaultAllocEntry, TypesToLogFreeEntry
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class CodeAddressServiceStringTable final
|
||||
|
@ -338,24 +297,6 @@ public:
|
|||
mStats.mDestroys++;
|
||||
}
|
||||
|
||||
static int DumpEntry(PLHashEntry* aHashEntry, int aIndex, void* aArg)
|
||||
{
|
||||
BloatEntry* entry = (BloatEntry*)aHashEntry->value;
|
||||
if (entry) {
|
||||
static_cast<nsTArray<BloatEntry*>*>(aArg)->AppendElement(entry);
|
||||
}
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
static int TotalEntries(PLHashEntry* aHashEntry, int aIndex, void* aArg)
|
||||
{
|
||||
BloatEntry* entry = (BloatEntry*)aHashEntry->value;
|
||||
if (entry && nsCRT::strcmp(entry->mClassName, "TOTAL") != 0) {
|
||||
entry->Total((BloatEntry*)aArg);
|
||||
}
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
void Total(BloatEntry* aTotal)
|
||||
{
|
||||
aTotal->mStats.mCreates += mStats.mCreates;
|
||||
|
@ -411,29 +352,10 @@ protected:
|
|||
nsTraceRefcntStats mStats;
|
||||
};
|
||||
|
||||
static void
|
||||
BloatViewFreeEntry(void* aPool, PLHashEntry* aHashEntry, unsigned aFlag)
|
||||
{
|
||||
if (aFlag == HT_FREE_ENTRY) {
|
||||
BloatEntry* entry = static_cast<BloatEntry*>(aHashEntry->value);
|
||||
delete entry;
|
||||
free(aHashEntry);
|
||||
}
|
||||
}
|
||||
|
||||
const static PLHashAllocOps bloatViewHashAllocOps = {
|
||||
DefaultAllocTable, DefaultFreeTable,
|
||||
DefaultAllocEntry, BloatViewFreeEntry
|
||||
};
|
||||
|
||||
static void
|
||||
RecreateBloatView()
|
||||
{
|
||||
gBloatView = PL_NewHashTable(256,
|
||||
PL_HashString,
|
||||
PL_CompareStrings,
|
||||
PL_CompareValues,
|
||||
&bloatViewHashAllocOps, nullptr);
|
||||
gBloatView = new BloatHash(256);
|
||||
}
|
||||
|
||||
static BloatEntry*
|
||||
|
@ -442,48 +364,39 @@ GetBloatEntry(const char* aTypeName, uint32_t aInstanceSize)
|
|||
if (!gBloatView) {
|
||||
RecreateBloatView();
|
||||
}
|
||||
BloatEntry* entry = nullptr;
|
||||
if (gBloatView) {
|
||||
entry = (BloatEntry*)PL_HashTableLookup(gBloatView, aTypeName);
|
||||
if (!entry && aInstanceSize > 0) {
|
||||
|
||||
entry = new BloatEntry(aTypeName, aInstanceSize);
|
||||
PLHashEntry* e = PL_HashTableAdd(gBloatView, aTypeName, entry);
|
||||
if (!e) {
|
||||
delete entry;
|
||||
entry = nullptr;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize,
|
||||
"Mismatched sizes were recorded in the memory leak logging table. "
|
||||
"The usual cause of this is having a templated class that uses "
|
||||
"MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. "
|
||||
"As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a "
|
||||
"non-templated base class. Another possible cause is a runnable with "
|
||||
"an mName that matches another refcounted class.");
|
||||
}
|
||||
BloatEntry* entry = gBloatView->Get(aTypeName);
|
||||
if (!entry && aInstanceSize > 0) {
|
||||
entry = new BloatEntry(aTypeName, aInstanceSize);
|
||||
gBloatView->Put(aTypeName, entry);
|
||||
} else {
|
||||
MOZ_ASSERT(aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize,
|
||||
"Mismatched sizes were recorded in the memory leak logging table. "
|
||||
"The usual cause of this is having a templated class that uses "
|
||||
"MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. "
|
||||
"As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a "
|
||||
"non-templated base class. Another possible cause is a runnable with "
|
||||
"an mName that matches another refcounted class.");
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
static int
|
||||
DumpSerialNumbers(PLHashEntry* aHashEntry, int aIndex, void* aClosure)
|
||||
static void
|
||||
DumpSerialNumbers(const SerialHash::Iterator& aHashEntry, FILE* aFd)
|
||||
{
|
||||
SerialNumberRecord* record =
|
||||
static_cast<SerialNumberRecord*>(aHashEntry->value);
|
||||
auto* outputFile = static_cast<FILE*>(aClosure);
|
||||
SerialNumberRecord* record = aHashEntry.Data();
|
||||
auto* outputFile = aFd;
|
||||
#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
|
||||
fprintf(outputFile, "%" PRIdPTR
|
||||
" @%p (%d references; %d from COMPtrs)\n",
|
||||
record->serialNumber,
|
||||
aHashEntry->key,
|
||||
aHashEntry.Key(),
|
||||
record->refCount,
|
||||
record->COMPtrCount);
|
||||
#else
|
||||
fprintf(outputFile, "%" PRIdPTR
|
||||
" @%p (%d references)\n",
|
||||
record->serialNumber,
|
||||
aHashEntry->key,
|
||||
aHashEntry.Key(),
|
||||
record->refCount);
|
||||
#endif
|
||||
if (!record->allocationStack.empty()) {
|
||||
|
@ -506,8 +419,6 @@ DumpSerialNumbers(PLHashEntry* aHashEntry, int aIndex, void* aClosure)
|
|||
fprintf(outputFile, "There is no JS context on the stack.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
|
||||
|
@ -545,7 +456,13 @@ nsTraceRefcnt::DumpStatistics()
|
|||
gLogging = NoLogging;
|
||||
|
||||
BloatEntry total("TOTAL", 0);
|
||||
PL_HashTableEnumerateEntries(gBloatView, BloatEntry::TotalEntries, &total);
|
||||
for (auto iter = gBloatView->Iter(); !iter.Done(); iter.Next()) {
|
||||
BloatEntry* entry = iter.Data();
|
||||
if (nsCRT::strcmp(entry->GetClassName(), "TOTAL") != 0) {
|
||||
entry->Total(&total);
|
||||
}
|
||||
}
|
||||
|
||||
const char* msg;
|
||||
if (gLogLeaksOnly) {
|
||||
msg = "ALL (cumulative) LEAK STATISTICS";
|
||||
|
@ -555,7 +472,10 @@ nsTraceRefcnt::DumpStatistics()
|
|||
const bool leaked = total.PrintDumpHeader(gBloatLog, msg);
|
||||
|
||||
nsTArray<BloatEntry*> entries;
|
||||
PL_HashTableEnumerateEntries(gBloatView, BloatEntry::DumpEntry, &entries);
|
||||
for (auto iter = gBloatView->Iter(); !iter.Done(); iter.Next()) {
|
||||
entries.AppendElement(iter.Data());
|
||||
}
|
||||
|
||||
const uint32_t count = entries.Length();
|
||||
|
||||
if (!gLogLeaksOnly || leaked) {
|
||||
|
@ -574,7 +494,9 @@ nsTraceRefcnt::DumpStatistics()
|
|||
|
||||
if (gSerialNumbers) {
|
||||
fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n");
|
||||
PL_HashTableEnumerateEntries(gSerialNumbers, DumpSerialNumbers, gBloatLog);
|
||||
for (auto iter = gSerialNumbers->Iter(); !iter.Done(); iter.Next()) {
|
||||
DumpSerialNumbers(iter, gBloatLog);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -584,87 +506,40 @@ void
|
|||
nsTraceRefcnt::ResetStatistics()
|
||||
{
|
||||
AutoTraceLogLock lock;
|
||||
if (gBloatView) {
|
||||
PL_HashTableDestroy(gBloatView);
|
||||
gBloatView = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
LogThisType(const char* aTypeName)
|
||||
{
|
||||
void* he = PL_HashTableLookup(gTypesToLog, aTypeName);
|
||||
return he != nullptr;
|
||||
}
|
||||
|
||||
static PLHashNumber
|
||||
HashNumber(const void* aKey)
|
||||
{
|
||||
return PLHashNumber(NS_PTR_TO_INT32(aKey));
|
||||
gBloatView = nullptr;
|
||||
}
|
||||
|
||||
static intptr_t
|
||||
GetSerialNumber(void* aPtr, bool aCreate)
|
||||
{
|
||||
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
|
||||
HashNumber(aPtr),
|
||||
aPtr);
|
||||
if (hep && *hep) {
|
||||
MOZ_RELEASE_ASSERT(!aCreate, "If an object already has a serial number, we should be destroying it.");
|
||||
return static_cast<SerialNumberRecord*>((*hep)->value)->serialNumber;
|
||||
}
|
||||
|
||||
if (!aCreate) {
|
||||
return 0;
|
||||
auto record = gSerialNumbers->Get(aPtr);
|
||||
return record ? record->serialNumber : 0;
|
||||
}
|
||||
|
||||
SerialNumberRecord* record = new SerialNumberRecord();
|
||||
auto entry = gSerialNumbers->LookupForAdd(aPtr);
|
||||
if (entry) {
|
||||
MOZ_CRASH("If an object already has a serial number, we should be destroying it.");
|
||||
}
|
||||
|
||||
auto record = entry.OrInsert([]() { return new SerialNumberRecord(); });
|
||||
WalkTheStackSavingLocations(record->allocationStack);
|
||||
PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr),
|
||||
aPtr, static_cast<void*>(record));
|
||||
if (gLogJSStacks) {
|
||||
record->SaveJSStack();
|
||||
}
|
||||
return gNextSerialNumber;
|
||||
}
|
||||
|
||||
static int32_t*
|
||||
GetRefCount(void* aPtr)
|
||||
{
|
||||
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
|
||||
HashNumber(aPtr),
|
||||
aPtr);
|
||||
if (hep && *hep) {
|
||||
return &(static_cast<SerialNumberRecord*>((*hep)->value)->refCount);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
|
||||
static int32_t*
|
||||
GetCOMPtrCount(void* aPtr)
|
||||
{
|
||||
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
|
||||
HashNumber(aPtr),
|
||||
aPtr);
|
||||
if (hep && *hep) {
|
||||
return &(static_cast<SerialNumberRecord*>((*hep)->value)->COMPtrCount);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
|
||||
|
||||
static void
|
||||
RecycleSerialNumberPtr(void* aPtr)
|
||||
{
|
||||
PL_HashTableRemove(gSerialNumbers, aPtr);
|
||||
gSerialNumbers->Remove(aPtr);
|
||||
}
|
||||
|
||||
static bool
|
||||
LogThisObj(intptr_t aSerialNumber)
|
||||
{
|
||||
return (bool)PL_HashTableLookup(gObjectsToLog, (const void*)aSerialNumber);
|
||||
return gObjectsToLog->Contains(aSerialNumber);
|
||||
}
|
||||
|
||||
using EnvCharType = mozilla::filesystem::Path::value_type;
|
||||
|
@ -802,54 +677,33 @@ InitTraceLog()
|
|||
if (classes) {
|
||||
// if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
|
||||
// as a list of class names to track
|
||||
gTypesToLog = PL_NewHashTable(256,
|
||||
PL_HashString,
|
||||
PL_CompareStrings,
|
||||
PL_CompareValues,
|
||||
&typesToLogHashAllocOps, nullptr);
|
||||
if (!gTypesToLog) {
|
||||
NS_WARNING("out of memory");
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n");
|
||||
} else {
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: ");
|
||||
const char* cp = classes;
|
||||
for (;;) {
|
||||
char* cm = (char*)strchr(cp, ',');
|
||||
if (cm) {
|
||||
*cm = '\0';
|
||||
}
|
||||
PL_HashTableAdd(gTypesToLog, strdup(cp), (void*)1);
|
||||
fprintf(stdout, "%s ", cp);
|
||||
if (!cm) {
|
||||
break;
|
||||
}
|
||||
*cm = ',';
|
||||
cp = cm + 1;
|
||||
gTypesToLog = new CharPtrSet(256);
|
||||
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: ");
|
||||
const char* cp = classes;
|
||||
for (;;) {
|
||||
char* cm = (char*)strchr(cp, ',');
|
||||
if (cm) {
|
||||
*cm = '\0';
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
gTypesToLog->PutEntry(cp);
|
||||
fprintf(stdout, "%s ", cp);
|
||||
if (!cm) {
|
||||
break;
|
||||
}
|
||||
*cm = ',';
|
||||
cp = cm + 1;
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
gSerialNumbers = PL_NewHashTable(256,
|
||||
HashNumber,
|
||||
PL_CompareValues,
|
||||
PL_CompareValues,
|
||||
&serialNumberHashAllocOps, nullptr);
|
||||
|
||||
|
||||
gSerialNumbers = new SerialHash(256);
|
||||
}
|
||||
|
||||
const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS");
|
||||
if (objects) {
|
||||
gObjectsToLog = PL_NewHashTable(256,
|
||||
HashNumber,
|
||||
PL_CompareValues,
|
||||
PL_CompareValues,
|
||||
nullptr, nullptr);
|
||||
gObjectsToLog = new IntPtrSet(256);
|
||||
|
||||
if (!gObjectsToLog) {
|
||||
NS_WARNING("out of memory");
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n");
|
||||
} else if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
|
||||
if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
|
||||
} else {
|
||||
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- only logging these objects: ");
|
||||
|
@ -875,7 +729,7 @@ InitTraceLog()
|
|||
bottom = top;
|
||||
}
|
||||
for (intptr_t serialno = bottom; serialno <= top; serialno++) {
|
||||
PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1);
|
||||
gObjectsToLog->PutEntry(serialno);
|
||||
fprintf(stdout, "%" PRIdPTR " ", serialno);
|
||||
}
|
||||
if (!cm) {
|
||||
|
@ -1093,18 +947,17 @@ NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt,
|
|||
// Here's the case where MOZ_COUNT_CTOR was not used,
|
||||
// yet we still want to see creation information:
|
||||
|
||||
bool loggingThisType = (!gTypesToLog || LogThisType(aClass));
|
||||
bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
|
||||
intptr_t serialno = 0;
|
||||
if (gSerialNumbers && loggingThisType) {
|
||||
serialno = GetSerialNumber(aPtr, aRefcnt == 1);
|
||||
MOZ_ASSERT(serialno != 0,
|
||||
"Serial number requested for unrecognized pointer! "
|
||||
"Are you memmoving a refcounted object?");
|
||||
int32_t* count = GetRefCount(aPtr);
|
||||
if (count) {
|
||||
(*count)++;
|
||||
auto record = gSerialNumbers->Get(aPtr);
|
||||
if (record) {
|
||||
++record->refCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
|
||||
|
@ -1143,18 +996,17 @@ NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass)
|
|||
}
|
||||
}
|
||||
|
||||
bool loggingThisType = (!gTypesToLog || LogThisType(aClass));
|
||||
bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
|
||||
intptr_t serialno = 0;
|
||||
if (gSerialNumbers && loggingThisType) {
|
||||
serialno = GetSerialNumber(aPtr, false);
|
||||
MOZ_ASSERT(serialno != 0,
|
||||
"Serial number requested for unrecognized pointer! "
|
||||
"Are you memmoving a refcounted object?");
|
||||
int32_t* count = GetRefCount(aPtr);
|
||||
if (count) {
|
||||
(*count)--;
|
||||
auto record = gSerialNumbers->Get(aPtr);
|
||||
if (record) {
|
||||
--record->refCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
|
||||
|
@ -1202,7 +1054,7 @@ NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize)
|
|||
}
|
||||
}
|
||||
|
||||
bool loggingThisType = (!gTypesToLog || LogThisType(aType));
|
||||
bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
|
||||
intptr_t serialno = 0;
|
||||
if (gSerialNumbers && loggingThisType) {
|
||||
serialno = GetSerialNumber(aPtr, true);
|
||||
|
@ -1239,7 +1091,7 @@ NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize)
|
|||
}
|
||||
}
|
||||
|
||||
bool loggingThisType = (!gTypesToLog || LogThisType(aType));
|
||||
bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
|
||||
intptr_t serialno = 0;
|
||||
if (gSerialNumbers && loggingThisType) {
|
||||
serialno = GetSerialNumber(aPtr, false);
|
||||
|
@ -1285,16 +1137,13 @@ NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject)
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t* count = GetCOMPtrCount(object);
|
||||
if (count) {
|
||||
(*count)++;
|
||||
}
|
||||
|
||||
auto record = gSerialNumbers->Get(object);
|
||||
int32_t count = record ? ++record->COMPtrCount : -1;
|
||||
bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
|
||||
|
||||
if (gCOMPtrLog && loggingThisObject) {
|
||||
fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n",
|
||||
object, serialno, count ? (*count) : -1, aCOMPtr);
|
||||
object, serialno, count, aCOMPtr);
|
||||
WalkTheStackCached(gCOMPtrLog);
|
||||
}
|
||||
}
|
||||
|
@ -1326,16 +1175,13 @@ NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject)
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t* count = GetCOMPtrCount(object);
|
||||
if (count) {
|
||||
(*count)--;
|
||||
}
|
||||
|
||||
auto record = gSerialNumbers->Get(object);
|
||||
int32_t count = record ? --record->COMPtrCount : -1;
|
||||
bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
|
||||
|
||||
if (gCOMPtrLog && loggingThisObject) {
|
||||
fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n",
|
||||
object, serialno, count ? (*count) : -1, aCOMPtr);
|
||||
object, serialno, count, aCOMPtr);
|
||||
WalkTheStackCached(gCOMPtrLog);
|
||||
}
|
||||
}
|
||||
|
@ -1346,22 +1192,10 @@ void
|
|||
nsTraceRefcnt::Shutdown()
|
||||
{
|
||||
gCodeAddressService = nullptr;
|
||||
if (gBloatView) {
|
||||
PL_HashTableDestroy(gBloatView);
|
||||
gBloatView = nullptr;
|
||||
}
|
||||
if (gTypesToLog) {
|
||||
PL_HashTableDestroy(gTypesToLog);
|
||||
gTypesToLog = nullptr;
|
||||
}
|
||||
if (gObjectsToLog) {
|
||||
PL_HashTableDestroy(gObjectsToLog);
|
||||
gObjectsToLog = nullptr;
|
||||
}
|
||||
if (gSerialNumbers) {
|
||||
PL_HashTableDestroy(gSerialNumbers);
|
||||
gSerialNumbers = nullptr;
|
||||
}
|
||||
gBloatView = nullptr;
|
||||
gTypesToLog = nullptr;
|
||||
gObjectsToLog = nullptr;
|
||||
gSerialNumbers = nullptr;
|
||||
maybeUnregisterAndCloseFile(gBloatLog);
|
||||
maybeUnregisterAndCloseFile(gRefcntsLog);
|
||||
maybeUnregisterAndCloseFile(gAllocLog);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "nsUnicharUtils.h"
|
||||
#include "nsPointerHashKeys.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -54,6 +55,7 @@ HashString(const nsACString& aStr)
|
|||
* nsUint32HashKey
|
||||
* nsUint64HashKey
|
||||
* nsFloatHashKey
|
||||
* IntPtrHashKey
|
||||
* nsPtrHashKey
|
||||
* nsClearingPtrHashKey
|
||||
* nsVoidPtrHashKey
|
||||
|
@ -284,6 +286,35 @@ private:
|
|||
const float mValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* hashkey wrapper using intptr_t KeyType
|
||||
*
|
||||
* @see nsTHashtable::EntryType for specification
|
||||
*/
|
||||
class IntPtrHashKey : public PLDHashEntryHdr
|
||||
{
|
||||
public:
|
||||
typedef const intptr_t& KeyType;
|
||||
typedef const intptr_t* KeyTypePointer;
|
||||
|
||||
explicit IntPtrHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
|
||||
IntPtrHashKey(const IntPtrHashKey& aToCopy) : mValue(aToCopy.mValue) {}
|
||||
~IntPtrHashKey() {}
|
||||
|
||||
KeyType GetKey() const { return mValue; }
|
||||
bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
|
||||
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey)
|
||||
{
|
||||
return mozilla::HashGeneric(*aKey);
|
||||
}
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
private:
|
||||
const intptr_t mValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* hashkey wrapper using nsISupports* KeyType
|
||||
*
|
||||
|
|
|
@ -29,32 +29,30 @@ interface nsIThreadManager : nsISupports
|
|||
{
|
||||
/**
|
||||
* Default number of bytes reserved for a thread's stack, if no stack size
|
||||
* is specified in newThread(). 0 means use platform default.
|
||||
*/
|
||||
const unsigned long DEFAULT_STACK_SIZE = 0;
|
||||
|
||||
%{C++
|
||||
/* DEFAULT_STACK_SIZE can be a little overzealous for many platforms. On
|
||||
* Linux and OS X, for instance, the default thread stack size is whatever
|
||||
* getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. The
|
||||
* default on Windows is 1MB, which is a little more reasonable. But
|
||||
* for thread pools, each individual thread often doesn't need that much
|
||||
* stack space.
|
||||
* is specified in newThread().
|
||||
*
|
||||
* We therefore have a separate setting for a reasonable stack size for
|
||||
* a thread pool worker thread.
|
||||
* Defaults can be a little overzealous for many platforms.
|
||||
*
|
||||
* On Linux and OS X, for instance, the default thread stack size is whatever
|
||||
* getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. Or, on Linux,
|
||||
* if the stack size is unlimited, we fall back to 2MB. This causes particular
|
||||
* problems on Linux, which allocates 2MB huge VM pages, and will often
|
||||
* immediately allocate them for any stacks which are 2MB or larger.
|
||||
*
|
||||
* The default on Windows is 1MB, which is a little more reasonable. But the
|
||||
* vast majority of our threads don't need anywhere near that much space.
|
||||
*
|
||||
* ASan and TSan builds, however, often need a bit more, so give them a the
|
||||
* platform default.
|
||||
*/
|
||||
%{C++
|
||||
#if defined(MOZ_ASAN) || defined(MOZ_TSAN)
|
||||
// Use the system default in ASAN builds, because the default is assumed
|
||||
// to be larger than the size we want to use and is hopefully sufficient
|
||||
// for ASAN.
|
||||
static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE;
|
||||
#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)
|
||||
static const uint32_t kThreadPoolStackSize = (256 * 1024);
|
||||
static constexpr uint32_t DEFAULT_STACK_SIZE = 0;
|
||||
#else
|
||||
// All other platforms use their system default.
|
||||
static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE;
|
||||
static constexpr uint32_t DEFAULT_STACK_SIZE = 256 * 1024;
|
||||
#endif
|
||||
|
||||
static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE;
|
||||
%}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +61,8 @@ interface nsIThreadManager : nsISupports
|
|||
* @param creationFlags
|
||||
* Reserved for future use. Pass 0.
|
||||
* @param stackSize
|
||||
* Number of bytes to reserve for the thread's stack.
|
||||
* Number of bytes to reserve for the thread's stack. 0 means use platform
|
||||
* default.
|
||||
*
|
||||
* @returns
|
||||
* The newly created nsIThread object.
|
||||
|
@ -77,7 +76,8 @@ interface nsIThreadManager : nsISupports
|
|||
* The name of the thread. Passing an empty name is equivalent to
|
||||
* calling newThread(0, stackSize), i.e. the thread will not be named.
|
||||
* @param stackSize
|
||||
* Number of bytes to reserve for the thread's stack.
|
||||
* Number of bytes to reserve for the thread's stack. 0 means use platform
|
||||
* default.
|
||||
*
|
||||
* @returns
|
||||
* The newly created nsIThread object.
|
||||
|
|
Загрузка…
Ссылка в новой задаче