/** * Construct a parser. * * @param env script environment * @param source source to parse * @param errors error manager * @param strict parser created with strict mode enabled. * @param lineOffset line offset to start counting lines from * @param log debug logger if one is needed */ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset, final DebugLogger log) { super(source, errors, strict, lineOffset); this.env = env; this.namespace = new Namespace(env.getNamespace()); this.scripting = env._scripting; if (this.scripting) { this.lineInfoReceiver = new Lexer.LineInfoReceiver() { @Override public void lineInfo(final int receiverLine, final int receiverLinePosition) { // update the parser maintained line information Parser.this.line = receiverLine; Parser.this.linePosition = receiverLinePosition; } }; } else { // non-scripting mode script can't have multi-line literals this.lineInfoReceiver = null; } this.log = log == null ? DebugLogger.DISABLED_LOGGER : log; }
CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final MethodType callSiteType, final Specialization specialization, final DebugLogger log) { this.specialization = specialization; if (specialization != null && specialization.isOptimistic()) { /* * An optimistic builtin with isOptimistic=true works like any optimistic generated function, i.e. it * can throw unwarranted optimism exceptions. As native functions trivially can't have parts of them * regenerated as restof methods, this only works if the methods are atomic/functional in their behavior * and doesn't modify state before an UOE can be thrown. If they aren't, we can reexecute a wider version * of the same builtin in a recompilation handler for FinalScriptFunctionData. There are several * candidate methods in Native* that would benefit from this, but I haven't had time to implement any * of them currently. In order to fit in with the relinking framework, the current thinking is * that the methods still take a program point to fit in with other optimistic functions, but * it is set to "first", which is the beginning of the method. The relinker can tell the difference * between builtin and JavaScript functions. This might change. TODO */ this.invoker = MH.insertArguments(invoker, invoker.type().parameterCount() - 1, UnwarrantedOptimismException.FIRST_PROGRAM_POINT); throw new AssertionError("Optimistic (UnwarrantedOptimismException throwing) builtin functions are currently not in use"); } this.invoker = invoker; this.constructor = constructor; this.flags = flags; this.callSiteType = callSiteType; this.log = log; }
private boolean hasApplies(final FunctionNode functionNode) { try { functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { @Override public boolean enterFunctionNode(final FunctionNode fn) { return fn == functionNode; } @Override public boolean enterCallNode(final CallNode callNode) { if (isApply(callNode)) { throw HAS_APPLIES; } return true; } }); } catch (final AppliesFoundException e) { return true; } log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); return false; // no applies }
/** * Construct a parser. * * @param env script environment * @param source source to parse * @param errors error manager * @param strict parser created with strict mode enabled. * @param lineOffset line offset to start counting lines from * @param log debug logger if one is needed */ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset, final DebugLogger log) { super(source, errors, strict, lineOffset); this.lc = new ParserContext(); this.defaultNames = new ArrayDeque<>(); this.env = env; this.namespace = new Namespace(env.getNamespace()); this.scripting = env._scripting; if (this.scripting) { this.lineInfoReceiver = new Lexer.LineInfoReceiver() { @Override public void lineInfo(final int receiverLine, final int receiverLinePosition) { // update the parser maintained line information Parser.this.line = receiverLine; Parser.this.linePosition = receiverLinePosition; } }; } else { // non-scripting mode script can't have multi-line literals this.lineInfoReceiver = null; } this.log = log == null ? DebugLogger.DISABLED_LOGGER : log; }
private boolean hasApplies(final FunctionNode functionNode) { try { functionNode.accept(new SimpleNodeVisitor() { @Override public boolean enterFunctionNode(final FunctionNode fn) { return fn == functionNode; } @Override public boolean enterCallNode(final CallNode callNode) { if (isApply(callNode)) { throw HAS_APPLIES; } return true; } }); } catch (final AppliesFoundException e) { return true; } log.fine("There are no applies in ", DebugLogger.quote(functionNode.getName()), " - nothing to do."); return false; // no applies }
private DebugLogger ensureInitialized(final Context context) { //lazy init, as there is not necessarily a context available when //a ScriptEnvironment gets initialize if (isEnabled() && log == null) { log = initLogger(context); if (log.isEnabled()) { this.timeSupplier = new TimeSupplier(); Runtime.getRuntime().addShutdownHook( new Thread() { @Override public void run() { //System.err.println because the context and the output streams may be gone //when the shutdown hook executes final StringBuilder sb = new StringBuilder(); for (final String str : timeSupplier.getStrings()) { sb.append('['). append(Timing.getLoggerName()). append("] "). append(str). append('\n'); } System.err.print(sb); } }); } } return log; }
private void logRecompile(final String reason, final FunctionNode fn, final MethodType type, final Map<Integer, Type> ipp) { if (log.isEnabled()) { log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", type); log.indent(); for (final String str : toStringInvalidations(ipp)) { log.fine(str); } log.unindent(); } }
/** * Tracer that is applied before a value is returned from the traced function. It will output the return * value and its class * * @param value return value for filter * @return return value unmodified */ static Object traceReturn(final DebugLogger logger, final Object value) { final String str = " return" + (VOID_TAG.equals(value) ? ";" : " " + stripName(value) + "; // [type=" + (value == null ? "null]" : stripName(value.getClass()) + ']')); if (logger == null) { err(str); } else if (logger.isEnabled()) { logger.log(TRACE_LEVEL, str); } return value; }
/** * Tracer that is applied before a function is called, printing the arguments * * @param tag tag to start the debug printout string * @param paramStart param index to start outputting from * @param args arguments to the function */ static void traceArgs(final DebugLogger logger, final String tag, final int paramStart, final Object... args) { final StringBuilder sb = new StringBuilder(); sb.append(tag); for (int i = paramStart; i < args.length; i++) { if (i == paramStart) { sb.append(" => args: "); } sb.append('\''). append(stripName(argString(args[i]))). append('\''). append(' '). append('['). append("type="). append(args[i] == null ? "null" : stripName(args[i].getClass())). append(']'); if (i + 1 < args.length) { sb.append(", "); } } if (logger == null) { err(sb.toString()); } else { logger.log(TRACE_LEVEL, sb); } stacktrace(logger); }
private static void stacktrace(final DebugLogger logger) { if (!PRINT_STACKTRACE) { return; } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(baos); new Throwable().printStackTrace(ps); final String st = baos.toString(); if (logger == null) { err(st); } else { logger.log(TRACE_LEVEL, st); } }
/** * Add a debug printout to a method handle, tracing parameters and return values * * @param logger a specific logger to which to write the output * @param level level over which to print * @param mh method handle to trace * @param paramStart first param to print/trace * @param printReturnValue should we print/trace return value if available? * @param tag start of trace message * @return traced method handle */ public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) { final MethodType type = mh.type(); //if there is no logger, or if it's set to log only coarser events //than the trace level, skip and return if (logger != null && logger.levelCoarserThan(level)) { return mh; } assert TRACE != null; MethodHandle trace = MethodHandles.insertArguments(TRACE, 0, logger, tag, paramStart); trace = MethodHandles.foldArguments( mh, trace.asCollector( Object[].class, type.parameterCount()). asType(type.changeReturnType(void.class))); final Class<?> retType = type.returnType(); if (printReturnValue) { if (retType != void.class) { final MethodHandle traceReturn = MethodHandles.insertArguments(TRACE_RETURN, 0, logger); trace = MethodHandles.filterReturnValue(trace, traceReturn.asType( traceReturn.type().changeParameterType(0, retType).changeReturnType(retType))); } else { trace = MethodHandles.filterReturnValue(trace, MethodHandles.insertArguments(TRACE_RETURN_VOID, 0, logger)); } } return trace; }
private static DebugLogger getLogger() { try { return Context.getContext().getLogger(RecompilableScriptFunctionData.class); } catch (final Exception e) { e.printStackTrace(); return DebugLogger.DISABLED_LOGGER; } }
@Override public DebugLogger initLogger(final Context ctxt) { final boolean optimisticTypes = env._optimistic_types; final boolean lazyCompilation = env._lazy_compilation; return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() { @Override public void accept(final DebugLogger newLogger) { if (!lazyCompilation) { newLogger.warning("WARNING: Running with lazy compilation switched off. This is not a default setting."); } newLogger.warning("Optimistic types are ", optimisticTypes ? "ENABLED." : "DISABLED."); } }); }
@Override public Node leaveCallNode(final CallNode callNode) { //apply needs to be a global symbol or we don't allow it final List<IdentNode> newParams = explodedArguments.peek(); if (isApply(callNode)) { final List<Expression> newArgs = new ArrayList<>(); for (final Expression arg : callNode.getArgs()) { if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { newArgs.addAll(newParams); } else { newArgs.add(arg); } } changed.add(lc.getCurrentFunction().getId()); final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); if (log.isEnabled()) { log.fine("Transformed ", callNode, " from apply to call => ", newCallNode, " in ", DebugLogger.quote(lc.getCurrentFunction().getName())); } return newCallNode; } return callNode; }
/** * Add a debug printout to a method handle, tracing parameters and return values * * @param logger a specific logger to which to write the output * @param level level over which to print * @param mh method handle to trace * @param paramStart first param to print/trace * @param printReturnValue should we print/trace return value if available? * @param tag start of trace message * @return traced method handle */ public static MethodHandle addDebugPrintout(final DebugLogger logger, final Level level, final MethodHandle mh, final int paramStart, final boolean printReturnValue, final Object tag) { final MethodType type = mh.type(); //if there is no logger, or if it's set to log only coarser events //than the trace level, skip and return if (logger == null || !logger.isLoggable(level)) { return mh; } assert TRACE != null; MethodHandle trace = MethodHandles.insertArguments(TRACE, 0, logger, tag, paramStart); trace = MethodHandles.foldArguments( mh, trace.asCollector( Object[].class, type.parameterCount()). asType(type.changeReturnType(void.class))); final Class<?> retType = type.returnType(); if (printReturnValue) { if (retType != void.class) { final MethodHandle traceReturn = MethodHandles.insertArguments(TRACE_RETURN, 0, logger); trace = MethodHandles.filterReturnValue(trace, traceReturn.asType( traceReturn.type().changeParameterType(0, retType).changeReturnType(retType))); } else { trace = MethodHandles.filterReturnValue(trace, MethodHandles.insertArguments(TRACE_RETURN_VOID, 0, logger)); } } return trace; }
@Override public DebugLogger getLogger() { return log; }
@Override public DebugLogger initLogger(final Context context) { return context.getLogger(this.getClass()); }
@Override public DebugLogger initLogger(final Context context) { return DebugLogger.DISABLED_LOGGER; }
private synchronized Class<?> compile(final Source source, final ErrorManager errMan, final boolean strict) { // start with no errors, no warnings. errMan.reset(); Class<?> script = findCachedClass(source); if (script != null) { final DebugLogger log = getLogger(Compiler.class); if (log.isEnabled()) { log.fine(new RuntimeEvent<>(Level.INFO, source), "Code cache hit for ", source, " avoiding recompile."); } return script; } StoredScript storedScript = null; FunctionNode functionNode = null; // We only use the code store here if optimistic types are disabled. With optimistic types, initial compilation // just creates a thin wrapper, and actual code is stored per function in RecompilableScriptFunctionData. final boolean useCodeStore = codeStore != null && !env._parse_only && !env._optimistic_types; final String cacheKey = useCodeStore ? CodeStore.getCacheKey(0, null) : null; if (useCodeStore) { storedScript = codeStore.load(source, cacheKey); } if (storedScript == null) { functionNode = new Parser(env, source, errMan, strict, getLogger(Parser.class)).parse(); if (errMan.hasErrors()) { return null; } if (env._print_ast || functionNode.getFlag(FunctionNode.IS_PRINT_AST)) { getErr().println(new ASTWriter(functionNode)); } if (env._print_parse || functionNode.getFlag(FunctionNode.IS_PRINT_PARSE)) { getErr().println(new PrintVisitor(functionNode, true, false)); } } if (env._parse_only) { return null; } final URL url = source.getURL(); final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader; final CodeSource cs = new CodeSource(url, (CodeSigner[])null); final CodeInstaller<ScriptEnvironment> installer = new ContextCodeInstaller(this, loader, cs); if (storedScript == null) { final CompilationPhases phases = Compiler.CompilationPhases.COMPILE_ALL; final Compiler compiler = new Compiler( this, env, installer, source, errMan, strict | functionNode.isStrict()); final FunctionNode compiledFunction = compiler.compile(functionNode, phases); if (errMan.hasErrors()) { return null; } script = compiledFunction.getRootClass(); compiler.persistClassInfo(cacheKey, compiledFunction); } else { Compiler.updateCompilationId(storedScript.getCompilationId()); script = install(storedScript, source, installer); } cacheClass(source, script); return script; }
private static void debug(final Object... msg) { final DebugLogger logger = getLoggerStatic(); if (logger != null) { logger.info(msg); } }
private static DebugLogger getLoggerStatic() { final Context context = Context.getContextTrustedOrNull(); return context == null ? null : context.getLogger(Source.class); }
@Override public DebugLogger getLogger() { return initLogger(Context.getContextTrusted()); }
@Override public DebugLogger initLogger(final Context ctxt) { return ctxt.getLogger(this.getClass()); }
CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final Specialization specialization) { this(invoker, constructor, 0, null, specialization, DebugLogger.DISABLED_LOGGER); }
@Override public DebugLogger initLogger(final Context context) { log = context.getLogger(getClass()); return log; }
static void traceReturnVoid(final DebugLogger logger) { traceReturn(logger, VOID_TAG); }
@Override public DebugLogger initLogger(final Context context) { return this.log = context.getLogger(this.getClass()); }