Skip to content

Commit

Permalink
fix: mishandling of message parameter in runtime.newErrorObject
Browse files Browse the repository at this point in the history
runtime.newErrorObject is used to implement the Error constructor, and as such
it takes input from JavaScript via calls like `new Error('xxx')`. The actual
underlying error information is stored in an ottoError object, which is
constructed using newError. newError takes a couple of mandatory arguments,
then treats the remaining parameters (collected as `in ...interface{}`) as a
printf-style format string and parameter list, which it uses to populate the
message field of the returned ottoError object. newErrorObject was passing the
message parameter from the Error function exposed to JavaScript directly
through to newError as the first optional parameter, which led to it being
treated as a format string, which resulted in any code like `throw new
Error('%s')` behaving incorrectly, with the resultant error having a message
like "%!s(MISSING)".

This change fixes this behaviour in the least intrusive way I could find, and
adds some tests to make sure it doesn't come back.

The logic for newErrorObject and newErrorObjectError are very similar, so it
was tempting to try to merge them, but it appears they're used in somewhat
fragile ways with very little test coverage so I'll leave that as a problem
for another day.
  • Loading branch information
deoxxa committed Jul 6, 2023
1 parent 9221440 commit 67dbb5d
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 3 deletions.
17 changes: 17 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,20 @@ func TestErrorStackProperty(t *testing.T) {
is(v.String(), "TypeError: uh oh\n at A (test.js:2:29)\n at B (test.js:3:26)\n at C (test.js:4:26)\n at test.js:8:10\n")
})
}

func TestErrorMessageContainingFormatCharacters(t *testing.T) {
tt(t, func() {
test, tester := test()

tester.Set("F", func(call FunctionCall) Value {
return call.Otto.MakeCustomError(call.ArgumentList[0].String(), call.ArgumentList[1].String())
})

test("Error('literal percent-s: %s')", "Error: literal percent-s: %s")
test("new Error('literal percent-s: %s')", "Error: literal percent-s: %s")
test("F('TestError', 'literal percent-s: %s')", "TestError: literal percent-s: %s")
test("raise: throw Error('literal percent-s: %s')", "Error: literal percent-s: %s")
test("raise: throw new Error('literal percent-s: %s')", "Error: literal percent-s: %s")
test("raise: throw F('TestError', 'literal percent-s: %s')", "TestError: literal percent-s: %s")
})
}
6 changes: 3 additions & 3 deletions type_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package otto
func (rt *runtime) newErrorObject(name string, message Value, stackFramesToPop int) *object {
obj := rt.newClassObject(classErrorName)
if message.IsDefined() {
msg := message.string()
obj.defineProperty("message", stringValue(msg), 0o111, false)
obj.value = newError(rt, name, stackFramesToPop, msg)
err := newError(rt, name, stackFramesToPop, "%s", message.string())
obj.defineProperty("message", err.messageValue(), 0o111, false)
obj.value = err
} else {
obj.value = newError(rt, name, stackFramesToPop)
}
Expand Down

0 comments on commit 67dbb5d

Please sign in to comment.