JS: prohibit declaration names that clash with JS builtin functions

See KT-18095, KT-17475, KT-18105, KT-5259
This commit is contained in:
Alexey Andreev
2017-10-16 18:51:14 +03:00
parent e0eea15a4c
commit 6adb62f3a2
12 changed files with 288 additions and 8 deletions
@@ -0,0 +1,56 @@
class C {
class <!JS_BUILTIN_NAME_CLASH!>prototype<!>
class <!JS_BUILTIN_NAME_CLASH!>length<!>
class <!JS_BUILTIN_NAME_CLASH!>`$metadata$`<!>
<!JS_BUILTIN_NAME_CLASH!>fun constructor()<!> {}
}
class D {
private class <!JS_BUILTIN_NAME_CLASH!>prototype<!>
private class <!JS_BUILTIN_NAME_CLASH!>length<!>
private class <!JS_BUILTIN_NAME_CLASH!>`$metadata$`<!>
private fun constructor() {}
}
class E {
@JsName("prototype")
class <!JS_BUILTIN_NAME_CLASH!>D<!>
<!JS_BUILTIN_NAME_CLASH!>@JsName("constructor")
fun f()<!> {}
}
class F {
@JsName("A")
class prototype
@JsName("B")
class length
@JsName("f")
fun constructor() {}
}
class prototype
class length
fun constructor() {
}
fun f() {
class prototype
class length
fun constructor() {}
}
external interface Object {
val constructor: Any?
}
@@ -0,0 +1,120 @@
package
public fun constructor(): kotlin.Unit
public fun f(): kotlin.Unit
public final class C {
public constructor C()
public final fun constructor(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final class `$metadata$` {
public constructor `$metadata$`()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class length {
public constructor length()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class prototype {
public constructor prototype()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public final class D {
public constructor D()
private final fun constructor(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
private final class `$metadata$` {
public constructor `$metadata$`()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private final class length {
public constructor length()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private final class prototype {
public constructor prototype()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public final class E {
public constructor E()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
@kotlin.js.JsName(name = "constructor") public final fun f(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
@kotlin.js.JsName(name = "prototype") public final class D {
public constructor D()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public final class F {
public constructor F()
@kotlin.js.JsName(name = "f") public final fun constructor(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
@kotlin.js.JsName(name = "B") public final class length {
public constructor length()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@kotlin.js.JsName(name = "A") public final class prototype {
public constructor prototype()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public external interface Object {
public abstract val constructor: kotlin.Any?
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class length {
public constructor length()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class prototype {
public constructor prototype()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -485,6 +485,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
doTest(fileName);
}
@TestMetadata("builtinClash.kt")
public void testBuiltinClash() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt");
doTest(fileName);
}
@TestMetadata("classAndFunction.kt")
public void testClassAndFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt");
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.resolve
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.js.naming.NameSuggestion
import org.jetbrains.kotlin.js.resolve.diagnostics.*
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
import org.jetbrains.kotlin.resolve.OverloadFilter
@@ -56,11 +57,13 @@ object JsPlatformConfigurator : PlatformConfigurator(
overridesBackwardCompatibilityHelper = OverridesBackwardCompatibilityHelper.DEFAULT
) {
override fun configureModuleComponents(container: StorageComponentContainer) {
container.useInstance(NameSuggestion())
container.useImpl<JsCallChecker>()
container.useInstance(SyntheticScopes.Empty)
container.useInstance(JsTypeSpecificityComparator)
container.useInstance(JsNameClashChecker())
container.useInstance(JsNameCharsChecker())
container.useImpl<JsNameClashChecker>()
container.useImpl<JsNameCharsChecker>()
container.useImpl<JsBuiltinNameClashChecker>()
container.useInstance(JsModuleClassLiteralChecker)
container.useImpl<JsReflectionAPICallChecker>()
container.useImpl<JsNativeRttiChecker>()
@@ -44,6 +44,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
Renderers.STRING, Renderers.COMPACT)
put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}",
Renderers.STRING, Renderers.COMPACT, Renderers.COMPACT)
put(ErrorsJs.JS_BUILTIN_NAME_CLASH, "JavaScript name generated for this declaration clashes with built-in declaration {1}",
Renderers.STRING)
put(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors")
put(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them")
put(ErrorsJs.JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all of the property accessors")
@@ -48,6 +48,8 @@ public interface ErrorsJs {
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory3<KtElement, String, DeclarationDescriptor, DeclarationDescriptor> JS_FAKE_NAME_CLASH =
DiagnosticFactory3.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<KtElement, String> JS_BUILTIN_NAME_CLASH = DiagnosticFactory1.create(
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2017 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.resolve.diagnostics
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.js.naming.NameSuggestion
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
class JsBuiltinNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker {
override fun check(
declaration: KtDeclaration, descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink, bindingContext: BindingContext
) {
if (AnnotationsUtils.isNativeObject(descriptor)) return
if (descriptor.containingDeclaration !is ClassDescriptor) return
val suggestedName = nameSuggestion.suggest(descriptor)!!
if (!suggestedName.stable) return
val simpleName = suggestedName.names.single()
if (descriptor is ClassDescriptor) {
if (simpleName in PROHIBITED_STATIC_NAMES) {
diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Function.$simpleName"))
}
}
else if (descriptor is CallableMemberDescriptor) {
if (simpleName in PROHIBITED_MEMBER_NAMES) {
diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Object.prototype.$simpleName"))
}
}
}
companion object {
@JvmField
val PROHIBITED_STATIC_NAMES = setOf("prototype", "length", "\$metadata\$")
@JvmField
val PROHIBITED_MEMBER_NAMES = setOf("constructor")
}
}
@@ -41,9 +41,7 @@ import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
class JsNameCharsChecker() : SimpleDeclarationChecker {
val suggestion = NameSuggestion()
class JsNameCharsChecker(private val suggestion: NameSuggestion) : SimpleDeclarationChecker {
override fun check(
declaration: KtDeclaration, descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink, bindingContext: BindingContext
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
import org.jetbrains.kotlin.resolve.scopes.MemberScope
class JsNameClashChecker : SimpleDeclarationChecker {
class JsNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker {
companion object {
private val COMMON_DIAGNOSTICS = setOf(
Errors.REDECLARATION,
@@ -38,7 +38,6 @@ class JsNameClashChecker : SimpleDeclarationChecker {
Errors.PACKAGE_OR_CLASSIFIER_REDECLARATION)
}
private val nameSuggestion = NameSuggestion()
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
private val clashedDescriptors = mutableSetOf<Pair<DeclarationDescriptor, String>>()
@@ -6331,6 +6331,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/topLevelFunctionAndParameter.kt");
doTest(fileName);
}
@TestMetadata("withBuiltin.kt")
public void testWithBuiltin() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/withBuiltin.kt");
doTest(fileName);
}
}
@TestMetadata("js/js.translator/testData/box/native")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.context;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.hash.LinkedHashMap;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.NameSuggestionKt;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.resolve.diagnostics.JsBuiltinNameClashChecker;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.context.generator.Generator;
import org.jetbrains.kotlin.js.translate.context.generator.Rule;
@@ -146,6 +148,11 @@ public final class StaticContext {
private final boolean isStdlib;
private static final Set<String> BUILTIN_JS_PROPERTIES = Sets.union(
JsBuiltinNameClashChecker.PROHIBITED_MEMBER_NAMES,
JsBuiltinNameClashChecker.PROHIBITED_STATIC_NAMES
);
public StaticContext(
@NotNull BindingTrace bindingTrace,
@NotNull JsConfig config,
@@ -615,7 +622,6 @@ public final class StaticContext {
}
private final class ScopeGenerator extends Generator<JsScope> {
public ScopeGenerator() {
Rule<JsScope> generateNewScopesForClassesWithNoAncestors = descriptor -> {
if (!(descriptor instanceof ClassDescriptor)) {
@@ -623,6 +629,9 @@ public final class StaticContext {
}
if (getSuperclass((ClassDescriptor) descriptor) == null) {
JsFunction function = new JsFunction(new JsRootScope(program), new JsBlock(), descriptor.toString());
for (String builtinName : BUILTIN_JS_PROPERTIES) {
function.getScope().declareName(builtinName);
}
scopeToFunction.put(function.getScope(), function);
return function.getScope();
}
@@ -0,0 +1,19 @@
// EXPECTED_REACHABLE_NODES: 1135
open class C {
private fun constructor() = "C.constructor"
fun f(): String = constructor()
}
class D : C()
fun box(): String {
val d = D()
val x = d.f()
if (x != "C.constructor") return "fail1: $x"
if (x.asDynamic().constructor === D::class.js) return "fail2"
return "OK"
}