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 }