src/jdk/nashorn/internal/runtime/Context.java

Wed, 05 Nov 2014 12:34:06 +0100

author
lagergren
date
Wed, 05 Nov 2014 12:34:06 +0100
changeset 1086
d0b26e6f602c
parent 1062
bf5f28dafa7c
child 1087
a119a11d49d8
permissions
-rw-r--r--

8057825: Bug in apply specialization - if an apply specialization that is available doesn't fit, a new one wouldn't be installed, if the new code generated as a specialization didn't manage to do the apply specialization. Basically changing a conditional to an unconditional.
Reviewed-by: attila, hannesw

jlaskey@3 1 /*
jlaskey@7 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
jlaskey@3 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jlaskey@3 4 *
jlaskey@3 5 * This code is free software; you can redistribute it and/or modify it
jlaskey@3 6 * under the terms of the GNU General Public License version 2 only, as
jlaskey@3 7 * published by the Free Software Foundation. Oracle designates this
jlaskey@3 8 * particular file as subject to the "Classpath" exception as provided
jlaskey@3 9 * by Oracle in the LICENSE file that accompanied this code.
jlaskey@3 10 *
jlaskey@3 11 * This code is distributed in the hope that it will be useful, but WITHOUT
jlaskey@3 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jlaskey@3 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jlaskey@3 14 * version 2 for more details (a copy is included in the LICENSE file that
jlaskey@3 15 * accompanied this code).
jlaskey@3 16 *
jlaskey@3 17 * You should have received a copy of the GNU General Public License version
jlaskey@3 18 * 2 along with this work; if not, write to the Free Software Foundation,
jlaskey@3 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jlaskey@3 20 *
jlaskey@3 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jlaskey@3 22 * or visit www.oracle.com if you need additional information or have any
jlaskey@3 23 * questions.
jlaskey@3 24 */
jlaskey@3 25
jlaskey@3 26 package jdk.nashorn.internal.runtime;
jlaskey@3 27
hannesw@828 28 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
attila@963 29 import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION;
hannesw@828 30 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
jlaskey@3 31 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
hannesw@1018 32 import static jdk.nashorn.internal.runtime.CodeStore.newCodeStore;
jlaskey@3 33 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
jlaskey@3 34 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
hannesw@845 35 import static jdk.nashorn.internal.runtime.Source.sourceFor;
jlaskey@3 36 import java.io.File;
jlaskey@3 37 import java.io.IOException;
jlaskey@3 38 import java.io.PrintWriter;
jlaskey@3 39 import java.lang.invoke.MethodHandle;
jlaskey@3 40 import java.lang.invoke.MethodHandles;
attila@963 41 import java.lang.invoke.MethodType;
lagergren@1028 42 import java.lang.invoke.SwitchPoint;
hannesw@769 43 import java.lang.ref.ReferenceQueue;
hannesw@769 44 import java.lang.ref.SoftReference;
hannesw@828 45 import java.lang.reflect.Field;
sundar@459 46 import java.lang.reflect.Modifier;
lagergren@110 47 import java.net.MalformedURLException;
jlaskey@3 48 import java.net.URL;
sundar@468 49 import java.security.AccessControlContext;
jlaskey@3 50 import java.security.AccessController;
jlaskey@3 51 import java.security.CodeSigner;
jlaskey@3 52 import java.security.CodeSource;
sundar@468 53 import java.security.Permissions;
jlaskey@3 54 import java.security.PrivilegedAction;
hannesw@828 55 import java.security.PrivilegedActionException;
hannesw@828 56 import java.security.PrivilegedExceptionAction;
sundar@468 57 import java.security.ProtectionDomain;
attila@963 58 import java.util.Collection;
hannesw@828 59 import java.util.HashMap;
hannesw@769 60 import java.util.LinkedHashMap;
sundar@350 61 import java.util.Map;
attila@962 62 import java.util.concurrent.atomic.AtomicLong;
attila@963 63 import java.util.function.Consumer;
attila@963 64 import java.util.function.Supplier;
attila@963 65 import java.util.logging.Level;
attila@963 66 import javax.script.ScriptEngine;
jlaskey@3 67 import jdk.internal.org.objectweb.asm.ClassReader;
jlaskey@3 68 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
sundar@964 69 import jdk.nashorn.api.scripting.ClassFilter;
sundar@322 70 import jdk.nashorn.api.scripting.ScriptObjectMirror;
jlaskey@3 71 import jdk.nashorn.internal.codegen.Compiler;
attila@963 72 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
lagergren@96 73 import jdk.nashorn.internal.codegen.ObjectClassGenerator;
lagergren@89 74 import jdk.nashorn.internal.ir.FunctionNode;
lagergren@96 75 import jdk.nashorn.internal.ir.debug.ASTWriter;
lagergren@89 76 import jdk.nashorn.internal.ir.debug.PrintVisitor;
attila@963 77 import jdk.nashorn.internal.lookup.MethodHandleFactory;
sundar@414 78 import jdk.nashorn.internal.objects.Global;
lagergren@89 79 import jdk.nashorn.internal.parser.Parser;
attila@963 80 import jdk.nashorn.internal.runtime.events.RuntimeEvent;
attila@963 81 import jdk.nashorn.internal.runtime.logging.DebugLogger;
attila@963 82 import jdk.nashorn.internal.runtime.logging.Loggable;
attila@963 83 import jdk.nashorn.internal.runtime.logging.Logger;
attila@963 84 import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo;
jlaskey@3 85 import jdk.nashorn.internal.runtime.options.Options;
jlaskey@3 86
jlaskey@3 87 /**
jlaskey@3 88 * This class manages the global state of execution. Context is immutable.
jlaskey@3 89 */
jlaskey@3 90 public final class Context {
sundar@492 91 // nashorn specific security runtime access permission names
sundar@492 92 /**
sundar@492 93 * Permission needed to pass arbitrary nashorn command line options when creating Context.
sundar@492 94 */
sundar@492 95 public static final String NASHORN_SET_CONFIG = "nashorn.setConfig";
sundar@492 96
sundar@492 97 /**
sundar@492 98 * Permission needed to create Nashorn Context instance.
sundar@492 99 */
sundar@492 100 public static final String NASHORN_CREATE_CONTEXT = "nashorn.createContext";
sundar@492 101
sundar@492 102 /**
sundar@492 103 * Permission needed to create Nashorn Global instance.
sundar@492 104 */
sundar@492 105 public static final String NASHORN_CREATE_GLOBAL = "nashorn.createGlobal";
sundar@492 106
sundar@492 107 /**
sundar@492 108 * Permission to get current Nashorn Context from thread local storage.
sundar@492 109 */
sundar@492 110 public static final String NASHORN_GET_CONTEXT = "nashorn.getContext";
sundar@492 111
sundar@492 112 /**
sundar@492 113 * Permission to use Java reflection/jsr292 from script code.
sundar@492 114 */
sundar@492 115 public static final String NASHORN_JAVA_REFLECTION = "nashorn.JavaReflection";
jlaskey@3 116
sundar@760 117 /**
sundar@760 118 * Permission to enable nashorn debug mode.
sundar@760 119 */
sundar@760 120 public static final String NASHORN_DEBUG_MODE = "nashorn.debugMode";
sundar@760 121
sundar@587 122 // nashorn load psuedo URL prefixes
sundar@587 123 private static final String LOAD_CLASSPATH = "classpath:";
sundar@587 124 private static final String LOAD_FX = "fx:";
sundar@587 125 private static final String LOAD_NASHORN = "nashorn:";
sundar@587 126
attila@963 127 private static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
attila@963 128 private static MethodType CREATE_PROGRAM_FUNCTION_TYPE = MethodType.methodType(ScriptFunction.class, ScriptObject.class);
attila@963 129
lagergren@1028 130 /**
lagergren@1028 131 * Keeps track of which builtin prototypes and properties have been relinked
lagergren@1028 132 * Currently we are conservative and associate the name of a builtin class with all
lagergren@1028 133 * its properties, so it's enough to invalidate a property to break all assumptions
lagergren@1028 134 * about a prototype. This can be changed to a more fine grained approach, but no one
lagergren@1028 135 * ever needs this, given the very rare occurance of swapping out only parts of
lagergren@1028 136 * a builtin v.s. the entire builtin object
lagergren@1028 137 */
lagergren@1028 138 private final Map<String, SwitchPoint> builtinSwitchPoints = new HashMap<>();
lagergren@1028 139
jlaskey@516 140 /* Force DebuggerSupport to be loaded. */
jlaskey@516 141 static {
jlaskey@516 142 DebuggerSupport.FORCELOAD = true;
jlaskey@516 143 }
jlaskey@516 144
lagergren@89 145 /**
lagergren@89 146 * ContextCodeInstaller that has the privilege of installing classes in the Context.
lagergren@89 147 * Can only be instantiated from inside the context and is opaque to other classes
lagergren@89 148 */
sundar@118 149 public static class ContextCodeInstaller implements CodeInstaller<ScriptEnvironment> {
lagergren@89 150 private final Context context;
lagergren@89 151 private final ScriptLoader loader;
lagergren@89 152 private final CodeSource codeSource;
hannesw@1062 153 private int usageCount = 0;
hannesw@1062 154 private int bytesDefined = 0;
hannesw@1062 155
hannesw@1062 156 // We reuse this installer for 10 compilations or 200000 defined bytes. Usually the first condition
hannesw@1062 157 // will occur much earlier, the second is a safety measure for very large scripts/functions.
hannesw@1062 158 private final static int MAX_USAGES = 10;
hannesw@1062 159 private final static int MAX_BYTES_DEFINED = 200_000;
lagergren@89 160
lagergren@89 161 private ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) {
lagergren@89 162 this.context = context;
lagergren@89 163 this.loader = loader;
lagergren@89 164 this.codeSource = codeSource;
lagergren@89 165 }
lagergren@89 166
lagergren@89 167 /**
attila@1030 168 * Return the script environment for this installer
lagergren@137 169 * @return ScriptEnvironment
lagergren@89 170 */
lagergren@89 171 @Override
sundar@118 172 public ScriptEnvironment getOwner() {
sundar@118 173 return context.env;
lagergren@89 174 }
lagergren@89 175
lagergren@89 176 @Override
attila@963 177 public Class<?> install(final String className, final byte[] bytecode) {
hannesw@1062 178 usageCount++;
hannesw@1062 179 bytesDefined += bytecode.length;
attila@963 180 final String binaryName = Compiler.binaryName(className);
attila@963 181 return loader.installClass(binaryName, bytecode, codeSource);
attila@963 182 }
hannesw@828 183
attila@963 184 @Override
attila@963 185 public void initialize(final Collection<Class<?>> classes, final Source source, final Object[] constants) {
attila@978 186 try {
attila@978 187 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
attila@963 188 @Override
attila@978 189 public Void run() throws Exception {
attila@978 190 for (final Class<?> clazz : classes) {
attila@978 191 //use reflection to write source and constants table to installed classes
attila@978 192 final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName());
attila@978 193 sourceField.setAccessible(true);
attila@978 194 sourceField.set(null, source);
hannesw@828 195
attila@978 196 final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName());
attila@978 197 constantsField.setAccessible(true);
attila@978 198 constantsField.set(null, constants);
attila@963 199 }
attila@978 200 return null;
hannesw@828 201 }
hannesw@828 202 });
attila@978 203 } catch (final PrivilegedActionException e) {
attila@978 204 throw new RuntimeException(e);
attila@978 205 }
lagergren@89 206 }
sundar@118 207
sundar@118 208 @Override
sundar@118 209 public void verify(final byte[] code) {
sundar@118 210 context.verify(code);
sundar@118 211 }
sundar@425 212
sundar@425 213 @Override
sundar@425 214 public long getUniqueScriptId() {
sundar@425 215 return context.getUniqueScriptId();
sundar@425 216 }
sundar@606 217
sundar@606 218 @Override
hannesw@1006 219 public void storeScript(final String cacheKey, final Source source, final String mainClassName,
attila@963 220 final Map<String,byte[]> classBytes, final Map<Integer, FunctionInitializer> initializers,
attila@963 221 final Object[] constants, final int compilationId) {
hannesw@828 222 if (context.codeStore != null) {
hannesw@1018 223 context.codeStore.store(cacheKey, source, mainClassName, classBytes, initializers, constants, compilationId);
hannesw@828 224 }
hannesw@828 225 }
attila@963 226
attila@963 227 @Override
attila@963 228 public StoredScript loadScript(final Source source, final String functionKey) {
attila@963 229 if (context.codeStore != null) {
hannesw@1018 230 return context.codeStore.load(source, functionKey);
attila@963 231 }
attila@963 232 return null;
attila@963 233 }
attila@1030 234
attila@1030 235 @Override
attila@1030 236 public CodeInstaller<ScriptEnvironment> withNewLoader() {
hannesw@1062 237 // Reuse this installer if we're within our limits.
hannesw@1062 238 if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) {
hannesw@1062 239 return this;
hannesw@1062 240 }
attila@1030 241 return new ContextCodeInstaller(context, context.createNewLoader(), codeSource);
attila@1030 242 }
attila@1030 243
attila@1030 244 @Override
attila@1030 245 public boolean isCompatibleWith(final CodeInstaller<ScriptEnvironment> other) {
attila@1030 246 if (other instanceof ContextCodeInstaller) {
attila@1030 247 final ContextCodeInstaller cci = (ContextCodeInstaller)other;
attila@1030 248 return cci.context == context && cci.codeSource == codeSource;
attila@1030 249 }
attila@1030 250 return false;
attila@1030 251 }
lagergren@89 252 }
lagergren@89 253
jlaskey@3 254 /** Is Context global debug mode enabled ? */
jlaskey@3 255 public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");
jlaskey@3 256
sundar@771 257 private static final ThreadLocal<Global> currentGlobal = new ThreadLocal<>();
jlaskey@3 258
hannesw@828 259 // in-memory cache for loaded classes
hannesw@769 260 private ClassCache classCache;
hannesw@769 261
hannesw@828 262 // persistent code store
hannesw@828 263 private CodeStore codeStore;
hannesw@828 264
jlaskey@3 265 /**
sundar@44 266 * Get the current global scope
sundar@44 267 * @return the current global scope
jlaskey@3 268 */
sundar@771 269 public static Global getGlobal() {
sundar@209 270 // This class in a package.access protected package.
sundar@209 271 // Trusted code only can call this method.
sundar@771 272 return currentGlobal.get();
jlaskey@3 273 }
jlaskey@3 274
jlaskey@3 275 /**
jlaskey@3 276 * Set the current global scope
jlaskey@3 277 * @param global the global scope
jlaskey@3 278 */
jlaskey@3 279 public static void setGlobal(final ScriptObject global) {
sundar@414 280 if (global != null && !(global instanceof Global)) {
sundar@771 281 throw new IllegalArgumentException("not a global!");
jlaskey@3 282 }
sundar@771 283 setGlobal((Global)global);
sundar@771 284 }
jlaskey@3 285
sundar@771 286 /**
sundar@771 287 * Set the current global scope
sundar@771 288 * @param global the global scope
sundar@771 289 */
sundar@771 290 public static void setGlobal(final Global global) {
sundar@771 291 // This class in a package.access protected package.
sundar@771 292 // Trusted code only can call this method.
attila@963 293 assert getGlobal() != global;
attila@963 294 //same code can be cached between globals, then we need to invalidate method handle constants
attila@963 295 if (global != null) {
attila@963 296 Global.getConstants().invalidateAll();
attila@963 297 }
sundar@771 298 currentGlobal.set(global);
jlaskey@3 299 }
jlaskey@3 300
jlaskey@3 301 /**
jlaskey@3 302 * Get context of the current global
jlaskey@3 303 * @return current global scope's context.
jlaskey@3 304 */
jlaskey@3 305 public static Context getContext() {
sundar@41 306 final SecurityManager sm = System.getSecurityManager();
sundar@41 307 if (sm != null) {
sundar@492 308 sm.checkPermission(new RuntimePermission(NASHORN_GET_CONTEXT));
sundar@41 309 }
sundar@41 310 return getContextTrusted();
sundar@41 311 }
sundar@41 312
sundar@41 313 /**
sundar@41 314 * Get current context's error writer
sundar@41 315 *
sundar@41 316 * @return error writer of the current context
sundar@41 317 */
sundar@41 318 public static PrintWriter getCurrentErr() {
sundar@771 319 final ScriptObject global = getGlobal();
sundar@41 320 return (global != null)? global.getContext().getErr() : new PrintWriter(System.err);
jlaskey@3 321 }
jlaskey@3 322
jlaskey@3 323 /**
jlaskey@3 324 * Output text to this Context's error stream
jlaskey@3 325 * @param str text to write
jlaskey@3 326 */
jlaskey@3 327 public static void err(final String str) {
jlaskey@3 328 err(str, true);
jlaskey@3 329 }
jlaskey@3 330
jlaskey@3 331 /**
jlaskey@3 332 * Output text to this Context's error stream, optionally with
jlaskey@3 333 * a newline afterwards
jlaskey@3 334 *
jlaskey@3 335 * @param str text to write
jlaskey@3 336 * @param crlf write a carriage return/new line after text
jlaskey@3 337 */
jlaskey@3 338 public static void err(final String str, final boolean crlf) {
sundar@41 339 final PrintWriter err = Context.getCurrentErr();
lagergren@11 340 if (err != null) {
lagergren@11 341 if (crlf) {
lagergren@11 342 err.println(str);
jlaskey@3 343 } else {
lagergren@11 344 err.print(str);
jlaskey@3 345 }
jlaskey@3 346 }
jlaskey@3 347 }
jlaskey@3 348
sundar@118 349 /** Current environment. */
sundar@118 350 private final ScriptEnvironment env;
sundar@118 351
sundar@118 352 /** is this context in strict mode? Cached from env. as this is used heavily. */
sundar@344 353 final boolean _strict;
sundar@118 354
sundar@41 355 /** class loader to resolve classes from script. */
sundar@41 356 private final ClassLoader appLoader;
sundar@41 357
jlaskey@3 358 /** Class loader to load classes from -classpath option, if set. */
jlaskey@3 359 private final ClassLoader classPathLoader;
jlaskey@3 360
jlaskey@3 361 /** Class loader to load classes compiled from scripts. */
jlaskey@3 362 private final ScriptLoader scriptLoader;
jlaskey@3 363
jlaskey@3 364 /** Current error manager. */
jlaskey@3 365 private final ErrorManager errors;
jlaskey@3 366
sundar@425 367 /** Unique id for script. Used only when --loader-per-compile=false */
sundar@427 368 private final AtomicLong uniqueScriptId;
sundar@425 369
sundar@964 370 /** Optional class filter to use for Java classes. Can be null. */
sundar@964 371 private final ClassFilter classFilter;
sundar@964 372
sundar@41 373 private static final ClassLoader myLoader = Context.class.getClassLoader();
jlaskey@3 374 private static final StructureLoader sharedLoader;
sundar@492 375
lagergren@610 376 /*package-private*/ @SuppressWarnings("static-method")
lagergren@610 377 ClassLoader getSharedLoader() {
sundar@552 378 return sharedLoader;
sundar@552 379 }
sundar@552 380
sundar@492 381 private static AccessControlContext createNoPermAccCtxt() {
sundar@492 382 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) });
sundar@492 383 }
sundar@492 384
sundar@492 385 private static AccessControlContext createPermAccCtxt(final String permName) {
sundar@492 386 final Permissions perms = new Permissions();
sundar@492 387 perms.add(new RuntimePermission(permName));
sundar@492 388 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
sundar@492 389 }
sundar@492 390
sundar@492 391 private static final AccessControlContext NO_PERMISSIONS_ACC_CTXT = createNoPermAccCtxt();
sundar@492 392 private static final AccessControlContext CREATE_LOADER_ACC_CTXT = createPermAccCtxt("createClassLoader");
sundar@492 393 private static final AccessControlContext CREATE_GLOBAL_ACC_CTXT = createPermAccCtxt(NASHORN_CREATE_GLOBAL);
jlaskey@3 394
jlaskey@3 395 static {
jlaskey@3 396 sharedLoader = AccessController.doPrivileged(new PrivilegedAction<StructureLoader>() {
jlaskey@3 397 @Override
jlaskey@3 398 public StructureLoader run() {
sundar@552 399 return new StructureLoader(myLoader);
jlaskey@3 400 }
sundar@492 401 }, CREATE_LOADER_ACC_CTXT);
jlaskey@3 402 }
jlaskey@3 403
jlaskey@3 404 /**
jlaskey@3 405 * ThrowErrorManager that throws ParserException upon error conditions.
jlaskey@3 406 */
jlaskey@3 407 public static class ThrowErrorManager extends ErrorManager {
jlaskey@3 408 @Override
jlaskey@3 409 public void error(final String message) {
jlaskey@3 410 throw new ParserException(message);
jlaskey@3 411 }
jlaskey@3 412
jlaskey@3 413 @Override
jlaskey@3 414 public void error(final ParserException e) {
jlaskey@3 415 throw e;
jlaskey@3 416 }
jlaskey@3 417 }
jlaskey@3 418
jlaskey@3 419 /**
jlaskey@3 420 * Constructor
jlaskey@3 421 *
jlaskey@3 422 * @param options options from command line or Context creator
jlaskey@3 423 * @param errors error manger
sundar@41 424 * @param appLoader application class loader
jlaskey@3 425 */
sundar@41 426 public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader) {
sundar@964 427 this(options, errors, appLoader, (ClassFilter)null);
sundar@964 428 }
sundar@964 429
sundar@964 430 /**
sundar@964 431 * Constructor
sundar@964 432 *
sundar@964 433 * @param options options from command line or Context creator
sundar@964 434 * @param errors error manger
sundar@964 435 * @param appLoader application class loader
sundar@964 436 * @param classFilter class filter to use
sundar@964 437 */
sundar@964 438 public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader, final ClassFilter classFilter) {
sundar@964 439 this(options, errors, new PrintWriter(System.out, true), new PrintWriter(System.err, true), appLoader, classFilter);
jlaskey@3 440 }
jlaskey@3 441
jlaskey@3 442 /**
jlaskey@3 443 * Constructor
jlaskey@3 444 *
jlaskey@3 445 * @param options options from command line or Context creator
jlaskey@3 446 * @param errors error manger
jlaskey@3 447 * @param out output writer for this Context
jlaskey@3 448 * @param err error writer for this Context
sundar@41 449 * @param appLoader application class loader
jlaskey@3 450 */
sundar@41 451 public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader) {
sundar@964 452 this(options, errors, out, err, appLoader, (ClassFilter)null);
sundar@964 453 }
sundar@964 454
sundar@964 455 /**
sundar@964 456 * Constructor
sundar@964 457 *
sundar@964 458 * @param options options from command line or Context creator
sundar@964 459 * @param errors error manger
sundar@964 460 * @param out output writer for this Context
sundar@964 461 * @param err error writer for this Context
sundar@964 462 * @param appLoader application class loader
sundar@964 463 * @param classFilter class filter to use
sundar@964 464 */
sundar@964 465 public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader, final ClassFilter classFilter) {
jlaskey@3 466 final SecurityManager sm = System.getSecurityManager();
jlaskey@3 467 if (sm != null) {
sundar@492 468 sm.checkPermission(new RuntimePermission(NASHORN_CREATE_CONTEXT));
jlaskey@3 469 }
jlaskey@3 470
sundar@964 471 this.classFilter = classFilter;
sundar@118 472 this.env = new ScriptEnvironment(options, out, err);
sundar@118 473 this._strict = env._strict;
sundar@41 474 this.appLoader = appLoader;
sundar@427 475 if (env._loader_per_compile) {
sundar@427 476 this.scriptLoader = null;
sundar@427 477 this.uniqueScriptId = null;
sundar@427 478 } else {
sundar@427 479 this.scriptLoader = createNewLoader();
sundar@427 480 this.uniqueScriptId = new AtomicLong();
sundar@427 481 }
jlaskey@3 482 this.errors = errors;
jlaskey@3 483
jlaskey@3 484 // if user passed -classpath option, make a class loader with that and set it as
jlaskey@3 485 // thread context class loader so that script can access classes from that path.
jlaskey@3 486 final String classPath = options.getString("classpath");
attila@963 487 if (!env._compile_only && classPath != null && !classPath.isEmpty()) {
jlaskey@3 488 // make sure that caller can create a class loader.
jlaskey@3 489 if (sm != null) {
jlaskey@3 490 sm.checkPermission(new RuntimePermission("createClassLoader"));
jlaskey@3 491 }
jlaskey@3 492 this.classPathLoader = NashornLoader.createClassLoader(classPath);
jlaskey@3 493 } else {
jlaskey@3 494 this.classPathLoader = null;
jlaskey@3 495 }
jlaskey@3 496
hannesw@769 497 final int cacheSize = env._class_cache_size;
hannesw@769 498 if (cacheSize > 0) {
hannesw@769 499 classCache = new ClassCache(cacheSize);
hannesw@769 500 }
hannesw@769 501
hannesw@828 502 if (env._persistent_cache) {
attila@963 503 try {
hannesw@1018 504 codeStore = newCodeStore(this);
attila@963 505 } catch (final IOException e) {
attila@963 506 throw new RuntimeException("Error initializing code cache", e);
hannesw@828 507 }
hannesw@828 508 }
hannesw@828 509
jlaskey@3 510 // print version info if asked.
sundar@118 511 if (env._version) {
jlaskey@3 512 getErr().println("nashorn " + Version.version());
jlaskey@3 513 }
jlaskey@3 514
sundar@118 515 if (env._fullversion) {
jlaskey@3 516 getErr().println("nashorn full version " + Version.fullVersion());
jlaskey@3 517 }
attila@963 518
attila@963 519 initLoggers();
jlaskey@3 520 }
jlaskey@3 521
sundar@964 522
sundar@964 523 /**
sundar@964 524 * Get the class filter for this context
sundar@964 525 * @return class filter
sundar@964 526 */
sundar@964 527 public ClassFilter getClassFilter() {
sundar@964 528 return classFilter;
sundar@964 529 }
sundar@964 530
jlaskey@3 531 /**
jlaskey@3 532 * Get the error manager for this context
jlaskey@3 533 * @return error manger
jlaskey@3 534 */
sundar@41 535 public ErrorManager getErrorManager() {
jlaskey@3 536 return errors;
jlaskey@3 537 }
jlaskey@3 538
jlaskey@3 539 /**
sundar@118 540 * Get the script environment for this context
sundar@118 541 * @return script environment
sundar@118 542 */
sundar@118 543 public ScriptEnvironment getEnv() {
sundar@118 544 return env;
sundar@118 545 }
sundar@118 546
sundar@118 547 /**
jlaskey@3 548 * Get the output stream for this context
jlaskey@3 549 * @return output print writer
jlaskey@3 550 */
jlaskey@3 551 public PrintWriter getOut() {
sundar@118 552 return env.getOut();
jlaskey@3 553 }
jlaskey@3 554
jlaskey@3 555 /**
jlaskey@3 556 * Get the error stream for this context
jlaskey@3 557 * @return error print writer
jlaskey@3 558 */
jlaskey@3 559 public PrintWriter getErr() {
sundar@118 560 return env.getErr();
jlaskey@3 561 }
jlaskey@3 562
lagergren@57 563 /**
sundar@44 564 * Get the PropertyMap of the current global scope
sundar@44 565 * @return the property map of the current global scope
sundar@44 566 */
lagergren@57 567 public static PropertyMap getGlobalMap() {
sundar@771 568 return Context.getGlobal().getMap();
sundar@44 569 }
sundar@44 570
jlaskey@3 571 /**
jlaskey@3 572 * Compile a top level script.
jlaskey@3 573 *
jlaskey@3 574 * @param source the source
jlaskey@3 575 * @param scope the scope
jlaskey@3 576 *
jlaskey@3 577 * @return top level function for script
jlaskey@3 578 */
sundar@86 579 public ScriptFunction compileScript(final Source source, final ScriptObject scope) {
sundar@86 580 return compileScript(source, scope, this.errors);
jlaskey@3 581 }
jlaskey@3 582
jlaskey@3 583 /**
sundar@849 584 * Interface to represent compiled code that can be re-used across many
sundar@849 585 * global scope instances
sundar@849 586 */
sundar@849 587 public static interface MultiGlobalCompiledScript {
sundar@849 588 /**
sundar@849 589 * Obtain script function object for a specific global scope object.
sundar@849 590 *
sundar@849 591 * @param newGlobal global scope for which function object is obtained
sundar@849 592 * @return script function for script level expressions
sundar@849 593 */
sundar@849 594 public ScriptFunction getFunction(final Global newGlobal);
sundar@849 595 }
sundar@849 596
sundar@849 597 /**
sundar@849 598 * Compile a top level script.
sundar@849 599 *
sundar@849 600 * @param source the script source
sundar@849 601 * @return reusable compiled script across many global scopes.
sundar@849 602 */
sundar@849 603 public MultiGlobalCompiledScript compileScript(final Source source) {
sundar@849 604 final Class<?> clazz = compile(source, this.errors, this._strict);
attila@963 605 final MethodHandle createProgramFunctionHandle = getCreateProgramFunctionHandle(clazz);
sundar@849 606
sundar@849 607 return new MultiGlobalCompiledScript() {
sundar@849 608 @Override
sundar@849 609 public ScriptFunction getFunction(final Global newGlobal) {
attila@963 610 return invokeCreateProgramFunctionHandle(createProgramFunctionHandle, newGlobal);
sundar@849 611 }
sundar@849 612 };
sundar@849 613 }
sundar@849 614
sundar@849 615 /**
jlaskey@3 616 * Entry point for {@code eval}
jlaskey@3 617 *
jlaskey@3 618 * @param initialScope The scope of this eval call
jlaskey@3 619 * @param string Evaluated code as a String
jlaskey@3 620 * @param callThis "this" to be passed to the evaluated code
jlaskey@3 621 * @param location location of the eval call
jlaskey@3 622 * @param strict is this {@code eval} call from a strict mode code?
attila@963 623 * @return the return value of the {@code eval}
attila@963 624 */
attila@963 625 public Object eval(final ScriptObject initialScope, final String string,
attila@963 626 final Object callThis, final Object location, final boolean strict) {
attila@963 627 return eval(initialScope, string, callThis, location, strict, false);
attila@963 628 }
attila@963 629
attila@963 630 /**
attila@963 631 * Entry point for {@code eval}
attila@963 632 *
attila@963 633 * @param initialScope The scope of this eval call
attila@963 634 * @param string Evaluated code as a String
attila@963 635 * @param callThis "this" to be passed to the evaluated code
attila@963 636 * @param location location of the eval call
attila@963 637 * @param strict is this {@code eval} call from a strict mode code?
attila@963 638 * @param evalCall is this called from "eval" builtin?
jlaskey@3 639 *
jlaskey@3 640 * @return the return value of the {@code eval}
jlaskey@3 641 */
attila@963 642 public Object eval(final ScriptObject initialScope, final String string,
attila@963 643 final Object callThis, final Object location, final boolean strict, final boolean evalCall) {
attila@963 644 final String file = location == UNDEFINED || location == null ? "<eval>" : location.toString();
attila@963 645 final Source source = sourceFor(file, string, evalCall);
jlaskey@3 646 final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval?
sundar@771 647 final Global global = Context.getGlobal();
jlaskey@3 648 ScriptObject scope = initialScope;
jlaskey@3 649
jlaskey@3 650 // ECMA section 10.1.1 point 2 says eval code is strict if it begins
jlaskey@3 651 // with "use strict" directive or eval direct call itself is made
jlaskey@3 652 // from from strict mode code. We are passed with caller's strict mode.
jlaskey@3 653 boolean strictFlag = directEval && strict;
jlaskey@3 654
jlaskey@3 655 Class<?> clazz = null;
jlaskey@3 656 try {
jlaskey@3 657 clazz = compile(source, new ThrowErrorManager(), strictFlag);
jlaskey@3 658 } catch (final ParserException e) {
jlaskey@3 659 e.throwAsEcmaException(global);
jlaskey@3 660 return null;
jlaskey@3 661 }
jlaskey@3 662
jlaskey@3 663 if (!strictFlag) {
jlaskey@3 664 // We need to get strict mode flag from compiled class. This is
jlaskey@3 665 // because eval code may start with "use strict" directive.
jlaskey@3 666 try {
lagergren@211 667 strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null);
jlaskey@3 668 } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
jlaskey@3 669 //ignored
jlaskey@3 670 strictFlag = false;
jlaskey@3 671 }
jlaskey@3 672 }
jlaskey@3 673
jlaskey@3 674 // In strict mode, eval does not instantiate variables and functions
jlaskey@3 675 // in the caller's environment. A new environment is created!
jlaskey@3 676 if (strictFlag) {
jlaskey@3 677 // Create a new scope object
sundar@771 678 final ScriptObject strictEvalScope = global.newObject();
jlaskey@3 679
jlaskey@3 680 // bless it as a "scope"
jlaskey@3 681 strictEvalScope.setIsScope();
jlaskey@3 682
jlaskey@3 683 // set given scope to be it's proto so that eval can still
jlaskey@3 684 // access caller environment vars in the new environment.
jlaskey@3 685 strictEvalScope.setProto(scope);
jlaskey@3 686 scope = strictEvalScope;
jlaskey@3 687 }
jlaskey@3 688
attila@963 689 final ScriptFunction func = getProgramFunction(clazz, scope);
jlaskey@3 690 Object evalThis;
jlaskey@3 691 if (directEval) {
attila@963 692 evalThis = callThis instanceof ScriptObject || strictFlag ? callThis : global;
jlaskey@3 693 } else {
jlaskey@3 694 evalThis = global;
jlaskey@3 695 }
jlaskey@3 696
jlaskey@3 697 return ScriptRuntime.apply(func, evalThis);
jlaskey@3 698 }
jlaskey@3 699
lagergren@247 700 private static Source loadInternal(final String srcStr, final String prefix, final String resourcePath) {
jlaskey@222 701 if (srcStr.startsWith(prefix)) {
jlaskey@222 702 final String resource = resourcePath + srcStr.substring(prefix.length());
jlaskey@222 703 // NOTE: even sandbox scripts should be able to load scripts in nashorn: scheme
jlaskey@222 704 // These scripts are always available and are loaded from nashorn.jar's resources.
jlaskey@222 705 return AccessController.doPrivileged(
jlaskey@222 706 new PrivilegedAction<Source>() {
jlaskey@222 707 @Override
jlaskey@222 708 public Source run() {
jlaskey@222 709 try {
jlaskey@222 710 final URL resURL = Context.class.getResource(resource);
attila@963 711 return resURL != null ? sourceFor(srcStr, resURL) : null;
jlaskey@222 712 } catch (final IOException exp) {
jlaskey@222 713 return null;
jlaskey@222 714 }
jlaskey@222 715 }
jlaskey@222 716 });
jlaskey@222 717 }
jlaskey@222 718
jlaskey@222 719 return null;
jlaskey@222 720 }
jlaskey@222 721
jlaskey@3 722 /**
jlaskey@3 723 * Implementation of {@code load} Nashorn extension. Load a script file from a source
jlaskey@3 724 * expression
jlaskey@3 725 *
jlaskey@3 726 * @param scope the scope
sundar@86 727 * @param from source expression for script
jlaskey@3 728 *
jlaskey@3 729 * @return return value for load call (undefined)
jlaskey@3 730 *
jlaskey@3 731 * @throws IOException if source cannot be found or loaded
jlaskey@3 732 */
sundar@86 733 public Object load(final ScriptObject scope, final Object from) throws IOException {
attila@963 734 final Object src = from instanceof ConsString ? from.toString() : from;
sundar@86 735 Source source = null;
jlaskey@3 736
sundar@86 737 // load accepts a String (which could be a URL or a file name), a File, a URL
sundar@86 738 // or a ScriptObject that has "name" and "source" (string valued) properties.
jlaskey@3 739 if (src instanceof String) {
lagergren@107 740 final String srcStr = (String)src;
sundar@587 741 if (srcStr.startsWith(LOAD_CLASSPATH)) {
attila@962 742 final URL url = getResourceURL(srcStr.substring(LOAD_CLASSPATH.length()));
attila@963 743 source = url != null ? sourceFor(url.toString(), url) : null;
sundar@587 744 } else {
sundar@587 745 final File file = new File(srcStr);
sundar@587 746 if (srcStr.indexOf(':') != -1) {
sundar@587 747 if ((source = loadInternal(srcStr, LOAD_NASHORN, "resources/")) == null &&
sundar@587 748 (source = loadInternal(srcStr, LOAD_FX, "resources/fx/")) == null) {
sundar@587 749 URL url;
sundar@587 750 try {
sundar@587 751 //check for malformed url. if malformed, it may still be a valid file
sundar@587 752 url = new URL(srcStr);
sundar@587 753 } catch (final MalformedURLException e) {
sundar@587 754 url = file.toURI().toURL();
sundar@587 755 }
hannesw@845 756 source = sourceFor(url.toString(), url);
lagergren@110 757 }
sundar@587 758 } else if (file.isFile()) {
hannesw@845 759 source = sourceFor(srcStr, file);
jlaskey@3 760 }
jlaskey@3 761 }
sundar@86 762 } else if (src instanceof File && ((File)src).isFile()) {
jlaskey@3 763 final File file = (File)src;
hannesw@845 764 source = sourceFor(file.getName(), file);
jlaskey@3 765 } else if (src instanceof URL) {
sundar@86 766 final URL url = (URL)src;
hannesw@845 767 source = sourceFor(url.toString(), url);
jlaskey@3 768 } else if (src instanceof ScriptObject) {
jlaskey@3 769 final ScriptObject sobj = (ScriptObject)src;
jlaskey@3 770 if (sobj.has("script") && sobj.has("name")) {
jlaskey@3 771 final String script = JSType.toString(sobj.get("script"));
jlaskey@3 772 final String name = JSType.toString(sobj.get("name"));
hannesw@845 773 source = sourceFor(name, script);
jlaskey@3 774 }
sundar@350 775 } else if (src instanceof Map) {
sundar@468 776 final Map<?,?> map = (Map<?,?>)src;
sundar@350 777 if (map.containsKey("script") && map.containsKey("name")) {
sundar@350 778 final String script = JSType.toString(map.get("script"));
sundar@350 779 final String name = JSType.toString(map.get("name"));
hannesw@845 780 source = sourceFor(name, script);
sundar@350 781 }
jlaskey@3 782 }
jlaskey@3 783
sundar@86 784 if (source != null) {
sundar@86 785 return evaluateSource(source, scope, scope);
sundar@86 786 }
sundar@86 787
lagergren@112 788 throw typeError("cant.load.script", ScriptRuntime.safeToString(from));
jlaskey@3 789 }
jlaskey@3 790
jlaskey@3 791 /**
jlaskey@317 792 * Implementation of {@code loadWithNewGlobal} Nashorn extension. Load a script file from a source
jlaskey@317 793 * expression, after creating a new global scope.
jlaskey@317 794 *
jlaskey@317 795 * @param from source expression for script
sundar@337 796 * @param args (optional) arguments to be passed to the loaded script
jlaskey@317 797 *
jlaskey@317 798 * @return return value for load call (undefined)
jlaskey@317 799 *
jlaskey@317 800 * @throws IOException if source cannot be found or loaded
jlaskey@317 801 */
sundar@337 802 public Object loadWithNewGlobal(final Object from, final Object...args) throws IOException {
sundar@771 803 final Global oldGlobal = getGlobal();
sundar@771 804 final Global newGlobal = AccessController.doPrivileged(new PrivilegedAction<Global>() {
jlaskey@319 805 @Override
sundar@771 806 public Global run() {
jlaskey@319 807 try {
sundar@492 808 return newGlobal();
jlaskey@319 809 } catch (final RuntimeException e) {
jlaskey@319 810 if (Context.DEBUG) {
jlaskey@319 811 e.printStackTrace();
jlaskey@319 812 }
jlaskey@319 813 throw e;
jlaskey@319 814 }
jlaskey@319 815 }
sundar@492 816 }, CREATE_GLOBAL_ACC_CTXT);
sundar@492 817 // initialize newly created Global instance
sundar@492 818 initGlobal(newGlobal);
sundar@771 819 setGlobal(newGlobal);
jlaskey@317 820
sundar@437 821 final Object[] wrapped = args == null? ScriptRuntime.EMPTY_ARRAY : ScriptObjectMirror.wrapArray(args, oldGlobal);
sundar@771 822 newGlobal.put("arguments", newGlobal.wrapAsObject(wrapped), env._strict);
sundar@337 823
jlaskey@317 824 try {
sundar@437 825 // wrap objects from newGlobal's world as mirrors - but if result
sundar@437 826 // is from oldGlobal's world, unwrap it!
sundar@437 827 return ScriptObjectMirror.unwrap(ScriptObjectMirror.wrap(load(newGlobal, from), newGlobal), oldGlobal);
jlaskey@317 828 } finally {
sundar@771 829 setGlobal(oldGlobal);
jlaskey@317 830 }
jlaskey@317 831 }
jlaskey@317 832
jlaskey@317 833 /**
jlaskey@3 834 * Load or get a structure class. Structure class names are based on the number of parameter fields
jlaskey@3 835 * and {@link AccessorProperty} fields in them. Structure classes are used to represent ScriptObjects
jlaskey@3 836 *
jlaskey@3 837 * @see ObjectClassGenerator
jlaskey@3 838 * @see AccessorProperty
jlaskey@3 839 * @see ScriptObject
jlaskey@3 840 *
jlaskey@131 841 * @param fullName full name of class, e.g. jdk.nashorn.internal.objects.JO2P1 contains 2 fields and 1 parameter.
jlaskey@3 842 *
sundar@128 843 * @return the {@code Class<?>} for this structure
jlaskey@3 844 *
jlaskey@3 845 * @throws ClassNotFoundException if structure class cannot be resolved
jlaskey@3 846 */
attila@963 847 @SuppressWarnings("unchecked")
attila@963 848 public static Class<? extends ScriptObject> forStructureClass(final String fullName) throws ClassNotFoundException {
sundar@552 849 if (System.getSecurityManager() != null && !StructureLoader.isStructureClass(fullName)) {
sundar@468 850 throw new ClassNotFoundException(fullName);
sundar@468 851 }
attila@963 852 return (Class<? extends ScriptObject>)Class.forName(fullName, true, sharedLoader);
jlaskey@3 853 }
jlaskey@3 854
jlaskey@3 855 /**
sundar@590 856 * Checks that the given Class can be accessed from no permissions context.
sundar@428 857 *
sundar@590 858 * @param clazz Class object
sundar@770 859 * @throws SecurityException if not accessible
sundar@428 860 */
lagergren@605 861 public static void checkPackageAccess(final Class<?> clazz) {
sundar@590 862 final SecurityManager sm = System.getSecurityManager();
sundar@590 863 if (sm != null) {
lagergren@605 864 Class<?> bottomClazz = clazz;
lagergren@605 865 while (bottomClazz.isArray()) {
sundar@590 866 bottomClazz = bottomClazz.getComponentType();
sundar@428 867 }
sundar@590 868 checkPackageAccess(sm, bottomClazz.getName());
sundar@428 869 }
sundar@428 870 }
sundar@428 871
sundar@428 872 /**
attila@719 873 * Checks that the given package name can be accessed from no permissions context.
attila@719 874 *
attila@719 875 * @param pkgName package name
sundar@770 876 * @throws SecurityException if not accessible
attila@719 877 */
attila@719 878 public static void checkPackageAccess(final String pkgName) {
attila@719 879 final SecurityManager sm = System.getSecurityManager();
attila@719 880 if (sm != null) {
hannesw@769 881 checkPackageAccess(sm, pkgName.endsWith(".") ? pkgName : pkgName + ".");
attila@719 882 }
attila@719 883 }
attila@719 884
attila@719 885 /**
sundar@468 886 * Checks that the given package can be accessed from no permissions context.
sundar@459 887 *
sundar@590 888 * @param sm current security manager instance
sundar@459 889 * @param fullName fully qualified package name
sundar@590 890 * @throw SecurityException if not accessible
sundar@590 891 */
sundar@590 892 private static void checkPackageAccess(final SecurityManager sm, final String fullName) {
sundar@590 893 sm.getClass(); // null check
sundar@590 894 final int index = fullName.lastIndexOf('.');
sundar@590 895 if (index != -1) {
sundar@590 896 final String pkgName = fullName.substring(0, index);
sundar@590 897 AccessController.doPrivileged(new PrivilegedAction<Void>() {
sundar@590 898 @Override
sundar@590 899 public Void run() {
sundar@590 900 sm.checkPackageAccess(pkgName);
sundar@590 901 return null;
sundar@590 902 }
sundar@590 903 }, NO_PERMISSIONS_ACC_CTXT);
sundar@590 904 }
sundar@590 905 }
sundar@590 906
sundar@590 907 /**
sundar@590 908 * Checks that the given Class can be accessed from no permissions context.
sundar@590 909 *
sundar@590 910 * @param clazz Class object
sundar@459 911 * @return true if package is accessible, false otherwise
sundar@459 912 */
lagergren@605 913 private static boolean isAccessiblePackage(final Class<?> clazz) {
sundar@459 914 try {
sundar@590 915 checkPackageAccess(clazz);
sundar@459 916 return true;
sundar@459 917 } catch (final SecurityException se) {
sundar@459 918 return false;
sundar@459 919 }
sundar@459 920 }
sundar@459 921
sundar@459 922 /**
sundar@468 923 * Checks that the given Class is public and it can be accessed from no permissions context.
sundar@459 924 *
sundar@459 925 * @param clazz Class object to check
sundar@459 926 * @return true if Class is accessible, false otherwise
sundar@459 927 */
sundar@459 928 public static boolean isAccessibleClass(final Class<?> clazz) {
sundar@590 929 return Modifier.isPublic(clazz.getModifiers()) && Context.isAccessiblePackage(clazz);
sundar@459 930 }
sundar@459 931
sundar@459 932 /**
jlaskey@3 933 * Lookup a Java class. This is used for JSR-223 stuff linking in from
lagergren@253 934 * {@code jdk.nashorn.internal.objects.NativeJava} and {@code jdk.nashorn.internal.runtime.NativeJavaPackage}
jlaskey@3 935 *
jlaskey@3 936 * @param fullName full name of class to load
jlaskey@3 937 *
sundar@128 938 * @return the {@code Class<?>} for the name
jlaskey@3 939 *
jlaskey@3 940 * @throws ClassNotFoundException if class cannot be resolved
jlaskey@3 941 */
jlaskey@3 942 public Class<?> findClass(final String fullName) throws ClassNotFoundException {
sundar@590 943 if (fullName.indexOf('[') != -1 || fullName.indexOf('/') != -1) {
sundar@590 944 // don't allow array class names or internal names.
sundar@590 945 throw new ClassNotFoundException(fullName);
sundar@590 946 }
sundar@590 947
sundar@964 948 // give chance to ClassFilter to filter out, if present
sundar@964 949 if (classFilter != null && !classFilter.exposeToScripts(fullName)) {
sundar@964 950 throw new ClassNotFoundException(fullName);
sundar@964 951 }
sundar@964 952
jlaskey@3 953 // check package access as soon as possible!
sundar@590 954 final SecurityManager sm = System.getSecurityManager();
sundar@590 955 if (sm != null) {
sundar@590 956 checkPackageAccess(sm, fullName);
sundar@590 957 }
jlaskey@3 958
sundar@41 959 // try the script -classpath loader, if that is set
jlaskey@3 960 if (classPathLoader != null) {
jlaskey@3 961 try {
jlaskey@3 962 return Class.forName(fullName, true, classPathLoader);
sundar@41 963 } catch (final ClassNotFoundException ignored) {
jlaskey@3 964 // ignore, continue search
jlaskey@3 965 }
jlaskey@3 966 }
jlaskey@3 967
sundar@41 968 // Try finding using the "app" loader.
sundar@41 969 return Class.forName(fullName, true, appLoader);
jlaskey@3 970 }
jlaskey@3 971
jlaskey@3 972 /**
jlaskey@3 973 * Hook to print stack trace for a {@link Throwable} that occurred during
jlaskey@3 974 * execution
jlaskey@3 975 *
jlaskey@3 976 * @param t throwable for which to dump stack
jlaskey@3 977 */
jlaskey@3 978 public static void printStackTrace(final Throwable t) {
jlaskey@3 979 if (Context.DEBUG) {
sundar@41 980 t.printStackTrace(Context.getCurrentErr());
jlaskey@3 981 }
jlaskey@3 982 }
jlaskey@3 983
jlaskey@3 984 /**
jlaskey@3 985 * Verify generated bytecode before emission. This is called back from the
sundar@118 986 * {@link ObjectClassGenerator} or the {@link Compiler}. If the "--verify-code" parameter
jlaskey@3 987 * hasn't been given, this is a nop
jlaskey@3 988 *
jlaskey@3 989 * Note that verification may load classes -- we don't want to do that unless
jlaskey@3 990 * user specified verify option. We check it here even though caller
jlaskey@3 991 * may have already checked that flag
jlaskey@3 992 *
jlaskey@3 993 * @param bytecode bytecode to verify
jlaskey@3 994 */
jlaskey@3 995 public void verify(final byte[] bytecode) {
sundar@118 996 if (env._verify_code) {
jlaskey@3 997 // No verification when security manager is around as verifier
jlaskey@3 998 // may load further classes - which should be avoided.
jlaskey@3 999 if (System.getSecurityManager() == null) {
sundar@469 1000 CheckClassAdapter.verify(new ClassReader(bytecode), sharedLoader, false, new PrintWriter(System.err, true));
jlaskey@3 1001 }
jlaskey@3 1002 }
jlaskey@3 1003 }
jlaskey@3 1004
jlaskey@3 1005 /**
jlaskey@67 1006 * Create and initialize a new global scope object.
jlaskey@67 1007 *
jlaskey@67 1008 * @return the initialized global scope object.
jlaskey@67 1009 */
sundar@771 1010 public Global createGlobal() {
jlaskey@67 1011 return initGlobal(newGlobal());
jlaskey@67 1012 }
jlaskey@67 1013
jlaskey@67 1014 /**
jlaskey@67 1015 * Create a new uninitialized global scope object
jlaskey@3 1016 * @return the global script object
jlaskey@3 1017 */
sundar@771 1018 public Global newGlobal() {
sundar@456 1019 return new Global(this);
jlaskey@67 1020 }
jlaskey@67 1021
jlaskey@67 1022 /**
jlaskey@67 1023 * Initialize given global scope object.
jlaskey@67 1024 *
lagergren@89 1025 * @param global the global
attila@963 1026 * @param engine the associated ScriptEngine instance, can be null
jlaskey@67 1027 * @return the initialized global scope object.
jlaskey@67 1028 */
attila@963 1029 public Global initGlobal(final Global global, final ScriptEngine engine) {
jlaskey@3 1030 // Need only minimal global object, if we are just compiling.
sundar@118 1031 if (!env._compile_only) {
sundar@771 1032 final Global oldGlobal = Context.getGlobal();
sundar@41 1033 try {
sundar@771 1034 Context.setGlobal(global);
sundar@41 1035 // initialize global scope with builtin global objects
attila@963 1036 global.initBuiltinObjects(engine);
sundar@41 1037 } finally {
sundar@771 1038 Context.setGlobal(oldGlobal);
sundar@41 1039 }
jlaskey@3 1040 }
jlaskey@3 1041
jlaskey@3 1042 return global;
jlaskey@3 1043 }
jlaskey@3 1044
jlaskey@3 1045 /**
attila@963 1046 * Initialize given global scope object.
attila@963 1047 *
attila@963 1048 * @param global the global
attila@963 1049 * @return the initialized global scope object.
sundar@44 1050 */
attila@963 1051 public Global initGlobal(final Global global) {
attila@963 1052 return initGlobal(global, null);
attila@963 1053 }
sundar@44 1054
sundar@44 1055 /**
sundar@44 1056 * Return the current global's context
sundar@44 1057 * @return current global's context
sundar@41 1058 */
sundar@41 1059 static Context getContextTrusted() {
sundar@771 1060 return ((ScriptObject)Context.getGlobal()).getContext();
sundar@41 1061 }
sundar@41 1062
attila@963 1063 static Context getContextTrustedOrNull() {
attila@963 1064 final Global global = Context.getGlobal();
attila@963 1065 return global == null ? null : ((ScriptObject)global).getContext();
attila@963 1066 }
attila@963 1067
sundar@41 1068 /**
jlaskey@3 1069 * Try to infer Context instance from the Class. If we cannot,
jlaskey@3 1070 * then get it from the thread local variable.
jlaskey@3 1071 *
jlaskey@3 1072 * @param clazz the class
jlaskey@3 1073 * @return context
jlaskey@3 1074 */
jlaskey@3 1075 static Context fromClass(final Class<?> clazz) {
jlaskey@3 1076 final ClassLoader loader = clazz.getClassLoader();
jlaskey@3 1077
sundar@552 1078 if (loader instanceof ScriptLoader) {
sundar@552 1079 return ((ScriptLoader)loader).getContext();
jlaskey@3 1080 }
jlaskey@3 1081
sundar@552 1082 return Context.getContextTrusted();
jlaskey@3 1083 }
jlaskey@3 1084
lagergren@605 1085 private URL getResourceURL(final String resName) {
sundar@587 1086 // try the classPathLoader if we have and then
sundar@587 1087 // try the appLoader if non-null.
sundar@587 1088 if (classPathLoader != null) {
sundar@587 1089 return classPathLoader.getResource(resName);
sundar@587 1090 } else if (appLoader != null) {
sundar@587 1091 return appLoader.getResource(resName);
sundar@587 1092 }
sundar@587 1093
sundar@587 1094 return null;
sundar@587 1095 }
sundar@587 1096
jlaskey@3 1097 private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) {
jlaskey@3 1098 ScriptFunction script = null;
jlaskey@3 1099
jlaskey@3 1100 try {
sundar@86 1101 script = compileScript(source, scope, new Context.ThrowErrorManager());
jlaskey@3 1102 } catch (final ParserException e) {
sundar@44 1103 e.throwAsEcmaException();
jlaskey@3 1104 }
jlaskey@3 1105
jlaskey@3 1106 return ScriptRuntime.apply(script, thiz);
jlaskey@3 1107 }
jlaskey@3 1108
attila@963 1109 private static ScriptFunction getProgramFunction(final Class<?> script, final ScriptObject scope) {
attila@963 1110 if (script == null) {
attila@963 1111 return null;
attila@963 1112 }
attila@963 1113 return invokeCreateProgramFunctionHandle(getCreateProgramFunctionHandle(script), scope);
sundar@849 1114 }
jlaskey@3 1115
attila@963 1116 private static MethodHandle getCreateProgramFunctionHandle(final Class<?> script) {
sundar@849 1117 try {
attila@963 1118 return LOOKUP.findStatic(script, CREATE_PROGRAM_FUNCTION.symbolName(), CREATE_PROGRAM_FUNCTION_TYPE);
attila@963 1119 } catch (NoSuchMethodException | IllegalAccessException e) {
attila@963 1120 throw new AssertionError("Failed to retrieve a handle for the program function for " + script.getName(), e);
sundar@849 1121 }
sundar@849 1122 }
jlaskey@3 1123
attila@963 1124 private static ScriptFunction invokeCreateProgramFunctionHandle(final MethodHandle createProgramFunctionHandle, final ScriptObject scope) {
attila@963 1125 try {
attila@963 1126 return (ScriptFunction)createProgramFunctionHandle.invokeExact(scope);
attila@963 1127 } catch (final RuntimeException|Error e) {
attila@963 1128 throw e;
attila@963 1129 } catch (final Throwable t) {
attila@963 1130 throw new AssertionError("Failed to create a program function", t);
jlaskey@3 1131 }
jlaskey@3 1132 }
jlaskey@3 1133
sundar@86 1134 private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) {
attila@963 1135 return getProgramFunction(compile(source, errMan, this._strict), scope);
jlaskey@3 1136 }
jlaskey@3 1137
sundar@86 1138 private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) {
jlaskey@3 1139 // start with no errors, no warnings.
jlaskey@3 1140 errMan.reset();
jlaskey@3 1141
hannesw@769 1142 Class<?> script = findCachedClass(source);
hannesw@769 1143 if (script != null) {
attila@963 1144 final DebugLogger log = getLogger(Compiler.class);
attila@963 1145 if (log.isEnabled()) {
attila@963 1146 log.fine(new RuntimeEvent<>(Level.INFO, source), "Code cache hit for ", source, " avoiding recompile.");
attila@963 1147 }
hannesw@769 1148 return script;
jlaskey@3 1149 }
jlaskey@3 1150
attila@963 1151 StoredScript storedScript = null;
hannesw@828 1152 FunctionNode functionNode = null;
hannesw@1035 1153 // We only use the code store here if optimistic types are disabled. With optimistic types,
hannesw@1035 1154 // code is stored per function in RecompilableScriptFunctionData.
hannesw@1035 1155 // TODO: This should really be triggered by lazy compilation, not optimistic types.
attila@963 1156 final boolean useCodeStore = env._persistent_cache && !env._parse_only && !env._optimistic_types;
attila@963 1157 final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null;
hannesw@828 1158
attila@963 1159 if (useCodeStore) {
hannesw@1018 1160 storedScript = codeStore.load(source, cacheKey);
jlaskey@3 1161 }
jlaskey@3 1162
attila@963 1163 if (storedScript == null) {
attila@963 1164 functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse();
lagergren@96 1165
hannesw@991 1166 if (errMan.hasErrors()) {
hannesw@828 1167 return null;
hannesw@828 1168 }
hannesw@828 1169
attila@963 1170 if (env._print_ast || functionNode.getFlag(FunctionNode.IS_PRINT_AST)) {
hannesw@828 1171 getErr().println(new ASTWriter(functionNode));
hannesw@828 1172 }
hannesw@828 1173
attila@963 1174 if (env._print_parse || functionNode.getFlag(FunctionNode.IS_PRINT_PARSE)) {
attila@963 1175 getErr().println(new PrintVisitor(functionNode, true, false));
hannesw@828 1176 }
lagergren@89 1177 }
lagergren@89 1178
lagergren@211 1179 if (env._parse_only) {
lagergren@211 1180 return null;
lagergren@211 1181 }
lagergren@211 1182
lagergren@89 1183 final URL url = source.getURL();
sundar@118 1184 final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader;
sundar@764 1185 final CodeSource cs = new CodeSource(url, (CodeSigner[])null);
sundar@118 1186 final CodeInstaller<ScriptEnvironment> installer = new ContextCodeInstaller(this, loader, cs);
jlaskey@3 1187
attila@963 1188 if (storedScript == null) {
attila@963 1189 final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL;
attila@963 1190
attila@963 1191 final Compiler compiler = new Compiler(
attila@963 1192 this,
attila@963 1193 env,
attila@963 1194 installer,
attila@963 1195 source,
hannesw@991 1196 errMan,
attila@963 1197 strict | functionNode.isStrict());
attila@963 1198
attila@963 1199 final FunctionNode compiledFunction = compiler.compile(functionNode, phases);
hannesw@991 1200 if (errMan.hasErrors()) {
hannesw@991 1201 return null;
hannesw@991 1202 }
attila@963 1203 script = compiledFunction.getRootClass();
attila@963 1204 compiler.persistClassInfo(cacheKey, compiledFunction);
hannesw@828 1205 } else {
attila@963 1206 Compiler.updateCompilationId(storedScript.getCompilationId());
attila@963 1207 script = install(storedScript, source, installer);
hannesw@828 1208 }
lagergren@89 1209
hannesw@769 1210 cacheClass(source, script);
jlaskey@3 1211 return script;
jlaskey@3 1212 }
jlaskey@3 1213
jlaskey@3 1214 private ScriptLoader createNewLoader() {
jlaskey@3 1215 return AccessController.doPrivileged(
jlaskey@3 1216 new PrivilegedAction<ScriptLoader>() {
jlaskey@3 1217 @Override
jlaskey@3 1218 public ScriptLoader run() {
sundar@552 1219 return new ScriptLoader(appLoader, Context.this);
jlaskey@3 1220 }
sundar@492 1221 }, CREATE_LOADER_ACC_CTXT);
jlaskey@3 1222 }
jlaskey@3 1223
sundar@427 1224 private long getUniqueScriptId() {
sundar@427 1225 return uniqueScriptId.getAndIncrement();
sundar@425 1226 }
hannesw@769 1227
hannesw@828 1228 /**
hannesw@828 1229 * Install a previously compiled class from the code cache.
hannesw@828 1230 *
attila@963 1231 * @param storedScript cached script containing class bytes and constants
hannesw@828 1232 * @return main script class
hannesw@828 1233 */
attila@963 1234 private static Class<?> install(final StoredScript storedScript, final Source source, final CodeInstaller<ScriptEnvironment> installer) {
hannesw@828 1235
hannesw@828 1236 final Map<String, Class<?>> installedClasses = new HashMap<>();
hannesw@1018 1237 final Map<String, byte[]> classBytes = storedScript.getClassBytes();
attila@963 1238 final Object[] constants = storedScript.getConstants();
attila@963 1239 final String mainClassName = storedScript.getMainClassName();
hannesw@1018 1240 final byte[] mainClassBytes = classBytes.get(mainClassName);
attila@963 1241 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
hannesw@1035 1242 final Map<Integer, FunctionInitializer> initializers = storedScript.getInitializers();
hannesw@828 1243
attila@963 1244 installedClasses.put(mainClassName, mainClass);
hannesw@828 1245
hannesw@1018 1246 for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
hannesw@828 1247 final String className = entry.getKey();
attila@963 1248 if (className.equals(mainClassName)) {
hannesw@828 1249 continue;
hannesw@828 1250 }
hannesw@828 1251 final byte[] code = entry.getValue();
hannesw@828 1252
attila@963 1253 installedClasses.put(className, installer.install(className, code));
hannesw@828 1254 }
attila@963 1255
attila@963 1256 installer.initialize(installedClasses.values(), source, constants);
attila@963 1257
attila@962 1258 for (final Object constant : constants) {
hannesw@828 1259 if (constant instanceof RecompilableScriptFunctionData) {
attila@963 1260 final RecompilableScriptFunctionData data = (RecompilableScriptFunctionData) constant;
attila@963 1261 data.initTransients(source, installer);
hannesw@1035 1262 final FunctionInitializer initializer = initializers.get(data.getFunctionNodeId());
hannesw@1035 1263 if (initializer != null) {
attila@963 1264 initializer.setCode(installedClasses.get(initializer.getClassName()));
attila@963 1265 data.initializeCode(initializer);
attila@963 1266 }
hannesw@828 1267 }
hannesw@828 1268 }
hannesw@828 1269
attila@963 1270 return mainClass;
hannesw@828 1271 }
hannesw@828 1272
hannesw@769 1273 /**
hannesw@769 1274 * Cache for compiled script classes.
hannesw@769 1275 */
hannesw@769 1276 @SuppressWarnings("serial")
hannesw@769 1277 private static class ClassCache extends LinkedHashMap<Source, ClassReference> {
hannesw@769 1278 private final int size;
hannesw@769 1279 private final ReferenceQueue<Class<?>> queue;
hannesw@769 1280
attila@962 1281 ClassCache(final int size) {
hannesw@769 1282 super(size, 0.75f, true);
hannesw@769 1283 this.size = size;
hannesw@769 1284 this.queue = new ReferenceQueue<>();
hannesw@769 1285 }
hannesw@769 1286
hannesw@769 1287 void cache(final Source source, final Class<?> clazz) {
hannesw@769 1288 put(source, new ClassReference(clazz, queue, source));
hannesw@769 1289 }
hannesw@769 1290
hannesw@769 1291 @Override
hannesw@769 1292 protected boolean removeEldestEntry(final Map.Entry<Source, ClassReference> eldest) {
hannesw@769 1293 return size() > size;
hannesw@769 1294 }
hannesw@769 1295
hannesw@769 1296 @Override
attila@962 1297 public ClassReference get(final Object key) {
hannesw@769 1298 for (ClassReference ref; (ref = (ClassReference)queue.poll()) != null; ) {
hannesw@769 1299 remove(ref.source);
hannesw@769 1300 }
hannesw@769 1301 return super.get(key);
hannesw@769 1302 }
hannesw@769 1303
hannesw@769 1304 }
hannesw@769 1305
hannesw@769 1306 private static class ClassReference extends SoftReference<Class<?>> {
hannesw@769 1307 private final Source source;
hannesw@769 1308
hannesw@769 1309 ClassReference(final Class<?> clazz, final ReferenceQueue<Class<?>> queue, final Source source) {
hannesw@769 1310 super(clazz, queue);
hannesw@769 1311 this.source = source;
hannesw@769 1312 }
hannesw@769 1313 }
hannesw@769 1314
hannesw@769 1315 // Class cache management
hannesw@769 1316 private Class<?> findCachedClass(final Source source) {
attila@962 1317 final ClassReference ref = classCache == null ? null : classCache.get(source);
hannesw@769 1318 return ref != null ? ref.get() : null;
hannesw@769 1319 }
hannesw@769 1320
hannesw@769 1321 private void cacheClass(final Source source, final Class<?> clazz) {
hannesw@769 1322 if (classCache != null) {
hannesw@769 1323 classCache.cache(source, clazz);
hannesw@769 1324 }
hannesw@769 1325 }
hannesw@769 1326
attila@963 1327 // logging
attila@963 1328 private final Map<String, DebugLogger> loggers = new HashMap<>();
attila@963 1329
attila@963 1330 private void initLoggers() {
attila@963 1331 ((Loggable)MethodHandleFactory.getFunctionality()).initLogger(this);
attila@963 1332 }
attila@963 1333
attila@963 1334 /**
attila@963 1335 * Get a logger, given a loggable class
attila@963 1336 * @param clazz a Loggable class
attila@963 1337 * @return debuglogger associated with that class
attila@963 1338 */
attila@963 1339 public DebugLogger getLogger(final Class<? extends Loggable> clazz) {
attila@963 1340 return getLogger(clazz, null);
attila@963 1341 }
attila@963 1342
attila@963 1343 /**
attila@963 1344 * Get a logger, given a loggable class
attila@963 1345 * @param clazz a Loggable class
attila@963 1346 * @param initHook an init hook - if this is the first time the logger is created in the context, run the init hook
attila@963 1347 * @return debuglogger associated with that class
attila@963 1348 */
attila@963 1349 public DebugLogger getLogger(final Class<? extends Loggable> clazz, final Consumer<DebugLogger> initHook) {
attila@963 1350 final String name = getLoggerName(clazz);
attila@963 1351 DebugLogger logger = loggers.get(name);
attila@963 1352 if (logger == null) {
attila@963 1353 if (!env.hasLogger(name)) {
attila@963 1354 return DebugLogger.DISABLED_LOGGER;
attila@963 1355 }
attila@963 1356 final LoggerInfo info = env._loggers.get(name);
attila@963 1357 logger = new DebugLogger(name, info.getLevel(), info.isQuiet());
attila@963 1358 if (initHook != null) {
attila@963 1359 initHook.accept(logger);
attila@963 1360 }
attila@963 1361 loggers.put(name, logger);
attila@963 1362 }
attila@963 1363 return logger;
attila@963 1364 }
attila@963 1365
attila@963 1366 /**
attila@963 1367 * Given a Loggable class, weave debug info info a method handle for that logger.
attila@963 1368 * Level.INFO is used
attila@963 1369 *
attila@963 1370 * @param clazz loggable
attila@963 1371 * @param mh method handle
attila@963 1372 * @param text debug printout to add
attila@963 1373 *
attila@963 1374 * @return instrumented method handle, or null if logger not enabled
attila@963 1375 */
attila@963 1376 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final MethodHandle mh, final Supplier<String> text) {
attila@963 1377 return addLoggingToHandle(clazz, Level.INFO, mh, Integer.MAX_VALUE, false, text);
attila@963 1378 }
attila@963 1379
attila@963 1380 /**
attila@963 1381 * Given a Loggable class, weave debug info info a method handle for that logger.
attila@963 1382 *
attila@963 1383 * @param clazz loggable
attila@963 1384 * @param level log level
attila@963 1385 * @param mh method handle
attila@963 1386 * @param paramStart first parameter to print
attila@963 1387 * @param printReturnValue should we print the return vaulue?
attila@963 1388 * @param text debug printout to add
attila@963 1389 *
attila@963 1390 * @return instrumented method handle, or null if logger not enabled
attila@963 1391 */
attila@963 1392 public MethodHandle addLoggingToHandle(final Class<? extends Loggable> clazz, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Supplier<String> text) {
attila@963 1393 final DebugLogger log = getLogger(clazz);
attila@963 1394 if (log.isEnabled()) {
attila@963 1395 return MethodHandleFactory.addDebugPrintout(log, level, mh, paramStart, printReturnValue, text.get());
attila@963 1396 }
attila@963 1397 return mh;
attila@963 1398 }
attila@963 1399
attila@963 1400 private static String getLoggerName(final Class<?> clazz) {
attila@963 1401 Class<?> current = clazz;
attila@963 1402 while (current != null) {
attila@963 1403 final Logger log = current.getAnnotation(Logger.class);
attila@963 1404 if (log != null) {
attila@963 1405 assert !"".equals(log.name());
attila@963 1406 return log.name();
attila@963 1407 }
attila@963 1408 current = current.getSuperclass();
attila@963 1409 }
attila@963 1410 assert false;
attila@963 1411 return null;
attila@963 1412 }
hannesw@769 1413
lagergren@1028 1414 /**
lagergren@1028 1415 * This is a special kind of switchpoint used to guard builtin
lagergren@1028 1416 * properties and prototypes. In the future it might contain
lagergren@1028 1417 * logic to e.g. multiple switchpoint classes.
lagergren@1028 1418 */
lagergren@1028 1419 public static final class BuiltinSwitchPoint extends SwitchPoint {
lagergren@1028 1420 //empty
lagergren@1028 1421 }
lagergren@1028 1422
lagergren@1028 1423 /**
lagergren@1028 1424 * Create a new builtin switchpoint and return it
lagergren@1028 1425 * @param name key name
lagergren@1028 1426 * @return new builtin switchpoint
lagergren@1028 1427 */
lagergren@1028 1428 public SwitchPoint newBuiltinSwitchPoint(final String name) {
lagergren@1028 1429 assert builtinSwitchPoints.get(name) == null;
lagergren@1028 1430 final SwitchPoint sp = new BuiltinSwitchPoint();
lagergren@1028 1431 builtinSwitchPoints.put(name, sp);
lagergren@1028 1432 return sp;
lagergren@1028 1433 }
lagergren@1028 1434
lagergren@1028 1435 /**
lagergren@1028 1436 * Return the builtin switchpoint for a particular key name
lagergren@1028 1437 * @param name key name
lagergren@1028 1438 * @return builtin switchpoint or null if none
lagergren@1028 1439 */
lagergren@1028 1440 public SwitchPoint getBuiltinSwitchPoint(final String name) {
lagergren@1028 1441 return builtinSwitchPoints.get(name);
lagergren@1028 1442 }
lagergren@1028 1443
jlaskey@3 1444 }

mercurial