Commit 22643e21 authored by Pavel Fatin's avatar Pavel Fatin
Browse files

SystemIndependentInstrumenter: embed assertion methods

parent 9583f309
Showing with 364 additions and 17 deletions
+364 -17
......@@ -26,6 +26,7 @@
<module fileurl="file://$PROJECT_DIR$/android/rt/android-rt.iml" filepath="$PROJECT_DIR$/android/rt/android-rt.iml" group="android" />
<module fileurl="file://$PROJECT_DIR$/platform/annotations/annotations.iml" filepath="$PROJECT_DIR$/platform/annotations/annotations.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/platform/annotations/common/annotations-common.iml" filepath="$PROJECT_DIR$/platform/annotations/common/annotations-common.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/platform/annotations/internal/annotations-internal.iml" filepath="$PROJECT_DIR$/platform/annotations/internal/annotations-internal.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/platform/annotations/java8/annotations-java8.iml" filepath="$PROJECT_DIR$/platform/annotations/java8/annotations-java8.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/plugins/ant/ant.iml" filepath="$PROJECT_DIR$/plugins/ant/ant.iml" group="plugins" />
<module fileurl="file://$PROJECT_DIR$/plugins/ant/jps-plugin/ant-jps-plugin.iml" filepath="$PROJECT_DIR$/plugins/ant/jps-plugin/ant-jps-plugin.iml" group="plugins" />
......
......@@ -24,9 +24,9 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.PlatformProjectOpenProcessor;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemIndependent;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.SystemIndependent;
import java.io.File;
......
......@@ -22,8 +22,8 @@ import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
import org.jetbrains.jps.builders.java.ResourcesTargetType;
import org.jetbrains.jps.incremental.dependencies.DependencyResolvingBuilder;
import org.jetbrains.jps.incremental.instrumentation.NotNullInstrumentingBuilder;
import org.jetbrains.jps.incremental.instrumentation.SystemIndependentInstrumentingBuilder;
import org.jetbrains.jps.incremental.instrumentation.RmiStubsGenerator;
import org.jetbrains.jps.incremental.instrumentation.internal.SystemIndependentInstrumentingBuilder;
import org.jetbrains.jps.incremental.java.JavaBuilder;
import org.jetbrains.jps.incremental.resources.ResourcesBuilder;
import org.jetbrains.jps.service.SharedThreadPool;
......
/*
* Copyright 2000-2017 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 org.jetbrains.jps.incremental.instrumentation.internal;
import com.intellij.openapi.diagnostic.Logger;
/*
A template for the synthetic method embedded by the bytecode instrumentation.
*/
class AssertionMethodImpl {
private static void assertArgumentIsSystemIndependent(String className, String methodName, String parameterName, String argument) {
if (argument != null && argument.contains("\\")) {
String message = String.format("Argument for @SystemIndependent parameter '%s' of %s.%s must be system-independent: %s",
parameterName, className, methodName, argument);
IllegalArgumentException exception = new IllegalArgumentException(message);
StackTraceElement[] stackTrace = new StackTraceElement[exception.getStackTrace().length - 1];
System.arraycopy(exception.getStackTrace(), 1, stackTrace, 0, stackTrace.length);
exception.setStackTrace(stackTrace);
Logger.getInstance("#org.jetbrains.jps.incremental.instrumentation.AssertionMethodImpl").error(exception);
}
}
}
/*
* Copyright 2000-2017 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 org.jetbrains.jps.incremental.instrumentation.internal;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.ClassVisitor;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import java.io.IOException;
import java.io.InputStream;
class MethodTemplate {
private final byte[] myBytecode;
MethodTemplate(Class aClass) {
try {
myBytecode = readBytecodeOf(aClass);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private static byte[] readBytecodeOf(Class aClass) throws IOException {
InputStream stream = aClass.getResourceAsStream(aClass.getSimpleName() + ".class");
if (stream == null) {
throw new IllegalArgumentException("Cannot read class bytecode: " + aClass);
}
try {
return FileUtil.loadBytes(stream);
}
finally {
stream.close();
}
}
public void write(ClassVisitor writer, int accessModifier, String methodName) {
ClassReader reader = new ClassReader(myBytecode);
reader.accept(new ClassVisitor(Opcodes.API_VERSION) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return "<init>".equals(name) ? null : writer.visitMethod(access | accessModifier, methodName, desc, signature, exceptions);
}
}, 0);
}
}
/*
* Copyright 2000-2017 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 org.jetbrains.jps.incremental.instrumentation.internal;
import org.jetbrains.org.objectweb.asm.*;
import sun.management.counter.perf.InstrumentationException;
class SystemIndependentInstrumenter extends ClassVisitor {
static final String ANNOTATION = "@SystemIndependent";
static final String ANNOTATION_CLASS = "org/jetbrains/annotations/SystemIndependent";
static final String ASSERTION_SIGNATURE = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
private final String myAssertionMethodName;
private String myClassName;
private boolean myAssertionAdded;
SystemIndependentInstrumenter(ClassWriter writer, String assertionMethodName) {
super(Opcodes.API_VERSION, writer);
myAssertionMethodName = assertionMethodName;
}
public boolean isAssertionAdded() {
return myAssertionAdded;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
myClassName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
Type[] argumentTypes = Type.getArgumentTypes(desc);
Parameter[] parameters = new Parameter[argumentTypes.length];
int slot = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
for (int i = 0; i < argumentTypes.length; i++) {
Type argumentType = argumentTypes[i];
Parameter parameter = new Parameter();
parameter.name = Integer.toString(i);
parameter.slot = slot;
parameter.isString = argumentType.getSort() == Type.OBJECT && "java.lang.String".equals(argumentType.getClassName());
parameters[i] = parameter;
slot += argumentType.getSize();
}
return new MethodVisitor(api, super.visitMethod(access, name, desc, signature, exceptions)) {
private int myParameterIndex = 0;
@Override
public void visitParameter(String name, int access) {
parameters[myParameterIndex].name = name;
myParameterIndex++;
super.visitParameter(name, access);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
if (typePath == null) {
TypeReference ref = new TypeReference(typeRef);
if (ref.getSort() == TypeReference.METHOD_FORMAL_PARAMETER && ("L" + ANNOTATION_CLASS + ";").equals(desc)) {
parameters[ref.getFormalParameterIndex()].isSystemIndependent = true;
}
}
return super.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
@Override
public void visitCode() {
super.visitCode();
for (Parameter parameter : parameters) {
if (parameter.isSystemIndependent) {
if (!parameter.isString) {
throw new InstrumentationException("Only String can be annotated as " + ANNOTATION);
}
addAssertionFor(parameter.name, parameter.slot);
myAssertionAdded = true;
}
}
}
private void addAssertionFor(String parameterName, int parameterSlot) {
visitLdcInsn(myClassName);
visitLdcInsn(name);
visitLdcInsn(parameterName);
visitVarInsn(Opcodes.ALOAD, parameterSlot);
visitMethodInsn(Opcodes.INVOKESTATIC, myClassName, myAssertionMethodName, ASSERTION_SIGNATURE, false);
}
};
}
private static class Parameter {
String name;
int slot;
boolean isString;
boolean isSystemIndependent;
}
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jps.incremental.instrumentation;
package org.jetbrains.jps.incremental.instrumentation.internal;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import org.jetbrains.annotations.NotNull;
......@@ -22,12 +22,18 @@ import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.incremental.BinaryContent;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.CompiledClass;
import org.jetbrains.jps.incremental.instrumentation.BaseInstrumentingBuilder;
import org.jetbrains.jps.incremental.instrumentation.NotNullInstrumentingBuilder;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.org.objectweb.asm.*;
import sun.management.counter.perf.InstrumentationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.jetbrains.org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
/**
* Adds assertions for method / constructor parameters that are annotated as <code>@SystemDependent</code>.
......@@ -37,12 +43,7 @@ import java.io.IOException;
* TODO Add other kind of checks (method return, etc).
*/
public class SystemIndependentInstrumentingBuilder extends BaseInstrumentingBuilder {
private static final String ANNOTATION = "@SystemIndependent";
private static final String ANNOTATION_CLASS = "com/intellij/util/SystemIndependent";
private static final String ASSERTION_CLASS = "com/intellij/util/PathUtil";
private static final String ASSERTION_METHOD = "assertArgumentIsSystemIndependent";
private static final String ASSERTION_SIGNATURE = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
private MethodTemplate myAssertionMethodTemplate = new MethodTemplate(AssertionMethodImpl.class);
@NotNull
@Override
......@@ -62,7 +63,7 @@ public class SystemIndependentInstrumentingBuilder extends BaseInstrumentingBuil
@Override
protected boolean isEnabled(CompileContext context, InstrumentationClassFinder finder) throws IOException {
return finder.isAvaiable(ANNOTATION_CLASS + ".class");
return finder.isAvaiable(SystemIndependentInstrumenter.ANNOTATION_CLASS + ".class");
}
@Override
......@@ -77,9 +78,14 @@ public class SystemIndependentInstrumentingBuilder extends BaseInstrumentingBuil
ClassReader reader,
ClassWriter writer,
InstrumentationClassFinder finder) {
String assertionMethodName = unique("$$$assertArgumentIsSystemIndependent$$$", methodNamesFrom(reader));
try {
reader.accept(new MyClassVisitor(writer), 0);
return new BinaryContent(writer.toByteArray());
SystemIndependentInstrumenter instrumenter = new SystemIndependentInstrumenter(writer, assertionMethodName);
reader.accept(instrumenter, 0);
if (instrumenter.isAssertionAdded()) {
myAssertionMethodTemplate.write(writer, ACC_SYNTHETIC, assertionMethodName);
return new BinaryContent(writer.toByteArray());
}
}
catch (InstrumentationException e) {
context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, e.getMessage()));
......@@ -87,90 +93,27 @@ public class SystemIndependentInstrumentingBuilder extends BaseInstrumentingBuil
return null;
}
private static List<String> methodNamesFrom(ClassReader reader) {
List<String> myNames = new ArrayList<>();
private static class MyClassVisitor extends ClassVisitor {
private String myClassName;
MyClassVisitor(ClassWriter writer) {
super(Opcodes.API_VERSION, writer);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
myClassName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
Type[] argumentTypes = Type.getArgumentTypes(desc);
Parameter[] parameters = new Parameter[argumentTypes.length];
int slot = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
for (int i = 0; i < argumentTypes.length; i++) {
Type argumentType = argumentTypes[i];
Parameter parameter = new Parameter();
parameter.name = Integer.toString(i);
parameter.slot = slot;
parameter.isString = argumentType.getSort() == Type.OBJECT && "java.lang.String".equals(argumentType.getClassName());
parameters[i] = parameter;
slot += argumentType.getSize();
reader.accept(new ClassVisitor(Opcodes.API_VERSION) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
myNames.add(name);
return null;
}
}, 0);
return new MethodVisitor(api, super.visitMethod(access, name, desc, signature, exceptions)) {
private int myParameterIndex = 0;
@Override
public void visitParameter(String name, int access) {
parameters[myParameterIndex].name = name;
myParameterIndex++;
super.visitParameter(name, access);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
if (typePath == null) {
TypeReference ref = new TypeReference(typeRef);
if (ref.getSort() == TypeReference.METHOD_FORMAL_PARAMETER && ("L" + ANNOTATION_CLASS + ";").equals(desc)) {
parameters[ref.getFormalParameterIndex()].isSystemIndependent = true;
}
}
return super.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
@Override
public void visitCode() {
super.visitCode();
for (Parameter parameter : parameters) {
if (parameter.isSystemIndependent) {
if (!parameter.isString) {
throw new InstrumentationException("Only String can be annotated as " + ANNOTATION);
}
addAssertionFor(parameter.name, parameter.slot);
}
}
}
private void addAssertionFor(String parameterName, int parameterSlot) {
visitLdcInsn(myClassName);
visitLdcInsn(name);
visitLdcInsn(parameterName);
visitVarInsn(Opcodes.ALOAD, parameterSlot);
visitMethodInsn(Opcodes.INVOKESTATIC, ASSERTION_CLASS, ASSERTION_METHOD, ASSERTION_SIGNATURE, false);
}
};
}
return myNames;
}
private static class Parameter {
String name;
int slot;
boolean isString;
boolean isSystemIndependent;
private static String unique(String string, List<String> strings) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String name = i == 0 ? string : string + i;
if (!strings.contains(name)) {
return name;
}
}
throw new RuntimeException("Cannot make unique: " + string);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module 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" />
</component>
</module>
\ No newline at end of file
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util;
package org.jetbrains.annotations;
import java.lang.annotation.*;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util;
package org.jetbrains.annotations;
import java.lang.annotation.*;
......@@ -24,7 +24,6 @@ import java.lang.annotation.*;
*
* @see PathUtil#toSystemIndependentName(String)
* @see PathUtil#toSystemDependentName(String)
* @see PathUtil#assertArgumentIsSystemIndependent
*/
@Documented
@Retention(RetentionPolicy.CLASS)
......
......@@ -21,10 +21,10 @@ import com.intellij.openapi.extensions.AreaInstance;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.SystemIndependent;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
/**
* Represents a module in an IDEA project.
......
......@@ -18,10 +18,10 @@ package com.intellij.openapi.project;
import com.intellij.openapi.components.ComponentManager;
import com.intellij.openapi.extensions.AreaInstance;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemDependent;
import com.intellij.util.SystemIndependent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemDependent;
import org.jetbrains.annotations.SystemIndependent;
/**
* An object representing an IntelliJ project.
......
......@@ -133,7 +133,7 @@ public class PathUtil {
* <p>
* The violations are reported via the <code>LOG.error</code>.
* <p>
* This method is used by <code>SystemIndependentInstrumentingBuilder</code>.
* TODO SystemIndependentInstrumentingBuilder now embeds assertions directly, so we can remove this method.
*
* @param className Class name
* @param methodName Method name
......
......@@ -25,11 +25,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemDependent;
import com.intellij.util.SystemIndependent;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
import org.picocontainer.PicoContainer;
/**
......
......@@ -16,10 +16,9 @@
package com.intellij.ide;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemIndependent;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.SystemIndependent;
import java.io.File;
import java.util.ArrayList;
......
......@@ -17,7 +17,7 @@ package com.intellij.ide;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.util.SystemIndependent;
import org.jetbrains.annotations.SystemIndependent;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
......
......@@ -23,10 +23,10 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.PlatformProjectOpenProcessor;
import com.intellij.platform.ProjectBaseDirectory;
import com.intellij.util.SystemIndependent;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
import java.util.EnumSet;
......
......@@ -48,6 +48,7 @@ import gnu.trove.THashSet;
import org.imgscalr.Scalr;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
import javax.swing.*;
import java.awt.*;
......
......@@ -25,7 +25,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.BitUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemIndependent;
import org.jetbrains.annotations.SystemIndependent;
import java.awt.event.InputEvent;
import java.io.File;
......
......@@ -22,10 +22,10 @@ import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SystemIndependent;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
import org.picocontainer.PicoContainer;
/**
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment