001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.js.translate.utils;
018    
019    import com.intellij.openapi.util.text.StringUtil;
020    import com.intellij.util.Function;
021    import com.intellij.util.containers.ContainerUtil;
022    import kotlin.CollectionsKt;
023    import kotlin.jvm.functions.Function1;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.kotlin.backend.common.CodegenUtil;
026    import org.jetbrains.kotlin.descriptors.*;
027    import org.jetbrains.kotlin.descriptors.annotations.Annotations;
028    import org.jetbrains.kotlin.descriptors.impl.ConstructorDescriptorImpl;
029    import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
030    import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
031    import org.jetbrains.kotlin.name.FqNameUnsafe;
032    import org.jetbrains.kotlin.name.Name;
033    import org.jetbrains.kotlin.resolve.DescriptorUtils;
034    import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
035    import org.jetbrains.kotlin.resolve.scopes.KtScope;
036    
037    import java.util.*;
038    
039    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
040    
041    public class ManglingUtils {
042        private ManglingUtils() {}
043    
044        public static final Comparator<CallableDescriptor> CALLABLE_COMPARATOR = new CallableComparator();
045    
046        @NotNull
047        public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
048            return getStableMangledName(suggestedName, getFqName(descriptor).asString());
049        }
050    
051        @NotNull
052        public static String getSuggestedName(@NotNull DeclarationDescriptor descriptor) {
053            String suggestedName = descriptor.getName().asString();
054    
055            if (descriptor instanceof FunctionDescriptor ||
056                descriptor instanceof PropertyDescriptor && DescriptorUtils.isExtension((PropertyDescriptor) descriptor)
057            ) {
058                suggestedName = getMangledName((CallableMemberDescriptor) descriptor);
059            }
060    
061            return suggestedName;
062        }
063    
064        @NotNull
065        private static String getMangledName(@NotNull CallableMemberDescriptor descriptor) {
066            if (needsStableMangling(descriptor)) {
067                return getStableMangledName(descriptor);
068            }
069    
070            return getSimpleMangledName(descriptor);
071        }
072    
073        //TODO extend logic for nested/inner declarations
074        private static boolean needsStableMangling(CallableMemberDescriptor descriptor) {
075            // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration
076            // for avoid clashing names when inheritance.
077            if (DescriptorUtils.isOverride(descriptor)) {
078                return true;
079            }
080    
081            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
082    
083            if (containingDeclaration instanceof PackageFragmentDescriptor) {
084                return descriptor.getVisibility().isPublicAPI();
085            }
086            else if (containingDeclaration instanceof ClassDescriptor) {
087                ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
088    
089                // Use stable mangling when it's inside an overridable declaration to avoid clashing names on inheritance.
090                if (classDescriptor.getModality().isOverridable()) {
091                    return true;
092                }
093    
094                // valueOf() is created in the library with a mangled name for every enum class
095                if (descriptor instanceof FunctionDescriptor && CodegenUtil.isEnumValueOfMethod((FunctionDescriptor) descriptor)) {
096                    return true;
097                }
098    
099                // Don't use stable mangling when it inside a non-public API declaration.
100                if (!classDescriptor.getVisibility().isPublicAPI()) {
101                    return false;
102                }
103    
104                // Ignore the `protected` visibility because it can be use outside a containing declaration
105                // only when the containing declaration is overridable.
106                if (descriptor.getVisibility() == Visibilities.PUBLIC) {
107                    return true;
108                }
109    
110                return false;
111            }
112    
113            assert containingDeclaration instanceof CallableMemberDescriptor :
114                    "containingDeclaration for descriptor have unsupported type for mangling, " +
115                    "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration;
116    
117            return false;
118        }
119    
120        @NotNull
121        public static String getMangledMemberNameForExplicitDelegation(
122                @NotNull String suggestedName,
123                @NotNull FqNameUnsafe classFqName,
124                @NotNull FqNameUnsafe typeFqName
125        ) {
126            String forCalculateId = classFqName.asString() + ":" + typeFqName.asString();
127            return getStableMangledName(suggestedName, forCalculateId);
128        }
129    
130        @NotNull
131        private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) {
132            int absHashCode = Math.abs(forCalculateId.hashCode());
133            String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$");
134            return suggestedName + suffix;
135        }
136    
137        @NotNull
138        private static String getStableMangledName(@NotNull CallableDescriptor descriptor) {
139            String suggestedName = getSuggestedName(descriptor);
140            return getStableMangledName(suggestedName, getArgumentTypesAsString(descriptor));
141        }
142    
143        @NotNull
144        private static String getSuggestedName(@NotNull CallableDescriptor descriptor) {
145            if (descriptor instanceof ConstructorDescriptor && !((ConstructorDescriptor) descriptor).isPrimary()) {
146                return descriptor.getContainingDeclaration().getName().asString();
147            }
148            else {
149                return descriptor.getName().asString();
150            }
151        }
152    
153        @NotNull
154        private static String getSimpleMangledName(@NotNull CallableMemberDescriptor descriptor) {
155            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
156    
157            KtScope jetScope = null;
158    
159            String nameToCompare = descriptor.getName().asString();
160    
161            if (descriptor instanceof ConstructorDescriptor) {
162                nameToCompare = containingDeclaration.getName().asString();
163                containingDeclaration = containingDeclaration.getContainingDeclaration();
164            }
165    
166            if (containingDeclaration instanceof PackageFragmentDescriptor) {
167                jetScope = ((PackageFragmentDescriptor) containingDeclaration).getMemberScope();
168            }
169            else if (containingDeclaration instanceof ClassDescriptor) {
170                jetScope = ((ClassDescriptor) containingDeclaration).getDefaultType().getMemberScope();
171            }
172    
173            int counter = 0;
174    
175            if (jetScope != null) {
176                final String finalNameToCompare = nameToCompare;
177    
178                Collection<DeclarationDescriptor> declarations = jetScope.getDescriptors(DescriptorKindFilter.CALLABLES, KtScope.Companion.getALL_NAME_FILTER());
179                List<CallableDescriptor> overloadedFunctions =
180                        CollectionsKt.flatMap(declarations, new Function1<DeclarationDescriptor, Iterable<? extends CallableDescriptor>>() {
181                    @Override
182                    public Iterable<? extends CallableDescriptor> invoke(DeclarationDescriptor declarationDescriptor) {
183                        if (declarationDescriptor instanceof ClassDescriptor && finalNameToCompare.equals(declarationDescriptor.getName().asString())) {
184                            ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
185                            Collection<ConstructorDescriptor> constructors = classDescriptor.getConstructors();
186    
187                            if (!DescriptorUtilsKt.hasPrimaryConstructor(classDescriptor)) {
188                                ConstructorDescriptorImpl fakePrimaryConstructor =
189                                        ConstructorDescriptorImpl.create(classDescriptor, Annotations.Companion.getEMPTY(), true, SourceElement.NO_SOURCE);
190                                return CollectionsKt.plus(constructors, fakePrimaryConstructor);
191                            }
192    
193                            return constructors;
194                        }
195    
196                        if (!(declarationDescriptor instanceof CallableMemberDescriptor)) return Collections.emptyList();
197    
198                        CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) declarationDescriptor;
199    
200                        String name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(callableMemberDescriptor);
201    
202                        // when name == null it's mean that it's not native.
203                        if (name == null) {
204                            // skip functions without arguments, because we don't use mangling for them
205                            if (needsStableMangling(callableMemberDescriptor) && !callableMemberDescriptor.getValueParameters().isEmpty()) return Collections.emptyList();
206    
207                            // TODO add prefix for property: get_$name and set_$name
208                            name = callableMemberDescriptor.getName().asString();
209                        }
210    
211                        if (finalNameToCompare.equals(name)) return Collections.singletonList(callableMemberDescriptor);
212    
213                        return Collections.emptyList();
214                    }
215                });
216    
217                if (overloadedFunctions.size() > 1) {
218                    Collections.sort(overloadedFunctions, CALLABLE_COMPARATOR);
219                    counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor);
220                    assert counter >= 0;
221                }
222            }
223    
224            String name = getSuggestedName(descriptor);
225            return counter == 0 ? name : name + '_' + counter;
226        }
227    
228        private static String getArgumentTypesAsString(CallableDescriptor descriptor) {
229            StringBuilder argTypes = new StringBuilder();
230    
231            ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
232            if (receiverParameter != null) {
233                argTypes.append(DescriptorUtilsKt.getJetTypeFqName(receiverParameter.getType(), true)).append(".");
234            }
235    
236            argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function<ValueParameterDescriptor, String>() {
237                @Override
238                public String fun(ValueParameterDescriptor descriptor) {
239                    return DescriptorUtilsKt.getJetTypeFqName(descriptor.getType(), true);
240                }
241            }, ","));
242    
243            return argTypes.toString();
244        }
245    
246        @NotNull
247        public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) {
248            Collection<FunctionDescriptor> functions =
249                    descriptor.getDefaultType().getMemberScope().getFunctions(Name.identifier(functionName), NoLookupLocation.FROM_BACKEND);
250            assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor;
251            return getSuggestedName((DeclarationDescriptor) functions.iterator().next());
252        }
253    
254        private static class CallableComparator implements Comparator<CallableDescriptor> {
255            @Override
256            public int compare(@NotNull CallableDescriptor a, @NotNull CallableDescriptor b) {
257                // primary constructors
258                if (a instanceof ConstructorDescriptor && ((ConstructorDescriptor) a).isPrimary()) {
259                    if (!(b instanceof ConstructorDescriptor) || !((ConstructorDescriptor) b).isPrimary()) return -1;
260                }
261                else if (b instanceof ConstructorDescriptor && ((ConstructorDescriptor) b).isPrimary()) {
262                    return 1;
263                }
264    
265                // native functions
266                if (isNativeOrOverrideNative(a)) {
267                    if (!isNativeOrOverrideNative(b)) return -1;
268                }
269                else if (isNativeOrOverrideNative(b)) {
270                    return 1;
271                }
272    
273                // be visibility
274                // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b.
275                Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
276                if (result != null && result != 0) return result;
277    
278                // by arity
279                int aArity = arity(a);
280                int bArity = arity(b);
281                if (aArity != bArity) return aArity - bArity;
282    
283                // by stringify argument types
284                String aArguments = getArgumentTypesAsString(a);
285                String bArguments = getArgumentTypesAsString(b);
286                assert aArguments != bArguments;
287    
288                return aArguments.compareTo(bArguments);
289            }
290    
291            private static int arity(CallableDescriptor descriptor) {
292                return descriptor.getValueParameters().size() + (descriptor.getExtensionReceiverParameter() == null ? 0 : 1);
293            }
294    
295            private static boolean isNativeOrOverrideNative(CallableDescriptor descriptor) {
296                if (!(descriptor instanceof CallableMemberDescriptor)) return false;
297    
298                if (AnnotationsUtils.isNativeObject(descriptor)) return true;
299    
300                Set<CallableMemberDescriptor> declarations = DescriptorUtils.getAllOverriddenDeclarations((CallableMemberDescriptor) descriptor);
301                for (CallableMemberDescriptor memberDescriptor : declarations) {
302                    if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
303                }
304                return false;
305            }
306        }
307    }