Skip to content

Commit

Permalink
Merge pull request #11 from Roenke/fix-illegal-access-errors
Browse files Browse the repository at this point in the history
Fix illegal access errors when trace expression evaluating
  • Loading branch information
bibaev authored Apr 11, 2017
2 parents 1630f78 + c935078 commit 9f669d5
Show file tree
Hide file tree
Showing 28 changed files with 745 additions and 37 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ apply plugin: 'java'
apply plugin: "kotlin"

group 'com.intellij.debugger.stream'
version '0.0.6'
version '0.0.7'

intellij {
type = 'IC'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class TraceStreamAction extends AnAction {
private static final Logger LOG = Logger.getInstance(TraceStreamAction.class);

private final DebuggerPositionResolver myPositionResolver = new DebuggerPositionResolverImpl();
private final TraceExpressionBuilder myExpressionBuilder = new TraceExpressionBuilderImpl();
private final TraceResultInterpreter myResultInterpreter = new TraceResultInterpreterImpl();
private final StreamChainBuilder myChainBuilder = new StreamChainBuilderImpl();

Expand All @@ -51,7 +50,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
if (chain != null) {
final EvaluationAwareTraceWindow window = new EvaluationAwareTraceWindow(session.getProject(), chain);
ApplicationManager.getApplication().invokeLater(window::show);
final StreamTracer tracer = new EvaluateExpressionTracer(session, myExpressionBuilder, myResultInterpreter);
final TraceExpressionBuilderImpl expressionBuilder = new TraceExpressionBuilderImpl(session.getProject());
final StreamTracer tracer = new EvaluateExpressionTracer(session, expressionBuilder, myResultInterpreter);
tracer.trace(chain, new TracingCallback() {
@Override
public void evaluated(@NotNull TracingResult result, @NotNull EvaluationContextImpl context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.intellij.debugger.streams.psi

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor

/**
* @author Vitaliy.Bibaev
*/
interface PsiElementTransformer {
fun transform(element: PsiElement): Unit

abstract class Base: PsiElementTransformer {
override fun transform(element: PsiElement) {
element.accept(visitor)
}

protected abstract val visitor: PsiElementVisitor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package com.intellij.debugger.streams.psi.impl

import com.intellij.codeInsight.ChangeContextUtil
import com.intellij.codeInsight.generation.GenerateMembersUtil
import com.intellij.codeInsight.generation.OverrideImplementUtil.*
import com.intellij.codeInsight.generation.PsiGenerationInfo
import com.intellij.debugger.streams.psi.PsiElementTransformer
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.*
import com.intellij.psi.util.*
import com.intellij.refactoring.util.RefactoringChangeUtil
import java.util.*

/**
* Copied with minor changes from: LambdaCanBeReplacedWithAnonymousInspection
* Cannot reuse because the call OverrideImplementUtil.overrideOrImplement require a VirtualFile
*
* @author Vitaliy.Bibaev
*/
object LambdaToAnonymousTransformer : PsiElementTransformer.Base() {
val LOG = Logger.getInstance("#" + LambdaToAnonymousTransformer::class.java.name)

override val visitor: PsiElementVisitor
get() = object : JavaRecursiveElementVisitor() {
override fun visitLambdaExpression(lambdaExpression: PsiLambdaExpression?) {
if (lambdaExpression == null || !isConvertible(lambdaExpression)) return
val paramListCopy = (lambdaExpression.parameterList.copy() as PsiParameterList).parameters
val functionalInterfaceType = lambdaExpression.functionalInterfaceType ?: return
val method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType) ?: return

val blockText = getBodyText(lambdaExpression) ?: return

val project = lambdaExpression.project
val psiElementFactory = JavaPsiFacade.getElementFactory(project)
var blockFromText = psiElementFactory.createCodeBlockFromText(blockText, lambdaExpression)
qualifyThisExpressions(lambdaExpression, psiElementFactory, blockFromText)
blockFromText = psiElementFactory.createCodeBlockFromText(blockFromText.text, null)

var newExpression = psiElementFactory.createExpressionFromText("new " + functionalInterfaceType.canonicalText + "(){}",
lambdaExpression) as PsiNewExpression
newExpression = lambdaExpression.replace(newExpression) as PsiNewExpression

val anonymousClass = newExpression.anonymousClass
LOG.assertTrue(anonymousClass != null)
val infos = overrideOrImplement(anonymousClass!!, method)
if (infos.size == 1) {
val member = infos[0].psiMember
val parameters = member.parameterList.parameters
if (parameters.size == paramListCopy.size) {
for (i in parameters.indices) {
val parameter = parameters[i]
val lambdaParamName = paramListCopy[i].name
if (lambdaParamName != null) {
parameter.setName(lambdaParamName)
}
}
}
val codeBlock = member.body
LOG.assertTrue(codeBlock != null)
codeBlock!!.replace(blockFromText)

val parent = anonymousClass.parent.parent
if (parent is PsiTypeCastExpression && RedundantCastUtil.isCastRedundant(parent)) {
val operand = parent.operand
LOG.assertTrue(operand != null)
parent.replace(operand!!)
}
}
}
}

private fun overrideOrImplement(psiClass: PsiAnonymousClass, baseMethod: PsiMethod): List<PsiGenerationInfo<PsiMethod>> {
val prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false))
if (prototypes.isEmpty()) return emptyList()

val substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseMethod.containingClass!!, psiClass, PsiSubstitutor.EMPTY)
val anchor = getDefaultAnchorToOverrideOrImplement(psiClass, baseMethod, substitutor)
return GenerateMembersUtil.insertMembersBeforeAnchor<PsiGenerationInfo<PsiMethod>>(psiClass, anchor, prototypes)
}

private fun getBodyText(lambda: PsiLambdaExpression): String? {
var blockText: String?
val body = lambda.body
if (body is PsiExpression) {
val returnType = LambdaUtil.getFunctionalInterfaceReturnType(lambda)
blockText = "{"
blockText += if (PsiType.VOID == returnType) "" else "return "
blockText += body.text + ";}"
}
else if (body != null) {
blockText = body.text
}
else {
blockText = null
}
return blockText
}

private fun qualifyThisExpressions(lambdaExpression: PsiLambdaExpression,
psiElementFactory: PsiElementFactory,
blockFromText: PsiCodeBlock): Unit {
ChangeContextUtil.encodeContextInfo(blockFromText, true)
val thisClass = RefactoringChangeUtil.getThisClass(lambdaExpression)
val thisClassName = if (thisClass != null && thisClass !is PsiSyntheticClass) thisClass.name else null
if (thisClassName != null) {
val thisAccessExpr = RefactoringChangeUtil.createThisExpression(lambdaExpression.manager, thisClass)
ChangeContextUtil.decodeContextInfo(blockFromText, thisClass, thisAccessExpr)
val replacements = HashSet<PsiExpression>()
blockFromText.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitClass(aClass: PsiClass) {}

override fun visitSuperExpression(expression: PsiSuperExpression) {
super.visitSuperExpression(expression)
if (expression.qualifier == null) {
replacements.add(expression)
}
}

override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
if (thisAccessExpr != null) {
val psiMethod = expression.resolveMethod()
val methodExpression = expression.methodExpression
if (psiMethod != null && !psiMethod.hasModifierProperty(PsiModifier.STATIC) && methodExpression.qualifierExpression == null) {
replacements.add(expression)
}
}
}
})
for (expression in replacements) {
if (expression is PsiSuperExpression) {
expression.replace(psiElementFactory.createExpressionFromText(thisClassName + "." + expression.getText(), expression))
}
else if (expression is PsiMethodCallExpression) {
expression.methodExpression.qualifierExpression = thisAccessExpr
}
else {
LOG.error("Unexpected expression")
}
}
}
}

private fun isConvertible(lambdaExpression: PsiLambdaExpression): Boolean {
val thisClass = PsiTreeUtil.getParentOfType(lambdaExpression, PsiClass::class.java, true)
if (thisClass == null || thisClass is PsiAnonymousClass) {
val body = lambdaExpression.body ?: return false
val disabled = BooleanArray(1)
body.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitThisExpression(expression: PsiThisExpression) {
disabled[0] = true
}

override fun visitSuperExpression(expression: PsiSuperExpression) {
disabled[0] = true
}
})
if (disabled[0]) return false
}
val functionalInterfaceType = lambdaExpression.functionalInterfaceType
if (functionalInterfaceType != null &&
LambdaUtil.isLambdaFullyInferred(lambdaExpression, functionalInterfaceType) &&
LambdaUtil.isFunctionalType(functionalInterfaceType)) {
val interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType)
if (interfaceMethod != null) {
val substitutor = LambdaUtil.getSubstitutor(interfaceMethod, PsiUtil.resolveGenericsClassInType(functionalInterfaceType))
if (interfaceMethod.getSignature(substitutor).parameterTypes.any { !PsiTypesUtil.isDenotableType(it) }) return false
val returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType)
return PsiTypesUtil.isDenotableType(returnType)
}
}

return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.intellij.debugger.streams.psi.impl

import com.intellij.debugger.streams.psi.PsiElementTransformer
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethodReferenceExpression
import com.intellij.refactoring.util.LambdaRefactoringUtil

/**
* @author Vitaliy.Bibaev
*/
object MethodReferenceToLambdaTransformer : PsiElementTransformer.Base() {
override val visitor: PsiElementVisitor
get() = object : JavaRecursiveElementVisitor() {
override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression?) {
super.visitMethodReferenceExpression(expression)
if (expression == null) return
LambdaRefactoringUtil.convertMethodReferenceToLambda(expression, false, true)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.intellij.debugger.streams.psi.impl

import com.intellij.debugger.streams.psi.PsiElementTransformer
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiElementVisitor

/**
* @author Vitaliy.Bibaev
*/
object ToObjectInheritorTransformer: PsiElementTransformer.Base() {
override val visitor: PsiElementVisitor
get() = object : JavaElementVisitor() {}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.intellij.debugger.streams.trace.impl;

import com.intellij.debugger.streams.psi.impl.LambdaToAnonymousTransformer;
import com.intellij.debugger.streams.psi.impl.MethodReferenceToLambdaTransformer;
import com.intellij.debugger.streams.psi.impl.ToObjectInheritorTransformer;
import com.intellij.debugger.streams.trace.TraceExpressionBuilder;
import com.intellij.debugger.streams.trace.impl.handler.HandlerFactory;
import com.intellij.debugger.streams.trace.impl.handler.PeekCall;
Expand All @@ -8,7 +11,13 @@
import com.intellij.debugger.streams.wrapper.ProducerStreamCall;
import com.intellij.debugger.streams.wrapper.StreamChain;
import com.intellij.debugger.streams.wrapper.impl.StreamChainImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElementFactory;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;

Expand All @@ -29,6 +38,12 @@ public class TraceExpressionBuilderImpl implements TraceExpressionBuilder {
private static final String RESULT_EXPRESSION =
RESULT_VARIABLE_NAME + " = new java.lang.Object[]{ info, streamResult, elapsedTime };" + LINE_SEPARATOR;

private final Project myProject;

public TraceExpressionBuilderImpl(@NotNull Project project) {
myProject = project;
}

@NotNull
@Override
public String createTraceExpression(@NotNull StreamChain chain) {
Expand All @@ -44,9 +59,23 @@ public String createTraceExpression(@NotNull StreamChain chain) {

final String tracingCall = buildStreamExpression(traceChain);

final String result = RESULT_VARIABLE_DECLARATION +
String.format("{" + LINE_SEPARATOR + "%s" + LINE_SEPARATOR + " }" + LINE_SEPARATOR + RESULT_VARIABLE_NAME,
declarations + tracingCall + fillingInfoArray);
final String evaluationCodeBlock = String.format("{" + LINE_SEPARATOR + "%s" + LINE_SEPARATOR + " }",
declarations + tracingCall + fillingInfoArray);
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject);
final String expression = ApplicationManager.getApplication().runReadAction((Computable<String>)() -> {
final PsiCodeBlock block = elementFactory.createCodeBlockFromText(evaluationCodeBlock, chain.getContext());

LOG.info("before transformation: " + LINE_SEPARATOR + block.getText());

MethodReferenceToLambdaTransformer.INSTANCE.transform(block);
LambdaToAnonymousTransformer.INSTANCE.transform(block);
ToObjectInheritorTransformer.INSTANCE.transform(block);
return block.getText();
});

final String result = RESULT_VARIABLE_DECLARATION + LINE_SEPARATOR +
expression + LINE_SEPARATOR +
RESULT_VARIABLE_NAME + ";";
LOG.info("stream expression to trace:" + LINE_SEPARATOR + result);
return result;
}
Expand Down Expand Up @@ -79,7 +108,7 @@ private static StreamChain buildTraceChain(@NotNull StreamChain chain,

newIntermediateCalls.addAll(terminatorHandler.additionalCallsBefore());

return new StreamChainImpl(producerCall, newIntermediateCalls, chain.getTerminationCall());
return new StreamChainImpl(producerCall, newIntermediateCalls, chain.getTerminationCall(), chain.getContext());
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.intellij.debugger.streams.wrapper;

import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;

import java.util.List;
Expand All @@ -24,4 +25,7 @@ public interface StreamChain {
String getText();

int length();

@NotNull
PsiElement getContext();
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public StreamChain build(@NotNull PsiElement startElement) {
else if (StreamCallType.TERMINATOR.equals(type)) {
final TerminatorStreamCallImpl terminator =
new TerminatorStreamCallImpl(callName, callArgs, prevCallType, currentType.equals(GenericType.VOID));
return new StreamChainImpl(producer, intermediateStreamCalls, terminator);
return new StreamChainImpl(producer, intermediateStreamCalls, terminator, startElement);
}
else {
throw new RuntimeException("wrong operation type!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.intellij.debugger.streams.trace.impl.TraceExpressionBuilderImpl;
import com.intellij.debugger.streams.wrapper.*;
import com.intellij.psi.PsiElement;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;

Expand All @@ -31,13 +32,15 @@ public class StreamChainImpl implements StreamChain {
private final ProducerStreamCall myProducer;
private final List<IntermediateStreamCall> myIntermediateCalls;
private final TerminatorStreamCall myTerminator;
private final PsiElement myContext;

public StreamChainImpl(@NotNull ProducerStreamCall producer,
@NotNull List<IntermediateStreamCall> intermediateCalls,
@NotNull TerminatorStreamCall terminator) {
@NotNull TerminatorStreamCall terminator, PsiElement context) {
myProducer = producer;
myIntermediateCalls = intermediateCalls;
myTerminator = terminator;
myContext = context;
}

@NotNull
Expand Down Expand Up @@ -90,6 +93,12 @@ public int length() {
return 2 + myIntermediateCalls.size();
}

@NotNull
@Override
public PsiElement getContext() {
return myContext;
}

private StreamCall doGetCall(int index) {
if (index == 0) {
return myProducer;
Expand Down
Loading

0 comments on commit 9f669d5

Please sign in to comment.