Snapshot of commit d5ec1d5018ed24f1b4f32b1d09df6dbd7e2fc425
from branch master of git://git.jetbrains.org/idea/community.git
diff --git a/java/compiler/instrumentation-util/instrumentation-util.iml b/java/compiler/instrumentation-util/instrumentation-util.iml
new file mode 100644
index 0000000..60d62d3
--- /dev/null
+++ b/java/compiler/instrumentation-util/instrumentation-util.iml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="asm4" level="project" />
+ </component>
+</module>
+
diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java
new file mode 100644
index 0000000..abde877
--- /dev/null
+++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java
@@ -0,0 +1,765 @@
+package com.intellij.compiler.instrumentation;
+
+import org.jetbrains.asm4.ClassReader;
+import org.jetbrains.asm4.ClassVisitor;
+import org.jetbrains.asm4.MethodVisitor;
+import org.jetbrains.asm4.Opcodes;
+import sun.misc.Resource;
+
+import java.io.*;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 2/16/12
+ */
+public class InstrumentationClassFinder {
+ private static final PseudoClass[] EMPTY_PSEUDOCLASS_ARRAY = new PseudoClass[0];
+ private static final String CLASS_RESOURCE_EXTENSION = ".class";
+ private static final URL[] URL_EMPTY_ARRAY = new URL[0];
+ private final Map<String, PseudoClass> myLoaded = new HashMap<String, PseudoClass>(); // className -> class object
+ private final ClassFinderClasspath myPlatformClasspath;
+ private final ClassFinderClasspath myClasspath;
+ private final URL[] myPlatformUrls;
+ private final URL[] myClasspathUrls;
+ private ClassLoader myLoader;
+ private byte[] myBuffer;
+
+ public InstrumentationClassFinder(final URL[] cp) {
+ this(URL_EMPTY_ARRAY, cp);
+ }
+
+ public InstrumentationClassFinder(final URL[] platformUrls, final URL[] classpathUrls) {
+ myPlatformUrls = platformUrls;
+ myClasspathUrls = classpathUrls;
+ myPlatformClasspath = new ClassFinderClasspath(platformUrls);
+ myClasspath = new ClassFinderClasspath(classpathUrls);
+ }
+
+ // compatibility with legacy code requiring ClassLoader
+ public ClassLoader getLoader() {
+ ClassLoader loader = myLoader;
+ if (loader != null) {
+ return loader;
+ }
+ final URLClassLoader platformLoader = myPlatformUrls.length > 0 ? new URLClassLoader(myPlatformUrls, null) : null;
+ final ClassLoader cpLoader = new URLClassLoader(myClasspathUrls, platformLoader);
+ loader = new ClassLoader(cpLoader) {
+
+ public InputStream getResourceAsStream(String name) {
+ InputStream is = null;
+ is = super.getResourceAsStream(name);
+ if (is == null) {
+ try {
+ is = InstrumentationClassFinder.this.getResourceAsStream(name);
+ }
+ catch (IOException ignored) {
+ }
+ }
+ return is;
+ }
+
+ protected Class findClass(String name) throws ClassNotFoundException {
+ final InputStream is = lookupClassBeforeClasspath(name.replace('.', '/'));
+ if (is == null) {
+ throw new ClassNotFoundException("Class not found: " + name.replace('/', '.')); // ensure presentable class name in error message
+ }
+ try {
+ final byte[] bytes = loadBytes(is);
+ return defineClass(name, bytes, 0, bytes.length);
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+ };
+ myLoader = loader;
+ return loader;
+ }
+
+ public void releaseResources() {
+ myPlatformClasspath.releaseResources();
+ myClasspath.releaseResources();
+ myLoaded.clear();
+ myBuffer = null;
+ myLoader = null;
+ }
+
+ public PseudoClass loadClass(final String name) throws IOException, ClassNotFoundException{
+ final String internalName = name.replace('.', '/'); // normalize
+ final PseudoClass aClass = myLoaded.get(internalName);
+ if (aClass != null) {
+ return aClass;
+ }
+
+ final InputStream is = getClassBytesAsStream(internalName);
+
+ if (is == null) {
+ throw new ClassNotFoundException("Class not found: " + name.replace('/', '.')); // ensure presentable class name in error message
+ }
+
+ try {
+ final PseudoClass result = loadPseudoClass(is);
+ myLoaded.put(internalName, result);
+ return result;
+ }
+ finally {
+ is.close();
+ }
+ }
+
+ public void cleanCachedData(String className) {
+ myLoaded.remove(className.replace('.', '/'));
+ }
+
+ public InputStream getClassBytesAsStream(String className) throws IOException {
+ final String internalName = className.replace('.', '/'); // normalize
+ InputStream is = null;
+ // first look into platformCp
+ final String resourceName = internalName + CLASS_RESOURCE_EXTENSION;
+ Resource resource = myPlatformClasspath.getResource(resourceName, false);
+ if (resource != null) {
+ is = resource.getInputStream();
+ }
+ // second look into memory and classspath
+ if (is == null) {
+ is = lookupClassBeforeClasspath(internalName);
+ }
+
+ if (is == null) {
+ resource = myClasspath.getResource(resourceName, false);
+ if (resource != null) {
+ is = resource.getInputStream();
+ }
+ }
+
+ if (is == null) {
+ is = lookupClassAfterClasspath(internalName);
+ }
+ return is;
+ }
+
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
+ InputStream is = null;
+
+ Resource resource = myPlatformClasspath.getResource(resourceName, false);
+ if (resource != null) {
+ is = resource.getInputStream();
+ }
+
+ if (is == null) {
+ resource = myClasspath.getResource(resourceName, false);
+ if (resource != null) {
+ is = resource.getInputStream();
+ }
+ }
+
+ return is;
+ }
+
+ protected InputStream lookupClassBeforeClasspath(final String internalClassName) {
+ return null;
+ }
+
+ protected InputStream lookupClassAfterClasspath(final String internalClassName) {
+ return null;
+ }
+
+ private PseudoClass loadPseudoClass(InputStream is) throws IOException {
+ final ClassReader reader = new ClassReader(is);
+ final V visitor = new V();
+
+ reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+
+ return new PseudoClass(visitor.myName, visitor.mySuperclassName, visitor.myInterfaces, visitor.myModifiers, visitor.myMethods);
+ }
+
+ public final class PseudoClass {
+ private final String myName;
+ private final String mySuperClass;
+ private final String[] myInterfaces;
+ private final int myModifiers;
+ private final List<PseudoMethod> myMethods;
+
+ private PseudoClass(final String name, final String superClass, final String[] interfaces, final int modifiers, List<PseudoMethod> methods) {
+ myName = name;
+ mySuperClass = superClass;
+ myInterfaces = interfaces;
+ myModifiers = modifiers;
+ myMethods = methods;
+ }
+
+ public int getModifiers() {
+ return myModifiers;
+ }
+
+ public boolean isInterface() {
+ return (myModifiers & Opcodes.ACC_INTERFACE) > 0;
+ }
+
+ public String getName() {
+ return myName;
+ }
+
+ public List<PseudoMethod> getMethods() {
+ return myMethods;
+ }
+
+ public List<PseudoMethod> findMethods(String name) {
+ final List<PseudoMethod> result = new ArrayList<PseudoMethod>();
+ for (PseudoMethod method : myMethods) {
+ if (method.getName().equals(name)){
+ result.add(method);
+ }
+ }
+ return result;
+ }
+
+ public PseudoMethod findMethod(String name, String descriptor) {
+ for (PseudoMethod method : myMethods) {
+ if (method.getName().equals(name) && method.getSignature().equals(descriptor)){
+ return method;
+ }
+ }
+ return null;
+ }
+
+ public InstrumentationClassFinder getFinder() {
+ return InstrumentationClassFinder.this;
+ }
+
+ public PseudoClass getSuperClass() throws IOException, ClassNotFoundException {
+ final String superClass = mySuperClass;
+ return superClass != null? loadClass(superClass) : null;
+ }
+
+ public PseudoClass[] getInterfaces() throws IOException, ClassNotFoundException {
+ if (myInterfaces == null) {
+ return EMPTY_PSEUDOCLASS_ARRAY;
+ }
+
+ final PseudoClass[] result = new PseudoClass[myInterfaces.length];
+
+ for (int i = 0; i < result.length; i++) {
+ result[i] = loadClass(myInterfaces[i]);
+ }
+
+ return result;
+ }
+
+ public boolean equals (final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ return getName().equals(((PseudoClass)o).getName());
+ }
+
+ private boolean isSubclassOf(final PseudoClass x) throws IOException, ClassNotFoundException {
+ for (PseudoClass c = this; c != null; c = c.getSuperClass()) {
+ final PseudoClass superClass = c.getSuperClass();
+
+ if (superClass != null && superClass.equals(x)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean implementsInterface(final PseudoClass x) throws IOException, ClassNotFoundException {
+ for (PseudoClass c = this; c != null; c = c.getSuperClass()) {
+ final PseudoClass[] tis = c.getInterfaces();
+ for (final PseudoClass ti : tis) {
+ if (ti.equals(x) || ti.implementsInterface(x)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isAssignableFrom(final PseudoClass x) throws IOException, ClassNotFoundException {
+ if (this.equals(x)) {
+ return true;
+ }
+ if (x.isSubclassOf(this)) {
+ return true;
+ }
+ if (x.implementsInterface(this)) {
+ return true;
+ }
+ if (x.isInterface() && "java/lang/Object".equals(getName())) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasDefaultPublicConstructor() {
+ for (PseudoMethod method : myMethods) {
+ if ("<init>".equals(method.getName()) && "()V".equals(method.getSignature())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getDescriptor() {
+ return "L" + myName + ";";
+ }
+ }
+
+ public static final class PseudoMethod {
+ private final int myAccess;
+ private final String myName;
+ private final String mySignature;
+
+ public PseudoMethod(int access, String name, String signature) {
+ myAccess = access;
+ myName = name;
+ mySignature = signature;
+ }
+
+ public int getModifiers() {
+ return myAccess;
+ }
+
+ public String getName() {
+ return myName;
+ }
+
+ public String getSignature() {
+ return mySignature;
+ }
+ }
+
+ private static class V extends ClassVisitor {
+ public String mySuperclassName = null;
+ public String[] myInterfaces = null;
+ public String myName = null;
+ public int myModifiers;
+ private final List<PseudoMethod> myMethods = new ArrayList<PseudoMethod>();
+
+ private V() {
+ super(Opcodes.ASM4);
+ }
+
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if ((access & Opcodes.ACC_PUBLIC) > 0) {
+ myMethods.add(new PseudoMethod(access, name, desc));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ public void visit(int version, int access, String pName, String signature, String pSuperName, String[] pInterfaces) {
+ mySuperclassName = pSuperName;
+ myInterfaces = pInterfaces;
+ myName = pName;
+ myModifiers = access;
+ }
+ }
+
+ static class ClassFinderClasspath {
+ private static final String FILE_PROTOCOL = "file";
+
+ private final Stack<URL> myUrls = new Stack<URL>();
+ private final List<Loader> myLoaders = new ArrayList<Loader>();
+ private final Map<URL,Loader> myLoadersMap = new HashMap<URL, Loader>();
+
+ public ClassFinderClasspath(URL[] urls) {
+ if (urls.length > 0) {
+ for (int i = urls.length - 1; i >= 0; i--) {
+ myUrls.push(urls[i]);
+ }
+ }
+ }
+
+ public Resource getResource(String s, boolean flag) {
+ int i = 0;
+ for (Loader loader; (loader = getLoader(i)) != null; i++) {
+ Resource resource = loader.getResource(s, flag);
+ if (resource != null) {
+ return resource;
+ }
+ }
+
+ return null;
+ }
+
+ public void releaseResources() {
+ for (Loader loader : myLoaders) {
+ loader.releaseResources();
+ }
+ myLoaders.clear();
+ myLoadersMap.clear();
+ }
+
+ private synchronized Loader getLoader(int i) {
+ while (myLoaders.size() < i + 1) {
+ URL url;
+ synchronized (myUrls) {
+ if (myUrls.empty()) {
+ return null;
+ }
+ url = myUrls.pop();
+ }
+
+ if (myLoadersMap.containsKey(url)) {
+ continue;
+ }
+
+ Loader loader;
+ try {
+ loader = getLoader(url, myLoaders.size());
+ if (loader == null) {
+ continue;
+ }
+ }
+ catch (IOException ioexception) {
+ continue;
+ }
+
+ myLoaders.add(loader);
+ myLoadersMap.put(url, loader);
+ }
+
+ return myLoaders.get(i);
+ }
+
+ private Loader getLoader(final URL url, int index) throws IOException {
+ String s;
+ try {
+ s = url.toURI().getSchemeSpecificPart();
+ }
+ catch (URISyntaxException thisShouldNotHappen) {
+ thisShouldNotHappen.printStackTrace();
+ s = url.getFile();
+ }
+
+ Loader loader = null;
+ if (s != null && new File(s).isDirectory()) {
+ if (FILE_PROTOCOL.equals(url.getProtocol())) {
+ loader = new FileLoader(url, index);
+ }
+ }
+ else {
+ loader = new JarLoader(url, index);
+ }
+
+ return loader;
+ }
+
+
+ private abstract static class Loader {
+ protected static final String JAR_PROTOCOL = "jar";
+ protected static final String FILE_PROTOCOL = "file";
+
+ private final URL myURL;
+ private final int myIndex;
+
+ protected Loader(URL url, int index) {
+ myURL = url;
+ myIndex = index;
+ }
+
+
+ protected URL getBaseURL() {
+ return myURL;
+ }
+
+ public abstract Resource getResource(final String name, boolean flag);
+
+ public abstract void releaseResources();
+
+ public int getIndex() {
+ return myIndex;
+ }
+ }
+
+ private static class FileLoader extends Loader {
+ private final File myRootDir;
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ FileLoader(URL url, int index) throws IOException {
+ super(url, index);
+ if (!FILE_PROTOCOL.equals(url.getProtocol())) {
+ throw new IllegalArgumentException("url");
+ }
+ else {
+ final String s = unescapePercentSequences(url.getFile().replace('/', File.separatorChar));
+ myRootDir = new File(s);
+ }
+ }
+
+ public void releaseResources() {
+ }
+
+ public Resource getResource(final String name, boolean check) {
+ URL url = null;
+ File file = null;
+
+ try {
+ url = new URL(getBaseURL(), name);
+ if (!url.getFile().startsWith(getBaseURL().getFile())) {
+ return null;
+ }
+
+ file = new File(myRootDir, name.replace('/', File.separatorChar));
+ if (!check || file.exists()) { // check means we load or process resource so we check its existence via old way
+ return new FileResource(name, url, file, !check);
+ }
+ }
+ catch (Exception exception) {
+ if (!check && file != null && file.exists()) {
+ try { // we can not open the file if it is directory, Resource still can be created
+ return new FileResource(name, url, file, false);
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+ return null;
+ }
+
+ private class FileResource extends Resource {
+ private final String myName;
+ private final URL myUrl;
+ private final File myFile;
+
+ public FileResource(String name, URL url, File file, boolean willLoadBytes) throws IOException {
+ myName = name;
+ myUrl = url;
+ myFile = file;
+ if (willLoadBytes) getByteBuffer(); // check for existence by creating cached file input stream
+ }
+
+ public String getName() {
+ return myName;
+ }
+
+ public URL getURL() {
+ return myUrl;
+ }
+
+ public URL getCodeSourceURL() {
+ return getBaseURL();
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(myFile));
+ }
+
+ public int getContentLength() throws IOException {
+ return -1;
+ }
+
+ public String toString() {
+ return myFile.getAbsolutePath();
+ }
+ }
+
+ public String toString() {
+ return "FileLoader [" + myRootDir + "]";
+ }
+ }
+
+ private class JarLoader extends Loader {
+ private final URL myURL;
+ private ZipFile myZipFile;
+
+ JarLoader(URL url, int index) throws IOException {
+ super(new URL(JAR_PROTOCOL, "", -1, url + "!/"), index);
+ myURL = url;
+ }
+
+ public void releaseResources() {
+ final ZipFile zipFile = myZipFile;
+ if (zipFile != null) {
+ myZipFile = null;
+ try {
+ zipFile.close();
+ }
+ catch (IOException e) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ private ZipFile acquireZipFile() throws IOException {
+ ZipFile zipFile = myZipFile;
+ if (zipFile == null) {
+ zipFile = doGetZipFile();
+ myZipFile = zipFile;
+ }
+ return zipFile;
+ }
+
+ private ZipFile doGetZipFile() throws IOException {
+ if (FILE_PROTOCOL.equals(myURL.getProtocol())) {
+ String s = unescapePercentSequences(myURL.getFile().replace('/', File.separatorChar));
+ if (!new File(s).exists()) {
+ throw new FileNotFoundException(s);
+ }
+ else {
+ return new ZipFile(s);
+ }
+ }
+
+ return null;
+ }
+
+ public Resource getResource(String name, boolean flag) {
+ try {
+ final ZipFile file = acquireZipFile();
+ if (file != null) {
+ final ZipEntry entry = file.getEntry(name);
+ if (entry != null) {
+ return new JarResource(entry, new URL(getBaseURL(), name));
+ }
+ }
+ }
+ catch (Exception e) {
+ return null;
+ }
+ return null;
+ }
+
+ private class JarResource extends Resource {
+ private final ZipEntry myEntry;
+ private final URL myUrl;
+
+ public JarResource(ZipEntry name, URL url) {
+ myEntry = name;
+ myUrl = url;
+ }
+
+ public String getName() {
+ return myEntry.getName();
+ }
+
+ public URL getURL() {
+ return myUrl;
+ }
+
+ public URL getCodeSourceURL() {
+ return myURL;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ try {
+ final ZipFile file = acquireZipFile();
+ if (file == null) {
+ return null;
+ }
+
+ final InputStream inputStream = file.getInputStream(myEntry);
+ if (inputStream == null) {
+ return null; // if entry was not found
+ }
+ return new FilterInputStream(inputStream) {};
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public int getContentLength() {
+ return (int)myEntry.getSize();
+ }
+ }
+
+ public String toString() {
+ return "JarLoader [" + myURL + "]";
+ }
+ }
+ }
+
+
+ private static String unescapePercentSequences(String s) {
+ if (s.indexOf('%') == -1) {
+ return s;
+ }
+ StringBuilder decoded = new StringBuilder();
+ final int len = s.length();
+ int i = 0;
+ while (i < len) {
+ char c = s.charAt(i);
+ if (c == '%') {
+ List<Integer> bytes = new ArrayList<Integer>();
+ while (i + 2 < len && s.charAt(i) == '%') {
+ final int d1 = decode(s.charAt(i + 1));
+ final int d2 = decode(s.charAt(i + 2));
+ if (d1 != -1 && d2 != -1) {
+ bytes.add(((d1 & 0xf) << 4 | d2 & 0xf));
+ i += 3;
+ }
+ else {
+ break;
+ }
+ }
+ if (!bytes.isEmpty()) {
+ final byte[] bytesArray = new byte[bytes.size()];
+ for (int j = 0; j < bytes.size(); j++) {
+ bytesArray[j] = (byte)bytes.get(j).intValue();
+ }
+ try {
+ decoded.append(new String(bytesArray, "UTF-8"));
+ continue;
+ }
+ catch (UnsupportedEncodingException ignored) {
+ }
+ }
+ }
+
+ decoded.append(c);
+ i++;
+ }
+ return decoded.toString();
+ }
+
+ private static int decode(char c) {
+ if ((c >= '0') && (c <= '9')){
+ return c - '0';
+ }
+ if ((c >= 'a') && (c <= 'f')){
+ return c - 'a' + 10;
+ }
+ if ((c >= 'A') && (c <= 'F')){
+ return c - 'A' + 10;
+ }
+ return -1;
+ }
+
+ public byte[] loadBytes(InputStream stream) {
+ byte[] buf = myBuffer;
+ if (buf == null) {
+ buf = new byte[512];
+ myBuffer = buf;
+ }
+
+ final ByteArrayOutputStream result = new ByteArrayOutputStream();
+ try {
+ while (true) {
+ int n = stream.read(buf, 0, buf.length);
+ if (n <= 0) {
+ break;
+ }
+ result.write(buf, 0, n);
+ }
+ result.close();
+ }
+ catch (IOException ignored) {
+ }
+ return result.toByteArray();
+ }
+
+}
diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumenterClassWriter.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumenterClassWriter.java
new file mode 100644
index 0000000..b2f93c5
--- /dev/null
+++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumenterClassWriter.java
@@ -0,0 +1,43 @@
+package com.intellij.compiler.instrumentation;
+
+import org.jetbrains.asm4.ClassWriter;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 3/27/12
+*/
+public class InstrumenterClassWriter extends ClassWriter {
+ private final InstrumentationClassFinder myFinder;
+
+ public InstrumenterClassWriter(int flags, final InstrumentationClassFinder finder) {
+ super(flags);
+ myFinder = finder;
+ }
+
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ try {
+ final InstrumentationClassFinder.PseudoClass cls1 = myFinder.loadClass(type1);
+ final InstrumentationClassFinder.PseudoClass cls2 = myFinder.loadClass(type2);
+ if (cls1.isAssignableFrom(cls2)) {
+ return cls1.getName();
+ }
+ if (cls2.isAssignableFrom(cls1)) {
+ return cls2.getName();
+ }
+ if (cls1.isInterface() || cls2.isInterface()) {
+ return "java/lang/Object";
+ }
+ else {
+ InstrumentationClassFinder.PseudoClass c = cls1;
+ do {
+ c = c.getSuperClass();
+ }
+ while (!c.isAssignableFrom(cls2));
+ return c.getName();
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.toString(), e);
+ }
+ }
+}
diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java
new file mode 100644
index 0000000..77bc538
--- /dev/null
+++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * 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 com.intellij.compiler.notNullVerification;
+
+import org.jetbrains.asm4.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author ven
+ */
+public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcodes {
+ private static final String NOT_NULL_CLASS_NAME = "org/jetbrains/annotations/NotNull";
+ private static final String NOT_NULL_TYPE = "L"+ NOT_NULL_CLASS_NAME + ";";
+ private static final String SYNTHETIC_CLASS_NAME = "java/lang/Synthetic";
+ private static final String SYNTHETIC_TYPE = "L" + SYNTHETIC_CLASS_NAME + ";";
+ private static final String IAE_CLASS_NAME = "java/lang/IllegalArgumentException";
+ private static final String ISE_CLASS_NAME = "java/lang/IllegalStateException";
+ private static final String STRING_CLASS_NAME = "java/lang/String";
+ private static final String CONSTRUCTOR_NAME = "<init>";
+ private static final String EXCEPTION_INIT_SIGNATURE = "(L" + STRING_CLASS_NAME + ";)V";
+
+ private static final String NULL_ARG_MESSAGE = "Argument %d for @NotNull parameter of %s.%s must not be null";
+ private static final String NULL_RESULT_MESSAGE = "@NotNull method %s.%s must not return null";
+
+ private String myClassName;
+ private boolean myIsModification = false;
+ private RuntimeException myPostponedError;
+
+ public NotNullVerifyingInstrumenter(final ClassVisitor classVisitor) {
+ super(Opcodes.ASM4, classVisitor);
+ }
+
+ public boolean isModification() {
+ return myIsModification;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ myClassName = name;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {
+ final Type[] args = Type.getArgumentTypes(desc);
+ final Type returnType = Type.getReturnType(desc);
+ final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM4, v) {
+
+ private final List<Integer> myNotNullParams = new ArrayList<Integer>();
+ private int mySyntheticCount = 0;
+ private boolean myIsNotNull = false;
+ private Label myStartGeneratedCodeLabel;
+
+ public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) {
+ final AnnotationVisitor av = mv.visitParameterAnnotation(parameter, anno, visible);
+ if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_TYPE)) {
+ myNotNullParams.add(new Integer(parameter));
+ }
+ else if (anno.equals(SYNTHETIC_TYPE)) {
+ // see http://forge.ow2.org/tracker/?aid=307392&group_id=23&atid=100023&func=detail
+ mySyntheticCount++;
+ }
+ return av;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) {
+ final AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
+ if (isReferenceType(returnType) && anno.equals(NOT_NULL_TYPE)) {
+ myIsNotNull = true;
+ }
+
+ return av;
+ }
+
+ @Override
+ public void visitCode() {
+ if (myNotNullParams.size() > 0) {
+ myStartGeneratedCodeLabel = new Label();
+ mv.visitLabel(myStartGeneratedCodeLabel);
+ }
+ for (Integer param : myNotNullParams) {
+ int var = ((access & ACC_STATIC) == 0) ? 1 : 0;
+ for (int i = 0; i < param; ++i) {
+ var += args[i].getSize();
+ }
+ mv.visitVarInsn(ALOAD, var);
+
+ Label end = new Label();
+ mv.visitJumpInsn(IFNONNULL, end);
+
+ generateThrow(IAE_CLASS_NAME, String.format(NULL_ARG_MESSAGE, param - mySyntheticCount, myClassName, name), end);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+ final boolean isStatic = (access & ACC_STATIC) != 0;
+ final boolean isParameter = isStatic ? index < args.length : index <= args.length;
+ final Label label = (isParameter && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start;
+ mv.visitLocalVariable(name, desc, signature, label, end, index);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (opcode == ARETURN) {
+ if (myIsNotNull) {
+ mv.visitInsn(DUP);
+ final Label skipLabel = new Label();
+ mv.visitJumpInsn(IFNONNULL, skipLabel);
+ generateThrow(ISE_CLASS_NAME, String.format(NULL_RESULT_MESSAGE, myClassName, name), skipLabel);
+ }
+ }
+
+ mv.visitInsn(opcode);
+ }
+
+ private void generateThrow(final String exceptionClass, final String descr, final Label end) {
+ mv.visitTypeInsn(NEW, exceptionClass);
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn(descr);
+ mv.visitMethodInsn(INVOKESPECIAL, exceptionClass, CONSTRUCTOR_NAME, EXCEPTION_INIT_SIGNATURE);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(end);
+
+ myIsModification = true;
+ processPostponedErrors();
+ }
+
+ @Override
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ try {
+ super.visitMaxs(maxStack, maxLocals);
+ }
+ catch (Throwable e) {
+ registerError(name, "visitMaxs", e);
+ }
+ }
+ };
+ }
+
+ private static boolean isReferenceType(final Type type) {
+ return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY;
+ }
+
+ private void registerError(String methodName, String operationName, Throwable e) {
+ if (myPostponedError == null) {
+ // throw the first error that occurred
+ Throwable err = e.getCause();
+ if (err == null) {
+ err = e;
+ }
+ myPostponedError = new RuntimeException("Operation '" + operationName + "' failed for " + myClassName + "." + methodName + "(): " + err.getMessage(), err);
+ }
+ if (myIsModification) {
+ processPostponedErrors();
+ }
+ }
+
+ private void processPostponedErrors() {
+ final RuntimeException error = myPostponedError;
+ if (error != null) {
+ throw error;
+ }
+ }
+}