Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/CodeGenUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ private static void fillInCommon(JSDescriptor.Builder builder, ScriptNode script
builder.hasRestArg = scriptOrFn.hasRestParameter();
builder.hasDefaultParameters = scriptOrFn.getDefaultParams() != null;

// Calculate arity (function.length) - count params before first default
builder.arity = FunctionNode.calculateFunctionArity(scriptOrFn);

builder.rawSourceStart = scriptOrFn.getRawSourceStart();
builder.rawSourceEnd = scriptOrFn.getRawSourceEnd();
}
Expand Down
9 changes: 9 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/JSDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public final class JSDescriptor<T extends ScriptOrFn<T>> implements Serializable
private final int languageVersion;
private final int paramAndVarCount;
private final int paramCount;
private final int arity; // number of params before first default for reporting as length
private final SecurityController securityController;
private final Object securityDomain;
private final int functionType;
Expand Down Expand Up @@ -72,6 +73,7 @@ public JSDescriptor(
int languageVersion,
int paramAndVarCount,
int paramCount,
int arity,
boolean hasDefaultParameters,
boolean requiresActivationFrame,
boolean requiresArgumentObject,
Expand Down Expand Up @@ -109,6 +111,7 @@ public JSDescriptor(
this.languageVersion = languageVersion;
this.paramAndVarCount = paramAndVarCount;
this.paramCount = paramCount;
this.arity = arity;
this.securityController = securityController;
this.securityDomain = securityDomain;
this.functionType = functionType;
Expand Down Expand Up @@ -195,6 +198,10 @@ public int getParamCount() {
return paramCount;
}

public int getArity() {
return arity;
}

public boolean getParamOrVarConst(int index) {
return paramIsConst[index];
}
Expand Down Expand Up @@ -302,6 +309,7 @@ public static class Builder<T extends ScriptOrFn<T>> {
public int languageVersion;
public int paramAndVarCount;
public int paramCount;
public int arity; // number of params before first default for reporting as length
public boolean hasDefaultParameters;
public boolean requiresActivationFrame;
public boolean requiresArgumentObject;
Expand Down Expand Up @@ -371,6 +379,7 @@ private JSDescriptor<T> build(JSDescriptor<?> parent, Consumer<JSDescriptor<?>>
languageVersion,
paramAndVarCount,
paramCount,
arity,
hasDefaultParameters,
requiresActivationFrame,
requiresArgumentObject,
Expand Down
8 changes: 4 additions & 4 deletions rhino/src/main/java/org/mozilla/javascript/JSFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public boolean isStrict() {

@Override
public int getArity() {
return getParamCount();
return descriptor.getArity();
}

public DebuggableScript getDebuggableView() {
Expand All @@ -61,14 +61,14 @@ protected int getLanguageVersion() {

@Override
public int getLength() {
int paramCount = getParamCount();
int arity = descriptor.getArity();
if (getLanguageVersion() != Context.VERSION_1_2) {
return paramCount;
return arity;
}
Context cx = Context.getContext();
NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this);
if (activation == null) {
return paramCount;
return arity;
}
return activation.originalArgs.length;
}
Expand Down
52 changes: 52 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ast/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -503,4 +503,56 @@ public void visit(NodeVisitor v) {
}
}
}

/**
* Calculate the arity (function.length) for a function. According to ECMAScript spec, the
* length property should only count parameters up to (but not including) the first one with a
* default value.
*
* <p>Ref: ECMA 2026, 15.1.5 Static Semantics: ExpectedArgumentCount
*
* @param scriptOrFn the function or script node
* @return the arity (number of parameters before first default parameter)
*/
public static int calculateFunctionArity(ScriptNode scriptOrFn) {
int paramCount = scriptOrFn.getParamCount();
int arity = paramCount;

if (scriptOrFn instanceof FunctionNode) {
FunctionNode fnNode = (FunctionNode) scriptOrFn;
java.util.List<Object> defaultParams = fnNode.getDefaultParams();

if (defaultParams != null && !defaultParams.isEmpty()) {
// defaultParams stores pairs: [paramName (String), defaultValue (AstNode), ...]
// count up to the first parameter that has a default value
java.util.List<AstNode> params = fnNode.getParams();
if (params != null && !params.isEmpty()) {
for (int i = 0; i < defaultParams.size(); i += 2) {
if (defaultParams.get(i) instanceof String) {
String paramWithDefault = (String) defaultParams.get(i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would just declare this before the if above


for (int paramIndex = 0; paramIndex < params.size(); paramIndex++) {
AstNode param = params.get(paramIndex);
if (param instanceof Name) {
String paramName = ((Name) param).getIdentifier();
if (paramName.equals(paramWithDefault)) {
arity = paramIndex;
break;
}
}
}
if (arity != paramCount) break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need this if - the only time you assign arity is at like 538, which is already followed by a break

}
}
}
}
}

// Rest parameters don't count toward length
if (scriptOrFn.hasRestParameter()) {
arity = Math.max(0, arity - 1);
}

return arity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ private void buildDescriptor(
cfw.addLoadConstant(builder.languageVersion);
cfw.addLoadConstant(builder.paramAndVarCount);
cfw.addLoadConstant(builder.paramCount);
cfw.addLoadConstant(builder.arity);
cfw.add(builder.hasDefaultParameters ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
cfw.add(builder.requiresActivationFrame ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
cfw.add(builder.requiresArgumentObject ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,4 +463,28 @@ public void parserThrowsOnLowerLanguageLevelArrowBasic() throws Exception {
Utils.assertEvaluatorException_1_8(
"Default values are only supported in version >= 200", script + "()");
}

// Test that length property reflects the number of parameters without defaults
@Test
public void arrowFunctionLengthSimple() throws Exception {
Utils.assertWithAllModes_ES6(1, "var f = (a, b = 39) => a; f.length");
}

@Test
public void arrowFunctionLengthWithDefaultParameters() throws Exception {
final String script =
"var callCount = 0;\n"
+ "var ref;\n"
+ "ref = (a, b = 39,) => {\n"
+ " if (a !== 42) throw 'a should be 42';\n"
+ " if (b !== 39) throw 'b should be 39';\n"
+ " callCount = callCount + 1;\n"
+ "};\n"
+ "\n"
+ "ref(42, undefined, 1);\n"
+ "if (callCount !== 1) throw 'arrow function not invoked exactly once';\n"
+ "ref.length;";

Utils.assertWithAllModes_ES6(1, script);
}
}
27 changes: 6 additions & 21 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3572,7 +3572,7 @@ language/expressions/array 22/52 (42.31%)
spread-sngl-iter.js
spread-sngl-literal.js

language/expressions/arrow-function 150/343 (43.73%)
language/expressions/arrow-function 148/343 (43.15%)
dstr/ary-init-iter-close.js
dstr/ary-init-iter-get-err.js
dstr/ary-init-iter-get-err-array-prototype.js
Expand Down Expand Up @@ -3704,9 +3704,7 @@ language/expressions/arrow-function 150/343 (43.73%)
dflt-params-duplicates.js non-strict
dflt-params-ref-later.js
dflt-params-ref-self.js
dflt-params-trailing-comma.js
eval-var-scope-syntax-err.js non-strict
length-dflt.js
lexical-new.target.js {unsupported: [new.target]}
lexical-new.target-closure-returned.js {unsupported: [new.target]}
lexical-super-call-from-within-constructor.js
Expand Down Expand Up @@ -4187,7 +4185,7 @@ language/expressions/equals 0/47 (0.0%)

language/expressions/exponentiation 0/44 (0.0%)

language/expressions/function 149/264 (56.44%)
language/expressions/function 147/264 (55.68%)
dstr/ary-init-iter-close.js
dstr/ary-init-iter-get-err.js
dstr/ary-init-iter-get-err-array-prototype.js
Expand Down Expand Up @@ -4305,9 +4303,7 @@ language/expressions/function 149/264 (56.44%)
dflt-params-ref-later.js
dflt-params-ref-self.js
dflt-params-rest.js
dflt-params-trailing-comma.js
eval-var-scope-syntax-err.js non-strict
length-dflt.js
name-arguments-strict-body.js non-strict
named-no-strict-reassign-fn-name-in-body.js non-strict
named-no-strict-reassign-fn-name-in-body-in-arrow.js non-strict
Expand Down Expand Up @@ -4335,7 +4331,7 @@ language/expressions/function 149/264 (56.44%)
unscopables-with.js non-strict
unscopables-with-in-nested-fn.js non-strict

language/expressions/generators 179/290 (61.72%)
language/expressions/generators 177/290 (61.03%)
dstr/ary-init-iter-close.js
dstr/ary-init-iter-get-err.js
dstr/ary-init-iter-get-err-array-prototype.js
Expand Down Expand Up @@ -4476,11 +4472,9 @@ language/expressions/generators 179/290 (61.72%)
dflt-params-ref-later.js
dflt-params-ref-self.js
dflt-params-rest.js
dflt-params-trailing-comma.js
eval-body-proto-realm.js
eval-var-scope-syntax-err.js non-strict
has-instance.js
length-dflt.js
named-no-strict-reassign-fn-name-in-body.js non-strict
named-no-strict-reassign-fn-name-in-body-in-arrow.js non-strict
named-no-strict-reassign-fn-name-in-body-in-eval.js non-strict
Expand Down Expand Up @@ -4641,7 +4635,7 @@ language/expressions/new 22/59 (37.29%)

~language/expressions/new.target

language/expressions/object 688/1170 (58.8%)
language/expressions/object 683/1170 (58.38%)
dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]}
dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]}
dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]}
Expand Down Expand Up @@ -5233,7 +5227,6 @@ language/expressions/object 688/1170 (58.8%)
method-definition/gen-meth-dflt-params-ref-later.js
method-definition/gen-meth-dflt-params-ref-self.js
method-definition/gen-meth-dflt-params-rest.js
method-definition/gen-meth-dflt-params-trailing-comma.js
method-definition/gen-meth-eval-var-scope-syntax-err.js non-strict
method-definition/gen-meth-object-destructuring-param-strict-body.js
method-definition/gen-meth-rest-param-strict-body.js
Expand All @@ -5243,7 +5236,6 @@ language/expressions/object 688/1170 (58.8%)
method-definition/gen-yield-spread-arr-single.js
method-definition/gen-yield-spread-obj.js
method-definition/generator-invoke-fn-strict.js non-strict
method-definition/generator-length-dflt.js
method-definition/generator-param-init-yield.js non-strict
method-definition/generator-param-redecl-let.js
method-definition/generator-prop-name-yield-expr.js non-strict
Expand All @@ -5254,12 +5246,10 @@ language/expressions/object 688/1170 (58.8%)
method-definition/meth-dflt-params-ref-later.js
method-definition/meth-dflt-params-ref-self.js
method-definition/meth-dflt-params-rest.js
method-definition/meth-dflt-params-trailing-comma.js
method-definition/meth-eval-var-scope-syntax-err.js non-strict
method-definition/meth-object-destructuring-param-strict-body.js
method-definition/meth-rest-param-strict-body.js
method-definition/name-invoke-fn-strict.js non-strict
method-definition/name-length-dflt.js
method-definition/name-param-id-yield.js non-strict
method-definition/name-param-init-yield.js non-strict
method-definition/name-param-redecl.js
Expand Down Expand Up @@ -5325,7 +5315,6 @@ language/expressions/object 688/1170 (58.8%)
scope-setter-body-lex-distinc.js non-strict
scope-setter-paramsbody-var-open.js
setter-body-strict-inside.js non-strict
setter-length-dflt.js
setter-param-arguments-strict-inside.js non-strict
setter-param-eval-strict-inside.js non-strict
yield-non-strict-access.js non-strict
Expand Down Expand Up @@ -6680,7 +6669,7 @@ language/statements/for-of 438/751 (58.32%)
typedarray-backed-by-resizable-buffer-shrink-mid-iteration.js {unsupported: [resizable-arraybuffer]}
typedarray-backed-by-resizable-buffer-shrink-to-zero-mid-iteration.js {unsupported: [resizable-arraybuffer]}

language/statements/function 161/451 (35.7%)
language/statements/function 159/451 (35.25%)
dstr/ary-init-iter-close.js
dstr/ary-init-iter-get-err.js
dstr/ary-init-iter-get-err-array-prototype.js
Expand Down Expand Up @@ -6817,9 +6806,7 @@ language/statements/function 161/451 (35.7%)
dflt-params-ref-later.js
dflt-params-ref-self.js
dflt-params-rest.js
dflt-params-trailing-comma.js
eval-var-scope-syntax-err.js non-strict
length-dflt.js
name-arguments-strict-body.js non-strict
name-eval-strict-body.js non-strict
object-destructuring-param-strict-body.js
Expand All @@ -6840,7 +6827,7 @@ language/statements/function 161/451 (35.7%)
unscopables-with.js non-strict
unscopables-with-in-nested-fn.js non-strict

language/statements/generators 165/266 (62.03%)
language/statements/generators 163/266 (61.28%)
dstr/ary-init-iter-close.js
dstr/ary-init-iter-get-err.js
dstr/ary-init-iter-get-err-array-prototype.js
Expand Down Expand Up @@ -6981,10 +6968,8 @@ language/statements/generators 165/266 (62.03%)
dflt-params-ref-later.js
dflt-params-ref-self.js
dflt-params-rest.js
dflt-params-trailing-comma.js
eval-var-scope-syntax-err.js non-strict
has-instance.js
length-dflt.js
object-destructuring-param-strict-body.js
prototype-own-properties.js
prototype-value.js
Expand Down