cross-compilation: when instrumenting notnulls, resolve agains target jdk classes rather than self runtime

This commit is contained in:
Eugene Zhuravlev
2012-02-16 17:14:25 +01:00
parent 826430de64
commit 5eeeb403d0
4 changed files with 401 additions and 166 deletions
@@ -0,0 +1,192 @@
package org.jetbrains.jps.incremental.java;
import com.intellij.util.lang.ClassPath;
import org.jetbrains.jps.javac.OutputFileObject;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.EmptyVisitor;
import sun.misc.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* @author Eugene Zhuravlev
* Date: 2/16/12
*/
class InstrumentationClassFinder {
private static final PseudoClass[] EMPTY_PSEUDOCLASS_ARRAY = new PseudoClass[0];
private static final String CLASS_RESOURCE_EXTENSION = ".class";
private final Map<String, PseudoClass> myLoaded = new HashMap<String, PseudoClass>(); // className -> class object
private final ClassPath myPlatformClasspath;
private final ClassPath myClasspath;
private final OutputFilesSink myCompiled;
public InstrumentationClassFinder(final URL[] platformClasspath, final URL[] classpath, OutputFilesSink compiled) {
myCompiled = compiled;
myPlatformClasspath = new ClassPath(platformClasspath, true, false);
myClasspath = new ClassPath(classpath, true, false);
}
public PseudoClass loadClass(final String internalName) throws IOException, ClassNotFoundException{
final PseudoClass aClass = myLoaded.get(internalName);
if (aClass != null) {
return aClass;
}
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) {
final OutputFileObject.Content content = myCompiled.lookupClassBytes(internalName.replace("/", "."));
if (content != null) {
is = new ByteArrayInputStream(content.getBuffer(), content.getOffset(), content.getLength());
}
}
if (is == null) {
resource = myClasspath.getResource(resourceName, false);
if (resource != null) {
is = resource.getInputStream();
}
}
if (is == null) {
throw new ClassNotFoundException("Class not found: " + internalName);
}
try {
final PseudoClass result = loadPseudoClass(is);
myLoaded.put(internalName, result);
return result;
}
finally {
is.close();
}
}
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.myIsInterface);
}
public final class PseudoClass {
private final String myName;
private final String mySuperClass;
private final String[] myInterfaces;
private final boolean isInterface;
private PseudoClass(final String name, final String superClass, final String[] interfaces, final boolean anInterface) {
myName = name;
mySuperClass = superClass;
myInterfaces = interfaces;
isInterface = anInterface;
}
public PseudoClass getSuperClass() throws IOException, ClassNotFoundException {
final String superClass = mySuperClass;
return superClass != null? loadClass(superClass) : null;
}
private 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() && getName().equals("java/lang/Object")) {
return true;
}
return false;
}
public boolean isInterface() {
return isInterface;
}
public String getName() {
return myName;
}
}
private static class V extends EmptyVisitor {
public String mySuperclassName = null;
public String[] myInterfaces = null;
public String myName = null;
public boolean myIsInterface = false;
public void visitAttribute(Attribute attr) {
super.visitAttribute(attr);
}
public void visit(int version, int access, String pName, String signature, String pSuperName, String[] pInterfaces) {
mySuperclassName = pSuperName;
myInterfaces = pInterfaces;
myName = pName;
myIsInterface = (access & Opcodes.ACC_INTERFACE) > 0;
}
}
}
@@ -25,7 +25,6 @@ import org.jetbrains.jps.api.RequestFuture;
import org.jetbrains.jps.incremental.*;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.incremental.storage.BuildDataManager;
import org.jetbrains.jps.incremental.storage.SourceToFormMapping;
@@ -268,14 +267,15 @@ public class JavaBuilder extends ModuleLevelBuilder {
context.checkCanceled();
final ClassLoader compiledClassesLoader = createInstrumentationClassLoader(classpath, platformCp, chunkSourcePath, outputSink);
final InstrumentationClassFinder finder = createInstrumentationClassFinder(platformCp, classpath, outputSink);
context.checkCanceled();
if (!forms.isEmpty()) {
try {
context.processMessage(new ProgressMessage("Instrumenting forms [" + chunkName + "]"));
instrumentForms(context, chunk, chunkSourcePath, compiledClassesLoader, forms, outputSink);
final ClassLoader loader = createInstrumentationClassLoader(platformCp, classpath, chunkSourcePath, outputSink);
instrumentForms(context, chunk, chunkSourcePath, loader, finder, forms, outputSink);
}
finally {
context.processMessage(new ProgressMessage("Finished instrumenting forms [" + chunkName + "]"));
@@ -287,7 +287,7 @@ public class JavaBuilder extends ModuleLevelBuilder {
if (addNotNullAssertions) {
try {
context.processMessage(new ProgressMessage("Adding NotNull assertions [" + chunkName + "]"));
instrumentNotNull(context, outputSink, compiledClassesLoader);
instrumentNotNull(context, outputSink, finder);
}
finally {
context.processMessage(new ProgressMessage("Finished adding NotNull assertions [" + chunkName + "]"));
@@ -485,8 +485,37 @@ public class JavaBuilder extends ModuleLevelBuilder {
return heapSize;
}
private static ClassLoader createInstrumentationClassLoader(Collection<File> classpath,
Collection<File> platformCp,
private static InstrumentationClassFinder createInstrumentationClassFinder(Collection<File> platformCp,
Collection<File> classpath,
OutputFilesSink outputSink) throws MalformedURLException {
final URL[] platformUrls = new URL[platformCp.size()];
int index = 0;
for (File file : platformCp) {
platformUrls[index++] = file.toURI().toURL();
}
final URL[] urls = new URL[classpath.size() + 1];
index = 0;
for (File file : classpath) {
urls[index++] = file.toURI().toURL();
}
urls[index++] = getResourcePath(GridConstraints.class).toURI().toURL(); // forms_rt.jar
//urls.add(getResourcePath(CellConstraints.class).toURI().toURL()); // jgoodies-forms
final Map<String, byte[]> compiled = new HashMap<String, byte[]>();
for (OutputFileObject fileObject : outputSink.getFileObjects()) {
final String name = fileObject.getClassName();
if (name != null) {
final OutputFileObject.Content content = fileObject.getContent();
if (content != null) {
compiled.put(name, content.toByteArray());
}
}
}
return new InstrumentationClassFinder(platformUrls, urls, outputSink);
}
private static ClassLoader createInstrumentationClassLoader(Collection<File> platformCp, Collection<File> classpath,
Map<File, String> chunkSourcePath,
OutputFilesSink outputSink) throws MalformedURLException {
final List<URL> urls = new ArrayList<URL>();
@@ -605,14 +634,14 @@ public class JavaBuilder extends ModuleLevelBuilder {
}
// todo: probably instrument other NotNull-like annotations defined in project settings?
private static void instrumentNotNull(CompileContext context, OutputFilesSink sink, final ClassLoader loader) {
private static void instrumentNotNull(CompileContext context, OutputFilesSink sink, final InstrumentationClassFinder finder) {
for (final OutputFileObject fileObject : sink.getFileObjects()) {
final OutputFileObject.Content originalContent = fileObject.getContent();
final ClassReader reader = new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
final int version = getClassFileVersion(reader);
if (version >= Opcodes.V1_5) {
boolean success = false;
final ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), loader);
final ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
try {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
@@ -645,7 +674,7 @@ public class JavaBuilder extends ModuleLevelBuilder {
ModuleChunk chunk,
final Map<File, String> chunkSourcePath,
final ClassLoader loader,
Collection<File> formsToInstrument,
final InstrumentationClassFinder finder, Collection<File> formsToInstrument,
OutputFilesSink outputSink) throws ProjectBuildException {
final Map<String, File> class2form = new HashMap<String, File>();
@@ -686,13 +715,12 @@ public class JavaBuilder extends ModuleLevelBuilder {
final File alreadyProcessedForm = class2form.get(classToBind);
if (alreadyProcessedForm != null) {
context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, formFile.getAbsolutePath() +
": The form is bound to the class " +
classToBind +
".\nAnother form " +
alreadyProcessedForm.getAbsolutePath() +
" is also bound to this class",
formFile.getAbsolutePath()));
context.processMessage(
new CompilerMessage(
BUILDER_NAME, BuildMessage.Kind.WARNING,
formFile.getAbsolutePath() + ": The form is bound to the class " + classToBind + ".\nAnother form " + alreadyProcessedForm.getAbsolutePath() + " is also bound to this class",
formFile.getAbsolutePath())
);
continue;
}
@@ -705,7 +733,7 @@ public class JavaBuilder extends ModuleLevelBuilder {
new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
final int version = getClassFileVersion(classReader);
final InstrumenterClassWriter classWriter = new InstrumenterClassWriter(getAsmClassWriterFlags(version), loader);
final InstrumenterClassWriter classWriter = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
final AsmCodeGenerator codeGenerator = new AsmCodeGenerator(rootContainer, loader, nestedFormsLoader, false, classWriter);
final byte[] patchedBytes = codeGenerator.patchClass(classReader);
if (patchedBytes != null) {
@@ -858,165 +886,39 @@ public class JavaBuilder extends ModuleLevelBuilder {
}
}
private static class OutputFilesSink implements OutputFileConsumer {
private final CompileContext myContext;
private final Set<File> mySuccessfullyCompiled = new HashSet<File>();
private final Set<File> myProblematic = new HashSet<File>();
private final List<OutputFileObject> myFileObjects = new ArrayList<OutputFileObject>();
private final Map<String, OutputFileObject> myCompiledClasses = new HashMap<String, OutputFileObject>();
public OutputFilesSink(CompileContext context) {
myContext = context;
}
public void save(final @NotNull OutputFileObject fileObject) {
final String className = fileObject.getClassName();
if (className != null) {
final OutputFileObject.Content content = fileObject.getContent();
if (content != null) {
synchronized (myCompiledClasses) {
myCompiledClasses.put(className, fileObject);
}
}
}
synchronized (myFileObjects) {
myFileObjects.add(fileObject);
}
}
@Nullable
public OutputFileObject.Content lookupClassBytes(String className) {
synchronized (myCompiledClasses) {
final OutputFileObject object = myCompiledClasses.get(className);
return object != null ? object.getContent() : null;
}
}
public List<OutputFileObject> getFileObjects() {
return Collections.unmodifiableList(myFileObjects);
}
public void writePendingData() {
try {
if (!myFileObjects.isEmpty()) {
final FileGeneratedEvent event = new FileGeneratedEvent();
try {
for (OutputFileObject fileObject : myFileObjects) {
try {
writeToDisk(fileObject);
final File rootFile = fileObject.getOutputRoot();
if (rootFile != null) {
event.add(rootFile.getPath(), fileObject.getRelativePath());
}
}
catch (IOException e) {
myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage()));
}
}
}
finally {
myContext.processMessage(event);
}
}
}
finally {
myFileObjects.clear();
myCompiledClasses.clear();
}
}
public Set<File> getSuccessfullyCompiled() {
return Collections.unmodifiableSet(mySuccessfullyCompiled);
}
private void writeToDisk(@NotNull OutputFileObject fileObject) throws IOException {
final File file = fileObject.getFile();
final OutputFileObject.Content content = fileObject.getContent();
if (content == null) {
throw new IOException("Missing content for file " + file);
}
try {
_writeToFile(file, content);
}
catch (IOException e) {
// assuming the reason is non-existing parent
final File parentFile = file.getParentFile();
if (parentFile == null) {
throw e;
}
if (!parentFile.mkdirs()) {
throw e;
}
// second attempt
_writeToFile(file, content);
}
final File source = fileObject.getSourceFile();
if (source != null && !myProblematic.contains(source)) {
mySuccessfullyCompiled.add(source);
final String className = fileObject.getClassName();
if (className != null) {
myContext.processMessage(new ProgressMessage("Compiled " + className));
}
}
}
private static void _writeToFile(final File file, OutputFileObject.Content content) throws IOException {
final OutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
try {
stream.write(content.getBuffer(), content.getOffset(), content.getLength());
}
finally {
stream.close();
}
}
public void markError(OutputFileObject outputClassFile) {
final File source = outputClassFile.getSourceFile();
if (source != null) {
myProblematic.add(source);
}
}
}
public static class InstrumenterClassWriter extends ClassWriter {
private final ClassLoader myClassLoader;
private final InstrumentationClassFinder myFinder;
public InstrumenterClassWriter(int flags, final ClassLoader pseudoLoader) {
public InstrumenterClassWriter(int flags, final InstrumentationClassFinder finder) {
super(flags);
myClassLoader = pseudoLoader;
myFinder = finder;
}
protected String getCommonSuperClass(final String type1, final String type2) {
Class c, d;
try {
//c = Class.forName(type1.replace('/', '.'), true, myClassLoader);
//d = Class.forName(type2.replace('/', '.'), true, myClassLoader);
c = myClassLoader.loadClass(type1.replace('/', '.'));
d = myClassLoader.loadClass(type2.replace('/', '.'));
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);
}
if (c.isAssignableFrom(d)) {
return type1;
}
if (d.isAssignableFrom(c)) {
return type2;
}
if (c.isInterface() || d.isInterface()) {
return "java/lang/Object";
}
else {
do {
c = c.getSuperclass();
}
while (!c.isAssignableFrom(d));
return c.getName().replace('.', '/');
}
}
}
@@ -0,0 +1,141 @@
package org.jetbrains.jps.incremental.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.javac.OutputFileConsumer;
import org.jetbrains.jps.javac.OutputFileObject;
import java.io.*;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: 2/16/12
*/
class OutputFilesSink implements OutputFileConsumer {
private final CompileContext myContext;
private final Set<File> mySuccessfullyCompiled = new HashSet<File>();
private final Set<File> myProblematic = new HashSet<File>();
private final List<OutputFileObject> myFileObjects = new ArrayList<OutputFileObject>();
private final Map<String, OutputFileObject> myCompiledClasses = new HashMap<String, OutputFileObject>();
public OutputFilesSink(CompileContext context) {
myContext = context;
}
public void save(final @NotNull OutputFileObject fileObject) {
final String className = fileObject.getClassName();
if (className != null) {
final OutputFileObject.Content content = fileObject.getContent();
if (content != null) {
synchronized (myCompiledClasses) {
myCompiledClasses.put(className, fileObject);
}
}
}
synchronized (myFileObjects) {
myFileObjects.add(fileObject);
}
}
@Nullable
public OutputFileObject.Content lookupClassBytes(String className) {
synchronized (myCompiledClasses) {
final OutputFileObject object = myCompiledClasses.get(className);
return object != null ? object.getContent() : null;
}
}
public List<OutputFileObject> getFileObjects() {
return Collections.unmodifiableList(myFileObjects);
}
public void writePendingData() {
try {
if (!myFileObjects.isEmpty()) {
final FileGeneratedEvent event = new FileGeneratedEvent();
try {
for (OutputFileObject fileObject : myFileObjects) {
try {
writeToDisk(fileObject);
final File rootFile = fileObject.getOutputRoot();
if (rootFile != null) {
event.add(rootFile.getPath(), fileObject.getRelativePath());
}
}
catch (IOException e) {
myContext.processMessage(new CompilerMessage(JavaBuilder.BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage()));
}
}
}
finally {
myContext.processMessage(event);
}
}
}
finally {
myFileObjects.clear();
myCompiledClasses.clear();
}
}
public Set<File> getSuccessfullyCompiled() {
return Collections.unmodifiableSet(mySuccessfullyCompiled);
}
private void writeToDisk(@NotNull OutputFileObject fileObject) throws IOException {
final File file = fileObject.getFile();
final OutputFileObject.Content content = fileObject.getContent();
if (content == null) {
throw new IOException("Missing content for file " + file);
}
try {
_writeToFile(file, content);
}
catch (IOException e) {
// assuming the reason is non-existing parent
final File parentFile = file.getParentFile();
if (parentFile == null) {
throw e;
}
if (!parentFile.mkdirs()) {
throw e;
}
// second attempt
_writeToFile(file, content);
}
final File source = fileObject.getSourceFile();
if (source != null && !myProblematic.contains(source)) {
mySuccessfullyCompiled.add(source);
final String className = fileObject.getClassName();
if (className != null) {
myContext.processMessage(new ProgressMessage("Compiled " + className));
}
}
}
private static void _writeToFile(final File file, OutputFileObject.Content content) throws IOException {
final OutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
try {
stream.write(content.getBuffer(), content.getOffset(), content.getLength());
}
finally {
stream.close();
}
}
public void markError(OutputFileObject outputClassFile) {
final File source = outputClassFile.getSourceFile();
if (source != null) {
myProblematic.add(source);
}
}
}
@@ -35,7 +35,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
class ClassPath {
public class ClassPath {
private final Stack<URL> myUrls = new Stack<URL>();
private final ArrayList<Loader> myLoaders = new ArrayList<Loader>();
private final HashMap<URL,Loader> myLoadersMap = new HashMap<URL, Loader>();