JS: report error from backend, when inline function is called recursively

This commit is contained in:
Alexey Tsvetkov
2015-03-10 14:59:30 +03:00
parent 7e69a5ac9a
commit 24f2121f9b
10 changed files with 121 additions and 59 deletions
@@ -18,15 +18,18 @@ package com.google.dart.compiler.backend.js.ast.metadata
import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.builtins.InlineStrategy import org.jetbrains.kotlin.builtins.InlineStrategy
import kotlin.properties.Delegates
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import com.intellij.psi.PsiElement
public var JsName.staticRef: JsNode? by MetadataProperty(default = null) public var JsName.staticRef: JsNode? by MetadataProperty(default = null)
public var JsInvocation.inlineStrategy: InlineStrategy? by MetadataProperty(default = null) public var JsInvocation.inlineStrategy: InlineStrategy? by MetadataProperty(default = null)
public var JsInvocation.descriptor: CallableDescriptor? by MetadataProperty(default = null) public var JsInvocation.descriptor: CallableDescriptor? by MetadataProperty(default = null)
public var JsInvocation.psiElement: PsiElement? by MetadataProperty(default = null)
public var JsFunction.isLocal: Boolean by MetadataProperty(default = false) public var JsFunction.isLocal: Boolean by MetadataProperty(default = false)
public var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false) public var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false)
@@ -38,7 +38,7 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by Delegates.lazy {
put(ErrorsJs.REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED, "Callable references for builtin members are not supported yet: ''{0}''", RenderFirstLineOfElementText) put(ErrorsJs.REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED, "Callable references for builtin members are not supported yet: ''{0}''", RenderFirstLineOfElementText)
put(ErrorsJs.NON_TOPLEVEL_CLASS_DECLARATION, "Non-toplevel {0} declarations not supported yet", Renderers.STRING) put(ErrorsJs.NON_TOPLEVEL_CLASS_DECLARATION, "Non-toplevel {0} declarations not supported yet", Renderers.STRING)
put(ErrorsJs.SECONDARY_CONSTRUCTOR, "Secondary constructors not supported yet") put(ErrorsJs.SECONDARY_CONSTRUCTOR, "Secondary constructors not supported yet")
put(ErrorsJs.INLINE_CALL_CYCLE, "The call is a part of inline cycle")
this this
} }
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.resolve.diagnostics; package org.jetbrains.kotlin.js.resolve.diagnostics;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
@@ -44,6 +45,7 @@ public interface ErrorsJs {
DiagnosticFactory1<JetElement, JetElement> REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED = DiagnosticFactory1.create(ERROR, DEFAULT); DiagnosticFactory1<JetElement, JetElement> REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED = DiagnosticFactory1.create(ERROR, DEFAULT);
DiagnosticFactory1<JetNamedDeclaration, String> NON_TOPLEVEL_CLASS_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_NAME); DiagnosticFactory1<JetNamedDeclaration, String> NON_TOPLEVEL_CLASS_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_NAME);
DiagnosticFactory0<JetNamedDeclaration> SECONDARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0<JetNamedDeclaration> SECONDARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> INLINE_CALL_CYCLE = DiagnosticFactory0.create(ERROR, DEFAULT);
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() { Object _initializer = new Object() {
+1
View File
@@ -13,5 +13,6 @@
<orderEntry type="module" module-name="js.translator" /> <orderEntry type="module" module-name="js.translator" />
<orderEntry type="module" module-name="util" /> <orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="js.parser" /> <orderEntry type="module" module-name="js.parser" />
<orderEntry type="module" module-name="frontend" />
</component> </component>
</module> </module>
@@ -18,16 +18,16 @@ package org.jetbrains.kotlin.js.inline;
import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.metadata.MetadataPackage; import com.google.dart.compiler.backend.js.ast.metadata.MetadataPackage;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.InlineStrategy; import org.jetbrains.kotlin.builtins.InlineStrategy;
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
import org.jetbrains.kotlin.js.inline.context.*; import org.jetbrains.kotlin.js.inline.context.*;
import org.jetbrains.kotlin.js.inline.exception.InlineRecursionException; import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs;
import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import java.util.IdentityHashMap; import java.util.*;
import java.util.Set;
import java.util.Stack;
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement; import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement;
import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions; import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions;
@@ -42,6 +42,11 @@ public class JsInliner extends JsVisitorWithContextImpl {
private final Set<JsFunction> processedFunctions = IdentitySet(); private final Set<JsFunction> processedFunctions = IdentitySet();
private final Set<JsFunction> inProcessFunctions = IdentitySet(); private final Set<JsFunction> inProcessFunctions = IdentitySet();
private final FunctionReader functionReader; private final FunctionReader functionReader;
private final DiagnosticSink trace;
// these are needed for error reporting, when inliner detects cycle
private final Stack<JsFunction> namedFunctionsStack = new Stack<JsFunction>();
private final LinkedList<JsCallInfo> inlineCallInfos = new LinkedList<JsCallInfo>();
/** /**
* A statement can contain more, than one inlineable sub-expressions. * A statement can contain more, than one inlineable sub-expressions.
@@ -76,7 +81,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
public static JsProgram process(@NotNull TranslationContext context) { public static JsProgram process(@NotNull TranslationContext context) {
JsProgram program = context.program(); JsProgram program = context.program();
IdentityHashMap<JsName, JsFunction> functions = collectNamedFunctions(program); IdentityHashMap<JsName, JsFunction> functions = collectNamedFunctions(program);
JsInliner inliner = new JsInliner(functions, new FunctionReader(context)); JsInliner inliner = new JsInliner(functions, new FunctionReader(context), context.bindingTrace());
inliner.accept(program); inliner.accept(program);
removeUnusedFunctionDefinitions(program, functions); removeUnusedFunctionDefinitions(program, functions);
return program; return program;
@@ -84,19 +89,24 @@ public class JsInliner extends JsVisitorWithContextImpl {
private JsInliner( private JsInliner(
@NotNull IdentityHashMap<JsName, JsFunction> functions, @NotNull IdentityHashMap<JsName, JsFunction> functions,
@NotNull FunctionReader functionReader @NotNull FunctionReader functionReader,
@NotNull DiagnosticSink trace
) { ) {
this.functions = functions; this.functions = functions;
this.functionReader = functionReader; this.functionReader = functionReader;
this.trace = trace;
} }
@Override @Override
public boolean visit(JsFunction function, JsContext context) { public boolean visit(JsFunction function, JsContext context) {
inliningContexts.push(new JsInliningContext(function)); inliningContexts.push(new JsInliningContext(function));
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
if (inProcessFunctions.contains(function)) throw new InlineRecursionException();
inProcessFunctions.add(function); inProcessFunctions.add(function);
if (functions.containsValue(function)) {
namedFunctionsStack.push(function);
}
return super.visit(function, context); return super.visit(function, context);
} }
@@ -112,16 +122,27 @@ public class JsInliner extends JsVisitorWithContextImpl {
inProcessFunctions.remove(function); inProcessFunctions.remove(function);
inliningContexts.pop(); inliningContexts.pop();
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
namedFunctionsStack.pop();
}
} }
@Override @Override
public boolean visit(JsInvocation call, JsContext context) { public boolean visit(JsInvocation call, JsContext context) {
if (call == null) {
return false;
}
if (shouldInline(call) && canInline(call)) { if (shouldInline(call) && canInline(call)) {
JsFunction containingFunction = getCurrentNamedFunction();
if (containingFunction != null) {
inlineCallInfos.add(new JsCallInfo(call, containingFunction));
}
JsFunction definition = getFunctionContext().getFunctionDefinition(call); JsFunction definition = getFunctionContext().getFunctionDefinition(call);
if (inProcessFunctions.contains(definition)) {
reportInlineCycle(call, definition);
return false;
}
if (!processedFunctions.contains(definition)) { if (!processedFunctions.contains(definition)) {
accept(definition); accept(definition);
} }
@@ -132,6 +153,19 @@ public class JsInliner extends JsVisitorWithContextImpl {
return !lastStatementWasShifted; return !lastStatementWasShifted;
} }
@Override
public void endVisit(JsInvocation x, JsContext ctx) {
JsCallInfo lastCallInfo = null;
if (!inlineCallInfos.isEmpty()) {
lastCallInfo = inlineCallInfos.getLast();
}
if (lastCallInfo != null && lastCallInfo.call == x) {
inlineCallInfos.removeLast();
}
}
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) { private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
JsInliningContext inliningContext = getInliningContext(); JsInliningContext inliningContext = getInliningContext();
FunctionContext functionContext = getFunctionContext(); FunctionContext functionContext = getFunctionContext();
@@ -141,6 +175,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
JsStatement inlineableBody = inlineableResult.getInlineableBody(); JsStatement inlineableBody = inlineableResult.getInlineableBody();
JsExpression resultExpression = inlineableResult.getResultExpression(); JsExpression resultExpression = inlineableResult.getResultExpression();
StatementContext statementContext = inliningContext.getStatementContext(); StatementContext statementContext = inliningContext.getStatementContext();
accept(inlineableBody);
/** /**
* Assumes, that resultExpression == null, when result is not needed. * Assumes, that resultExpression == null, when result is not needed.
@@ -183,6 +218,30 @@ public class JsInliner extends JsVisitorWithContextImpl {
return getInliningContext().getFunctionContext(); return getInliningContext().getFunctionContext();
} }
@Nullable
private JsFunction getCurrentNamedFunction() {
if (namedFunctionsStack.empty()) return null;
return namedFunctionsStack.peek();
}
private void reportInlineCycle(@NotNull JsInvocation call, @NotNull JsFunction calledFunction) {
MetadataPackage.setInlineStrategy(call, InlineStrategy.NOT_INLINE);
Iterator<JsCallInfo> it = inlineCallInfos.descendingIterator();
while (it.hasNext()) {
JsCallInfo callInfo = it.next();
PsiElement psiElement = MetadataPackage.getPsiElement(callInfo.call);
if (psiElement != null) {
trace.report(ErrorsJs.INLINE_CALL_CYCLE.on(psiElement));
}
if (callInfo.containingFunction == calledFunction) {
break;
}
}
}
private boolean canInline(@NotNull JsInvocation call) { private boolean canInline(@NotNull JsInvocation call) {
FunctionContext functionContext = getFunctionContext(); FunctionContext functionContext = getFunctionContext();
return functionContext.hasFunctionDefinition(call); return functionContext.hasFunctionDefinition(call);
@@ -193,7 +252,6 @@ public class JsInliner extends JsVisitorWithContextImpl {
return strategy != null && strategy.isInline(); return strategy != null && strategy.isInline();
} }
private class JsInliningContext implements InliningContext { private class JsInliningContext implements InliningContext {
private final FunctionContext functionContext; private final FunctionContext functionContext;
@@ -245,4 +303,17 @@ public class JsInliner extends JsVisitorWithContextImpl {
return functionContext; return functionContext;
} }
} }
private static class JsCallInfo {
@NotNull
public final JsInvocation call;
@NotNull
public final JsFunction containingFunction;
private JsCallInfo(@NotNull JsInvocation call, @NotNull JsFunction function) {
this.call = call;
containingFunction = function;
}
}
} }
@@ -1,23 +0,0 @@
/*
* Copyright 2010-2015 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.js.inline.exception;
public class InlineRecursionException extends RuntimeException {
public InlineRecursionException() {
super("Encountered mutual inline recursion, inline will lead to stack overflow");
}
}
@@ -71,12 +71,14 @@ public final class K2JSTranslator {
BindingTrace bindingTrace = analysisResult.getBindingTrace(); BindingTrace bindingTrace = analysisResult.getBindingTrace();
TopDownAnalyzerFacadeForJS.checkForErrors(Config.withJsLibAdded(files, config), bindingTrace.getBindingContext()); TopDownAnalyzerFacadeForJS.checkForErrors(Config.withJsLibAdded(files, config), bindingTrace.getBindingContext());
ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor(); ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor();
TranslationContext context = Translation.generateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config);
Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics(); Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics();
TranslationContext context = Translation.generateAst(bindingTrace, files, mainCallParameters, moduleDescriptor, config);
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
JsProgram program = JsInliner.process(context); JsProgram program = JsInliner.process(context);
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
return new TranslationResult.Success(config, files, program, diagnostics); return new TranslationResult.Success(config, files, program, diagnostics);
} }
} }
@@ -33,9 +33,7 @@ import org.jetbrains.kotlin.psi.JetStringTemplateExpression;
import org.jetbrains.kotlin.psi.ValueArgument; import org.jetbrains.kotlin.psi.ValueArgument;
import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.TemporaryBindingTrace; import org.jetbrains.kotlin.resolve.TemporaryBindingTrace;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.JetType;
@@ -45,6 +43,7 @@ import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getFunctionDescriptor;
import static org.jetbrains.kotlin.js.translate.utils.UtilsPackage.setInlineCallMetadata; import static org.jetbrains.kotlin.js.translate.utils.UtilsPackage.setInlineCallMetadata;
import static org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage.getFunctionResolvedCallWithAssert; import static org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage.getFunctionResolvedCallWithAssert;
import static org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker.isJsCall; import static org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker.isJsCall;
@@ -64,10 +63,9 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
} }
JsExpression callExpression = (new CallExpressionTranslator(expression, receiver, context)).translate(); JsExpression callExpression = (new CallExpressionTranslator(expression, receiver, context)).translate();
CallableDescriptor descriptor = getFunctionDescriptor(expression, context);
if (!resolvedCall.isSafeCall() && shouldBeInlined(expression, context)) { if (!resolvedCall.isSafeCall() && shouldBeInlined(expression, context)) {
setInlineCallMetadata(callExpression, descriptor, context); setInlineCallMetadata(callExpression, expression, context);
} }
return callExpression; return callExpression;
@@ -94,21 +92,6 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
return false; return false;
} }
@NotNull
private static CallableDescriptor getFunctionDescriptor(
@NotNull JetCallExpression expression,
@NotNull TranslationContext context
) {
ResolvedCall<?> resolvedCall = CallUtilPackage.getResolvedCall(expression, context.bindingContext());
assert resolvedCall != null;
if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
return ((VariableAsFunctionResolvedCall) resolvedCall).getVariableCall().getCandidateDescriptor();
}
return resolvedCall.getCandidateDescriptor();
}
private CallExpressionTranslator( private CallExpressionTranslator(
@NotNull JetCallExpression expression, @NotNull JetCallExpression expression,
@Nullable JsExpression receiver, @Nullable JsExpression receiver,
@@ -19,9 +19,14 @@ package org.jetbrains.kotlin.js.translate.utils;
import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.lexer.JetToken; import org.jetbrains.kotlin.lexer.JetToken;
import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -132,4 +137,19 @@ public final class PsiUtils {
assert rangeExpression != null; assert rangeExpression != null;
return rangeExpression; return rangeExpression;
} }
@NotNull
public static CallableDescriptor getFunctionDescriptor(
@NotNull JetCallExpression expression,
@NotNull TranslationContext context
) {
ResolvedCall<?> resolvedCall = CallUtilPackage.getResolvedCall(expression, context.bindingContext());
assert resolvedCall != null;
if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
return ((VariableAsFunctionResolvedCall) resolvedCall).getVariableCall().getCandidateDescriptor();
}
return resolvedCall.getCandidateDescriptor();
}
} }
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.js.inline.util.isCallInvocation
import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.JetCallExpression
/** /**
* Recursively walks expression and sets metadata for all invocations of descriptor. * Recursively walks expression and sets metadata for all invocations of descriptor.
@@ -38,9 +39,10 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
*/ */
fun setInlineCallMetadata( fun setInlineCallMetadata(
expression: JsExpression, expression: JsExpression,
descriptor: CallableDescriptor, jetCall: JetCallExpression,
context: TranslationContext context: TranslationContext
) { ) {
val descriptor = PsiUtils.getFunctionDescriptor(jetCall, context)
assert(CallExpressionTranslator.shouldBeInlined(descriptor)) { assert(CallExpressionTranslator.shouldBeInlined(descriptor)) {
"Expected descriptor of callable, that should be inlined, but got: $descriptor" "Expected descriptor of callable, that should be inlined, but got: $descriptor"
} }
@@ -56,6 +58,7 @@ fun setInlineCallMetadata(
if (name == invocation.name) { if (name == invocation.name) {
invocation.descriptor = descriptor invocation.descriptor = descriptor
invocation.inlineStrategy = InlineStrategy.IN_PLACE invocation.inlineStrategy = InlineStrategy.IN_PLACE
invocation.psiElement = jetCall
} }
} }
} }