Bug 1286009 - Distinguish failure reasons of JS_{En,De}codeScript. r=luke

This commit is contained in:
Nicolas B. Pierron 2016-10-12 12:13:22 +00:00
parent 97b9917978
commit c4ddfd637a
11 changed files with 212 additions and 117 deletions

View File

@ -14,6 +14,5 @@ var test = (function () {
try {
evalWithCache(test, {});
} catch (x) {
assertEq(x.message.includes("AsmJS"), true);
assertEq(x.message.includes("XDR"), true);
assertEq(x.message.includes("Asm.js is not supported by XDR"), true);
}

View File

@ -162,7 +162,6 @@ MSG_DEF(JSMSG_USER_DEFINED_ERROR, 0, JSEXN_ERR, "JS_ReportError was called"
// Internal errors
MSG_DEF(JSMSG_ALLOC_OVERFLOW, 0, JSEXN_INTERNALERR, "allocation size overflow")
MSG_DEF(JSMSG_BAD_BUILD_ID, 0, JSEXN_INTERNALERR, "bad build ID for XDR script")
MSG_DEF(JSMSG_BAD_BYTECODE, 1, JSEXN_INTERNALERR, "unimplemented JavaScript bytecode {0}")
MSG_DEF(JSMSG_BUFFER_TOO_SMALL, 0, JSEXN_INTERNALERR, "buffer too small")
MSG_DEF(JSMSG_BUILD_ID_NOT_AVAILABLE, 0, JSEXN_INTERNALERR, "build ID is not available")

View File

@ -26,13 +26,17 @@ FreezeThaw(JSContext* cx, JS::HandleScript script)
// freeze
uint32_t nbytes;
void* memory = JS_EncodeScript(cx, script, &nbytes);
if (!memory)
void* memory = nullptr;
TranscodeResult rs = JS_EncodeScript(cx, script, &nbytes, &memory);
if (rs != TranscodeResult_Ok)
return nullptr;
// thaw
JSScript* script2 = JS_DecodeScript(cx, memory, nbytes);
JS::RootedScript script2(cx);
rs = JS_DecodeScript(cx, memory, nbytes, &script2);
js_free(memory);
if (rs != TranscodeResult_Ok)
return nullptr;
return script2;
}

View File

@ -6592,44 +6592,50 @@ JS::detail::AssertArgumentsAreSane(JSContext* cx, HandleValue value)
}
#endif /* JS_DEBUG */
JS_PUBLIC_API(void*)
JS_EncodeScript(JSContext* cx, HandleScript scriptArg, uint32_t* lengthp)
JS_PUBLIC_API(TranscodeResult)
JS_EncodeScript(JSContext* cx, HandleScript scriptArg,
uint32_t* lengthp, void** buffer)
{
XDREncoder encoder(cx);
RootedScript script(cx, scriptArg);
if (!encoder.codeScript(&script))
return nullptr;
return encoder.forgetData(lengthp);
*buffer = nullptr;
if (encoder.codeScript(&script))
*buffer = encoder.forgetData(lengthp);
MOZ_ASSERT(bool(*buffer) == (encoder.resultCode() == TranscodeResult_Ok));
return encoder.resultCode();
}
JS_PUBLIC_API(void*)
JS_EncodeInterpretedFunction(JSContext* cx, HandleObject funobjArg, uint32_t* lengthp)
JS_PUBLIC_API(TranscodeResult)
JS_EncodeInterpretedFunction(JSContext* cx, HandleObject funobjArg,
uint32_t* lengthp, void** buffer)
{
XDREncoder encoder(cx);
RootedFunction funobj(cx, &funobjArg->as<JSFunction>());
if (!encoder.codeFunction(&funobj))
return nullptr;
return encoder.forgetData(lengthp);
*buffer = nullptr;
if (encoder.codeFunction(&funobj))
*buffer = encoder.forgetData(lengthp);
MOZ_ASSERT(bool(*buffer) == (encoder.resultCode() == TranscodeResult_Ok));
return encoder.resultCode();
}
JS_PUBLIC_API(JSScript*)
JS_DecodeScript(JSContext* cx, const void* data, uint32_t length)
JS_PUBLIC_API(TranscodeResult)
JS_DecodeScript(JSContext* cx, const void* data, uint32_t length,
JS::MutableHandleScript scriptp)
{
XDRDecoder decoder(cx, data, length);
RootedScript script(cx);
if (!decoder.codeScript(&script))
return nullptr;
return script;
decoder.codeScript(scriptp);
MOZ_ASSERT(bool(scriptp) == (decoder.resultCode() == TranscodeResult_Ok));
return decoder.resultCode();
}
JS_PUBLIC_API(JSObject*)
JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length)
JS_PUBLIC_API(TranscodeResult)
JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length,
JS::MutableHandleFunction funp)
{
XDRDecoder decoder(cx, data, length);
RootedFunction funobj(cx);
if (!decoder.codeFunction(&funobj))
return nullptr;
return funobj;
decoder.codeFunction(funp);
MOZ_ASSERT(bool(funp) == (decoder.resultCode() == TranscodeResult_Ok));
return decoder.resultCode();
}
JS_PUBLIC_API(void)

View File

@ -5836,17 +5836,38 @@ class MOZ_RAII AutoHideScriptedCaller
* Encode/Decode interpreted scripts and functions to/from memory.
*/
extern JS_PUBLIC_API(void*)
JS_EncodeScript(JSContext* cx, JS::HandleScript script, uint32_t* lengthp);
enum TranscodeResult
{
// Successful encoding / decoding.
TranscodeResult_Ok = 0,
extern JS_PUBLIC_API(void*)
JS_EncodeInterpretedFunction(JSContext* cx, JS::HandleObject funobj, uint32_t* lengthp);
// A warning message, is set to the message out-param.
TranscodeResult_Failure = 0x100,
TranscodeResult_Failure_BadBuildId = TranscodeResult_Failure | 0x1,
TranscodeResult_Failure_RunOnceNotSupported = TranscodeResult_Failure | 0x2,
TranscodeResult_Failure_AsmJSNotSupported = TranscodeResult_Failure | 0x3,
TranscodeResult_Failure_UnknownClassKind = TranscodeResult_Failure | 0x4,
extern JS_PUBLIC_API(JSScript*)
JS_DecodeScript(JSContext* cx, const void* data, uint32_t length);
// A error, the JSContext has a pending exception.
TranscodeResult_Throw = 0x200
};
extern JS_PUBLIC_API(TranscodeResult)
JS_EncodeScript(JSContext* cx, JS::HandleScript script,
uint32_t* lengthp, void** buffer);
extern JS_PUBLIC_API(TranscodeResult)
JS_EncodeInterpretedFunction(JSContext* cx, JS::HandleObject funobj,
uint32_t* lengthp, void** buffer);
extern JS_PUBLIC_API(TranscodeResult)
JS_DecodeScript(JSContext* cx, const void* data, uint32_t length,
JS::MutableHandleScript scriptp);
extern JS_PUBLIC_API(TranscodeResult)
JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length,
JS::MutableHandleFunction funp);
extern JS_PUBLIC_API(JSObject*)
JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length);
namespace js {

View File

@ -358,10 +358,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
if (!comp->creationOptions().cloneSingletons() ||
!comp->behaviors().getSingletonsAsTemplates())
{
JS_ReportErrorASCII(cx,
"Can't serialize a run-once non-function script "
"when we're not doing singleton cloning");
return false;
return xdr->fail(TranscodeResult_Failure_RunOnceNotSupported);
}
}
}
@ -795,8 +792,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
funEnclosingScope = function->nonLazyScript()->enclosingScope();
} else {
MOZ_ASSERT(function->isAsmJSNative());
JS_ReportErrorASCII(cx, "AsmJS modules are not yet supported in XDR serialization.");
return false;
return xdr->fail(TranscodeResult_Failure_AsmJSNotSupported);
}
funEnclosingScopeIndex = FindScopeIndex(script, *funEnclosingScope);
@ -831,7 +827,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
default: {
MOZ_ASSERT(false, "Unknown class kind.");
return false;
return xdr->fail(TranscodeResult_Failure_UnknownClassKind);
}
}
}

View File

@ -1511,6 +1511,42 @@ CacheEntry_setBytecode(JSContext* cx, HandleObject cache, uint8_t* buffer, uint3
return true;
}
static bool
ConvertTranscodeResultToJSException(JSContext* cx, TranscodeResult rv)
{
switch (rv) {
case TranscodeResult_Ok:
return true;
default:
MOZ_FALLTHROUGH;
case TranscodeResult_Failure:
MOZ_ASSERT(!cx->isExceptionPending());
JS_ReportErrorASCII(cx, "generic warning");
return false;
case TranscodeResult_Failure_BadBuildId:
MOZ_ASSERT(!cx->isExceptionPending());
JS_ReportErrorASCII(cx, "the build-id does not match");
return false;
case TranscodeResult_Failure_RunOnceNotSupported:
MOZ_ASSERT(!cx->isExceptionPending());
JS_ReportErrorASCII(cx, "run-once script are not supported by XDR");
return false;
case TranscodeResult_Failure_AsmJSNotSupported:
MOZ_ASSERT(!cx->isExceptionPending());
JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
return false;
case TranscodeResult_Failure_UnknownClassKind:
MOZ_ASSERT(!cx->isExceptionPending());
JS_ReportErrorASCII(cx, "Unknown class kind, go fix it.");
return false;
case TranscodeResult_Throw:
MOZ_ASSERT(cx->isExceptionPending());
return false;
}
}
static bool
Evaluate(JSContext* cx, unsigned argc, Value* vp)
{
@ -1656,7 +1692,9 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
}
if (loadBytecode) {
script = JS_DecodeScript(cx, loadBuffer, loadLength);
TranscodeResult rv = JS_DecodeScript(cx, loadBuffer, loadLength, &script);
if (!ConvertTranscodeResultToJSException(cx, rv))
return false;
} else {
mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
(void) JS::Compile(cx, options, chars.start().get(), chars.length(), &script);
@ -1705,8 +1743,9 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
}
if (saveBytecode) {
saveBuffer = reinterpret_cast<uint8_t*>(JS_EncodeScript(cx, script, &saveLength));
if (!saveBuffer)
TranscodeResult rv = JS_EncodeScript(cx, script, &saveLength,
reinterpret_cast<void**>(&saveBuffer.rwget()));
if (!ConvertTranscodeResultToJSException(cx, rv))
return false;
}
}

View File

@ -58,6 +58,16 @@ XDRBuffer::grow(size_t n)
return true;
}
template<XDRMode mode>
void
XDRState<mode>::postProcessContextErrors(JSContext* cx)
{
if (cx->isExceptionPending()) {
MOZ_ASSERT(resultCode_ == TranscodeResult_Ok);
resultCode_ = TranscodeResult_Throw;
}
}
template<XDRMode mode>
bool
XDRState<mode>::codeChars(const Latin1Char* chars, size_t nchars)
@ -110,10 +120,8 @@ VersionCheck(XDRState<mode>* xdr)
if (!xdr->codeUint32(&buildIdLength))
return false;
if (mode == XDR_DECODE && buildIdLength != buildId.length()) {
JS_ReportErrorNumberASCII(xdr->cx(), GetErrorMessage, nullptr, JSMSG_BAD_BUILD_ID);
return false;
}
if (mode == XDR_DECODE && buildIdLength != buildId.length())
return xdr->fail(TranscodeResult_Failure_BadBuildId);
if (mode == XDR_ENCODE) {
if (!xdr->codeBytes(buildId.begin(), buildIdLength))
@ -131,11 +139,9 @@ VersionCheck(XDRState<mode>* xdr)
if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength))
return false;
if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength)) {
// We do not provide binary compatibility with older scripts.
JS_ReportErrorNumberASCII(xdr->cx(), GetErrorMessage, nullptr, JSMSG_BAD_BUILD_ID);
return false;
}
// We do not provide binary compatibility with older scripts.
if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength))
return xdr->fail(TranscodeResult_Failure_BadBuildId);
}
return true;
@ -143,18 +149,26 @@ VersionCheck(XDRState<mode>* xdr)
template<XDRMode mode>
bool
XDRState<mode>::codeFunction(MutableHandleFunction objp)
XDRState<mode>::codeFunction(MutableHandleFunction funp)
{
if (mode == XDR_DECODE)
objp.set(nullptr);
funp.set(nullptr);
else
MOZ_ASSERT(objp->nonLazyScript()->enclosingScope()->is<GlobalScope>());
MOZ_ASSERT(funp->nonLazyScript()->enclosingScope()->is<GlobalScope>());
if (!VersionCheck(this))
if (!VersionCheck(this)) {
postProcessContextErrors(cx());
return false;
}
RootedScope scope(cx(), &cx()->global()->emptyGlobalScope());
return XDRInterpretedFunction(this, scope, nullptr, objp);
if (!XDRInterpretedFunction(this, scope, nullptr, funp)) {
postProcessContextErrors(cx());
funp.set(nullptr);
return false;
}
return true;
}
template<XDRMode mode>
@ -166,10 +180,18 @@ XDRState<mode>::codeScript(MutableHandleScript scriptp)
else
MOZ_ASSERT(!scriptp->enclosingScope());
if (!VersionCheck(this))
if (!VersionCheck(this)) {
postProcessContextErrors(cx());
return false;
}
return XDRScript(this, nullptr, nullptr, nullptr, scriptp);
if (!XDRScript(this, nullptr, nullptr, nullptr, scriptp)) {
postProcessContextErrors(cx());
scriptp.set(nullptr);
return false;
}
return true;
}
template<XDRMode mode>

View File

@ -79,16 +79,28 @@ template <XDRMode mode>
class XDRState {
public:
XDRBuffer buf;
TranscodeResult resultCode_;
protected:
explicit XDRState(JSContext* cx)
: buf(cx) { }
: buf(cx), resultCode_(TranscodeResult_Ok) { }
public:
JSContext* cx() const {
return buf.cx();
}
// Record logical failures of XDR.
void postProcessContextErrors(JSContext* cx);
TranscodeResult resultCode() const {
return resultCode_;
}
bool fail(TranscodeResult code) {
MOZ_ASSERT(resultCode_ == TranscodeResult_Ok);
resultCode_ = code;
return false;
}
bool codeUint8(uint8_t* n) {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof *n);

View File

@ -29,31 +29,24 @@ ReadCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx,
if (NS_FAILED(rv))
return rv; // don't warn since NOT_AVAILABLE is an ok error
scriptp.set(JS_DecodeScript(cx, buf.get(), len));
if (!scriptp)
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
TranscodeResult code = JS_DecodeScript(cx, buf.get(), len, scriptp);
if (code == TranscodeResult_Ok)
return NS_OK;
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
}
nsresult
ReadCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
nsIPrincipal* systemPrincipal, JSFunction** functionp)
{
return NS_ERROR_FAILURE;
/* This doesn't actually work ...
nsAutoArrayPtr<char> buf;
uint32_t len;
nsresult rv = cache->GetBuffer(PromiseFlatCString(uri).get(),
getter_Transfers(buf), &len);
if (NS_FAILED(rv))
return rv; // don't warn since NOT_AVAILABLE is an ok error
JSObject* obj = JS_DecodeInterpretedFunction(cx, buf, len, nsJSPrincipals::get(systemPrincipal), nullptr);
if (!obj)
return NS_ERROR_OUT_OF_MEMORY;
JSFunction* function = JS_ValueToFunction(cx, OBJECT_TO_JSVAL(obj));
*functionp = function;
return NS_OK;*/
// This doesn't actually work ...
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
@ -63,14 +56,17 @@ WriteCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx,
MOZ_ASSERT(JS_GetScriptPrincipals(script) == nsJSPrincipals::get(systemPrincipal));
uint32_t size;
void* data = JS_EncodeScript(cx, script, &size);
if (!data) {
// JS_EncodeScript may have set a pending exception.
void* data = nullptr;
TranscodeResult code = JS_EncodeScript(cx, script, &size, &data);
if (code != TranscodeResult_Ok) {
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_FAILURE;
return NS_ERROR_OUT_OF_MEMORY;
}
MOZ_ASSERT(size);
MOZ_ASSERT(size && data);
nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), static_cast<char*>(data), size);
js_free(data);
return rv;
@ -80,19 +76,6 @@ nsresult
WriteCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
nsIPrincipal* systemPrincipal, JSFunction* function)
{
return NS_ERROR_FAILURE;
/* This doesn't actually work ...
uint32_t size;
void* data =
JS_EncodeInterpretedFunction(cx, JS_GetFunctionObject(function), &size);
if (!data) {
// JS_EncodeInterpretedFunction may have set a pending exception.
JS_ClearPendingException(cx);
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(size);
nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), static_cast<char*>(data), size);
js_free(data);
return rv;*/
// This doesn't actually work ...
return NS_ERROR_NOT_IMPLEMENTED;
}

View File

@ -1091,17 +1091,24 @@ WriteScriptOrFunction(nsIObjectOutputStream* stream, JSContext* cx,
uint32_t size;
void* data;
void* data = nullptr;
TranscodeResult code;
{
if (functionObj)
data = JS_EncodeInterpretedFunction(cx, functionObj, &size);
code = JS_EncodeInterpretedFunction(cx, functionObj, &size, &data);
else
data = JS_EncodeScript(cx, script, &size);
code = JS_EncodeScript(cx, script, &size, &data);
}
if (!data)
if (code != TranscodeResult_Ok) {
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
MOZ_ASSERT(size);
}
MOZ_ASSERT(size && data);
rv = stream->Write32(size);
if (NS_SUCCEEDED(rv))
rv = stream->WriteBytes(static_cast<char*>(data), size);
@ -1139,18 +1146,25 @@ ReadScriptOrFunction(nsIObjectInputStream* stream, JSContext* cx,
return rv;
{
TranscodeResult code;
if (scriptp) {
JSScript* script = JS_DecodeScript(cx, data, size);
if (!script)
rv = NS_ERROR_OUT_OF_MEMORY;
else
*scriptp = script;
Rooted<JSScript*> script(cx);
code = JS_DecodeScript(cx, data, size, &script);
if (code == TranscodeResult_Ok)
*scriptp = script.get();
} else {
JSObject* funobj = JS_DecodeInterpretedFunction(cx, data, size);
if (!funobj)
rv = NS_ERROR_OUT_OF_MEMORY;
else
*functionObjp = funobj;
Rooted<JSFunction*> funobj(cx);
code = JS_DecodeInterpretedFunction(cx, data, size, &funobj);
if (code == TranscodeResult_Ok)
*functionObjp = JS_GetFunctionObject(funobj.get());
}
if (code != TranscodeResult_Ok) {
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
}
}