1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.objects; 27 28 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31 import static jdk.nashorn.internal.runtime.Source.sourceFor; 32 33 import java.lang.invoke.MethodHandle; 34 import java.lang.invoke.MethodHandles; 35 import java.util.List; 36 import jdk.dynalink.linker.support.Lookup; 37 import jdk.nashorn.api.scripting.JSObject; 38 import jdk.nashorn.api.scripting.ScriptObjectMirror; 39 import jdk.nashorn.internal.objects.annotations.Attribute; 40 import jdk.nashorn.internal.objects.annotations.Constructor; 41 import jdk.nashorn.internal.objects.annotations.Function; 42 import jdk.nashorn.internal.objects.annotations.ScriptClass; 43 import jdk.nashorn.internal.parser.Parser; 44 import jdk.nashorn.internal.runtime.Context; 45 import jdk.nashorn.internal.runtime.JSType; 46 import jdk.nashorn.internal.runtime.ParserException; 47 import jdk.nashorn.internal.runtime.PropertyMap; 48 import jdk.nashorn.internal.runtime.ScriptEnvironment; 49 import jdk.nashorn.internal.runtime.ScriptFunction; 50 import jdk.nashorn.internal.runtime.ScriptObject; 51 import jdk.nashorn.internal.runtime.ScriptRuntime; 52 import jdk.nashorn.internal.runtime.linker.Bootstrap; 53 54 /** 55 * ECMA 15.3 Function Objects 56 * 57 * Note: instances of this class are never created. This class is not even a 58 * subclass of ScriptObject. But, we use this class to generate prototype and 59 * constructor for "Function". 60 */ 61 @ScriptClass("Function") 62 public final class NativeFunction { 63 64 /** apply arg converter handle */ 65 public static final MethodHandle TO_APPLY_ARGS = Lookup.findOwnStatic(MethodHandles.lookup(), "toApplyArgs", Object[].class, Object.class); 66 67 // initialized by nasgen 68 @SuppressWarnings("unused") 69 private static PropertyMap $nasgenmap$; 70 71 // do *not* create me! 72 private NativeFunction() { 73 throw new UnsupportedOperationException(); 74 } 75 76 /** 77 * ECMA 15.3.4.2 Function.prototype.toString ( ) 78 * 79 * @param self self reference 80 * @return string representation of Function 81 */ 82 @Function(attributes = Attribute.NOT_ENUMERABLE) 83 public static String toString(final Object self) { 84 if (!(self instanceof ScriptFunction)) { 85 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 86 } 87 return ((ScriptFunction)self).toSource(); 88 } 89 90 /** 91 * ECMA 15.3.4.3 Function.prototype.apply (thisArg, argArray) 92 * 93 * @param self self reference 94 * @param thiz {@code this} arg for apply 95 * @param array array of argument for apply 96 * @return result of apply 97 */ 98 @Function(attributes = Attribute.NOT_ENUMERABLE) 99 public static Object apply(final Object self, final Object thiz, final Object array) { 100 checkCallable(self); 101 final Object[] args = toApplyArgs(array); 102 103 if (self instanceof ScriptFunction) { 104 return ScriptRuntime.apply((ScriptFunction)self, thiz, args); 105 } else if (self instanceof ScriptObjectMirror) { 106 return ((JSObject)self).call(thiz, args); 107 } else if (self instanceof JSObject) { 108 final Global global = Global.instance(); 109 final Object result = ((JSObject) self).call(ScriptObjectMirror.wrap(thiz, global), 110 ScriptObjectMirror.wrapArray(args, global)); 111 return ScriptObjectMirror.unwrap(result, global); 112 } 113 throw new AssertionError("Should not reach here"); 114 } 115 116 /** 117 * Given an array-like object, converts it into a Java object array suitable for invocation of ScriptRuntime.apply 118 * or for direct invocation of the applied function. 119 * @param array the array-like object. Can be null in which case a zero-length array is created. 120 * @return the Java array 121 */ 122 public static Object[] toApplyArgs(final Object array) { 123 if (array instanceof NativeArguments) { 124 return ((NativeArguments)array).getArray().asObjectArray(); 125 } else if (array instanceof ScriptObject) { 126 // look for array-like object 127 final ScriptObject sobj = (ScriptObject)array; 128 final int n = lengthToInt(sobj.getLength()); 129 130 final Object[] args = new Object[n]; 131 for (int i = 0; i < args.length; i++) { 132 args[i] = sobj.get(i); 133 } 134 return args; 135 } else if (array instanceof Object[]) { 136 return (Object[])array; 137 } else if (array instanceof List) { 138 final List<?> list = (List<?>)array; 139 return list.toArray(new Object[0]); 140 } else if (array == null || array == UNDEFINED) { 141 return ScriptRuntime.EMPTY_ARRAY; 142 } else if (array instanceof JSObject) { 143 // look for array-like JSObject object 144 final JSObject jsObj = (JSObject)array; 145 final Object len = jsObj.hasMember("length")? jsObj.getMember("length") : Integer.valueOf(0); 146 final int n = lengthToInt(len); 147 148 final Object[] args = new Object[n]; 149 for (int i = 0; i < args.length; i++) { 150 args[i] = jsObj.hasSlot(i)? jsObj.getSlot(i) : UNDEFINED; 151 } 152 return args; 153 } else { 154 throw typeError("function.apply.expects.array"); 155 } 156 } 157 158 private static int lengthToInt(final Object len) { 159 final long ln = JSType.toUint32(len); 160 // NOTE: ECMASCript 5.1 section 15.3.4.3 says length should be treated as Uint32, but we wouldn't be able to 161 // allocate a Java array of more than MAX_VALUE elements anyway, so at this point we have to throw an error. 162 // People applying a function to more than 2^31 arguments will unfortunately be out of luck. 163 if (ln > Integer.MAX_VALUE) { 164 throw rangeError("range.error.inappropriate.array.length", JSType.toString(len)); 165 } 166 return (int)ln; 167 } 168 169 private static void checkCallable(final Object self) { 170 if (!(self instanceof ScriptFunction || (self instanceof JSObject && ((JSObject)self).isFunction()))) { 171 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 172 } 173 } 174 175 /** 176 * ECMA 15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, ... ] ] ) 177 * 178 * @param self self reference 179 * @param args arguments for call 180 * @return result of call 181 */ 182 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 183 public static Object call(final Object self, final Object... args) { 184 checkCallable(self); 185 186 final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; 187 Object[] arguments; 188 189 if (args.length > 1) { 190 arguments = new Object[args.length - 1]; 191 System.arraycopy(args, 1, arguments, 0, arguments.length); 192 } else { 193 arguments = ScriptRuntime.EMPTY_ARRAY; 194 } 195 196 if (self instanceof ScriptFunction) { 197 return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments); 198 } else if (self instanceof JSObject) { 199 return ((JSObject)self).call(thiz, arguments); 200 } 201 202 throw new AssertionError("should not reach here"); 203 } 204 205 /** 206 * ECMA 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, ...]]) 207 * 208 * @param self self reference 209 * @param args arguments for bind 210 * @return function with bound arguments 211 */ 212 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 213 public static Object bind(final Object self, final Object... args) { 214 final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; 215 216 Object[] arguments; 217 if (args.length > 1) { 218 arguments = new Object[args.length - 1]; 219 System.arraycopy(args, 1, arguments, 0, arguments.length); 220 } else { 221 arguments = ScriptRuntime.EMPTY_ARRAY; 222 } 223 224 return Bootstrap.bindCallable(self, thiz, arguments); 225 } 226 227 /** 228 * Nashorn extension: Function.prototype.toSource 229 * 230 * @param self self reference 231 * @return source for function 232 */ 233 @Function(attributes = Attribute.NOT_ENUMERABLE) 234 public static String toSource(final Object self) { 235 if (!(self instanceof ScriptFunction)) { 236 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 237 } 238 return ((ScriptFunction)self).toSource(); 239 } 240 241 /** 242 * ECMA 15.3.2.1 new Function (p1, p2, ... , pn, body) 243 * 244 * Constructor 245 * 246 * @param newObj is the new operator used for constructing this function 247 * @param self self reference 248 * @param args arguments 249 * @return new NativeFunction 250 */ 251 @Constructor(arity = 1) 252 public static ScriptFunction function(final boolean newObj, final Object self, final Object... args) { 253 final StringBuilder sb = new StringBuilder(); 254 255 sb.append("(function ("); 256 final String funcBody; 257 if (args.length > 0) { 258 final StringBuilder paramListBuf = new StringBuilder(); 259 for (int i = 0; i < args.length - 1; i++) { 260 paramListBuf.append(JSType.toString(args[i])); 261 if (i < args.length - 2) { 262 paramListBuf.append(","); 263 } 264 } 265 266 // now convert function body to a string 267 funcBody = JSType.toString(args[args.length - 1]); 268 269 final String paramList = paramListBuf.toString(); 270 if (!paramList.isEmpty()) { 271 checkFunctionParameters(paramList); 272 sb.append(paramList); 273 } 274 } else { 275 funcBody = null; 276 } 277 278 sb.append(") {\n"); 279 if (args.length > 0) { 280 checkFunctionBody(funcBody); 281 sb.append(funcBody); 282 sb.append('\n'); 283 } 284 sb.append("})"); 285 286 final Global global = Global.instance(); 287 final Context context = global.getContext(); 288 return (ScriptFunction)context.eval(global, sb.toString(), global, "<function>"); 289 } 290 291 private static void checkFunctionParameters(final String params) { 292 final Parser parser = getParser(params); 293 try { 294 parser.parseFormalParameterList(); 295 } catch (final ParserException pe) { 296 pe.throwAsEcmaException(); 297 } 298 } 299 300 private static void checkFunctionBody(final String funcBody) { 301 final Parser parser = getParser(funcBody); 302 try { 303 parser.parseFunctionBody(); 304 } catch (final ParserException pe) { 305 pe.throwAsEcmaException(); 306 } 307 } 308 309 private static Parser getParser(final String sourceText) { 310 final ScriptEnvironment env = Global.getEnv(); 311 return new Parser(env, sourceFor("<function>", sourceText), new Context.ThrowErrorManager(), env._strict, null); 312 } 313 }