diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java index d55cf5dbf..48a784c35 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java @@ -35,7 +35,7 @@ import org.teavm.model.ValueType; * @author Alexey Andreev */ public class ServiceLoaderSupport implements Generator, DependencyListener { - private Set achievedClasses; + private Set achievedClasses = new HashSet<>(); private Map> serviceMap = new HashMap<>(); private DependencyNode allClassesNode; private ClassLoader classLoader; diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java index a3b7656b9..cf4d71d9a 100644 --- a/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -152,11 +152,13 @@ public final class TeaVMRunner { try { tool.generate(); - tool.checkForMissingItems(); } catch (Exception e) { e.printStackTrace(System.err); System.exit(-2); } + if (!tool.getProblemProvider().getSevereProblems().isEmpty()) { + System.exit(-2); + } } private static void printUsage(Options options) { diff --git a/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java b/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java index 67249c67d..1b44f00b2 100644 --- a/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java +++ b/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraph.java @@ -32,7 +32,7 @@ public class DefaultCallGraph implements CallGraph { public DefaultCallGraphNode getNode(MethodReference method) { DefaultCallGraphNode node = nodes.get(method); if (node == null) { - node = new DefaultCallGraphNode(this); + node = new DefaultCallGraphNode(this, method); nodes.put(method, node); } return nodes.get(method); diff --git a/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java b/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java index 25e4633f5..421a1efc8 100644 --- a/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java +++ b/teavm-core/src/main/java/org/teavm/callgraph/DefaultCallGraphNode.java @@ -15,10 +15,7 @@ */ package org.teavm.callgraph; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import org.teavm.model.FieldReference; import org.teavm.model.InstructionLocation; import org.teavm.model.MethodReference; @@ -30,17 +27,18 @@ import org.teavm.model.MethodReference; public class DefaultCallGraphNode implements CallGraphNode { private DefaultCallGraph graph; private MethodReference method; - private Set callSites; + private Set callSites = new HashSet<>(); private transient Set safeCallSites; - private List callerCallSites; + private List callerCallSites = new ArrayList<>(); private transient List safeCallersCallSites; - private Set fieldAccessSites; + private Set fieldAccessSites = new HashSet<>(); private transient Set safeFieldAccessSites; - private Set classAccessSites; + private Set classAccessSites = new HashSet<>(); private transient Set safeClassAccessSites; - DefaultCallGraphNode(DefaultCallGraph graph) { + DefaultCallGraphNode(DefaultCallGraph graph, MethodReference method) { this.graph = graph; + this.method = method; } @Override diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index 2fdf098c1..31a99e82e 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -66,7 +66,7 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { private DependencyCheckerInterruptor interruptor; private boolean interrupted; private Diagnostics diagnostics; - private DefaultCallGraph callGraph = new DefaultCallGraph(); + DefaultCallGraph callGraph = new DefaultCallGraph(); public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, Diagnostics diagnostics) { @@ -495,6 +495,7 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { return diagnostics; } + @Override public CallGraph getCallGraph() { return callGraph; } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index 1080c23e2..232548688 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -41,6 +41,7 @@ class DependencyGraphBuilder { } public void buildGraph(MethodDependency dep) { + caller = dependencyChecker.callGraph.getNode(dep.getReference()); MethodReader method = dep.getMethod(); if (method.getProgram() == null || method.getProgram().basicBlockCount() == 0) { return; diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java index 25f59a580..c2a8916e3 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java @@ -16,6 +16,7 @@ package org.teavm.dependency; import java.util.Collection; +import org.teavm.callgraph.CallGraph; import org.teavm.model.ClassReaderSource; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -40,4 +41,6 @@ public interface DependencyInfo { MethodDependencyInfo getMethod(MethodReference methodRef); ClassDependencyInfo getClass(String className); + + CallGraph getCallGraph(); } diff --git a/teavm-core/src/main/java/org/teavm/diagnostics/DefaultProblemTextConsumer.java b/teavm-core/src/main/java/org/teavm/diagnostics/DefaultProblemTextConsumer.java new file mode 100644 index 000000000..8db0bf46d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/diagnostics/DefaultProblemTextConsumer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 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. + */ +package org.teavm.diagnostics; + +import org.teavm.model.FieldReference; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DefaultProblemTextConsumer implements ProblemTextConsumer { + private StringBuilder sb; + + public void clear() { + sb.setLength(0); + } + + public String getText() { + return sb.toString(); + } + + @Override + public void append(String text) { + sb.append(text); + } + + @Override + public void appendClass(String className) { + sb.append(className); + } + + @Override + public void appendMethod(MethodReference method) { + sb.append(method); + } + + @Override + public void appendField(FieldReference field) { + sb.append(field); + } + + @Override + public void appendLocation(InstructionLocation location) { + sb.append(location); + } +} diff --git a/teavm-core/src/main/java/org/teavm/diagnostics/Problem.java b/teavm-core/src/main/java/org/teavm/diagnostics/Problem.java index 1bab7cfd0..06a033648 100644 --- a/teavm-core/src/main/java/org/teavm/diagnostics/Problem.java +++ b/teavm-core/src/main/java/org/teavm/diagnostics/Problem.java @@ -17,6 +17,9 @@ package org.teavm.diagnostics; import java.util.Arrays; import org.teavm.model.CallLocation; +import org.teavm.model.FieldReference; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReference; /** * @@ -50,4 +53,96 @@ public class Problem { public Object[] getParams() { return params; } + + public void render(ProblemTextConsumer consumer) { + int index = 0; + while (index < text.length()) { + int next = text.indexOf("{{", index); + if (next < 0) { + break; + } + consumer.append(text.substring(index, next)); + next = parseParameter(consumer, next); + } + consumer.append(text.substring(index)); + } + + private int parseParameter(ProblemTextConsumer consumer, int index) { + int next = index + 2; + if (next >= text.length()) { + return index; + } + ParamType type; + switch (Character.toLowerCase(text.charAt(next++))) { + case 'c': + type = ParamType.CLASS; + break; + case 'm': + type = ParamType.METHOD; + break; + case 'f': + type = ParamType.FIELD; + break; + case 'l': + type = ParamType.LOCATION; + break; + default: + return index; + } + int digitsEnd = passDigits(index); + if (digitsEnd == next) { + return index; + } + int paramIndex = Integer.parseInt(text.substring(next, digitsEnd)); + if (paramIndex >= params.length) { + return index; + } + next = digitsEnd; + if (next + 1 >= text.length() || !text.substring(next, next + 2).equals("}}")) { + return index; + } + Object param = params[paramIndex]; + switch (type) { + case CLASS: + if (!(param instanceof String)) { + return index; + } + consumer.appendClass((String)param); + break; + case METHOD: + if (!(param instanceof MethodReference)) { + return index; + } + consumer.appendMethod((MethodReference)param); + break; + case FIELD: + if (!(param instanceof FieldReference)) { + return index; + } + consumer.appendField((FieldReference)param); + break; + case LOCATION: + if (!(param instanceof InstructionLocation)) { + return index; + } + consumer.appendLocation((InstructionLocation)param); + break; + } + next += 2; + return next; + } + + private int passDigits(int index) { + while (index < text.length() && Character.isDigit(text.charAt(index))) { + ++index; + } + return index; + } + + enum ParamType { + CLASS, + METHOD, + FIELD, + LOCATION + } } diff --git a/teavm-core/src/main/java/org/teavm/diagnostics/ProblemTextConsumer.java b/teavm-core/src/main/java/org/teavm/diagnostics/ProblemTextConsumer.java new file mode 100644 index 000000000..8f074787b --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/diagnostics/ProblemTextConsumer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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. + */ +package org.teavm.diagnostics; + +import org.teavm.model.FieldReference; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface ProblemTextConsumer { + void append(String text); + + void appendClass(String className); + + void appendMethod(MethodReference method); + + void appendField(FieldReference field); + + void appendLocation(InstructionLocation location); +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java index ec82e43d0..5ad2eba26 100644 --- a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -22,8 +22,13 @@ import org.teavm.cache.DiskCachedClassHolderSource; import org.teavm.cache.DiskProgramCache; import org.teavm.cache.DiskRegularMethodNodeCache; import org.teavm.cache.FileSymbolTable; +import org.teavm.callgraph.CallGraph; +import org.teavm.callgraph.CallGraphNode; +import org.teavm.callgraph.CallSite; import org.teavm.debugging.information.DebugInformation; import org.teavm.debugging.information.DebugInformationBuilder; +import org.teavm.diagnostics.DefaultProblemTextConsumer; +import org.teavm.diagnostics.Problem; import org.teavm.diagnostics.ProblemProvider; import org.teavm.javascript.RenderingContext; import org.teavm.model.*; @@ -294,7 +299,16 @@ public class TeaVMTool { cancelled = true; return; } - log.info("JavaScript file successfully built"); + ProblemProvider problemProvider = vm.getProblemProvider(); + if (problemProvider.getProblems().isEmpty()) { + log.info("JavaScript file successfully built"); + } else if (problemProvider.getSevereProblems().isEmpty()) { + log.info("JavaScript file built with warnings"); + describeProblems(vm); + } else { + log.info("JavaScript file built with errors"); + describeProblems(vm); + } if (debugInformationGenerated) { DebugInformation debugInfo = debugEmitter.getDebugInformation(); try (OutputStream debugInfoOut = new FileOutputStream(new File(targetDirectory, @@ -345,6 +359,67 @@ public class TeaVMTool { } } + private void describeProblems(TeaVM vm) { + CallGraph cg = vm.getDependencyInfo().getCallGraph(); + DefaultProblemTextConsumer consumer = new DefaultProblemTextConsumer(); + for (Problem problem : vm.getProblemProvider().getProblems()) { + consumer.clear(); + problem.render(consumer); + StringBuilder sb = new StringBuilder(); + sb.append(consumer.getText()); + renderCallStack(cg, problem.getLocation(), sb); + String problemText = sb.toString(); + switch (problem.getSeverity()) { + case ERROR: + log.error(problemText); + break; + case WARNING: + log.warning(problemText); + break; + } + } + } + + private void renderCallStack(CallGraph cg, CallLocation location, StringBuilder sb) { + if (location == null) { + return; + } + sb.append("\n at "); + renderCallLocation(location.getMethod(), location.getSourceLocation(), sb); + if (location.getMethod() != null) { + CallGraphNode node = cg.getNode(location.getMethod()); + while (true) { + Iterator callSites = node.getCallerCallSites().iterator(); + if (!callSites.hasNext()) { + break; + } + CallSite callSite = callSites.next(); + sb.append("\n at "); + renderCallLocation(node.getMethod(), callSite.getLocation(), sb); + node = callSite.getCaller(); + } + } + } + + private void renderCallLocation(MethodReference method, InstructionLocation location, StringBuilder sb) { + if (method != null) { + sb.append(method); + } else { + sb.append("unknown method"); + } + sb.append(' '); + if (location != null) { + sb.append("("); + String fileName = location.getFileName(); + if (fileName != null) { + sb.append(fileName.substring(fileName.lastIndexOf('/') + 1)); + sb.append(':'); + } + sb.append(location.getLine()); + sb.append(')'); + } + } + private void copySourceFiles() { if (vm.getWrittenClasses() == null) { return; diff --git a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index 1eed97cac..f60c77f63 100644 --- a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -247,7 +247,9 @@ public class BuildJavascriptMojo extends AbstractMojo { tool.setSourceMapsFileGenerated(sourceMapsGenerated); tool.setSourceFilesCopied(sourceFilesCopied); tool.generate(); - tool.checkForMissingItems(); + if (!tool.getProblemProvider().getSevereProblems().isEmpty()) { + throw new MojoExecutionException("Build error"); + } } catch (RuntimeException e) { throw new MojoExecutionException("Unexpected error occured", e); } catch (TeaVMToolException e) { diff --git a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java index 5276d63e4..363acd8e6 100644 --- a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java @@ -16,10 +16,7 @@ package org.teavm.maven; import org.teavm.dependency.*; -import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; -import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; +import org.teavm.model.*; /** * @@ -36,7 +33,7 @@ class TestExceptionDependency implements DependencyListener { } @Override - public void classAchieved(DependencyAgent agent, String className) { + public void classAchieved(DependencyAgent agent, String className, CallLocation location) { if (isException(agent.getClassSource(), className)) { allClasses.propagate(agent.getType(className)); } @@ -57,13 +54,13 @@ class TestExceptionDependency implements DependencyListener { } @Override - public void methodAchieved(DependencyAgent agent, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method, CallLocation location) { if (method.getReference().equals(getMessageRef)) { allClasses.connect(method.getVariable(0)); } } @Override - public void fieldAchieved(DependencyAgent agent, FieldDependency field) { + public void fieldAchieved(DependencyAgent agent, FieldDependency field, CallLocation location) { } }