diff --git a/.idea/libraries/coverage_plugin.xml b/.idea/libraries/coverage_plugin.xml new file mode 100644 index 00000000000..91df8c11739 --- /dev/null +++ b/.idea/libraries/coverage_plugin.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.kt index 84a6fe9d568..6ce34f21539 100644 --- a/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/jet/generators/tests/GenerateTests.kt @@ -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) { System.setProperty("java.awt.headless", "true") @@ -638,6 +639,10 @@ fun main(args: Array) { testClass(javaClass()) { model("debugger/selectExpression") } + + testClass(javaClass()) { + model("coverage/outputFiles") + } } testGroup("idea/tests", "compiler/testData") { diff --git a/idea/idea.iml b/idea/idea.iml index 21412d50803..ea77e4c848e 100644 --- a/idea/idea.iml +++ b/idea/idea.iml @@ -44,5 +44,6 @@ + \ No newline at end of file diff --git a/idea/src/META-INF/coverage.xml b/idea/src/META-INF/coverage.xml new file mode 100644 index 00000000000..ce1756d8453 --- /dev/null +++ b/idea/src/META-INF/coverage.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 69d3cdcc2fc..e2ec1a1f633 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -15,6 +15,7 @@ com.intellij.copyright JavaScriptDebugger org.jetbrains.android + Coverage diff --git a/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt new file mode 100644 index 00000000000..1bdb6aea475 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/coverage/KotlinCoverageExtension.kt @@ -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): 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> { 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 { + val typeMapper = JetPositionManager.createTypeMapper(srcFile, srcFile.getModuleInfo()) + val classNames = LinkedHashSet() + 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 + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java b/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java deleted file mode 100644 index c72fb74f455..00000000000 --- a/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.java +++ /dev/null @@ -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, CachedValue> myTypeMappers = new WeakHashMap, CachedValue>(); - - 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 getAllClasses(SourcePosition sourcePosition) throws NoDataException { - if (!(sourcePosition.getFile() instanceof JetFile)) { - throw new NoDataException(); - } - String name = classNameForPosition(sourcePosition); - List result = new ArrayList(); - if (name != null) { - result.addAll(myDebugProcess.getVirtualMachineProxy().classesByName(name)); - } - return result; - } - - @Nullable - private String classNameForPosition(final SourcePosition sourcePosition) { - final Ref 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 key = createKeyForTypeMapper(file); - - CachedValue value = myTypeMappers.get(key); - if(value == null) { - value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider() { - @Override - public Result compute() { - Project project = file.getProject(); - GlobalSearchScope packageFacadeScope = key.second.contentScope(); - Collection 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(packageFiles) - ); - state.beforeCompile(); - return new Result(state.getTypeMapper(), PsiModificationTracker.MODIFICATION_COUNT); - } - }, false); - - myTypeMappers.put(key, value); - } - - return value.getValue(); - } - - @NotNull - @Override - public List locationsOfLine(ReferenceType type, SourcePosition position) throws NoDataException { - if (!(position.getFile() instanceof JetFile)) { - throw new NoDataException(); - } - try { - int line = position.getLine() + 1; - List 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 value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider() { - @Override - public Result compute() { - return new Result(typeMapper, PsiModificationTracker.MODIFICATION_COUNT); - } - }, false); - - Pair 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 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 createKeyForTypeMapper(@NotNull JetFile file) { - return new Pair(file.getPackageFqName(), ResolvePackage.getModuleInfo(file)); - } - -} diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.kt b/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.kt new file mode 100644 index 00000000000..2f784819d8d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/debugger/JetPositionManager.kt @@ -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, CachedValue>() + + 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()) + 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 { + if (sourcePosition.getFile() !is JetFile) { + throw NoDataException() + } + val name = classNameForPosition(sourcePosition) + val result = ArrayList() + 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? = myTypeMappers.get(key) + if (value == null) { + value = CachedValuesManager.getManager(file.getProject()).createCachedValue( + {() -> + val typeMapper = createTypeMapper(file, key.second) + CachedValueProvider.Result(typeMapper, PsiModificationTracker.MODIFICATION_COUNT) + }, false) + + myTypeMappers.put(key, value) + } + + return value!!.getValue() + } + + override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List { + 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( + { () -> CachedValueProvider.Result(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()) + 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(), + javaClass(), + javaClass(), + javaClass(), + javaClass()) + + 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(), javaClass()) is JetPropertyAccessor + + private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) = + if (element is JetElement) element else PsiTreeUtil.getParentOfType(element, javaClass()) + + private fun getJvmInternalNameForImpl(typeMapper: JetTypeMapper, jetClass: JetClassOrObject): String? { + val classDescriptor = typeMapper.getBindingContext().get(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()) + } +} diff --git a/idea/testData/coverage/outputFiles/lambda.expected.txt b/idea/testData/coverage/outputFiles/lambda.expected.txt new file mode 100644 index 00000000000..39ea5e49821 --- /dev/null +++ b/idea/testData/coverage/outputFiles/lambda.expected.txt @@ -0,0 +1,2 @@ +org/demo/coverage/Foo +org/demo/coverage/Foo$bar$1 diff --git a/idea/testData/coverage/outputFiles/lambda.kt b/idea/testData/coverage/outputFiles/lambda.kt new file mode 100644 index 00000000000..3007ac1e940 --- /dev/null +++ b/idea/testData/coverage/outputFiles/lambda.kt @@ -0,0 +1,12 @@ +package org.demo.coverage + +public class Foo { + public fun forEach(fn: (Any?) -> Unit): Unit { + } + + public fun bar() { + forEach { + println("foo") + } + } +} diff --git a/idea/tests/org/jetbrains/jet/plugin/coverage/AbstractKotlinCoverageOutputFilesTest.kt b/idea/tests/org/jetbrains/jet/plugin/coverage/AbstractKotlinCoverageOutputFilesTest.kt new file mode 100644 index 00000000000..09f0fc81dae --- /dev/null +++ b/idea/tests/org/jetbrains/jet/plugin/coverage/AbstractKotlinCoverageOutputFilesTest.kt @@ -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")) + } +} diff --git a/idea/tests/org/jetbrains/jet/plugin/coverage/KotlinCoverageOutputFilesTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/coverage/KotlinCoverageOutputFilesTestGenerated.java new file mode 100644 index 00000000000..ec317bac214 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/plugin/coverage/KotlinCoverageOutputFilesTestGenerated.java @@ -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); + } +}