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
26package jdk.nashorn.internal.runtime;
27
28import java.io.PrintWriter;
29import java.util.HashMap;
30import java.util.List;
31import java.util.Locale;
32import java.util.Map;
33import java.util.StringTokenizer;
34import java.util.TimeZone;
35import java.util.logging.Level;
36import jdk.nashorn.internal.codegen.Namespace;
37import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
38import jdk.nashorn.internal.runtime.options.KeyValueOption;
39import jdk.nashorn.internal.runtime.options.LoggingOption;
40import jdk.nashorn.internal.runtime.options.LoggingOption.LoggerInfo;
41import jdk.nashorn.internal.runtime.options.Option;
42import jdk.nashorn.internal.runtime.options.Options;
43
44/**
45 * Script environment consists of command line options, arguments, script files
46 * and output and error writers, top level Namespace etc.
47 */
48public final class ScriptEnvironment {
49    // Primarily intended to be used in test environments so that eager compilation tests work without an
50    // error when tested with optimistic compilation.
51    private static final boolean ALLOW_EAGER_COMPILATION_SILENT_OVERRIDE = Options.getBooleanProperty(
52            "nashorn.options.allowEagerCompilationSilentOverride", false);
53
54    /** Output writer for this environment */
55    private final PrintWriter out;
56
57    /** Error writer for this environment */
58    private final PrintWriter err;
59
60    /** Top level namespace. */
61    private final Namespace namespace;
62
63    /** Current Options object. */
64    private final Options options;
65
66    /** Size of the per-global Class cache size */
67    public final int     _class_cache_size;
68
69    /** -classpath value. */
70    public final String  _classpath;
71
72    /** Only compile script, do not run it or generate other ScriptObjects */
73    public final boolean _compile_only;
74
75    /** Accept "const" keyword and treat it as variable. Interim feature */
76    public final boolean _const_as_var;
77
78    /** Accumulated callsite flags that will be used when bootstrapping script callsites */
79    public final int     _callsite_flags;
80
81    /** Generate line number table in class files */
82    public final boolean _debug_lines;
83
84    /** Put all variables in scopes to make them debuggable */
85    public final boolean _debug_scopes;
86
87    /** Directory in which source files and generated class files are dumped */
88    public final String  _dest_dir;
89
90    /** Display stack trace upon error, default is false */
91    public final boolean _dump_on_error;
92
93    /** Invalid lvalue expressions should be reported as early errors */
94    public final boolean _early_lvalue_error;
95
96    /** Empty statements should be preserved in the AST */
97    public final boolean _empty_statements;
98
99    /** Show full Nashorn version */
100    public final boolean _fullversion;
101
102    /** Launch using as fx application */
103    public final boolean _fx;
104
105    /** Use single Global instance per jsr223 engine instance. */
106    public final boolean _global_per_engine;
107
108    /** Enable experimental ECMAScript 6 features. */
109    public final boolean _es6;
110
111
112    /** Number of times a dynamic call site has to be relinked before it is
113     * considered unstable (and thus should be linked as if it were megamorphic).
114     */
115    public final int _unstable_relink_threshold;
116
117    /** Argument passed to compile only if optimistic compilation should take place */
118    public static final String COMPILE_ONLY_OPTIMISTIC_ARG = "optimistic";
119
120    /**
121     *  Behavior when encountering a function declaration in a lexical context where only statements are acceptable
122     * (function declarations are source elements, but not statements).
123     */
124    public enum FunctionStatementBehavior {
125        /**
126         * Accept the function declaration silently and treat it as if it were a function expression assigned to a local
127         * variable.
128         */
129        ACCEPT,
130        /**
131         * Log a parser warning, but accept the function declaration and treat it as if it were a function expression
132         * assigned to a local variable.
133         */
134        WARNING,
135        /**
136         * Raise a {@code SyntaxError}.
137         */
138        ERROR
139    }
140
141    /**
142     * Behavior when encountering a function declaration in a lexical context where only statements are acceptable
143     * (function declarations are source elements, but not statements).
144     */
145    public final FunctionStatementBehavior _function_statement;
146
147    /** Should lazy compilation take place */
148    public final boolean _lazy_compilation;
149
150    /** Should optimistic types be used */
151    public final boolean _optimistic_types;
152
153    /** Create a new class loaded for each compilation */
154    public final boolean _loader_per_compile;
155
156    /** --module-path, if any */
157    public final String _module_path;
158
159    /** --add-modules, if any */
160    public final String _add_modules;
161
162    /** Do not support Java support extensions. */
163    public final boolean _no_java;
164
165    /** Do not support non-standard syntax extensions. */
166    public final boolean _no_syntax_extensions;
167
168    /** Do not support typed arrays. */
169    public final boolean _no_typed_arrays;
170
171    /** Only parse the source code, do not compile */
172    public final boolean _parse_only;
173
174    /** Enable disk cache for compiled scripts */
175    public final boolean _persistent_cache;
176
177    /** Print the AST before lowering */
178    public final boolean _print_ast;
179
180    /** Print the AST after lowering */
181    public final boolean _print_lower_ast;
182
183    /** Print resulting bytecode for script */
184    public final boolean _print_code;
185
186    /** Directory (optional) to print files to */
187    public final String _print_code_dir;
188
189    /** List of functions to write to the print code dir, optional */
190    public final String _print_code_func;
191
192    /** Print memory usage for IR after each phase */
193    public final boolean _print_mem_usage;
194
195    /** Print function will no print newline characters */
196    public final boolean _print_no_newline;
197
198    /** Print AST in more human readable form */
199    public final boolean _print_parse;
200
201    /** Print AST in more human readable form after Lowering */
202    public final boolean _print_lower_parse;
203
204    /** print symbols and their contents for the script */
205    public final boolean _print_symbols;
206
207    /** is this environment in scripting mode? */
208    public final boolean _scripting;
209
210    /** is this environment in strict mode? */
211    public final boolean _strict;
212
213    /** print version info of Nashorn */
214    public final boolean _version;
215
216    /** should code verification be done of generated bytecode */
217    public final boolean _verify_code;
218
219    /** time zone for this environment */
220    public final TimeZone _timezone;
221
222    /** Local for error messages */
223    public final Locale _locale;
224
225    /** Logging */
226    public final Map<String, LoggerInfo> _loggers;
227
228    /** Timing */
229    public final Timing _timing;
230
231    /** Whether to use anonymous classes. See {@link #useAnonymousClasses(int)}. */
232    private final AnonymousClasses _anonymousClasses;
233    private enum AnonymousClasses {
234        AUTO,
235        OFF,
236        ON
237    }
238
239    /** Size threshold up to which we use anonymous classes in {@link AnonymousClasses#AUTO} setting */
240    private final int _anonymous_classes_threshold;
241
242    /** Default value for anonymous class threshold */
243    private final static int DEFAULT_ANON_CLASS_THRESHOLD = 512;
244
245    /**
246     * Constructor
247     *
248     * @param options a Options object
249     * @param out output print writer
250     * @param err error print writer
251     */
252    @SuppressWarnings("unused")
253    public ScriptEnvironment(final Options options, final PrintWriter out, final PrintWriter err) {
254        this.out = out;
255        this.err = err;
256        this.namespace = new Namespace();
257        this.options = options;
258
259        _class_cache_size     = options.getInteger("class.cache.size");
260        _classpath            = options.getString("classpath");
261        _compile_only         = options.getBoolean("compile.only");
262        _const_as_var         = options.getBoolean("const.as.var");
263        _debug_lines          = options.getBoolean("debug.lines");
264        _debug_scopes         = options.getBoolean("debug.scopes");
265        _dest_dir             = options.getString("d");
266        _dump_on_error        = options.getBoolean("doe");
267        _early_lvalue_error   = options.getBoolean("early.lvalue.error");
268        _empty_statements     = options.getBoolean("empty.statements");
269        _fullversion          = options.getBoolean("fullversion");
270        if (options.getBoolean("function.statement.error")) {
271            _function_statement = FunctionStatementBehavior.ERROR;
272        } else if (options.getBoolean("function.statement.warning")) {
273            _function_statement = FunctionStatementBehavior.WARNING;
274        } else {
275            _function_statement = FunctionStatementBehavior.ACCEPT;
276        }
277        _fx                   = options.getBoolean("fx");
278        _global_per_engine    = options.getBoolean("global.per.engine");
279        _optimistic_types     = options.getBoolean("optimistic.types");
280        final boolean lazy_compilation = options.getBoolean("lazy.compilation");
281        if (!lazy_compilation && _optimistic_types) {
282            if (!ALLOW_EAGER_COMPILATION_SILENT_OVERRIDE) {
283                throw new IllegalStateException(
284                        ECMAErrors.getMessage(
285                                "config.error.eagerCompilationConflictsWithOptimisticTypes",
286                                options.getOptionTemplateByKey("lazy.compilation").getName(),
287                                options.getOptionTemplateByKey("optimistic.types").getName()));
288            }
289            _lazy_compilation = true;
290        } else {
291            _lazy_compilation = lazy_compilation;
292        }
293        _loader_per_compile   = options.getBoolean("loader.per.compile");
294        _module_path          = options.getString("module.path");
295        _add_modules          = options.getString("add.modules");
296        _no_java              = options.getBoolean("no.java");
297        _no_syntax_extensions = options.getBoolean("no.syntax.extensions");
298        _no_typed_arrays      = options.getBoolean("no.typed.arrays");
299        _parse_only           = options.getBoolean("parse.only");
300        _persistent_cache     = options.getBoolean("persistent.code.cache");
301        _print_ast            = options.getBoolean("print.ast");
302        _print_lower_ast      = options.getBoolean("print.lower.ast");
303        _print_code           = options.getString("print.code") != null;
304        _print_mem_usage      = options.getBoolean("print.mem.usage");
305        _print_no_newline     = options.getBoolean("print.no.newline");
306        _print_parse          = options.getBoolean("print.parse");
307        _print_lower_parse    = options.getBoolean("print.lower.parse");
308        _print_symbols        = options.getBoolean("print.symbols");
309        _scripting            = options.getBoolean("scripting");
310        _strict               = options.getBoolean("strict");
311        _version              = options.getBoolean("version");
312        _verify_code          = options.getBoolean("verify.code");
313
314        final int configuredUrt = options.getInteger("unstable.relink.threshold");
315        // The default for this property is -1, so we can easily detect when
316        // it is not specified on command line.
317        if (configuredUrt < 0) {
318            // In this case, use a default of 8, or 16 for optimistic types.
319            // Optimistic types come with dual fields, and in order to get
320            // performance on benchmarks with a lot of object instantiation and
321            // then field reassignment, it can take slightly more relinks to
322            // become stable with type changes swapping out an entire property
323            // map and making a map guard fail. Also, honor the "nashorn.*"
324            // system property for now. It was documented in DEVELOPER_README
325            // so we should recognize it for the time being.
326            _unstable_relink_threshold = Options.getIntProperty(
327                    "nashorn.unstable.relink.threshold",
328                    _optimistic_types ? 16 : 8);
329        } else {
330            _unstable_relink_threshold = configuredUrt;
331        }
332
333        final String anonClasses = options.getString("anonymous.classes");
334        if (anonClasses == null || anonClasses.equals("auto")) {
335            _anonymousClasses = AnonymousClasses.AUTO;
336        } else if (anonClasses.equals("true")) {
337            _anonymousClasses = AnonymousClasses.ON;
338        } else if (anonClasses.equals("false")) {
339            _anonymousClasses = AnonymousClasses.OFF;
340        } else {
341            throw new RuntimeException("Unsupported value for anonymous classes: " + anonClasses);
342        }
343
344        this._anonymous_classes_threshold = Options.getIntProperty(
345                "nashorn.anonymous.classes.threshold", DEFAULT_ANON_CLASS_THRESHOLD);
346
347        final String language = options.getString("language");
348        if (language == null || language.equals("es5")) {
349            _es6 = false;
350        } else if (language.equals("es6")) {
351            _es6 = true;
352        } else {
353            throw new RuntimeException("Unsupported language: " + language);
354        }
355
356        String dir = null;
357        String func = null;
358        final String pc = options.getString("print.code");
359        if (pc != null) {
360            final StringTokenizer st = new StringTokenizer(pc, ",");
361            while (st.hasMoreTokens()) {
362                final StringTokenizer st2 = new StringTokenizer(st.nextToken(), ":");
363                while (st2.hasMoreTokens()) {
364                    final String cmd = st2.nextToken();
365                    if ("dir".equals(cmd)) {
366                        dir = st2.nextToken();
367                    } else if ("function".equals(cmd)) {
368                        func = st2.nextToken();
369                    }
370                }
371            }
372        }
373        _print_code_dir = dir;
374        _print_code_func = func;
375
376        int callSiteFlags = 0;
377        if (options.getBoolean("profile.callsites")) {
378            callSiteFlags |= NashornCallSiteDescriptor.CALLSITE_PROFILE;
379        }
380
381        if (options.get("trace.callsites") instanceof KeyValueOption) {
382            callSiteFlags |= NashornCallSiteDescriptor.CALLSITE_TRACE;
383            final KeyValueOption kv = (KeyValueOption)options.get("trace.callsites");
384            if (kv.hasValue("miss")) {
385                callSiteFlags |= NashornCallSiteDescriptor.CALLSITE_TRACE_MISSES;
386            }
387            if (kv.hasValue("enterexit") || (callSiteFlags & NashornCallSiteDescriptor.CALLSITE_TRACE_MISSES) == 0) {
388                callSiteFlags |= NashornCallSiteDescriptor.CALLSITE_TRACE_ENTEREXIT;
389            }
390            if (kv.hasValue("objects")) {
391                callSiteFlags |= NashornCallSiteDescriptor.CALLSITE_TRACE_VALUES;
392            }
393        }
394        this._callsite_flags = callSiteFlags;
395
396        final Option<?> timezoneOption = options.get("timezone");
397        if (timezoneOption != null) {
398            this._timezone = (TimeZone)timezoneOption.getValue();
399        } else {
400            this._timezone  = TimeZone.getDefault();
401        }
402
403        final Option<?> localeOption = options.get("locale");
404        if (localeOption != null) {
405            this._locale = (Locale)localeOption.getValue();
406        } else {
407            this._locale = Locale.getDefault();
408        }
409
410        final LoggingOption loggingOption = (LoggingOption)options.get("log");
411        this._loggers = loggingOption == null ? new HashMap<String, LoggerInfo>() : loggingOption.getLoggers();
412
413        final LoggerInfo timeLoggerInfo = _loggers.get(Timing.getLoggerName());
414        this._timing = new Timing(timeLoggerInfo != null && timeLoggerInfo.getLevel() != Level.OFF);
415    }
416
417    /**
418     * Get the output stream for this environment
419     * @return output print writer
420     */
421    public PrintWriter getOut() {
422        return out;
423    }
424
425    /**
426     * Get the error stream for this environment
427     * @return error print writer
428     */
429    public PrintWriter getErr() {
430        return err;
431    }
432
433    /**
434     * Get the namespace for this environment
435     * @return namespace
436     */
437    public Namespace getNamespace() {
438        return namespace;
439    }
440
441    /**
442     * Return the JavaScript files passed to the program
443     *
444     * @return a list of files
445     */
446    public List<String> getFiles() {
447        return options.getFiles();
448    }
449
450    /**
451     * Return the user arguments to the program, i.e. those trailing "--" after
452     * the filename
453     *
454     * @return a list of user arguments
455     */
456    public List<String> getArguments() {
457        return options.getArguments();
458    }
459
460    /**
461     * Check if there is a logger registered for a particular name: typically
462     * the "name" attribute of a Loggable annotation on a class
463     *
464     * @param name logger name
465     * @return true, if a logger exists for that name, false otherwise
466     */
467    public boolean hasLogger(final String name) {
468        return _loggers.get(name) != null;
469    }
470
471    /**
472     * Check if compilation/runtime timings are enabled
473     * @return true if enabled
474     */
475    public boolean isTimingEnabled() {
476        return _timing != null ? _timing.isEnabled() : false;
477    }
478
479    /**
480     * Returns true if compilation should use anonymous classes.
481     * @param sourceLength length of source being compiled.
482     * @return true if anonymous classes should be used
483     */
484    public boolean useAnonymousClasses(final int sourceLength) {
485        return _anonymousClasses == AnonymousClasses.ON
486                || (_anonymousClasses == AnonymousClasses.AUTO && sourceLength <= _anonymous_classes_threshold);
487    }
488
489}
490