In HtmlUnit, decode stack traces for all constructed exceptions

This commit is contained in:
Alexey Andreev 2018-11-26 15:58:51 +03:00
parent 0c03379206
commit 45d0a13c9b
10 changed files with 162 additions and 116 deletions

View File

@ -154,7 +154,12 @@ public class TThrowable extends RuntimeException {
} }
public void printStackTrace(TPrintStream stream) { public void printStackTrace(TPrintStream stream) {
stream.println(TString.wrap(getClass().getName() + ": " + getMessage())); stream.print(TString.wrap(getClass().getName()));
String message = getMessage();
if (message != null) {
stream.print(TString.wrap(": " + getMessage()));
}
stream.println();
if (stackTrace != null) { if (stackTrace != null) {
for (TStackTraceElement element : stackTrace) { for (TStackTraceElement element : stackTrace) {
stream.print(TString.wrap(" at ")); stream.print(TString.wrap(" at "));

View File

@ -57,6 +57,7 @@ import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation; import org.teavm.debugging.information.SourceLocation;
import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener; import org.teavm.dependency.DependencyListener;
import org.teavm.dependency.DependencyType;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.interop.PlatformMarker; import org.teavm.interop.PlatformMarker;
import org.teavm.interop.PlatformMarkers; import org.teavm.interop.PlatformMarkers;
@ -200,6 +201,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) {
MethodDependency dep; MethodDependency dep;
DependencyType stringType = dependencyAnalyzer.getType("java.lang.String");
dep = dependencyAnalyzer.linkMethod(new MethodReference(Class.class.getName(), "getClass", dep = dependencyAnalyzer.linkMethod(new MethodReference(Class.class.getName(), "getClass",
ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null); ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("org.teavm.platform.PlatformClass")); dep.getVariable(0).propagate(dependencyAnalyzer.getType("org.teavm.platform.PlatformClass"));
@ -208,23 +211,23 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class), dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class),
null); null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String")); dep.getVariable(0).propagate(stringType);
dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C")); dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C"));
dep.use(); dep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class,
char[].class, int.class, void.class), null); char[].class, int.class, void.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String")); dep.getVariable(0).propagate(stringType);
dep.getVariable(3).propagate(dependencyAnalyzer.getType("[C")); dep.getVariable(3).propagate(dependencyAnalyzer.getType("[C"));
dep.use(); dep.use();
MethodDependency internDep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "intern", MethodDependency internDep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "intern",
String.class), null); String.class), null);
internDep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String")); internDep.getVariable(0).propagate(stringType);
internDep.use(); internDep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "length", int.class), null); dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "length", int.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType("java.lang.String")); dep.getVariable(0).propagate(stringType);
dep.use(); dep.use();
dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class), null).use(); dependencyAnalyzer.linkMethod(new MethodReference(Object.class, "clone", Object.class), null).use();
@ -240,23 +243,39 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
dep.use(); dep.use();
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoClassDefFoundError.class.getName())); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoClassDefFoundError.class.getName()));
exceptionCons.getVariable(1).propagate(dependencyAnalyzer.getType("java.lang.String")); exceptionCons.getVariable(1).propagate(stringType);
exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchFieldError.class, "<init>", exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchFieldError.class, "<init>",
String.class, void.class), null); String.class, void.class), null);
exceptionCons.use(); exceptionCons.use();
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchFieldError.class.getName())); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchFieldError.class.getName()));
exceptionCons.getVariable(1).propagate(dependencyAnalyzer.getType("java.lang.String")); exceptionCons.getVariable(1).propagate(stringType);
exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchMethodError.class, "<init>", exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(NoSuchMethodError.class, "<init>",
String.class, void.class), null); String.class, void.class), null);
exceptionCons.use(); exceptionCons.use();
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchMethodError.class.getName())); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NoSuchMethodError.class.getName()));
exceptionCons.getVariable(1).propagate(dependencyAnalyzer.getType("java.lang.String")); exceptionCons.getVariable(1).propagate(stringType);
exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(
RuntimeException.class, "<init>", String.class, void.class), null); RuntimeException.class, "<init>", String.class, void.class), null);
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(RuntimeException.class.getName())); exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(RuntimeException.class.getName()));
exceptionCons.getVariable(1).propagate(dependencyAnalyzer.getType("java.lang.String")); exceptionCons.getVariable(1).propagate(stringType);
exceptionCons.use(); exceptionCons.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(
StackTraceElement.class, "<init>", String.class, String.class, String.class,
int.class, void.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType(StackTraceElement.class.getName()));
dep.getVariable(1).propagate(stringType);
dep.getVariable(2).propagate(stringType);
dep.getVariable(3).propagate(stringType);
dep.use();
dep = dependencyAnalyzer.linkMethod(new MethodReference(
Throwable.class, "setStackTrace", StackTraceElement[].class, void.class), null);
dep.getVariable(0).propagate(dependencyAnalyzer.getType(Throwable.class.getName()));
dep.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/StackTraceElement;"));
dep.getVariable(1).getArrayItem().propagate(dependencyAnalyzer.getType(StackTraceElement.class.getName()));
dep.use();
} }
@Override @Override

View File

@ -51,6 +51,8 @@ public class RuntimeRenderer {
renderRuntimeIntern(); renderRuntimeIntern();
renderRuntimeThreads(); renderRuntimeThreads();
renderRuntimeCreateException(); renderRuntimeCreateException();
renderCreateStackTraceElement();
renderSetStackTrace();
} catch (NamingException e) { } catch (NamingException e) {
throw new RenderingException("Error rendering runtime methods. See a cause for details", e); throw new RenderingException("Error rendering runtime methods. See a cause for details", e);
} catch (IOException e) { } catch (IOException e) {
@ -170,4 +172,28 @@ public class RuntimeRenderer {
writer.append("(message);").softNewLine(); writer.append("(message);").softNewLine();
writer.outdent().append("}").newLine(); writer.outdent().append("}").newLine();
} }
private void renderCreateStackTraceElement() throws IOException {
writer.append("function $rt_createStackElement(")
.append("className,").ws()
.append("methodName,").ws()
.append("fileName,").ws()
.append("lineNumber)").ws().append("{").indent().softNewLine();
writer.append("return ");
writer.append(writer.getNaming().getNameForInit(new MethodReference(StackTraceElement.class,
"<init>", String.class, String.class, String.class, int.class, void.class)));
writer.append("(className,").ws()
.append("methodName,").ws()
.append("fileName,").ws()
.append("lineNumber);").softNewLine();
writer.outdent().append("}").newLine();
}
private void renderSetStackTrace() throws IOException {
writer.append("function $rt_setStack(e,").ws().append("stack)").ws().append("{").indent().softNewLine();
writer.appendMethodBody(new MethodReference(Throwable.class, "setStackTrace", StackTraceElement[].class,
void.class));
writer.append("(e,").ws().append("stack);").softNewLine();
writer.outdent().append("}").newLine();
}
} }

View File

@ -228,6 +228,16 @@ function $rt_exception(ex) {
} }
err.$javaException = ex; err.$javaException = ex;
ex.$jsException = err; ex.$jsException = err;
if (typeof $rt_decodeStack === "function" && err.stack) {
var stack = $rt_decodeStack(err.stack);
var javaStack = $rt_createArray($rt_objcls(), stack.length);
for (var i = 0; i < stack.length; ++i) {
var element = stack[i];
javaStack.data[i] = $rt_createStackElement($rt_str(element.className),
$rt_str(element.methodName), $rt_str(element.fileName), element.lineNumber);
}
$rt_setStack(ex, javaStack);
}
} }
return err; return err;
} }
@ -393,7 +403,7 @@ var $rt_stderrBuffer = "";
var $rt_putStderr = typeof $rt_putStderrCustom === "function" ? $rt_putStderrCustom : function(ch) { var $rt_putStderr = typeof $rt_putStderrCustom === "function" ? $rt_putStderrCustom : function(ch) {
if (ch === 0xA) { if (ch === 0xA) {
if (console) { if (console) {
console.info($rt_stderrBuffer); console.error($rt_stderrBuffer);
} }
$rt_stderrBuffer = ""; $rt_stderrBuffer = "";
} else { } else {

View File

@ -18,6 +18,7 @@ package org.teavm.junit;
import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConsole;
import com.gargoylesoftware.htmlunit.WebWindow; import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlPage;
import java.io.File; import java.io.File;
@ -26,7 +27,10 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function; import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeJavaObject; import net.sourceforge.htmlunit.corejs.javascript.NativeJavaObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable; import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -59,17 +63,33 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
page.get().executeJavaScript(readFile(new File(run.getBaseDirectory(), run.getFileName()))); HtmlPage pageRef = page.get();
pageRef.executeJavaScript(readFile(new File(run.getBaseDirectory(), run.getFileName())));
boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true"));
File debugFile = decodeStack ? new File(run.getBaseDirectory(), run.getFileName() + ".teavmdbg") : null;
RhinoResultParser resultParser = new RhinoResultParser(debugFile);
AsyncResult asyncResult = new AsyncResult(); AsyncResult asyncResult = new AsyncResult();
Function function = (Function) page.get().executeJavaScript(readResource("teavm-htmlunit-adapter.js")) Function function = (Function) page.get().executeJavaScript(readResource("teavm-htmlunit-adapter.js"))
.getJavaScriptResult(); .getJavaScriptResult();
Object[] args = new Object[] { new NativeJavaObject(function, asyncResult, AsyncResult.class) }; Object[] args = new Object[] {
page.get().executeJavaScriptFunctionIfPossible(function, function, args, page.get()); decodeStack ? createStackDecoderFunction(resultParser) : null,
new NativeJavaObject(function, asyncResult, AsyncResult.class)
};
pageRef.executeJavaScriptFunctionIfPossible(function, function, args, page.get());
boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true")); resultParser.parseResult((Scriptable) asyncResult.getResult(), run.getCallback());
File debugFile = decodeStack ? new File(run.getBaseDirectory(), run.getFileName() + ".teavmdbg") : null; }
RhinoResultParser.parseResult((Scriptable) asyncResult.getResult(), run.getCallback(), debugFile);
private Function createStackDecoderFunction(RhinoResultParser resultParser) {
return new BaseFunction() {
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
String stack = args[0].toString();
return new NativeArray(resultParser.decodeStack(stack));
}
};
} }
private void cleanUp() { private void cleanUp() {
@ -86,7 +106,57 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
} }
private void init() { private void init() {
webClient.set(new WebClient(BrowserVersion.CHROME)); WebClient client = new WebClient(BrowserVersion.CHROME);
client.getWebConsole().setLogger(new WebConsole.Logger() {
@Override
public boolean isTraceEnabled() {
return false;
}
@Override
public void trace(Object message) {
}
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void debug(Object message) {
}
@Override
public boolean isInfoEnabled() {
return true;
}
@Override
public void info(Object message) {
System.out.println(message);
}
@Override
public boolean isWarnEnabled() {
return true;
}
@Override
public void warn(Object message) {
System.out.println(message);
}
@Override
public boolean isErrorEnabled() {
return true;
}
@Override
public void error(Object message) {
System.err.println(message);
}
});
webClient.set(client);
} }
private String readFile(File file) throws IOException { private String readFile(File file) throws IOException {

View File

@ -36,11 +36,17 @@ import org.teavm.model.MethodReference;
final class RhinoResultParser { final class RhinoResultParser {
private static Pattern pattern = Pattern.compile("(([A-Za-z_$]+)\\(\\))?@.+:([0-9]+)"); private static Pattern pattern = Pattern.compile("(([A-Za-z_$]+)\\(\\))?@.+:([0-9]+)");
private static Pattern lineSeparator = Pattern.compile("\\r\\n|\r|\n"); private static Pattern lineSeparator = Pattern.compile("\\r\\n|\r|\n");
private File debugFile;
private DebugInformation debugInformation;
private String[] script;
private RhinoResultParser() { RhinoResultParser(File debugFile) {
debugInformation = debugFile != null ? getDebugInformation(debugFile) : null;
script = getScript(new File(debugFile.getParentFile(),
debugFile.getName().substring(0, debugFile.getName().length() - 9)));
} }
static void parseResult(Scriptable result, TestRunCallback callback, File debugFile) { void parseResult(Scriptable result, TestRunCallback callback) {
if (result == null) { if (result == null) {
callback.complete(); callback.complete();
return; return;
@ -65,10 +71,8 @@ final class RhinoResultParser {
String stack = result.get("stack", result).toString(); String stack = result.get("stack", result).toString();
StackTraceElement[] decodedStack = null; StackTraceElement[] decodedStack = null;
if (debugInformation != null) { if (debugInformation != null) {
String[] script = getScript(new File(debugFile.getParentFile(),
debugFile.getName().substring(0, debugFile.getName().length() - 9)));
List<StackTraceElement> elements = new ArrayList<>(); List<StackTraceElement> elements = new ArrayList<>();
elements.addAll(Arrays.asList(decodeStack(stack, script, debugInformation))); elements.addAll(Arrays.asList(decodeStack(stack)));
List<StackTraceElement> currentElements = Arrays.asList(Thread.currentThread().getStackTrace()); List<StackTraceElement> currentElements = Arrays.asList(Thread.currentThread().getStackTrace());
elements.addAll(currentElements.subList(2, currentElements.size())); elements.addAll(currentElements.subList(2, currentElements.size()));
decodedStack = elements.toArray(new StackTraceElement[0]); decodedStack = elements.toArray(new StackTraceElement[0]);
@ -92,7 +96,7 @@ final class RhinoResultParser {
} }
} }
private static StackTraceElement[] decodeStack(String stack, String[] script, DebugInformation debugInformation) { StackTraceElement[] decodeStack(String stack) {
List<StackTraceElement> elements = new ArrayList<>(); List<StackTraceElement> elements = new ArrayList<>();
for (String line : lineSeparator.split(stack)) { for (String line : lineSeparator.split(stack)) {
Matcher matcher = pattern.matcher(line); Matcher matcher = pattern.matcher(line);

View File

@ -21,7 +21,7 @@ final class TestEntryPoint {
private TestEntryPoint() { private TestEntryPoint() {
} }
public static void run() throws Throwable { public static void run() {
before(); before();
try { try {
launchTest(); launchTest();

View File

@ -1,4 +1,7 @@
function runMain(callback) { var $rt_decodeStack;
function runMain(stackDecoder, callback) {
$rt_decodeStack = stackDecoder;
main([], function(result) { main([], function(result) {
var message = {}; var message = {};
if (result instanceof Error) { if (result instanceof Error) {

View File

@ -1,36 +0,0 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
main([], function(result) {
var message = {};
if (result instanceof Error) {
makeErrorMessage(message, result);
} else {
message.status = "ok";
}
window.parent.postMessage(JSON.stringify(message), "*");
});
function makeErrorMessage(message, e) {
message.status = "exception";
var stack = "";
if (e.$javaException && e.$javaException.constructor.$meta) {
stack = e.$javaException.constructor.$meta.name + ": ";
stack += e.$javaException.getMessage() || "";
stack += "\n";
}
message.stack = stack + e.stack;
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var runtimeSource = arguments[0];
var testSource = arguments[1];
var adapterSource = arguments[2];
var seleniumCallback = arguments[arguments.length - 1];
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
var doc = iframe.contentDocument;
window.jsErrors = [];
window.onerror = reportError;
iframe.contentWindow.onerror = reportError;
loadScripts([ runtimeSource, testSource, adapterSource ]);
window.addEventListener("message", handleMessage);
function handleMessage(event) {
window.removeEventListener("message", handleMessage);
document.body.removeChild(iframe);
seleniumCallback(event.data)
}
function loadScripts(scripts) {
for (var i = 0; i < scripts.length; ++i) {
var elem = doc.createElement("script");
elem.type = "text/javascript";
doc.head.appendChild(elem);
elem.text = scripts[i];
}
}
function reportError(error, url, line) {
window.jsErrors.push(error + " at " + line)
}
function report(error) {
window.jsErrors.push(error)
}
function globalEval(window, arg) {
eval.apply(window, [arg])
}