If I compile Foxhound in debug mode, I encounter the following crash in the JS shell:
let t = String.tainted("abc"); let f = function(s) { for(let i=0; i < 100; i++) { let c = s.charAt(i%s.length); } }; baselineCompile(f); for (var i=0; i<1; i++) { f(t) }
[1119479] Assertion failure: !addedFailurePath_ (multiple failure paths for instruction), at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.h:459
#1: ???[./obj-tf-release/dist/bin/js +0x3754ea4]
#2: ???[./obj-tf-release/dist/bin/js +0x3754b40]
#3: ???[./obj-tf-release/dist/bin/js +0x342ef9e]
#4: ???[./obj-tf-release/dist/bin/js +0x342fba5]
#5: ???[./obj-tf-release/dist/bin/js +0x3422053]
#6: ???[./obj-tf-release/dist/bin/js +0x34156aa]
#7: ???[./obj-tf-release/dist/bin/js +0x3437861]
#8: ???[./obj-tf-release/dist/bin/js +0x33a5448]
#9: ??? (???:???)
Segmentation fault (core dumped)
In Node.js the following code executes normally:
let tt = "abc"; let ff = function(s) { for(let i=0; i < 100; i++) { let c = s.charAt(i%s.length); } }; for (var i=0; i<1; i++) { ff(tt) }
The backtrace is the following:
(gdb) bt
#0 0x0000562bc1d0ceb2 in js::jit::CacheRegisterAllocator::setAddedFailurePath() (this=0x7fff6850c360) at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.h:459
#1 0x0000562bc1d0cb40 in js::jit::CacheIRCompiler::addFailurePath(js::jit::FailurePath**) (this=0x7fff6850b8d8, failure=0x7fff6850b490) at /home/leen/project-foxhound/js/src/jit/CacheIRCompiler.cpp:1635
#2 0x0000562bc19e6f9e in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::StringOperandId, js::jit::Int32OperandId, js::jit::BaselineCacheIRCompiler::StringCharOutOfBounds)
(this=0x7fff6850b8d8, strId=..., indexId=..., outOfBounds=js::jit::BaselineCacheIRCompiler::StringCharOutOfBounds::Failure) at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:1240
#3 0x0000562bc19e7ba5 in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::StringOperandId, js::jit::Int32OperandId, bool) (this=0x7fff6850b8d8, strId=..., indexId=..., handleOOB=false)
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:1313
#4 0x0000562bc19da053 in js::jit::BaselineCacheIRCompiler::emitLoadStringCharResult(js::jit::CacheIRReader&) (this=0x7fff6850b8d8, reader=...)
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.h:157
#5 0x0000562bc19cd6aa in js::jit::BaselineCacheIRCompiler::compile() (this=0x7fff6850b8d8) at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:227
#6 0x0000562bc19ef861 in js::jit::AttachBaselineCacheIRStub(JSContext*, js::jit::CacheIRWriter const&, js::jit::CacheKind, JSScript*, js::jit::ICScript*, js::jit::ICFallbackStub*, char const*)
(cx=0x7f5f6793a100, writer=..., kind=js::jit::CacheKind::Call, outerScript=0xf6bcfc8e060, icScript=0x7f5f667bed70, stub=0x7f5f667beee0, name=0x562bbeb2c388 "StringCharAt")
at /home/leen/project-foxhound/js/src/jit/BaselineCacheIRCompiler.cpp:2584
#7 0x0000562bc195d448 in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandleJS::Value)
(cx=0x7f5f6793a100, frame=0x7fff6850cdc8, stub=0x7f5f667beee0, argc=1, vp=0x7fff6850cd68, res=$JS::UndefinedValue()) at /home/leen/project-foxhound/js/src/jit/BaselineIC.cpp:1633
The issue seems to be the following code:
bool BaselineCacheIRCompiler::emitLoadStringCharResult(
StringOperandId strId, Int32OperandId indexId,
StringCharOutOfBounds outOfBounds) {
AutoOutputRegister output(*this);
Register str = allocator.useRegister(masm, strId);
Register index = allocator.useRegister(masm, indexId);
AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
AutoScratchRegister scratch3(allocator, masm);
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
// Foxhound: also check whether the string is tainted
masm.branchPtr(Assembler::NotEqual,
Address(str, JSString::offsetOfTaint()),
ImmPtr(nullptr),
failure->label());
// Bounds check, load string char.
Label done;
Label tagResult;
Label loadFailed;
if (outOfBounds == StringCharOutOfBounds::Failure) {
FailurePath* failure;
if (!addFailurePath(&failure)) {
return false;
}
masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
scratch3, failure->label());
masm.loadStringChar(str, index, scratch2, scratch1, scratch3,
failure->label());
allocator.discardStack(masm);
} else {
// Discard the stack before jumping to |done|.
allocator.discardStack(masm);
if (outOfBounds == StringCharOutOfBounds::EmptyString) {
// Return the empty string for out-of-bounds access.
masm.movePtr(ImmGCPtr(cx_->names().empty_), scratch1);
} else {
// Return |undefined| for out-of-bounds access.
masm.moveValue(UndefinedValue(), output.valueReg());
}
// This CacheIR op is always preceded by |LinearizeForCharAccess|, so we're
// guaranteed to see no nested ropes.
masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
scratch3, &done);
masm.loadStringChar(str, index, scratch2, scratch1, scratch3, &loadFailed);
}
// Load StaticString for this char. For larger code units perform a VM call.
Label vmCall;
masm.lookupStaticString(scratch2, scratch1, cx_->staticStrings(), &vmCall);
masm.jump(&tagResult);
if (outOfBounds != StringCharOutOfBounds::Failure) {
masm.bind(&loadFailed);
masm.assumeUnreachable("loadStringChar can't fail for linear strings");
}
{
masm.bind(&vmCall);
AutoStubFrame stubFrame(*this);
stubFrame.enter(masm, scratch3);
masm.Push(scratch2);
using Fn = JSLinearString* (*)(JSContext*, int32_t);
callVM<Fn, js::StringFromCharCode>(masm);
stubFrame.leave(masm);
masm.storeCallPointerResult(scratch1);
}
if (outOfBounds != StringCharOutOfBounds::UndefinedValue) {
masm.bind(&tagResult);
masm.bind(&done);
masm.tagValue(JSVAL_TYPE_STRING, scratch1, output.valueReg());
} else {
masm.bind(&tagResult);
masm.tagValue(JSVAL_TYPE_STRING, scratch1, output.valueReg());
masm.bind(&done);
}
return true;
}
The issue seems to be that we register two failure paths.
If I compile Foxhound in debug mode, I encounter the following crash in the JS shell:
In Node.js the following code executes normally:
The backtrace is the following:
The issue seems to be the following code:
The issue seems to be that we register two failure paths.