Merge pull request #530 from yole/coverage

Initial implementation of coverage support in IntelliJ IDEA plugin.
This commit is contained in:
Natalia Ukhorskaya
2014-12-10 12:35:10 +03:00
12 changed files with 610 additions and 441 deletions
+11
View File
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="coverage-plugin">
<CLASSES>
<root url="jar://$PROJECT_DIR$/ideaSDK/plugins/coverage/lib/coverage.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$PROJECT_DIR$/ideaSDK/sources/sources.zip!/plugins/coverage/src" />
</SOURCES>
</library>
</component>
@@ -133,6 +133,7 @@ import org.jetbrains.jet.checkers.AbstractJetDiagnosticsTestWithJsStdLib
import org.jetbrains.jet.renderer.AbstractDescriptorRendererTest
import org.jetbrains.jet.types.AbstractJetTypeBindingTest
import org.jetbrains.jet.plugin.debugger.evaluate.AbstractCodeFragmentCompletionHandlerTest
import org.jetbrains.jet.plugin.coverage.AbstractKotlinCoverageOutputFilesTest
fun main(args: Array<String>) {
System.setProperty("java.awt.headless", "true")
@@ -638,6 +639,10 @@ fun main(args: Array<String>) {
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
model("debugger/selectExpression")
}
testClass(javaClass<AbstractKotlinCoverageOutputFilesTest>()) {
model("coverage/outputFiles")
}
}
testGroup("idea/tests", "compiler/testData") {
+1
View File
@@ -44,5 +44,6 @@
<orderEntry type="module" module-name="idea-analysis" exported="" />
<orderEntry type="module" module-name="kotlin-android-plugin" />
<orderEntry type="module" module-name="js.frontend" />
<orderEntry type="library" scope="PROVIDED" name="coverage-plugin" level="project" />
</component>
</module>
+5
View File
@@ -0,0 +1,5 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<javaCoverageEngineExtension implementation="org.jetbrains.jet.plugin.coverage.KotlinCoverageExtension"/>
</extensions>
</idea-plugin>
+1
View File
@@ -15,6 +15,7 @@
<depends optional="true" config-file="kotlin-copyright.xml">com.intellij.copyright</depends>
<depends optional="true" config-file="javaScriptDebug.xml">JavaScriptDebugger</depends>
<depends optional="true" config-file="android.xml">org.jetbrains.android</depends>
<depends optional="true" config-file="coverage.xml">Coverage</depends>
<project-components>
<component>
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2014 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.jet.plugin.coverage
import com.intellij.coverage.JavaCoverageEngineExtension
import com.intellij.execution.configurations.RunConfigurationBase
import org.jetbrains.jet.plugin.run.JetRunConfiguration
import com.intellij.psi.PsiFile
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.coverage.CoverageSuitesBundle
import java.io.File
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.plugin.debugger.JetPositionManager
import org.jetbrains.jet.plugin.caches.resolve.getModuleInfo
import java.util.HashSet
import org.jetbrains.jet.lang.psi.JetTreeVisitor
import org.jetbrains.jet.lang.psi.JetDeclaration
import com.intellij.openapi.roots.ProjectRootManager
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.annotations.TestOnly
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression
import com.intellij.psi.PsiElement
import java.util.LinkedHashSet
public class KotlinCoverageExtension(): JavaCoverageEngineExtension() {
override fun isApplicableTo(conf: RunConfigurationBase?): Boolean = conf is JetRunConfiguration
override fun collectOutputFiles(srcFile: PsiFile,
output: VirtualFile?,
testoutput: VirtualFile?,
suite: CoverageSuitesBundle,
classFiles: MutableSet<File>): Boolean {
if (srcFile is JetFile) {
val fileIndex = ProjectRootManager.getInstance(srcFile.getProject()).getFileIndex()
if (fileIndex.isInLibraryClasses(srcFile.getVirtualFile()) ||
fileIndex.isInLibrarySource(srcFile.getVirtualFile())) {
return false
}
val classNames = ApplicationManager.getApplication().runReadAction<Set<String>> { collectOutputClassNames(srcFile) }
fun findUnder(name: String, root: VirtualFile?): File? {
if (root != null) {
val outputFile = File(root.getPath(), name + ".class")
if (outputFile.exists()) {
return outputFile
}
}
return null
}
classNames.map { findUnder(it, output) ?: findUnder(it, testoutput) }.filterNotNullTo(classFiles)
return true
}
return false
}
class object {
public fun collectOutputClassNames(srcFile: JetFile): Set<String> {
val typeMapper = JetPositionManager.createTypeMapper(srcFile, srcFile.getModuleInfo())
val classNames = LinkedHashSet<String>()
srcFile.acceptChildren(object: JetTreeVisitorVoid() {
override fun visitDeclaration(dcl: JetDeclaration) {
super.visitDeclaration(dcl)
collectClassName(dcl.getFirstChild())
}
override fun visitFunctionLiteralExpression(expression: JetFunctionLiteralExpression) {
super.visitFunctionLiteralExpression(expression)
collectClassName(expression.getFunctionLiteral().getFirstChild())
}
private fun collectClassName(element: PsiElement) {
val className = JetPositionManager.getClassNameForElement(element, typeMapper, srcFile, false)
if (className != null) {
classNames.add(className)
}
}
})
return classNames
}
}
}
@@ -1,441 +0,0 @@
/*
* Copyright 2010-2013 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.jet.plugin.debugger;
import com.intellij.debugger.NoDataException;
import com.intellij.debugger.PositionManager;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.requests.ClassPrepareRequestor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.LibraryUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.request.ClassPrepareRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.jet.analyzer.AnalysisResult;
import org.jetbrains.jet.codegen.AsmUtil;
import org.jetbrains.jet.codegen.ClassBuilderFactories;
import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.codegen.state.JetTypeMapper;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.jet.lang.resolve.extension.InlineAnalyzerExtension;
import org.jetbrains.jet.lang.resolve.java.JvmClassName;
import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.lang.types.lang.InlineStrategy;
import org.jetbrains.jet.lang.types.lang.InlineUtil;
import org.jetbrains.jet.plugin.caches.resolve.IdeaModuleInfo;
import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheService;
import org.jetbrains.jet.plugin.caches.resolve.ResolvePackage;
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils;
import org.jetbrains.jet.plugin.util.DebuggerUtils;
import org.jetbrains.org.objectweb.asm.Type;
import java.util.*;
import static org.jetbrains.jet.codegen.binding.CodegenBinding.asmTypeForAnonymousClass;
import static org.jetbrains.jet.plugin.stubindex.PackageIndexUtil.findFilesWithExactPackage;
public class JetPositionManager implements PositionManager {
private final DebugProcess myDebugProcess;
private final WeakHashMap<Pair<FqName, IdeaModuleInfo>, CachedValue<JetTypeMapper>> myTypeMappers = new WeakHashMap<Pair<FqName, IdeaModuleInfo>, CachedValue<JetTypeMapper>>();
public JetPositionManager(DebugProcess debugProcess) {
myDebugProcess = debugProcess;
}
@Override
@Nullable
public SourcePosition getSourcePosition(@Nullable Location location) throws NoDataException {
if (location == null) {
throw new NoDataException();
}
PsiFile psiFile = getPsiFileByLocation(location);
if (psiFile == null) {
throw new NoDataException();
}
int lineNumber;
try {
lineNumber = location.lineNumber() - 1;
}
catch (InternalError e) {
lineNumber = -1;
}
if (lineNumber >= 0) {
JetFunctionLiteral lambdaIfInside = getLambdaIfInside(location, (JetFile) psiFile, lineNumber);
if (lambdaIfInside != null) {
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression().getStatements().get(0));
}
return SourcePosition.createFromLine(psiFile, lineNumber);
}
throw new NoDataException();
}
private JetFunctionLiteral getLambdaIfInside(@NotNull Location location, @NotNull JetFile file, int lineNumber) {
String currentLocationFqName = location.declaringType().name();
if (currentLocationFqName == null) return null;
Integer start = CodeInsightUtils.getStartLineOffset(file, lineNumber);
Integer end = CodeInsightUtils.getEndLineOffset(file, lineNumber);
if (start == null || end == null) return null;
PsiElement[] literals = CodeInsightUtils.findElementsOfClassInRange(file, start, end, JetFunctionLiteral.class);
if (literals == null || literals.length == 0) return null;
boolean isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null;
JetTypeMapper typeMapper = !isInLibrary
? prepareTypeMapper(file)
: createTypeMapperForLibraryFile(file.findElementAt(start), file);
String currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(new FqName(currentLocationFqName)).getInternalName();
for (PsiElement literal : literals) {
JetFunctionLiteral functionLiteral = (JetFunctionLiteral) literal;
if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) {
continue;
}
String internalClassName = getClassNameForElement(literal.getFirstChild(), typeMapper, file, isInLibrary);
if (internalClassName.equals(currentLocationClassName)) {
return functionLiteral;
}
}
return null;
}
@Nullable
private PsiFile getPsiFileByLocation(@NotNull Location location) {
String sourceName;
try {
sourceName = location.sourceName();
}
catch (AbsentInformationException e) {
return null;
}
// JDI names are of form "package.Class$InnerClass"
String referenceFqName = location.declaringType().name();
String referenceInternalName = referenceFqName.replace('.', '/');
JvmClassName className = JvmClassName.byInternalName(referenceInternalName);
Project project = myDebugProcess.getProject();
if (DumbService.getInstance(project).isDumb()) return null;
return DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), className, sourceName, location.lineNumber() - 1);
}
@NotNull
@Override
public List<ReferenceType> getAllClasses(SourcePosition sourcePosition) throws NoDataException {
if (!(sourcePosition.getFile() instanceof JetFile)) {
throw new NoDataException();
}
String name = classNameForPosition(sourcePosition);
List<ReferenceType> result = new ArrayList<ReferenceType>();
if (name != null) {
result.addAll(myDebugProcess.getVirtualMachineProxy().classesByName(name));
}
return result;
}
@Nullable
private String classNameForPosition(final SourcePosition sourcePosition) {
final Ref<String> result = Ref.create();
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
@SuppressWarnings("unchecked")
public void run() {
JetFile file = (JetFile) sourcePosition.getFile();
boolean isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null;
JetTypeMapper typeMapper = !isInLibrary ? prepareTypeMapper(file) : createTypeMapperForLibraryFile(sourcePosition.getElementAt(), file);
result.set(getClassNameForElement(sourcePosition.getElementAt(), typeMapper, file, isInLibrary));
}
});
return result.get();
}
@SuppressWarnings("unchecked")
public static String getClassNameForElement(
@Nullable PsiElement notPositionedElement,
@NotNull JetTypeMapper typeMapper,
@NotNull JetFile file,
boolean isInLibrary
) {
PsiElement element = getElementToCalculateClassName(notPositionedElement);
if (element instanceof JetClassOrObject) {
return getJvmInternalNameForImpl(typeMapper, (JetClassOrObject) element);
}
else if (element instanceof JetFunctionLiteral) {
if (isInlinedLambda((JetFunctionLiteral) element, typeMapper.getBindingContext())) {
return getClassNameForElement(element.getParent(), typeMapper, file, isInLibrary);
} else {
Type asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), ((JetFunctionLiteral) element));
return asmType.getInternalName();
}
}
else if (element instanceof JetClassInitializer) {
PsiElement parent = getElementToCalculateClassName(element.getParent());
// Class-object initializer
if (parent instanceof JetObjectDeclaration && ((JetObjectDeclaration) parent).isClassObject()) {
return getClassNameForElement(parent.getParent(), typeMapper, file, isInLibrary);
}
return getClassNameForElement(element, typeMapper, file, isInLibrary);
}
else if (element instanceof JetProperty && (!((JetProperty) element).isTopLevel() || !isInLibrary)) {
if (isInPropertyAccessor(notPositionedElement)) {
JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(element, JetClassOrObject.class);
if (classOrObject != null) {
return getJvmInternalNameForImpl(typeMapper, classOrObject);
}
}
VariableDescriptor descriptor = (VariableDescriptor) typeMapper.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, element);
if (!(descriptor instanceof PropertyDescriptor)) {
return getClassNameForElement(element.getParent(), typeMapper, file, isInLibrary);
}
return getJvmInternalNameForPropertyOwner(typeMapper, (PropertyDescriptor) descriptor);
}
else if (element instanceof JetNamedFunction) {
PsiElement parent = getElementToCalculateClassName(element);
if (parent instanceof JetClassOrObject) {
return getJvmInternalNameForImpl(typeMapper, (JetClassOrObject) parent);
}
else if (parent != null) {
Type asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), (JetElement) element);
return asmType.getInternalName();
}
}
if (isInLibrary) {
JetElement elementAtForLibraryFile = getElementToCreateTypeMapperForLibraryFile(notPositionedElement);
assert elementAtForLibraryFile != null : "Couldn't find element at breakpoint for library file " + file.getName()
+ (notPositionedElement == null ? "" : ", notPositionedElement = " + JetPsiUtil.getElementTextWithContext( (JetElement) notPositionedElement));
return DebuggerPackage.findPackagePartInternalNameForLibraryFile(elementAtForLibraryFile);
}
return PackagePartClassUtils.getPackagePartInternalName(file);
}
@Nullable
private static JetDeclaration getElementToCalculateClassName(@Nullable PsiElement notPositionedElement) {
//noinspection unchecked
return PsiTreeUtil.getParentOfType(notPositionedElement,
JetClassOrObject.class,
JetFunctionLiteral.class, JetNamedFunction.class,
JetProperty.class,
JetClassInitializer.class);
}
@NotNull
public static String getJvmInternalNameForPropertyOwner(@NotNull JetTypeMapper typeMapper, @NotNull PropertyDescriptor descriptor) {
return typeMapper.mapOwner(
AsmUtil.isPropertyWithBackingFieldInOuterClass(descriptor) ? descriptor.getContainingDeclaration() : descriptor,
true)
.getInternalName();
}
private static boolean isInPropertyAccessor(@Nullable PsiElement element) {
//noinspection unchecked
return element instanceof JetPropertyAccessor || ((JetElement) PsiTreeUtil.getParentOfType(element, JetProperty.class, JetPropertyAccessor.class)) instanceof JetPropertyAccessor;
}
@Nullable
private static JetElement getElementToCreateTypeMapperForLibraryFile(@Nullable PsiElement element) {
return element instanceof JetElement ? (JetElement) element : PsiTreeUtil.getParentOfType(element, JetElement.class);
}
@Nullable
private static String getJvmInternalNameForImpl(JetTypeMapper typeMapper, JetClassOrObject jetClass) {
ClassDescriptor classDescriptor = typeMapper.getBindingContext().get(BindingContext.CLASS, jetClass);
if (classDescriptor == null) {
return null;
}
if (jetClass instanceof JetClass && ((JetClass) jetClass).isTrait()) {
return typeMapper.mapTraitImpl(classDescriptor).getInternalName();
}
return typeMapper.mapClass(classDescriptor).getInternalName();
}
private static JetTypeMapper createTypeMapperForLibraryFile(@Nullable PsiElement notPositionedElement, @NotNull JetFile file) {
JetElement element = getElementToCreateTypeMapperForLibraryFile(notPositionedElement);
AnalysisResult analysisResult = ResolvePackage.analyzeAndGetResult(element);
GenerationState state = new GenerationState(file.getProject(), ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.getModuleDescriptor(),
analysisResult.getBindingContext(),
Collections.singletonList(file)
);
state.beforeCompile();
return state.getTypeMapper();
}
private JetTypeMapper prepareTypeMapper(final JetFile file) {
final Pair<FqName, IdeaModuleInfo> key = createKeyForTypeMapper(file);
CachedValue<JetTypeMapper> value = myTypeMappers.get(key);
if(value == null) {
value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<JetTypeMapper>() {
@Override
public Result<JetTypeMapper> compute() {
Project project = file.getProject();
GlobalSearchScope packageFacadeScope = key.second.contentScope();
Collection<JetFile> packageFiles = findFilesWithExactPackage(key.first, packageFacadeScope, project);
AnalysisResult analysisResult = KotlinCacheService.OBJECT$.getInstance(project).getAnalysisResults(packageFiles);
analysisResult.throwIfError();
GenerationState state = new GenerationState(project, ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.getModuleDescriptor(), analysisResult.getBindingContext(),
new ArrayList<JetFile>(packageFiles)
);
state.beforeCompile();
return new Result<JetTypeMapper>(state.getTypeMapper(), PsiModificationTracker.MODIFICATION_COUNT);
}
}, false);
myTypeMappers.put(key, value);
}
return value.getValue();
}
@NotNull
@Override
public List<Location> locationsOfLine(ReferenceType type, SourcePosition position) throws NoDataException {
if (!(position.getFile() instanceof JetFile)) {
throw new NoDataException();
}
try {
int line = position.getLine() + 1;
List<Location> locations = myDebugProcess.getVirtualMachineProxy().versionHigher("1.4")
? type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line)
: type.locationsOfLine(line);
if (locations == null || locations.isEmpty()) throw new NoDataException();
return locations;
}
catch (AbsentInformationException e) {
throw new NoDataException();
}
}
@Override
public ClassPrepareRequest createPrepareRequest(ClassPrepareRequestor classPrepareRequestor,
SourcePosition sourcePosition) throws NoDataException {
if (!(sourcePosition.getFile() instanceof JetFile)) {
throw new NoDataException();
}
String className = classNameForPosition(sourcePosition);
if (className == null) {
return null;
}
return myDebugProcess.getRequestsManager().createClassPrepareRequest(classPrepareRequestor, className.replace('/', '.'));
}
@TestOnly
public void addTypeMapper(JetFile file, final JetTypeMapper typeMapper) {
CachedValue<JetTypeMapper> value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<JetTypeMapper>() {
@Override
public Result<JetTypeMapper> compute() {
return new Result<JetTypeMapper>(typeMapper, PsiModificationTracker.MODIFICATION_COUNT);
}
}, false);
Pair<FqName, IdeaModuleInfo> key = createKeyForTypeMapper(file);
myTypeMappers.put(key, value);
}
public static boolean isInlinedLambda(@NotNull JetFunctionLiteral functionLiteral, @NotNull BindingContext context) {
PsiElement functionLiteralExpression = functionLiteral.getParent();
if (functionLiteralExpression == null) return false;
PsiElement parent = functionLiteralExpression.getParent();
PsiElement valueArgument = functionLiteralExpression;
while (parent instanceof JetParenthesizedExpression ||
parent instanceof JetBinaryExpressionWithTypeRHS ||
parent instanceof JetLabeledExpression) {
valueArgument = parent;
parent = parent.getParent();
}
while (parent instanceof ValueArgument ||
parent instanceof JetValueArgumentList) {
parent = parent.getParent();
}
if (!(parent instanceof JetElement)) return false;
ResolvedCall<?> call = CallUtilPackage.getResolvedCall((JetElement) parent, context);
if (call == null) return false;
InlineStrategy inlineType = InlineUtil.getInlineType(call.getResultingDescriptor());
if (!inlineType.isInline()) return false;
for (Map.Entry<ValueParameterDescriptor, ResolvedValueArgument> entry : call.getValueArguments().entrySet()) {
ValueParameterDescriptor valueParameterDescriptor = entry.getKey();
ResolvedValueArgument resolvedValueArgument = entry.getValue();
for (ValueArgument next : resolvedValueArgument.getArguments()) {
JetExpression expression = next.getArgumentExpression();
if (valueArgument == expression) {
return InlineAnalyzerExtension.checkInlinableParameter(
valueParameterDescriptor, expression,
call.getResultingDescriptor(), null
);
}
}
}
return false;
}
private static Pair<FqName, IdeaModuleInfo> createKeyForTypeMapper(@NotNull JetFile file) {
return new Pair<FqName, IdeaModuleInfo>(file.getPackageFqName(), ResolvePackage.getModuleInfo(file));
}
}
@@ -0,0 +1,391 @@
/*
* Copyright 2010-2014 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.jet.plugin.debugger
import com.intellij.debugger.NoDataException
import com.intellij.debugger.PositionManager
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.*
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import com.sun.jdi.request.ClassPrepareRequest
import org.jetbrains.annotations.TestOnly
import org.jetbrains.jet.analyzer.AnalysisResult
import org.jetbrains.jet.codegen.AsmUtil
import org.jetbrains.jet.codegen.ClassBuilderFactories
import org.jetbrains.jet.codegen.state.GenerationState
import org.jetbrains.jet.codegen.state.JetTypeMapper
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.resolve.calls.callUtil.*
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.jet.lang.resolve.extension.InlineAnalyzerExtension
import org.jetbrains.jet.lang.resolve.java.JvmClassName
import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.types.lang.InlineStrategy
import org.jetbrains.jet.lang.types.lang.InlineUtil
import org.jetbrains.jet.plugin.caches.resolve.IdeaModuleInfo
import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheService
import org.jetbrains.jet.plugin.caches.resolve.*
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils
import org.jetbrains.jet.plugin.util.DebuggerUtils
import org.jetbrains.org.objectweb.asm.Type
import java.util.*
import org.jetbrains.jet.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
import org.jetbrains.jet.plugin.stubindex.PackageIndexUtil
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import com.intellij.psi.stubs.StubElement
import org.jetbrains.jet.plugin.util.application.runReadAction
public class JetPositionManager(private val myDebugProcess: DebugProcess) : PositionManager {
private val myTypeMappers = WeakHashMap<Pair<FqName, IdeaModuleInfo>, CachedValue<JetTypeMapper>>()
override fun getSourcePosition(location: Location?): SourcePosition? {
if (location == null) {
throw NoDataException()
}
val psiFile = getPsiFileByLocation(location)
if (psiFile == null) {
throw NoDataException()
}
val lineNumber = try {
location.lineNumber() - 1
}
catch (e: InternalError) {
-1
}
if (lineNumber >= 0) {
val lambdaIfInside = getLambdaIfInside(location, psiFile as JetFile, lineNumber)
if (lambdaIfInside != null) {
return SourcePosition.createFromElement(lambdaIfInside.getBodyExpression().getStatements().get(0))
}
return SourcePosition.createFromLine(psiFile, lineNumber)
}
throw NoDataException()
}
private fun getLambdaIfInside(location: Location, file: JetFile, lineNumber: Int): JetFunctionLiteral? {
val currentLocationFqName = location.declaringType().name()
if (currentLocationFqName == null) return null
val start = CodeInsightUtils.getStartLineOffset(file, lineNumber)
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
if (start == null || end == null) return null
val literals = CodeInsightUtils.findElementsOfClassInRange(file, start, end, javaClass<JetFunctionLiteral>())
if (literals == null || literals.size() == 0) return null
val isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null
val typeMapper = if (!isInLibrary)
prepareTypeMapper(file)
else
createTypeMapperForLibraryFile(file.findElementAt(start), file)
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).getInternalName()
for (literal in literals) {
val functionLiteral = literal as JetFunctionLiteral
if (isInlinedLambda(functionLiteral, typeMapper.getBindingContext())) {
continue
}
val internalClassName = getClassNameForElement(literal.getFirstChild(), typeMapper, file, isInLibrary)
if (internalClassName == currentLocationClassName) {
return functionLiteral
}
}
return null
}
private fun getPsiFileByLocation(location: Location): PsiFile? {
val sourceName: String
try {
sourceName = location.sourceName()
}
catch (e: AbsentInformationException) {
return null
}
// JDI names are of form "package.Class$InnerClass"
val referenceFqName = location.declaringType().name()
val referenceInternalName = referenceFqName.replace('.', '/')
val className = JvmClassName.byInternalName(referenceInternalName)
val project = myDebugProcess.getProject()
if (DumbService.getInstance(project).isDumb()) return null
return DebuggerUtils.findSourceFileForClass(project, GlobalSearchScope.allScope(project), className, sourceName, location.lineNumber() - 1)
}
override fun getAllClasses(sourcePosition: SourcePosition): List<ReferenceType> {
if (sourcePosition.getFile() !is JetFile) {
throw NoDataException()
}
val name = classNameForPosition(sourcePosition)
val result = ArrayList<ReferenceType>()
if (name != null) {
result.addAll(myDebugProcess.getVirtualMachineProxy().classesByName(name))
}
return result
}
private fun classNameForPosition(sourcePosition: SourcePosition): String? {
return runReadAction {
val file = sourcePosition.getFile() as JetFile
val isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null
val typeMapper = if (!isInLibrary) prepareTypeMapper(file) else createTypeMapperForLibraryFile(sourcePosition.getElementAt(), file)
getClassNameForElement(sourcePosition.getElementAt(), typeMapper, file, isInLibrary)
}
}
private fun prepareTypeMapper(file: JetFile): JetTypeMapper {
val key = createKeyForTypeMapper(file)
var value: CachedValue<JetTypeMapper>? = myTypeMappers.get(key)
if (value == null) {
value = CachedValuesManager.getManager(file.getProject()).createCachedValue<JetTypeMapper>(
{() ->
val typeMapper = createTypeMapper(file, key.second)
CachedValueProvider.Result<JetTypeMapper>(typeMapper, PsiModificationTracker.MODIFICATION_COUNT)
}, false)
myTypeMappers.put(key, value)
}
return value!!.getValue()
}
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
if (position.getFile() !is JetFile) {
throw NoDataException()
}
try {
val line = position.getLine() + 1
val locations = if (myDebugProcess.getVirtualMachineProxy().versionHigher("1.4"))
type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line)
else
type.locationsOfLine(line)
if (locations == null || locations.isEmpty()) throw NoDataException()
return locations
}
catch (e: AbsentInformationException) {
throw NoDataException()
}
}
override fun createPrepareRequest(classPrepareRequestor: ClassPrepareRequestor, sourcePosition: SourcePosition): ClassPrepareRequest? {
if (sourcePosition.getFile() !is JetFile) {
throw NoDataException()
}
val className = classNameForPosition(sourcePosition)
if (className == null) {
return null
}
return myDebugProcess.getRequestsManager().createClassPrepareRequest(classPrepareRequestor, className.replace('/', '.'))
}
TestOnly
public fun addTypeMapper(file: JetFile, typeMapper: JetTypeMapper) {
val value = CachedValuesManager.getManager(file.getProject()).createCachedValue<JetTypeMapper>(
{ () -> CachedValueProvider.Result<JetTypeMapper>(typeMapper, PsiModificationTracker.MODIFICATION_COUNT) }, false)
val key = createKeyForTypeMapper(file)
myTypeMappers.put(key, value)
}
class object {
public fun createTypeMapper(file: JetFile, moduleInfo: IdeaModuleInfo): JetTypeMapper {
val project = file.getProject()
val packageFacadeScope = moduleInfo.contentScope()
val packageFiles = PackageIndexUtil.findFilesWithExactPackage(file.getPackageFqName(), packageFacadeScope, project)
val analysisResult = KotlinCacheService.getInstance(project).getAnalysisResults(packageFiles)
analysisResult.throwIfError()
val state = GenerationState(project, ClassBuilderFactories.THROW_EXCEPTION, analysisResult.moduleDescriptor,
analysisResult.bindingContext, packageFiles.toList())
state.beforeCompile()
return state.getTypeMapper()
}
public fun getClassNameForElement(notPositionedElement: PsiElement?, typeMapper: JetTypeMapper, file: JetFile, isInLibrary: Boolean): String? {
val element = getElementToCalculateClassName(notPositionedElement)
when {
element is JetClassOrObject -> return getJvmInternalNameForImpl(typeMapper, element)
element is JetFunctionLiteral -> {
if (isInlinedLambda(element, typeMapper.getBindingContext())) {
return getClassNameForElement(element.getParent(), typeMapper, file, isInLibrary)
}
else {
val asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), element)
return asmType.getInternalName()
}
}
element is JetClassInitializer -> {
val parent = getElementToCalculateClassName(element.getParent())
// Class-object initializer
if (parent is JetObjectDeclaration && parent.isClassObject()) {
return getClassNameForElement(parent.getParent(), typeMapper, file, isInLibrary)
}
return getClassNameForElement(element, typeMapper, file, isInLibrary)
}
element is JetProperty && (!element.isTopLevel() || !isInLibrary) -> {
if (isInPropertyAccessor(notPositionedElement)) {
val classOrObject = PsiTreeUtil.getParentOfType(element, javaClass<JetClassOrObject>())
if (classOrObject != null) {
return getJvmInternalNameForImpl(typeMapper, classOrObject)
}
}
val descriptor = typeMapper.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
if (descriptor !is PropertyDescriptor) {
return getClassNameForElement(element.getParent(), typeMapper, file, isInLibrary)
}
return getJvmInternalNameForPropertyOwner(typeMapper, descriptor)
}
element is JetNamedFunction -> {
val parent = getElementToCalculateClassName(element)
if (parent is JetClassOrObject) {
return getJvmInternalNameForImpl(typeMapper, parent)
}
else if (parent != null) {
val asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), element)
return asmType.getInternalName()
}
}
}
if (isInLibrary) {
val elementAtForLibraryFile = getElementToCreateTypeMapperForLibraryFile(notPositionedElement)
assert(elementAtForLibraryFile != null) {
"Couldn't find element at breakpoint for library file " + file.getName() +
(if (notPositionedElement == null) "" else ", notPositionedElement = " + JetPsiUtil.getElementTextWithContext(notPositionedElement))
}
return findPackagePartInternalNameForLibraryFile(elementAtForLibraryFile!!)
}
return PackagePartClassUtils.getPackagePartInternalName(file)
}
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?) =
PsiTreeUtil.getParentOfType(notPositionedElement,
javaClass<JetClassOrObject>(),
javaClass<JetFunctionLiteral>(),
javaClass<JetNamedFunction>(),
javaClass<JetProperty>(),
javaClass<JetClassInitializer>())
public fun getJvmInternalNameForPropertyOwner(typeMapper: JetTypeMapper, descriptor: PropertyDescriptor): String {
return typeMapper.mapOwner(
if (AsmUtil.isPropertyWithBackingFieldInOuterClass(descriptor)) descriptor.getContainingDeclaration() else descriptor,
true).getInternalName()
}
private fun isInPropertyAccessor(element: PsiElement?) =
element is JetPropertyAccessor ||
PsiTreeUtil.getParentOfType(element, javaClass<JetProperty>(), javaClass<JetPropertyAccessor>()) is JetPropertyAccessor
private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) =
if (element is JetElement) element else PsiTreeUtil.getParentOfType(element, javaClass<JetElement>())
private fun getJvmInternalNameForImpl(typeMapper: JetTypeMapper, jetClass: JetClassOrObject): String? {
val classDescriptor = typeMapper.getBindingContext().get<PsiElement, ClassDescriptor>(BindingContext.CLASS, jetClass)
if (classDescriptor == null) {
return null
}
if (jetClass is JetClass && jetClass.isTrait()) {
return typeMapper.mapTraitImpl(classDescriptor).getInternalName()
}
return typeMapper.mapClass(classDescriptor).getInternalName()
}
private fun createTypeMapperForLibraryFile(notPositionedElement: PsiElement?, file: JetFile): JetTypeMapper {
val element = getElementToCreateTypeMapperForLibraryFile(notPositionedElement)
val analysisResult = element!!.analyzeAndGetResult()
val state = GenerationState(file.getProject(), ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor, analysisResult.bindingContext, listOf(file))
state.beforeCompile()
return state.getTypeMapper()
}
public fun isInlinedLambda(functionLiteral: JetFunctionLiteral, context: BindingContext): Boolean {
val functionLiteralExpression = functionLiteral.getParent()
if (functionLiteralExpression == null) return false
var parent = functionLiteralExpression.getParent()
var valueArgument: PsiElement = functionLiteralExpression
while (parent is JetParenthesizedExpression || parent is JetBinaryExpressionWithTypeRHS || parent is JetLabeledExpression) {
valueArgument = parent
parent = parent.getParent()
}
while (parent is ValueArgument || parent is JetValueArgumentList) {
parent = parent.getParent()
}
if (parent !is JetElement) return false
val call = (parent as JetElement).getResolvedCall(context)
if (call == null) return false
val inlineType = InlineUtil.getInlineType(call.getResultingDescriptor())
if (!inlineType.isInline()) return false
for ((valueParameterDescriptor, resolvedValueArgument) in call.getValueArguments()) {
for (next in resolvedValueArgument.getArguments()) {
val expression = next.getArgumentExpression()
if (valueArgument == expression) {
return InlineAnalyzerExtension.checkInlinableParameter(valueParameterDescriptor, expression, call.getResultingDescriptor(), null)
}
}
}
return false
}
private fun createKeyForTypeMapper(file: JetFile) = Pair(file.getPackageFqName(), file.getModuleInfo())
}
}
@@ -0,0 +1,2 @@
org/demo/coverage/Foo
org/demo/coverage/Foo$bar$1
@@ -0,0 +1,12 @@
package org.demo.coverage
public class Foo {
public fun forEach(fn: (Any?) -> Unit): Unit {
}
public fun bar() {
forEach {
println("foo")
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2014 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.jet.plugin.coverage
import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase
import org.jetbrains.jet.plugin.PluginTestCaseBase
import org.jetbrains.jet.lang.psi.JetFile
import kotlin.test.assertNotNull
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import kotlin.test.assertEquals
import java.io.File
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.jet.JetTestUtils
public abstract class AbstractKotlinCoverageOutputFilesTest(): JetLightCodeInsightFixtureTestCase() {
private val TEST_DATA_PATH = PluginTestCaseBase.getTestDataPathBase() + "/coverage/outputFiles"
override fun getTestDataPath(): String? = TEST_DATA_PATH
public fun doTest(path: String) {
val kotlinFile = myFixture.configureByFile(path) as JetFile
val actualClasses = KotlinCoverageExtension.collectOutputClassNames(kotlinFile)
JetTestUtils.assertEqualsToFile(File(path.replace(".kt", ".expected.txt")), actualClasses.join("\n"))
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2014 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.jet.plugin.coverage;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/coverage/outputFiles")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class KotlinCoverageOutputFilesTestGenerated extends AbstractKotlinCoverageOutputFilesTest {
public void testAllFilesPresentInOutputFiles() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/coverage/outputFiles"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/coverage/outputFiles/lambda.kt");
doTest(fileName);
}
}