All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.resolve.calls.checkers.InlineChecker Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2016 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.kotlin.resolve.calls.checkers;

import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.FunctionTypesKt;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.lexer.KtToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.util.OperatorNameConventions;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import static org.jetbrains.kotlin.diagnostics.Errors.NON_LOCAL_RETURN_NOT_ALLOWED;
import static org.jetbrains.kotlin.diagnostics.Errors.USAGE_IS_NOT_INLINABLE;
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.allowsNonLocalReturns;
import static org.jetbrains.kotlin.resolve.inline.InlineUtil.checkNonLocalReturnUsage;

class InlineChecker implements CallChecker {
    private final FunctionDescriptor descriptor;
    private final Set inlinableParameters = new LinkedHashSet();
    private final EffectiveVisibility inlineFunEffectiveVisibility;
    private final boolean isEffectivelyPrivateApiFunction;

    public InlineChecker(@NotNull FunctionDescriptor descriptor) {
        assert InlineUtil.isInline(descriptor) : "This extension should be created only for inline functions: " + descriptor;
        this.descriptor = descriptor;
        this.inlineFunEffectiveVisibility = EffectiveVisibilityKt.effectiveVisibility(descriptor, descriptor.getVisibility(), true);
        this.isEffectivelyPrivateApiFunction = DescriptorUtilsKt.isEffectivelyPrivateApi(descriptor);
        for (ValueParameterDescriptor param : descriptor.getValueParameters()) {
            if (isInlinableParameter(param)) {
                inlinableParameters.add(param);
            }
        }
    }

    @Override
    public void check(@NotNull ResolvedCall resolvedCall, @NotNull PsiElement reportOn, @NotNull CallCheckerContext context) {
        KtExpression expression = resolvedCall.getCall().getCalleeExpression();
        if (expression == null) {
            return;
        }

        //checking that only invoke or inlinable extension called on function parameter
        CallableDescriptor targetDescriptor = resolvedCall.getResultingDescriptor();
        checkCallWithReceiver(context, targetDescriptor, resolvedCall.getDispatchReceiver(), expression);
        checkCallWithReceiver(context, targetDescriptor, resolvedCall.getExtensionReceiver(), expression);

        if (inlinableParameters.contains(targetDescriptor)) {
            if (!isInsideCall(expression)) {
                context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(expression, expression, descriptor));
            }
        }

        for (Map.Entry entry : resolvedCall.getValueArguments().entrySet()) {
            ResolvedValueArgument value = entry.getValue();
            ValueParameterDescriptor valueDescriptor = entry.getKey();
            if (!(value instanceof DefaultValueArgument)) {
                for (ValueArgument argument : value.getArguments()) {
                    checkValueParameter(context, targetDescriptor, argument, valueDescriptor);
                }
            }
        }

        checkVisibilityAndAccess(targetDescriptor, expression, context);
        checkRecursion(context, targetDescriptor, expression);
    }

    private static boolean isInsideCall(KtExpression expression) {
        KtElement parent = KtPsiUtil.getParentCallIfPresent(expression);
        if (parent instanceof KtBinaryExpression) {
            KtToken token = KtPsiUtil.getOperationToken((KtOperationExpression) parent);
            if (token == KtTokens.EQ || token == KtTokens.ANDAND || token == KtTokens.OROR) {
                //assignment
                return false;
            }
        }

        if (parent != null) {
            //UGLY HACK
            //check there is no casts
            PsiElement current = expression;
            while (current != parent) {
                if (current instanceof KtBinaryExpressionWithTypeRHS) {
                    return false;
                }
                current = current.getParent();
            }
        }

        return parent != null;
    }

    private void checkValueParameter(
            @NotNull CallCheckerContext context,
            @NotNull CallableDescriptor targetDescriptor,
            @NotNull ValueArgument targetArgument,
            @NotNull ValueParameterDescriptor targetParameterDescriptor
    ) {
        KtExpression argumentExpression = targetArgument.getArgumentExpression();
        if (argumentExpression == null) {
            return;
        }
        CallableDescriptor argumentCallee = getCalleeDescriptor(context, argumentExpression, false);

        if (argumentCallee != null && inlinableParameters.contains(argumentCallee)) {
            if (InlineUtil.isInline(targetDescriptor) && isInlinableParameter(targetParameterDescriptor)) {
                if (allowsNonLocalReturns(argumentCallee) && !allowsNonLocalReturns(targetParameterDescriptor)) {
                    context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(argumentExpression, argumentExpression));
                }
                else {
                    checkNonLocalReturn(context, argumentCallee, argumentExpression);
                }
            }
            else {
                context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(argumentExpression, argumentExpression, descriptor));
            }
        }
    }

    private void checkCallWithReceiver(
            @NotNull CallCheckerContext context,
            @NotNull CallableDescriptor targetDescriptor,
            @Nullable ReceiverValue receiver,
            @Nullable KtExpression expression
    ) {
        if (receiver == null) return;

        CallableDescriptor varDescriptor = null;
        KtExpression receiverExpression = null;
        if (receiver instanceof ExpressionReceiver) {
            receiverExpression = ((ExpressionReceiver) receiver).getExpression();
            varDescriptor = getCalleeDescriptor(context, receiverExpression, true);
        }
        else if (receiver instanceof ExtensionReceiver) {
            ExtensionReceiver extensionReceiver = (ExtensionReceiver) receiver;
            CallableDescriptor extension = extensionReceiver.getDeclarationDescriptor();

            varDescriptor = extension.getExtensionReceiverParameter();
            assert varDescriptor != null : "Extension should have receiverParameterDescriptor: " + extension;

            receiverExpression = expression;
        }

        if (inlinableParameters.contains(varDescriptor)) {
            //check that it's invoke or inlinable extension
            checkLambdaInvokeOrExtensionCall(context, varDescriptor, targetDescriptor, receiverExpression);
        }
    }

    @Nullable
    private static CallableDescriptor getCalleeDescriptor(
            @NotNull CallCheckerContext context,
            @NotNull KtExpression expression,
            boolean unwrapVariableAsFunction
    ) {
        if (!(expression instanceof KtSimpleNameExpression || expression instanceof KtThisExpression)) return null;

        ResolvedCall thisCall = CallUtilKt.getResolvedCall(expression, context.getTrace().getBindingContext());
        if (unwrapVariableAsFunction && thisCall instanceof VariableAsFunctionResolvedCall) {
            return ((VariableAsFunctionResolvedCall) thisCall).getVariableCall().getResultingDescriptor();
        }
        return thisCall != null ? thisCall.getResultingDescriptor() : null;
    }

    private void checkLambdaInvokeOrExtensionCall(
            @NotNull CallCheckerContext context,
            @NotNull CallableDescriptor lambdaDescriptor,
            @NotNull CallableDescriptor callDescriptor,
            @NotNull KtExpression receiverExpression
    ) {
        boolean inlinableCall = isInvokeOrInlineExtension(callDescriptor);
        if (!inlinableCall) {
            context.getTrace().report(USAGE_IS_NOT_INLINABLE.on(receiverExpression, receiverExpression, descriptor));
        }
        else {
            checkNonLocalReturn(context, lambdaDescriptor, receiverExpression);
        }
    }

    private void checkRecursion(
            @NotNull CallCheckerContext context,
            @NotNull CallableDescriptor targetDescriptor,
            @NotNull KtElement expression
    ) {
        if (targetDescriptor.getOriginal() == descriptor) {
            context.getTrace().report(Errors.RECURSION_IN_INLINE.on(expression, expression, descriptor));
        }
    }

    private static boolean isInlinableParameter(@NotNull ParameterDescriptor descriptor) {
        return InlineUtil.isInlineLambdaParameter(descriptor) && !descriptor.getType().isMarkedNullable();
    }

    private static boolean isInvokeOrInlineExtension(@NotNull CallableDescriptor descriptor) {
        if (!(descriptor instanceof SimpleFunctionDescriptor)) {
            return false;
        }

        DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
        boolean isInvoke =
                descriptor.getName().equals(OperatorNameConventions.INVOKE) &&
                containingDeclaration instanceof ClassDescriptor &&
                FunctionTypesKt.isFunctionType(((ClassDescriptor) containingDeclaration).getDefaultType());

        return isInvoke || InlineUtil.isInline(descriptor);
    }

    private void checkVisibilityAndAccess(
            @NotNull CallableDescriptor calledDescriptor,
            @NotNull KtElement expression,
            @NotNull CallCheckerContext context
    ) {
        EffectiveVisibility calledFunEffectiveVisibility =
                isDefinedInInlineFunction(calledDescriptor) ?
                EffectiveVisibility.Public.INSTANCE :
                EffectiveVisibilityKt.effectiveVisibility(calledDescriptor, calledDescriptor.getVisibility(), true);

        boolean isCalledFunPublicOrPublishedApi = calledFunEffectiveVisibility.getPublicApi();
        boolean isInlineFunPublicOrPublishedApi = inlineFunEffectiveVisibility.getPublicApi();
        if (isInlineFunPublicOrPublishedApi &&
            !isCalledFunPublicOrPublishedApi &&
            calledDescriptor.getVisibility() != Visibilities.LOCAL) {
            context.getTrace().report(Errors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor, descriptor));
        }
        else {
            checkPrivateClassMemberAccess(calledDescriptor, expression, context);
        }

        if (!(calledDescriptor instanceof ConstructorDescriptor) &&
            isInlineFunPublicOrPublishedApi &&
            inlineFunEffectiveVisibility.toVisibility() != Visibilities.PROTECTED &&
            calledFunEffectiveVisibility.toVisibility() == Visibilities.PROTECTED) {
            context.getTrace().report(Errors.PROTECTED_CALL_FROM_PUBLIC_INLINE.on(expression, calledDescriptor));
        }
    }

    private void checkPrivateClassMemberAccess(
            @NotNull DeclarationDescriptor declarationDescriptor,
            @NotNull KtElement expression,
            @NotNull CallCheckerContext context
    ) {
        if (!isEffectivelyPrivateApiFunction) {
            if (DescriptorUtilsKt.isInsidePrivateClass(declarationDescriptor)) {
                context.getTrace().report(Errors.PRIVATE_CLASS_MEMBER_FROM_INLINE.on(expression, declarationDescriptor, descriptor));
            }
        }
    }

    private boolean isDefinedInInlineFunction(@NotNull DeclarationDescriptorWithVisibility startDescriptor) {
        DeclarationDescriptorWithVisibility parent = startDescriptor;

        while (parent != null) {
            if (parent.getContainingDeclaration() == descriptor) return true;

            parent = DescriptorUtils.getParentOfType(parent, DeclarationDescriptorWithVisibility.class);
        }

        return false;
    }

    private void checkNonLocalReturn(
            @NotNull CallCheckerContext context,
            @NotNull CallableDescriptor inlinableParameterDescriptor,
            @NotNull KtExpression parameterUsage
    ) {
        if (!allowsNonLocalReturns(inlinableParameterDescriptor)) return;

        if (!checkNonLocalReturnUsage(descriptor, parameterUsage, context.getResolutionContext())) {
            context.getTrace().report(NON_LOCAL_RETURN_NOT_ALLOWED.on(parameterUsage, parameterUsage));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy