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