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.internal.objects.annotations.Attribute; 39 import jdk.nashorn.internal.objects.annotations.Constructor; 40 import jdk.nashorn.internal.objects.annotations.Function; 41 import jdk.nashorn.internal.objects.annotations.ScriptClass; 42 import jdk.nashorn.internal.parser.Parser; 43 import jdk.nashorn.internal.runtime.Context; 44 import jdk.nashorn.internal.runtime.JSType; 45 import jdk.nashorn.internal.runtime.ParserException; 46 import jdk.nashorn.internal.runtime.PropertyMap; 47 import jdk.nashorn.internal.runtime.ScriptEnvironment; 48 import jdk.nashorn.internal.runtime.ScriptFunction; 49 import jdk.nashorn.internal.runtime.ScriptObject; 50 import jdk.nashorn.internal.runtime.ScriptRuntime; 51 import jdk.nashorn.internal.runtime.linker.Bootstrap; 52 53 /** 54 * ECMA 15.3 Function Objects 55 * 56 * Note: instances of this class are never created. This class is not even a 57 * subclass of ScriptObject. But, we use this class to generate prototype and 58 * constructor for "Function". 59 */ 60 @ScriptClass("Function") 61 public final class NativeFunction { 62 63 /** apply arg converter handle */ 64 public static final MethodHandle TO_APPLY_ARGS = Lookup.findOwnStatic(MethodHandles.lookup(), "toApplyArgs", Object[].class, Object.class); 65 66 // initialized by nasgen 67 @SuppressWarnings("unused") 68 private static PropertyMap $nasgenmap$; 69 70 // do *not* create me! 71 private NativeFunction() { 72 throw new UnsupportedOperationException(); 73 } 74 75 /** 76 * ECMA 15.3.4.2 Function.prototype.toString ( ) 77 * 78 * @param self self reference 79 * @return string representation of Function 80 */ 81 @Function(attributes = Attribute.NOT_ENUMERABLE) 82 public static String toString(final Object self) { 83 if (!(self instanceof ScriptFunction)) { 84 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 85 } 86 return ((ScriptFunction)self).toSource(); 87 } 88 89 /** 90 * ECMA 15.3.4.3 Function.prototype.apply (thisArg, argArray) 91 * 92 * @param self self reference 93 * @param thiz {@code this} arg for apply 94 * @param array array of argument for apply 95 * @return result of apply 96 */ 97 @Function(attributes = Attribute.NOT_ENUMERABLE) 98 public static Object apply(final Object self, final Object thiz, final Object array) { 99 checkCallable(self); 100 final Object[] args = toApplyArgs(array); 101 102 if (self instanceof ScriptFunction) { 103 return ScriptRuntime.apply((ScriptFunction)self, thiz, args); 104 } else if (self instanceof JSObject) { 105 return ((JSObject)self).call(thiz, args); 106 } 107 throw new AssertionError("Should not reach here"); 108 } 109 110 /** 111 * Given an array-like object, converts it into a Java object array suitable for invocation of ScriptRuntime.apply 112 * or for direct invocation of the applied function. 113 * @param array the array-like object. Can be null in which case a zero-length array is created. 114 * @return the Java array 115 */ 116 public static Object[] toApplyArgs(final Object array) { 117 if (array instanceof NativeArguments) { 118 return ((NativeArguments)array).getArray().asObjectArray(); 119 } else if (array instanceof ScriptObject) { 120 // look for array-like object 121 final ScriptObject sobj = (ScriptObject)array; 122 final int n = lengthToInt(sobj.getLength()); 123 124 final Object[] args = new Object[n]; 125 for (int i = 0; i < args.length; i++) { 126 args[i] = sobj.get(i); 127 } 128 return args; 129 } else if (array instanceof Object[]) { 130 return (Object[])array; 131 } else if (array instanceof List) { 132 final List<?> list = (List<?>)array; 133 return list.toArray(new Object[0]); 134 } else if (array == null || array == UNDEFINED) { 135 return ScriptRuntime.EMPTY_ARRAY; 136 } else if (array instanceof JSObject) { 137 // look for array-like JSObject object 138 final JSObject jsObj = (JSObject)array; 139 final Object len = jsObj.hasMember("length")? jsObj.getMember("length") : Integer.valueOf(0); 140 final int n = lengthToInt(len); 141 142 final Object[] args = new Object[n]; 143 for (int i = 0; i < args.length; i++) { 144 args[i] = jsObj.hasSlot(i)? jsObj.getSlot(i) : UNDEFINED; 145 } 146 return args; 147 } else { 148 throw typeError("function.apply.expects.array"); 149 } 150 } 151 152 private static int lengthToInt(final Object len) { 153 final long ln = JSType.toUint32(len); 154 // NOTE: ECMASCript 5.1 section 15.3.4.3 says length should be treated as Uint32, but we wouldn't be able to 155 // allocate a Java array of more than MAX_VALUE elements anyway, so at this point we have to throw an error. 156 // People applying a function to more than 2^31 arguments will unfortunately be out of luck. 157 if (ln > Integer.MAX_VALUE) { 158 throw rangeError("range.error.inappropriate.array.length", JSType.toString(len)); 159 } 160 return (int)ln; 161 } 162 163 private static void checkCallable(final Object self) { 164 if (!(self instanceof ScriptFunction || (self instanceof JSObject && ((JSObject)self).isFunction()))) { 165 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 166 } 167 } 168 169 /** 170 * ECMA 15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, ... ] ] ) 171 * 172 * @param self self reference 173 * @param args arguments for call 174 * @return result of call 175 */ 176 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 177 public static Object call(final Object self, final Object... args) { 178 checkCallable(self); 179 180 final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; 181 Object[] arguments; 182 183 if (args.length > 1) { 184 arguments = new Object[args.length - 1]; 185 System.arraycopy(args, 1, arguments, 0, arguments.length); 186 } else { 187 arguments = ScriptRuntime.EMPTY_ARRAY; 188 } 189 190 if (self instanceof ScriptFunction) { 191 return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments); 192 } else if (self instanceof JSObject) { 193 return ((JSObject)self).call(thiz, arguments); 194 } 195 196 throw new AssertionError("should not reach here"); 197 } 198 199 /** 200 * ECMA 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, ...]]) 201 * 202 * @param self self reference 203 * @param args arguments for bind 204 * @return function with bound arguments 205 */ 206 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 207 public static Object bind(final Object self, final Object... args) { 208 final Object thiz = (args.length == 0) ? UNDEFINED : args[0]; 209 210 Object[] arguments; 211 if (args.length > 1) { 212 arguments = new Object[args.length - 1]; 213 System.arraycopy(args, 1, arguments, 0, arguments.length); 214 } else { 215 arguments = ScriptRuntime.EMPTY_ARRAY; 216 } 217 218 return Bootstrap.bindCallable(self, thiz, arguments); 219 } 220 221 /** 222 * Nashorn extension: Function.prototype.toSource 223 * 224 * @param self self reference 225 * @return source for function 226 */ 227 @Function(attributes = Attribute.NOT_ENUMERABLE) 228 public static String toSource(final Object self) { 229 if (!(self instanceof ScriptFunction)) { 230 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 231 } 232 return ((ScriptFunction)self).toSource(); 233 } 234 235 /** 236 * ECMA 15.3.2.1 new Function (p1, p2, ... , pn, body) 237 * 238 * Constructor 239 * 240 * @param newObj is the new operator used for constructing this function 241 * @param self self reference 242 * @param args arguments 243 * @return new NativeFunction 244 */ 245 @Constructor(arity = 1) 246 public static ScriptFunction function(final boolean newObj, final Object self, final Object... args) { 247 final StringBuilder sb = new StringBuilder(); 248 249 sb.append("(function ("); 250 final String funcBody; 251 if (args.length > 0) { 252 final StringBuilder paramListBuf = new StringBuilder(); 253 for (int i = 0; i < args.length - 1; i++) { 254 paramListBuf.append(JSType.toString(args[i])); 255 if (i < args.length - 2) { 256 paramListBuf.append(","); 257 } 258 } 259 260 // now convert function body to a string 261 funcBody = JSType.toString(args[args.length - 1]); 262 263 final String paramList = paramListBuf.toString(); 264 if (!paramList.isEmpty()) { 265 checkFunctionParameters(paramList); 266 sb.append(paramList); 267 } 268 } else { 269 funcBody = null; 270 } 271 272 sb.append(") {\n"); 273 if (args.length > 0) { 274 checkFunctionBody(funcBody); 275 sb.append(funcBody); 276 sb.append('\n'); 277 } 278 sb.append("})"); 279 280 final Global global = Global.instance(); 281 final Context context = global.getContext(); 282 return (ScriptFunction)context.eval(global, sb.toString(), global, "<function>"); 283 } 284 285 private static void checkFunctionParameters(final String params) { 286 final Parser parser = getParser(params); 287 try { 288 parser.parseFormalParameterList(); 289 } catch (final ParserException pe) { 290 pe.throwAsEcmaException(); 291 } 292 } 293 294 private static void checkFunctionBody(final String funcBody) { 295 final Parser parser = getParser(funcBody); 296 try { 297 parser.parseFunctionBody(); 298 } catch (final ParserException pe) { 299 pe.throwAsEcmaException(); 300 } 301 } 302 303 private static Parser getParser(final String sourceText) { 304 final ScriptEnvironment env = Global.getEnv(); 305 return new Parser(env, sourceFor("<function>", sourceText), new Context.ThrowErrorManager(), env._strict, null); 306 } 307 }