// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. package com.google.dart.runner; import com.google.common.collect.Lists; import com.google.debugging.sourcemap.SourceMapping; import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping; import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Runs a given JS-script in d8 (part of V8). */ public class V8Launcher implements JavaScriptLauncher { private static class Drainer implements Runnable { private final InputStream stream; private final List lines; private final PrintStream out; public Drainer(InputStream stream, PrintStream out, List lines) { this.stream = stream; this.lines = lines; this.out = out; } @Override public void run() { BufferedReader r = new BufferedReader(new InputStreamReader(stream)); String str; try { while ((str = r.readLine()) != null) { if (lines != null) { lines.add(str); } out.println(str); } } catch (IOException e) { throw new AssertionError(e); } } } private static final String D8_ENVIRONMENT_VARIABLE = "D8_EXEC"; private final SourceMapping appSourceMap; private static final String EOL = System.getProperty("line.separator"); /** * */ public V8Launcher(SourceMapping appSourceMap) { this.appSourceMap = appSourceMap; } @Override public void execute(String jsScript, String sourceName, String[] args, RunnerOptions options, PrintStream stdout, PrintStream stderr) throws RunnerError { if (!isConfigured()) { throw new RunnerError("Please set the " + D8_ENVIRONMENT_VARIABLE + " environment variable."); } File sourceFile; try { sourceFile = writeTempFile(sourceName, jsScript); } catch (IOException e) { throw new RunnerError(e); } try { ArrayList command = new ArrayList(); command.add(v8Executable().getAbsolutePath()); command.add(sourceFile.getAbsolutePath()); if (options.shouldProfile()) { command.add("--prof"); } command.add("--"); command.addAll(Arrays.asList(args)); int exitValue; Process p = null; List stdOutLines = Lists.newArrayList(); Thread stdOutDrain = null; Thread stdErrDrain = null; try { p = Runtime.getRuntime().exec(command.toArray(new String[0])); // TODO(floitsch): we should handle timeouts (but how long should we wait?). stdOutDrain = new Thread(new Drainer(p.getInputStream(), stdout, stdOutLines)); stdErrDrain = new Thread(new Drainer(p.getErrorStream(), stderr, null)); stdOutDrain.start(); stdErrDrain.start(); try { exitValue = p.waitFor(); } catch (InterruptedException e) { throw new RuntimeException(e); } } catch (IOException e) { throw new RunnerError(e); } finally { try { if (stdOutDrain != null) { stdOutDrain.join(); } if (stdErrDrain != null) { stdErrDrain.join(); } } catch (InterruptedException e) { throw new RuntimeException(e); } try { p.getInputStream().close(); p.getOutputStream().close(); p.getOutputStream().close(); } catch (IOException e) { throw new RunnerError(e); } p.destroy(); } if (exitValue != 0) { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter); if (options.verbose()) { out.println(jsScript); } out.println("Execution failed."); String str = mapStackEntry(decodeStackTraceFromString(stdOutLines), appSourceMap); if (str != null) { out.println("Mapped stack trace:"); out.println(str); out.println(""); } out.println("V8 execution returned non-zero exit-code: " + p.exitValue()); out.flush(); throw new RunnerError(stringWriter.toString()); } } finally { sourceFile.delete(); } } private String mapStackEntry(List entries, SourceMapping map) { if (entries != null) { StringBuilder sb = new StringBuilder(); for (StackEntry entry : entries) { SourceMapping sm = getSourceMapForFile(entry.file, map); // TODO(johnlenz): Try to translate the method name. String method = (entry.method.isEmpty()) ? "" : " (" + entry.method + ")"; if (sm != null) { OriginalMapping mapping = sm.getMappingForLine(entry.line, entry.column); if (mapping != null) { String file = mapping.getOriginalFile(); int line = mapping.getLineNumber(); int column = mapping.getColumnPosition(); sb.append(" at MAPPED : " + file + ":" + line + ":" + column + method + EOL); continue; } } sb.append(" at UNMAPPED: " + entry.file + ":" + entry.line + ":" + entry.column + method + EOL); } return sb.toString(); } return null; } SourceMapping getSourceMapForFile(String file, SourceMapping map) { return map; } static class StackEntry { String method; String file; int line; int column; } private List decodeStackTraceFromString(List lines) { List entries = Lists.newArrayList(); boolean seenFirst = false; for (String str : lines) { StackEntry entry = decodeStackEntry(str); if (entry == null) { if (seenFirst) { break; } else { continue; } } else { seenFirst = true; } entries.add(entry); } return entries.isEmpty() ? null : entries; } private StackEntry decodeStackEntry(String str) { final String PREFIX = " at "; if (str.startsWith(PREFIX)) { StackEntry entry = new StackEntry(); int start = str.indexOf("("); int end = str.indexOf(")"); entry.method = ""; String location; if (start == -1) { location = str.substring(PREFIX.length()); } else { entry.method = str.substring(7, start-1); location = str.substring(start+1, end); } return decodeLocation(entry, location); } return null; } private StackEntry decodeLocation(StackEntry entry, String location) { String[] parts = location.split(":"); if (parts.length >= 3) { String file = parts[0]; for (int i = 1; i <= parts.length-3; i++) { file += ":" + parts[i]; } entry.file = file; entry.line = Integer.valueOf(parts[parts.length - 2]); entry.column = Integer.valueOf(parts[parts.length - 1]); return entry; } return null; } private File writeTempFile(String name, String content) throws IOException { // The first argument to createTempFile must be at least three characters long, and be a // valid file-name. name = name.replace('/', '_'); File file = File.createTempFile("dart_" + name, ".js"); FileWriter writer = new FileWriter(file); try { writer.write(content); } finally { writer.close(); } return file; } private static File v8Executable() { String d8Path = System.getProperty("com.google.dart.runner.d8", System.getenv(D8_ENVIRONMENT_VARIABLE)); if (d8Path == null) { String testSrcDir = System.getenv("TEST_SRCDIR"); if (testSrcDir == null) { return null; } return new File(new File(new File(new File(testSrcDir, "google3"), "third_party"), "v8"), "d8"); } else { return new File(d8Path); } } /** * @return true if the D8_EXEC environment variable is correctly set up. */ public static boolean isConfigured() { File file = v8Executable(); if (file == null) { return false; } return file.canExecute(); } }