From b4347b4eb8432d741bef29b3c160e483c1f1eddb Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Thu, 27 Feb 2014 10:04:51 +0400 Subject: [PATCH] Adds optional support of throwing NPE when calling method on null instance --- .../java/lang/ClassNativeGenerator.java | 11 ++-- .../org/teavm/classlib/java/lang/TClass.java | 1 + .../NullPointerExceptionTransformer.java | 58 +++++++++++++++++++ .../org/teavm/javascript/RuntimeSupport.java | 31 ++++++++++ teavm-html4j/pom.xml | 6 +- .../org/teavm/maven/BuildJavascriptMojo.java | 52 +++++++++++++++++ .../teavm/maven/BuildJavascriptTestMojo.java | 52 +++++++++++++++++ 7 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/javascript/NullPointerExceptionTransformer.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/RuntimeSupport.java diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java index 250be112b..d6d506e4c 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java @@ -138,11 +138,10 @@ public class ClassNativeGenerator implements Generator, Injector, DependencyPlug writer.append("var cls = " + self + ".$data;").softNewLine(); writer.append("var ctor = cls.$$constructor$$;").softNewLine(); writer.append("if (!ctor) {").indent().softNewLine(); - /*writer.append("var ex = new ").appendClass(InstantiationException.class.getName()).append("();").softNewLine(); + writer.append("var ex = new ").appendClass(InstantiationException.class.getName()).append("();").softNewLine(); writer.appendMethodBody(new MethodReference(InstantiationException.class.getName(), new MethodDescriptor( - "", ValueType.VOID))).append("(ex);").softNewLine();*/ - //writer.append("$rt_throw(ex);").softNewLine(); - writer.append("return null;").softNewLine(); + "", ValueType.VOID))).append("(ex);").softNewLine(); + writer.append("$rt_throw(ex);").softNewLine(); writer.outdent().append("}").softNewLine(); writer.append("var instance = new cls();").softNewLine(); writer.append("ctor(instance);").softNewLine(); @@ -180,6 +179,10 @@ public class ClassNativeGenerator implements Generator, Injector, DependencyPlug case "getDeclaringClass": graph.getResult().propagate("java.lang.Class"); break; + case "newInstance": + checker.linkMethod(new MethodReference(InstantiationException.class.getName(), "", + ValueType.VOID), graph.getStack()).use(); + break; } } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index a2c9984ee..94139507b 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -133,6 +133,7 @@ public class TClass extends TObject { } @GeneratedBy(ClassNativeGenerator.class) + @PluggableDependency(ClassNativeGenerator.class) public native T newInstance() throws TInstantiationException, TIllegalAccessException; @GeneratedBy(ClassNativeGenerator.class) diff --git a/teavm-core/src/main/java/org/teavm/javascript/NullPointerExceptionTransformer.java b/teavm-core/src/main/java/org/teavm/javascript/NullPointerExceptionTransformer.java new file mode 100644 index 000000000..25937fba0 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/NullPointerExceptionTransformer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 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.javascript; + +import org.teavm.model.*; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; + +/** + * + * @author Alexey Andreev + */ +public class NullPointerExceptionTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassReaderSource innerSource) { + for (MethodHolder method : cls.getMethods()) { + Program program = method.getProgram(); + if (program == null) { + continue; + } + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + transformBlock(block); + } + } + } + + private void transformBlock(BasicBlock block) { + for (int i = 0; i < block.getInstructions().size(); ++i) { + Instruction insn = block.getInstructions().get(i); + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction)insn; + if (invoke.getType() != InvocationType.VIRTUAL) { + continue; + } + InvokeInstruction checkInvoke = new InvokeInstruction(); + checkInvoke.setMethod(new MethodReference(RuntimeSupport.class.getName(), "requireNonNull", + ValueType.object("java.lang.Object"), ValueType.VOID)); + checkInvoke.setType(InvocationType.SPECIAL); + checkInvoke.getArguments().add(invoke.getInstance()); + block.getInstructions().add(i++, checkInvoke); + } + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/RuntimeSupport.java b/teavm-core/src/main/java/org/teavm/javascript/RuntimeSupport.java new file mode 100644 index 000000000..f6106f294 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/RuntimeSupport.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 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.javascript; + +/** + * + * @author Alexey Andreev + */ +public final class RuntimeSupport { + private RuntimeSupport() { + } + + public static void requireNonNull(Object obj) { + if (obj == null) { + throw new NullPointerException(); + } + } +} diff --git a/teavm-html4j/pom.xml b/teavm-html4j/pom.xml index 6cf1a6a79..8e98e9fc2 100644 --- a/teavm-html4j/pom.xml +++ b/teavm-html4j/pom.xml @@ -90,9 +90,9 @@ true ${project.build.directory}/javascript-tck org.teavm.html4j.testing.KOTestAdapter - + + org.teavm.javascript.NullPointerExceptionTransformer + diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index e1493d2de..ad1dd5412 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -16,6 +16,8 @@ package org.teavm.maven; import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -33,6 +35,7 @@ import org.apache.maven.project.MavenProject; import org.teavm.common.ThreadPoolFiniteExecutor; import org.teavm.javascript.JavascriptBuilder; import org.teavm.javascript.JavascriptBuilderFactory; +import org.teavm.model.ClassHolderTransformer; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -77,6 +80,9 @@ public class BuildJavascriptMojo extends AbstractMojo { @Parameter(required = false) private int numThreads = 1; + @Parameter + private String[] transformers; + public void setProject(MavenProject project) { this.project = project; } @@ -109,6 +115,14 @@ public class BuildJavascriptMojo extends AbstractMojo { this.numThreads = numThreads; } + public String[] getTransformers() { + return transformers; + } + + public void setTransformers(String[] transformers) { + this.transformers = transformers; + } + @Override public void execute() throws MojoExecutionException { Log log = getLog(); @@ -133,6 +147,9 @@ public class BuildJavascriptMojo extends AbstractMojo { builder.setMinifying(minifying); builder.setBytecodeLogging(bytecodeLogging); builder.installPlugins(); + for (ClassHolderTransformer transformer : instantiateTransformers(classLoader)) { + builder.add(transformer); + } builder.prepare(); MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( ValueType.object("java.lang.String")), ValueType.VOID); @@ -167,6 +184,41 @@ public class BuildJavascriptMojo extends AbstractMojo { } } + private List instantiateTransformers(ClassLoader classLoader) + throws MojoExecutionException { + List transformerInstances = new ArrayList<>(); + if (transformers == null) { + return transformerInstances; + } + for (String transformerName : transformers) { + Class transformerRawType; + try { + transformerRawType = Class.forName(transformerName, true, classLoader); + } catch (ClassNotFoundException e) { + throw new MojoExecutionException("Transformer not found: " + transformerName, e); + } + if (!ClassHolderTransformer.class.isAssignableFrom(transformerRawType)) { + throw new MojoExecutionException("Transformer " + transformerName + " is not subtype of " + + ClassHolderTransformer.class.getName()); + } + Class transformerType = transformerRawType.asSubclass( + ClassHolderTransformer.class); + Constructor ctor; + try { + ctor = transformerType.getConstructor(); + } catch (NoSuchMethodException e) { + throw new MojoExecutionException("Transformer " + transformerName + " has no default constructor"); + } + try { + ClassHolderTransformer transformer = ctor.newInstance(); + transformerInstances.add(transformer); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new MojoExecutionException("Error instantiating transformer " + transformerName, e); + } + } + return transformerInstances; + } + private ClassLoader prepareClassLoader() throws MojoExecutionException { try { Log log = getLog(); diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java index e44e6e994..08a92dee4 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java @@ -90,6 +90,11 @@ public class BuildJavascriptTestMojo extends AbstractMojo { @Parameter private String adapterClass = JUnitTestAdapter.class.getName(); + @Parameter + private String[] transformers; + + private List transformerInstances; + public void setProject(MavenProject project) { this.project = project; } @@ -122,6 +127,14 @@ public class BuildJavascriptTestMojo extends AbstractMojo { this.wildcards = wildcards; } + public String[] getTransformers() { + return transformers; + } + + public void setTransformers(String[] transformers) { + this.transformers = transformers; + } + @Override public void execute() throws MojoExecutionException, MojoFailureException { Runnable finalizer = null; @@ -148,6 +161,7 @@ public class BuildJavascriptTestMojo extends AbstractMojo { findTests(classHolder); } + transformerInstances = instantiateTransformers(classLoader); File allTestsFile = new File(outputDir, "tests/all.js"); try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { allTestsWriter.write("doRunTests = function() {\n"); @@ -291,6 +305,9 @@ public class BuildJavascriptTestMojo extends AbstractMojo { JavascriptBuilder builder = builderFactory.create(); builder.setMinifying(minifying); builder.installPlugins(); + for (ClassHolderTransformer transformer : transformerInstances) { + builder.add(transformer); + } builder.prepare(); File file = new File(outputDir, targetName); try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { @@ -453,4 +470,39 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } } } + + private List instantiateTransformers(ClassLoader classLoader) + throws MojoExecutionException { + List transformerInstances = new ArrayList<>(); + if (transformers == null) { + return transformerInstances; + } + for (String transformerName : transformers) { + Class transformerRawType; + try { + transformerRawType = Class.forName(transformerName, true, classLoader); + } catch (ClassNotFoundException e) { + throw new MojoExecutionException("Transformer not found: " + transformerName, e); + } + if (!ClassHolderTransformer.class.isAssignableFrom(transformerRawType)) { + throw new MojoExecutionException("Transformer " + transformerName + " is not subtype of " + + ClassHolderTransformer.class.getName()); + } + Class transformerType = transformerRawType.asSubclass( + ClassHolderTransformer.class); + Constructor ctor; + try { + ctor = transformerType.getConstructor(); + } catch (NoSuchMethodException e) { + throw new MojoExecutionException("Transformer " + transformerName + " has no default constructor"); + } + try { + ClassHolderTransformer transformer = ctor.newInstance(); + transformerInstances.add(transformer); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new MojoExecutionException("Error instantiating transformer " + transformerName, e); + } + } + return transformerInstances; + } }