1 /*
   2  * Copyright (c) 2010, 2013, 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 package jdk.nashorn.internal.runtime;
  26 
  27 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
  28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
  29 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
  30 
  31 import java.io.NotSerializableException;
  32 import java.io.ObjectInputStream;
  33 import java.io.ObjectOutputStream;
  34 import java.lang.invoke.CallSite;
  35 import java.lang.invoke.ConstantCallSite;
  36 import java.lang.invoke.MethodHandle;
  37 import java.lang.invoke.MethodHandles;
  38 import java.lang.invoke.MethodHandles.Lookup;
  39 import java.lang.invoke.MethodType;
  40 import java.lang.reflect.Array;
  41 import java.util.Arrays;
  42 import jdk.nashorn.internal.codegen.CompilerConstants;
  43 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
  44 import jdk.nashorn.internal.codegen.types.Type;
  45 import jdk.nashorn.internal.lookup.MethodHandleFactory;
  46 import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
  47 import jdk.nashorn.internal.objects.Global;
  48 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  49 
  50 /**
  51  * Used to signal to the linker to relink the callee
  52  */
  53 @SuppressWarnings("serial")
  54 public final class RewriteException extends Exception {
  55     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
  56 
  57     // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
  58     // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
  59     private ScriptObject runtimeScope;
  60 
  61     // Contents of bytecode slots
  62     private Object[] byteCodeSlots;
  63 
  64     private final int[] previousContinuationEntryPoints;
  65 
  66     /** Call for getting the contents of the bytecode slots in the exception */
  67     public static final Call GET_BYTECODE_SLOTS       = virtualCallNoLookup(RewriteException.class, "getByteCodeSlots", Object[].class);
  68     /** Call for getting the program point in the exception */
  69     public static final Call GET_PROGRAM_POINT        = virtualCallNoLookup(RewriteException.class, "getProgramPoint", int.class);
  70     /** Call for getting the return value for the exception */
  71     public static final Call GET_RETURN_VALUE         = virtualCallNoLookup(RewriteException.class, "getReturnValueDestructive", Object.class);
  72     /** Call for the populate array bootstrap */
  73     public static final Call BOOTSTRAP                = staticCallNoLookup(RewriteException.class, "populateArrayBootstrap", CallSite.class, Lookup.class, String.class, MethodType.class, int.class);
  74 
  75     /** Call for populating an array with local variable state */
  76     private static final Call POPULATE_ARRAY           = staticCall(MethodHandles.lookup(), RewriteException.class, "populateArray", Object[].class, Object[].class, int.class, Object[].class);
  77 
  78     /** Call for converting an array to a long array. */
  79     public static final Call TO_LONG_ARRAY   = staticCallNoLookup(RewriteException.class, "toLongArray",   long[].class, Object.class, RewriteException.class);
  80     /** Call for converting an array to a double array. */
  81     public static final Call TO_DOUBLE_ARRAY = staticCallNoLookup(RewriteException.class, "toDoubleArray", double[].class, Object.class, RewriteException.class);
  82     /** Call for converting an array to an object array. */
  83     public static final Call TO_OBJECT_ARRAY = staticCallNoLookup(RewriteException.class, "toObjectArray", Object[].class, Object.class, RewriteException.class);
  84     /** Call for converting an object to null if it can't be represented as an instance of a class. */
  85     public static final Call INSTANCE_OR_NULL = staticCallNoLookup(RewriteException.class, "instanceOrNull", Object.class, Object.class, Class.class);
  86     /** Call for asserting the length of an array. */
  87     public static final Call ASSERT_ARRAY_LENGTH = staticCallNoLookup(RewriteException.class, "assertArrayLength", void.class, Object[].class, int.class);
  88 
  89     private RewriteException(
  90             final UnwarrantedOptimismException e,
  91             final Object[] byteCodeSlots,
  92             final String[] byteCodeSymbolNames,
  93             final int[] previousContinuationEntryPoints) {
  94         super("", e, false, Context.DEBUG);
  95         this.byteCodeSlots = byteCodeSlots;
  96         this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames);
  97         this.previousContinuationEntryPoints = previousContinuationEntryPoints;
  98     }
  99 
 100     /**
 101      * Constructor for a rewrite exception thrown from an optimistic function.
 102      * @param e the {@link UnwarrantedOptimismException} that triggered this exception.
 103      * @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
 104      * @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
 105      * have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
 106      * effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
 107      * and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
 108      * the more unnecessary deoptimizing compilations can be avoided.
 109      * @return a new rewrite exception
 110      */
 111     public static RewriteException create(final UnwarrantedOptimismException e,
 112             final Object[] byteCodeSlots,
 113             final String[] byteCodeSymbolNames) {
 114         return create(e, byteCodeSlots, byteCodeSymbolNames, null);
 115     }
 116 
 117     /**
 118      * Constructor for a rewrite exception thrown from a rest-of method.
 119      * @param e the {@link UnwarrantedOptimismException} that triggered this exception.
 120      * @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
 121      * @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
 122      * have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
 123      * effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
 124      * and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
 125      * the more unnecessary deoptimizing compilations can be avoided.
 126      * @param previousContinuationEntryPoints an array of continuation entry points that were already executed during
 127      * one logical invocation of the function (a rest-of triggering a rest-of triggering a...)
 128      * @return a new rewrite exception
 129      */
 130     public static RewriteException create(final UnwarrantedOptimismException e,
 131             final Object[] byteCodeSlots,
 132             final String[] byteCodeSymbolNames,
 133             final int[] previousContinuationEntryPoints) {
 134         return new RewriteException(e, byteCodeSlots, byteCodeSymbolNames, previousContinuationEntryPoints);
 135     }
 136 
 137     /**
 138      * Bootstrap method for populate array
 139      * @param lookup     lookup
 140      * @param name       name (ignored)
 141      * @param type       method type for signature
 142      * @param startIndex start index to start writing to
 143      * @return callsite to array populator (constant)
 144      */
 145     public static CallSite populateArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type, final int startIndex) {
 146         MethodHandle mh = POPULATE_ARRAY.methodHandle();
 147         mh = MH.insertArguments(mh, 1, startIndex);
 148         mh = MH.asCollector(mh, Object[].class, type.parameterCount() - 1);
 149         mh = MH.asType(mh, type);
 150         return new ConstantCallSite(mh);
 151     }
 152 
 153     private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames) {
 154         final ScriptObject locals = Global.newEmptyInstance();
 155         final int l = Math.min(byteCodeSlots.length, byteCodeSymbolNames.length);
 156         ScriptObject runtimeScope = null;
 157         final String scopeName = CompilerConstants.SCOPE.symbolName();
 158         for(int i = 0; i < l; ++i) {
 159             final String name = byteCodeSymbolNames[i];
 160             final Object value = byteCodeSlots[i];
 161             if(scopeName.equals(name)) {
 162                 assert runtimeScope == null;
 163                 runtimeScope = (ScriptObject)value;
 164             } else if(name != null) {
 165                 locals.set(name, value, NashornCallSiteDescriptor.CALLSITE_STRICT);
 166             }
 167         }
 168         locals.setProto(runtimeScope);
 169         return locals;
 170     }
 171 
 172     /**
 173      * Array populator used for saving the local variable state into the array contained in the
 174      * RewriteException
 175      * @param arrayToBePopluated array to be populated
 176      * @param startIndex start index to write to
 177      * @param items items with which to populate the array
 178      * @return the populated array - same array object
 179      */
 180     public static Object[] populateArray(final Object[] arrayToBePopluated, final int startIndex, final Object[] items) {
 181         System.arraycopy(items, 0, arrayToBePopluated, startIndex, items.length);
 182         return arrayToBePopluated;
 183     }
 184 
 185     /**
 186      * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
 187      * a long array in the continued method. Normally, it will also be a long array in the original (interrupted by
 188      * deoptimization) method, but it can actually be an int array that underwent widening in the new code version.
 189      * @param obj the object that has to be converted into a long array
 190      * @param e the exception being processed
 191      * @return a long array
 192      */
 193     public static long[] toLongArray(final Object obj, final RewriteException e) {
 194         if(obj instanceof long[]) {
 195             return (long[])obj;
 196         }
 197 
 198         assert obj instanceof int[];
 199 
 200         final int[] in = (int[])obj;
 201         final long[] out = new long[in.length];
 202         for(int i = 0; i < in.length; ++i) {
 203             out[i] = in[i];
 204         }
 205         return e.replaceByteCodeValue(in, out);
 206     }
 207 
 208     /**
 209      * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
 210      * a double array in the continued method. Normally, it will also be a double array in the original (interrupted by
 211      * deoptimization) method, but it can actually be an int or long array that underwent widening in the new code version.
 212      * @param obj the object that has to be converted into a double array
 213      * @param e the exception being processed
 214      * @return a double array
 215      */
 216     public static double[] toDoubleArray(final Object obj, final RewriteException e) {
 217         if(obj instanceof double[]) {
 218             return (double[])obj;
 219         }
 220 
 221         assert obj instanceof int[] || obj instanceof long[];
 222 
 223         final int l = Array.getLength(obj);
 224         final double[] out = new double[l];
 225         for(int i = 0; i < l; ++i) {
 226             out[i] = Array.getDouble(obj, i);
 227         }
 228         return e.replaceByteCodeValue(obj, out);
 229     }
 230 
 231     /**
 232      * Continuation handler calls this method when a local variable carried over into the continuation is expected to be
 233      * an Object array in the continued method. Normally, it will also be an Object array in the original (interrupted by
 234      * deoptimization) method, but it can actually be an int, long, or double array that underwent widening in the new
 235      * code version.
 236      * @param obj the object that has to be converted into an Object array
 237      * @param e the exception being processed
 238      * @return an Object array
 239      */
 240     public static Object[] toObjectArray(final Object obj, final RewriteException e) {
 241         if(obj instanceof Object[]) {
 242             return (Object[])obj;
 243         }
 244 
 245         assert obj instanceof int[] || obj instanceof long[] || obj instanceof double[] : obj + " is " + obj.getClass().getName();
 246 
 247         final int l = Array.getLength(obj);
 248         final Object[] out = new Object[l];
 249         for(int i = 0; i < l; ++i) {
 250             out[i] = Array.get(obj, i);
 251         }
 252         return e.replaceByteCodeValue(obj, out);
 253     }
 254 
 255     /**
 256      * Continuation handler calls this method when a local variable carried over into the continuation is expected to
 257      * have a certain type, but the value can have a different type coming from the deoptimized method as it was a dead
 258      * store. If we had precise liveness analysis, we wouldn't need this.
 259      * @param obj the object inspected for being of a particular type
 260      * @param clazz the type the object must belong to
 261      * @return the object if it belongs to the type, or null otherwise
 262      */
 263     public static Object instanceOrNull(final Object obj, final Class<?> clazz) {
 264         return clazz.isInstance(obj) ? obj : null;
 265     }
 266 
 267     /**
 268      * Asserts the length of an array. Invoked from continuation handler only when running with assertions enabled.
 269      * The array can, in fact, have more elements than asserted, but they must all have Undefined as their value. The
 270      * method does not test for the array having less elements than asserted, as those would already have caused an
 271      * {@code ArrayIndexOutOfBoundsException} to be thrown as the continuation handler attempts to access the missing
 272      * elements.
 273      * @param arr the array
 274      * @param length the asserted length
 275      */
 276     public static void assertArrayLength(final Object[] arr, final int length) {
 277         for(int i = arr.length; i-- > length;) {
 278             if(arr[i] != ScriptRuntime.UNDEFINED) {
 279                 throw new AssertionError(String.format("Expected array length %d, but it is %d", length, i + 1));
 280             }
 281         }
 282     }
 283 
 284     private <T> T replaceByteCodeValue(final Object in, final T out) {
 285         for(int i = 0; i < byteCodeSlots.length; ++i) {
 286             if(byteCodeSlots[i] == in) {
 287                 byteCodeSlots[i] = out;
 288             }
 289         }
 290         return out;
 291     }
 292 
 293     private UnwarrantedOptimismException getUOE() {
 294         return (UnwarrantedOptimismException)getCause();
 295     }
 296     /**
 297      * Get return value. This method is destructive, after it is invoked subsequent invocation of either
 298      * {@link #getByteCodeSlots()} or this method will return null. This method is invoked from the generated
 299      * continuation code as the last step before continuing the execution, and we need to make sure we don't hang on to
 300      * either the entry bytecode slot values or the return value and prevent them from being garbage collected.
 301      * @return return value
 302      */
 303     public Object getReturnValueDestructive() {
 304         assert byteCodeSlots != null;
 305         byteCodeSlots = null;
 306         runtimeScope = null;
 307         return getUOE().getReturnValueDestructive();
 308     }
 309 
 310     Object getReturnValueNonDestructive() {
 311         return getUOE().getReturnValueNonDestructive();
 312     }
 313 
 314     /**
 315      * Get return type
 316      * @return return type
 317      */
 318     public Type getReturnType() {
 319         return getUOE().getReturnType();
 320     }
 321 
 322     /**
 323      * Get the program point.
 324      * @return program point.
 325      */
 326     public int getProgramPoint() {
 327         return getUOE().getProgramPoint();
 328     }
 329 
 330     /**
 331      * Get the bytecode slot contents.
 332      * @return bytecode slot contents.
 333      */
 334     public Object[] getByteCodeSlots() {
 335         return byteCodeSlots == null ? null : byteCodeSlots.clone();
 336     }
 337 
 338     /**
 339      * @return an array of continuation entry points that were already executed during one logical invocation of the
 340      * function (a rest-of triggering a rest-of triggering a...)
 341      */
 342     public int[] getPreviousContinuationEntryPoints() {
 343         return previousContinuationEntryPoints == null ? null : previousContinuationEntryPoints.clone();
 344     }
 345 
 346     /**
 347      * Returns the runtime scope that was in effect when the exception was thrown.
 348      * @return the runtime scope.
 349      */
 350     public ScriptObject getRuntimeScope() {
 351         return runtimeScope;
 352     }
 353 
 354     private static String stringify(final Object returnValue) {
 355         if (returnValue == null) {
 356             return "null";
 357         }
 358         String str = returnValue.toString();
 359         if (returnValue instanceof String) {
 360             str = '\'' + str + '\'';
 361         } else if (returnValue instanceof Double) {
 362             str += 'd';
 363         } else if (returnValue instanceof Long) {
 364             str += 'l';
 365         }
 366         return str;
 367     }
 368 
 369     @Override
 370     public String getMessage() {
 371         return getMessage(false);
 372     }
 373 
 374     /**
 375      * Short toString function for message
 376      * @return short message
 377      */
 378     public String getMessageShort() {
 379         return getMessage(true);
 380     }
 381 
 382     private String getMessage(final boolean isShort) {
 383         final StringBuilder sb = new StringBuilder();
 384 
 385         //program point
 386         sb.append("[pp=").
 387             append(getProgramPoint()).
 388             append(", ");
 389 
 390         //slot contents
 391         if (!isShort) {
 392             final Object[] slots = byteCodeSlots;
 393             if (slots != null) {
 394                 sb.append("slots=").
 395                     append(Arrays.asList(slots)).
 396                     append(", ");
 397             }
 398         }
 399 
 400         //return type
 401         sb.append("type=").
 402             append(getReturnType()).
 403             append(", ");
 404 
 405         //return value
 406         sb.append("value=").
 407             append(stringify(getReturnValueNonDestructive())).
 408             append(")]");
 409 
 410         return sb.toString();
 411     }
 412 
 413     private void writeObject(final ObjectOutputStream out) throws NotSerializableException {
 414         throw new NotSerializableException(getClass().getName());
 415     }
 416 
 417     private void readObject(final ObjectInputStream in) throws NotSerializableException {
 418         throw new NotSerializableException(getClass().getName());
 419     }
 420 }