generalized instrumentation

This commit is contained in:
Eugene Zhuravlev
2012-11-25 19:42:56 +01:00
parent 0cf0ad50dc
commit e75d0c16fe
9 changed files with 263 additions and 207 deletions
@@ -772,6 +772,12 @@ public class IncProjectBuilder {
// return true if changed something, false otherwise
private boolean runModuleLevelBuilders(final CompileContext context, final ModuleChunk chunk) throws ProjectBuildException, IOException {
for (BuilderCategory category : BuilderCategory.values()) {
for (ModuleLevelBuilder builder : myBuilderRegistry.getBuilders(category)) {
builder.chunkBuildStarted(context, chunk);
}
}
boolean doneSomething = false;
boolean rebuildFromScratchRequested = false;
float stageCount = myTotalModuleLevelBuilderCount;
@@ -878,7 +884,7 @@ public class IncProjectBuilder {
outputConsumer.clear();
for (BuilderCategory category : BuilderCategory.values()) {
for (ModuleLevelBuilder builder : myBuilderRegistry.getBuilders(category)) {
builder.cleanupChunkResources(context);
builder.chunkBuildFinished(context, chunk);
}
}
}
@@ -4,8 +4,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.BuildTargetType;
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
import org.jetbrains.jps.builders.java.ResourcesTargetType;
import org.jetbrains.jps.incremental.instrumentation.NotNullInstrumentingBuilder;
import org.jetbrains.jps.incremental.java.JavaBuilder;
import org.jetbrains.jps.incremental.java.NotNullInstrumentingBuilder;
import org.jetbrains.jps.incremental.resources.ResourcesBuilder;
import org.jetbrains.jps.service.SharedThreadPool;
@@ -57,6 +57,9 @@ public abstract class ModuleLevelBuilder extends Builder {
return myCategory;
}
public void cleanupChunkResources(CompileContext context) {
public void chunkBuildStarted(CompileContext context, ModuleChunk chunk) {
}
public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
}
}
@@ -0,0 +1,160 @@
package org.jetbrains.jps.incremental.instrumentation;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.compiler.instrumentation.InstrumenterClassWriter;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassVisitor;
import org.jetbrains.asm4.ClassWriter;
import org.jetbrains.asm4.Opcodes;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.ProjectPaths;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.incremental.*;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.javac.BinaryContent;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Eugene Zhuravlev
* Date: 11/25/12
*/
public abstract class BaseInstrumentingBuilder extends ModuleLevelBuilder{
private static final Key<InstrumentationClassFinder> CLASS_FINDER = Key.create("_cached_instrumentation_class_finder_");
public BaseInstrumentingBuilder() {
super(BuilderCategory.CLASS_INSTRUMENTER);
}
@Override
public void chunkBuildStarted(CompileContext context, ModuleChunk chunk) {
}
@Override
public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
final InstrumentationClassFinder finder = CLASS_FINDER.get(context);
CLASS_FINDER.set(context, null);
if (finder != null) {
finder.releaseResources();
}
}
@Override
public final ExitCode build(CompileContext context, ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
ExitCode exitCode = ExitCode.NOTHING_DONE;
if (outputConsumer.getCompiledClasses().isEmpty() || !isEnabled(context, chunk)) {
return exitCode;
}
InstrumentationClassFinder finder = null;
final String progress = getProgressMessage();
final boolean shouldShowProgress = !StringUtil.isEmptyOrSpaces(progress);
if (shouldShowProgress) {
context.processMessage(new ProgressMessage(progress + " [" + chunk.getName() + "]"));
}
try {
for (CompiledClass compiledClass : outputConsumer.getCompiledClasses().values()) {
final BinaryContent originalContent = compiledClass.getContent();
final ClassReader reader = new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
final int version = getClassFileVersion(reader);
if (!canInstrument(compiledClass, version)) {
continue;
}
if (finder == null) { // lazy init for this particular builder
finder = CLASS_FINDER.get(context); // try using shared finder
if (finder == null) {
final ProjectPaths paths = context.getProjectPaths();
final Collection<File> platformCp = paths.getPlatformCompilationClasspath(chunk, false);
final Collection<File> classpath = new ArrayList<File>();
classpath.addAll(paths.getCompilationClasspath(chunk, false));
classpath.addAll(ProjectPaths.getSourceRootsWithDependents(chunk).keySet());
finder = createInstrumentationClassFinder(platformCp, classpath, outputConsumer);
CLASS_FINDER.set(context, finder);
}
}
final ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
final BinaryContent instrumented = instrument(context, compiledClass, reader, writer, finder);
if (instrumented != null) {
compiledClass.setContent(instrumented);
exitCode = ExitCode.OK;
}
}
}
finally {
if (shouldShowProgress) {
context.processMessage(new ProgressMessage("")); // cleanup progress
}
}
return exitCode;
}
protected abstract boolean isEnabled(CompileContext context, ModuleChunk chunk);
protected abstract boolean canInstrument(CompiledClass compiledClass, int classFileVersion);
@Nullable
protected abstract BinaryContent instrument(CompileContext context,
CompiledClass compiled,
ClassReader reader,
ClassWriter writer,
InstrumentationClassFinder finder);
protected abstract String getProgressMessage();
// utility methods
public static InstrumentationClassFinder createInstrumentationClassFinder(Collection<File> platformCp, Collection<File> cp, final OutputConsumer outputConsumer) 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[cp.size()];
index = 0;
for (File file : cp) {
urls[index++] = file.toURI().toURL();
}
return new InstrumentationClassFinder(platformUrls, urls) {
protected InputStream lookupClassBeforeClasspath(String internalClassName) {
final BinaryContent content = outputConsumer.lookupClassBytes(internalClassName.replace("/", "."));
if (content != null) {
return new ByteArrayInputStream(content.getBuffer(), content.getOffset(), content.getLength());
}
return null;
}
};
}
public static int getAsmClassWriterFlags(int version) {
return version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS;
}
public static int getClassFileVersion(ClassReader reader) {
final Ref<Integer> result = new Ref<Integer>(0);
reader.accept(new ClassVisitor(Opcodes.ASM4) {
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
result.set(version);
}
}, 0);
return result.get();
}
}
@@ -0,0 +1,78 @@
package org.jetbrains.jps.incremental.instrumentation;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassWriter;
import org.jetbrains.asm4.Opcodes;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.cmdline.ProjectDescriptor;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.CompiledClass;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.javac.BinaryContent;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import java.io.File;
/**
* @author Eugene Zhuravlev
* Date: 11/21/12
*/
public class NotNullInstrumentingBuilder extends BaseInstrumentingBuilder{
public NotNullInstrumentingBuilder() {
}
@NotNull
@Override
public String getPresentableName() {
return "NotNull instrumentation";
}
@Override
protected String getProgressMessage() {
return "Adding @NotNull assertions...";
}
@Override
protected boolean isEnabled(CompileContext context, ModuleChunk chunk) {
final ProjectDescriptor pd = context.getProjectDescriptor();
return JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(pd.getProject()).isAddNotNullAssertions();
}
@Override
protected boolean canInstrument(CompiledClass compiledClass, int classFileVersion) {
return classFileVersion >= Opcodes.V1_5;
}
// todo: probably instrument other NotNull-like annotations defined in project settings?
@Override
@Nullable
protected BinaryContent instrument(CompileContext context,
CompiledClass compiledClass,
ClassReader reader,
ClassWriter writer,
InstrumentationClassFinder finder) {
try {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
if (instrumenter.isModification()) {
return new BinaryContent(writer.toByteArray());
}
}
catch (Throwable e) {
final StringBuilder msg = new StringBuilder();
msg.append("@NotNull instrumentation failed ");
final File sourceFile = compiledClass.getSourceFile();
msg.append(" for ").append(sourceFile.getName());
msg.append(": ").append(e.getMessage());
context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, msg.toString(), sourceFile.getPath()));
}
return null;
}
}
@@ -681,7 +681,7 @@ public class JavaBuilder extends ModuleLevelBuilder {
}
@Override
public void cleanupChunkResources(CompileContext context) {
public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
JavaBuilderUtil.cleanupChunkResources(context);
}
@@ -1,147 +0,0 @@
package org.jetbrains.jps.incremental.java;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.compiler.instrumentation.InstrumenterClassWriter;
import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
import com.intellij.openapi.util.Ref;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassVisitor;
import org.jetbrains.asm4.ClassWriter;
import org.jetbrains.asm4.Opcodes;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.ProjectPaths;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.cmdline.ProjectDescriptor;
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.ProgressMessage;
import org.jetbrains.jps.javac.BinaryContent;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Eugene Zhuravlev
* Date: 11/21/12
*/
public class NotNullInstrumentingBuilder extends ModuleLevelBuilder{
public NotNullInstrumentingBuilder() {
super(BuilderCategory.CLASS_INSTRUMENTER);
}
@NotNull
@Override
public String getPresentableName() {
return "NotNull instrumentation";
}
@Override
public ExitCode build(CompileContext context,
ModuleChunk chunk,
DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
ExitCode exitCode = ExitCode.NOTHING_DONE;
final ProjectDescriptor pd = context.getProjectDescriptor();
final boolean addNotNullAssertions = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(pd.getProject()).isAddNotNullAssertions();
if (addNotNullAssertions && !outputConsumer.getCompiledClasses().isEmpty()) {
final ProjectPaths paths = context.getProjectPaths();
final Collection<File> classpath = paths.getCompilationClasspath(chunk, false);
final Collection<File> platformCp = paths.getPlatformCompilationClasspath(chunk, false);
final InstrumentationClassFinder finder = createInstrumentationClassFinder(platformCp, classpath, outputConsumer);
try {
try {
context.processMessage(new ProgressMessage("Adding @NotNull assertions... [" + chunk.getName() + "]"));
exitCode = instrumentNotNull(context, outputConsumer, finder);
}
finally {
context.processMessage(new ProgressMessage("Finished adding @NotNull assertions [" + chunk.getName() + "]"));
}
}
finally {
finder.releaseResources();
}
}
return exitCode;
}
// todo: probably instrument other NotNull-like annotations defined in project settings?
private ExitCode instrumentNotNull(CompileContext context, OutputConsumer outputConsumer, final InstrumentationClassFinder finder) {
boolean doneSomething = false;
for (final CompiledClass compiledClass : outputConsumer.getCompiledClasses().values()) {
final BinaryContent originalContent = compiledClass.getContent();
final ClassReader reader = new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
final int version = getClassFileVersion(reader);
if (version >= Opcodes.V1_5) {
final ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
try {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
if (instrumenter.isModification()) {
compiledClass.setContent(new BinaryContent(writer.toByteArray()));
doneSomething = true;
}
}
catch (Throwable e) {
doneSomething = true;
final StringBuilder msg = new StringBuilder();
msg.append("@NotNull instrumentation failed ");
final File sourceFile = compiledClass.getSourceFile();
msg.append(" for ").append(sourceFile.getName());
msg.append(": ").append(e.getMessage());
context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, msg.toString(), sourceFile.getPath()));
}
}
}
return doneSomething? ExitCode.OK : ExitCode.NOTHING_DONE;
}
private static InstrumentationClassFinder createInstrumentationClassFinder(Collection<File> platformCp, Collection<File> classpath, final OutputConsumer outputConsumer) throws MalformedURLException {
final URL[] platformUrls = new URL[platformCp.size()];
int index = 0;
for (File file : platformCp) {
platformUrls[index++] = file.toURI().toURL();
}
final List<URL> urls = new ArrayList<URL>(classpath.size());
for (File file : classpath) {
urls.add(file.toURI().toURL());
}
return new InstrumentationClassFinder(platformUrls, urls.toArray(new URL[urls.size()])) {
protected InputStream lookupClassBeforeClasspath(String internalClassName) {
final BinaryContent content = outputConsumer.lookupClassBytes(internalClassName.replace("/", "."));
if (content != null) {
return new ByteArrayInputStream(content.getBuffer(), content.getOffset(), content.getLength());
}
return null;
}
};
}
private static int getClassFileVersion(ClassReader reader) {
final Ref<Integer> result = new Ref<Integer>(0);
reader.accept(new ClassVisitor(Opcodes.ASM4) {
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
result.set(version);
}
}, 0);
return result.get();
}
private static int getAsmClassWriterFlags(int version) {
return version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS;
}
}
@@ -252,7 +252,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
}
@Override
public void cleanupChunkResources(CompileContext context) {
public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
JavaBuilderUtil.cleanupChunkResources(context);
STUB_TO_SRC.set(context, null);
}
@@ -18,7 +18,6 @@ package org.jetbrains.jps.uiDesigner.compiler;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.compiler.instrumentation.InstrumenterClassWriter;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.uiDesigner.compiler.*;
import com.intellij.uiDesigner.compiler.Utils;
@@ -28,9 +27,6 @@ import com.intellij.uiDesigner.lw.LwRootContainer;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassVisitor;
import org.jetbrains.asm4.ClassWriter;
import org.jetbrains.asm4.Opcodes;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.ProjectPaths;
import org.jetbrains.jps.builders.DirtyFilesHolder;
@@ -38,6 +34,7 @@ import org.jetbrains.jps.builders.FileProcessor;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
import org.jetbrains.jps.incremental.*;
import org.jetbrains.jps.incremental.instrumentation.BaseInstrumentingBuilder;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
@@ -48,8 +45,6 @@ import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerConfiguration;
import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerExtensionService;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
@@ -93,10 +88,16 @@ public class FormsInstrumenter extends FormsBuilder {
try {
final ProjectPaths paths = context.getProjectPaths();
final Collection<File> classpath = paths.getCompilationClasspath(chunk, false);
final Collection<File> platformCp = paths.getPlatformCompilationClasspath(chunk, false);
final List<File> classpath = new ArrayList<File>();
classpath.addAll(paths.getCompilationClasspath(chunk, false));
classpath.add(getResourcePath(GridConstraints.class)); // forms_rt.jar
final Map<File, String> chunkSourcePath = ProjectPaths.getSourceRootsWithDependents(chunk);
final InstrumentationClassFinder finder = createInstrumentationClassFinder(platformCp, classpath, chunkSourcePath, outputConsumer);
classpath.addAll(chunkSourcePath.keySet()); // sourcepath for loading forms resources
final InstrumentationClassFinder finder =
BaseInstrumentingBuilder.createInstrumentationClassFinder(platformCp, classpath, outputConsumer);
try {
instrumentForms(context, chunk, chunkSourcePath, finder, forms, outputConsumer);
@@ -181,8 +182,8 @@ public class FormsInstrumenter extends FormsBuilder {
final ClassReader classReader =
new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
final int version = getClassFileVersion(classReader);
final InstrumenterClassWriter classWriter = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
final int version = BaseInstrumentingBuilder.getClassFileVersion(classReader);
final InstrumenterClassWriter classWriter = new InstrumenterClassWriter(BaseInstrumentingBuilder.getAsmClassWriterFlags(version), finder);
final AsmCodeGenerator codeGenerator = new AsmCodeGenerator(rootContainer, finder, nestedFormsLoader, false, classWriter);
final byte[] patchedBytes = codeGenerator.patchClass(classReader);
if (patchedBytes != null) {
@@ -241,51 +242,6 @@ public class FormsInstrumenter extends FormsBuilder {
}
}
private static int getClassFileVersion(ClassReader reader) {
final Ref<Integer> result = new Ref<Integer>(0);
reader.accept(new ClassVisitor(Opcodes.ASM4) {
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
result.set(version);
}
}, 0);
return result.get();
}
private static int getAsmClassWriterFlags(int version) {
return version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS;
}
private static InstrumentationClassFinder createInstrumentationClassFinder(
Collection<File> platformCp, Collection<File> cp, Map<File, String> sourcePath, final OutputConsumer outputConsumer
) throws MalformedURLException {
final URL[] platformUrls = new URL[platformCp.size()];
int index = 0;
for (File file : platformCp) {
platformUrls[index++] = file.toURI().toURL();
}
final List<URL> urls = new ArrayList<URL>(cp.size() + sourcePath.size() + 1);
for (File file : cp) {
urls.add(file.toURI().toURL());
}
urls.add(getResourcePath(GridConstraints.class).toURI().toURL()); // forms_rt.jar
for (File file : sourcePath.keySet()) { // sourcepath for loading forms resources
urls.add(file.toURI().toURL());
}
return new InstrumentationClassFinder(platformUrls, urls.toArray(new URL[urls.size()])) {
protected InputStream lookupClassBeforeClasspath(String internalClassName) {
final BinaryContent content = outputConsumer.lookupClassBytes(internalClassName.replace("/", "."));
if (content != null) {
return new ByteArrayInputStream(content.getBuffer(), content.getOffset(), content.getLength());
}
return null;
}
};
}
private static File getResourcePath(Class aClass) {
return new File(PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"));
}