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 }