diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index fb1c7227f1..3a68d4da71 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -2696,13 +2696,27 @@ public static Object applyOrCall(boolean isApply, return function.call(cx, scope, callThis, callArgs); } + + /** + * @return true if the passed in Scriptable looks like an array + */ + private static boolean isArrayLike(Scriptable obj) + { + return obj != null && ( + obj instanceof NativeArray || + obj instanceof Arguments || + ScriptableObject.hasProperty(obj, "length") + ); + } static Object[] getApplyArguments(Context cx, Object arg1) { if (arg1 == null || arg1 == Undefined.instance) { return ScriptRuntime.emptyArgs; - } else if (arg1 instanceof NativeArray || arg1 instanceof Arguments) { + } else if ( arg1 instanceof Scriptable && isArrayLike((Scriptable) arg1) ) { return cx.getElements((Scriptable) arg1); + } else if( arg1 instanceof ScriptableObject ) { + return ScriptRuntime.emptyArgs; } else { throw ScriptRuntime.typeError0("msg.arg.isnt.array"); } diff --git a/testsrc/org/mozilla/javascript/tests/es5/FunctionApplyArrayLikeArguments.java b/testsrc/org/mozilla/javascript/tests/es5/FunctionApplyArrayLikeArguments.java new file mode 100644 index 0000000000..e8c83da6fa --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/es5/FunctionApplyArrayLikeArguments.java @@ -0,0 +1,72 @@ +/* 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/. */ + +/* + * Tests for Function.prototype.apply method + */ +package org.mozilla.javascript.tests.es5; +import org.mozilla.javascript.*; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import org.junit.Before; +import org.junit.After; + +public class FunctionApplyArrayLikeArguments { + + private static final int[] OPTIMIZATIONS = {-1, 0, 1}; + + private Scriptable m_scope; + + @Before + public void init() { + Context cx = Context.enter(); + cx.setOptimizationLevel(-1); + m_scope = cx.initStandardObjects(); + } + + @After + public void cleanup() { + Context.exit(); + } + + private void test(String testCode, Object expected) { + Object result = null; + try{ + result = eval(testCode); + } catch(Exception e) { + result = "EXCEPTIONCAUGHT"; + } + assertEquals(expected, result); + } + + private Object eval(String source) { + Context cx = Context.getCurrentContext(); + return cx.evaluateString(m_scope, source, "source", 1, null); + + } + + @Test + public void testArrayLikeArgumentsOfFunctionApply() { + for(int optIdx = 0; optIdx < OPTIMIZATIONS.length; optIdx++) { + test("function test() { return arguments[0]; }"+ + "test.apply(this, {});", Undefined.instance); + + test("function test() { return arguments[0]; }"+ + "test.apply(this, {'length':1, '0':'banana'});", "banana"); + + test("function test() { return arguments[0]; }"+ + "test.apply(this, {'length':'1', '0':'lala'});", "lala"); + + test("function test() { return arguments[0]; }"+ + "test.apply(2,2);", "EXCEPTIONCAUGHT"); + + test("function test() { return arguments[0]; }"+ + "test.apply(this,{'length':'abc', '0':'banana'});", Undefined.instance); + + test("function test() { return arguments[0]; }"+ + "test.apply(this,{'length':function(){return 1;}, '0':'banana'});", Undefined.instance); + } + } +}