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.collections.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.MemberScope;
035    
036    import java.util.*;
037    
038    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
039    
040    public class ManglingUtils {
041        private ManglingUtils() {}
042    
043        private static final Comparator<CallableDescriptor> CALLABLE_COMPARATOR = new CallableComparator();
044    
045        @NotNull
046        public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
047            return getStableMangledName(suggestedName, getFqName(descriptor).asString());
048        }
049    
050        @NotNull
051        public static String getSuggestedName(@NotNull DeclarationDescriptor descriptor) {
052            String suggestedName = descriptor.getName().isSpecial() ? "f" : descriptor.getName().getIdentifier();
053    
054            if (descriptor instanceof FunctionDescriptor ||
055                descriptor instanceof PropertyDescriptor && DescriptorUtils.isExtension((PropertyDescriptor) descriptor)
056            ) {
057                suggestedName = getMangledName((CallableMemberDescriptor) descriptor);
058            }
059    
060            ClassDescriptor localClass = null;
061            if (descriptor instanceof ConstructorDescriptor) {
062                ConstructorDescriptor constructor = (ConstructorDescriptor) descriptor;
063                localClass = constructor.getContainingDeclaration();
064            }
065            else if (descriptor instanceof ClassDescriptor) {
066                localClass = (ClassDescriptor) descriptor;
067            }
068    
069            if (DescriptorUtils.isDescriptorWithLocalVisibility(localClass)) {
070                suggestedName = getSuggestedLocalPrefix(localClass) + suggestedName;
071            }
072    
073            return suggestedName;
074        }
075    
076        @NotNull
077        private static String getSuggestedLocalPrefix(@NotNull DeclarationDescriptor descriptor) {
078            List<String> parts = new ArrayList<String>();
079            while (true) {
080                descriptor = descriptor.getContainingDeclaration();
081                if (descriptor == null || descriptor instanceof ClassOrPackageFragmentDescriptor) {
082                    break;
083                }
084                parts.add(descriptor.getName().isSpecial() ? "f" : descriptor.getName().getIdentifier());
085            }
086    
087            Collections.reverse(parts);
088            String result = StringUtil.join(parts, "$");
089            return !result.isEmpty() ? result + "$" : "";
090        }
091    
092        @NotNull
093        private static String getMangledName(@NotNull CallableMemberDescriptor descriptor) {
094            if (needsStableMangling(descriptor)) {
095                return getStableMangledName(descriptor);
096            }
097    
098            return getSimpleMangledName(descriptor);
099        }
100    
101        //TODO extend logic for nested/inner declarations
102        private static boolean needsStableMangling(CallableMemberDescriptor descriptor) {
103            // Use stable mangling for overrides because we use stable mangling when any function inside a overridable declaration
104            // for avoid clashing names when inheritance.
105            if (DescriptorUtils.isOverride(descriptor)) {
106                return true;
107            }
108    
109            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
110    
111            if (containingDeclaration instanceof PackageFragmentDescriptor) {
112                return descriptor.getVisibility().isPublicAPI();
113            }
114            else if (containingDeclaration instanceof ClassDescriptor) {
115                ClassDescriptor classDescriptor = (ClassDescriptor) containingDeclaration;
116    
117                // Use stable mangling when it's inside an overridable declaration to avoid clashing names on inheritance.
118                if (!ModalityKt.isFinalOrEnum(classDescriptor)) {
119                    return true;
120                }
121    
122                // valueOf() is created in the library with a mangled name for every enum class
123                if (descriptor instanceof FunctionDescriptor && CodegenUtil.isEnumValueOfMethod((FunctionDescriptor) descriptor)) {
124                    return true;
125                }
126    
127                // Don't use stable mangling when it inside a non-public API declaration.
128                if (!classDescriptor.getVisibility().isPublicAPI()) {
129                    return false;
130                }
131    
132                // Ignore the `protected` visibility because it can be use outside a containing declaration
133                // only when the containing declaration is overridable.
134                if (descriptor.getVisibility() == Visibilities.PUBLIC) {
135                    return true;
136                }
137    
138                return false;
139            }
140    
141            assert containingDeclaration instanceof CallableMemberDescriptor :
142                    "containingDeclaration for descriptor have unsupported type for mangling, " +
143                    "descriptor: " + descriptor + ", containingDeclaration: " + containingDeclaration;
144    
145            return false;
146        }
147    
148        @NotNull
149        public static String getMangledMemberNameForExplicitDelegation(
150                @NotNull String suggestedName,
151                @NotNull FqNameUnsafe classFqName,
152                @NotNull FqNameUnsafe typeFqName
153        ) {
154            String forCalculateId = classFqName.asString() + ":" + typeFqName.asString();
155            return getStableMangledName(suggestedName, forCalculateId);
156        }
157    
158        @NotNull
159        private static String getStableMangledName(@NotNull String suggestedName, String forCalculateId) {
160            int absHashCode = Math.abs(forCalculateId.hashCode());
161            String suffix = absHashCode == 0 ? "" : ("_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$");
162            return suggestedName + suffix;
163        }
164    
165        @NotNull
166        private static String getStableMangledName(@NotNull CallableDescriptor descriptor) {
167            String suggestedName = getSuggestedName(descriptor);
168            return getStableMangledName(suggestedName, getArgumentTypesAsString(descriptor));
169        }
170    
171        @NotNull
172        private static String getSuggestedName(@NotNull CallableDescriptor descriptor) {
173            if (descriptor instanceof ConstructorDescriptor && !((ConstructorDescriptor) descriptor).isPrimary()) {
174                return descriptor.getContainingDeclaration().getName().asString() + "_init";
175            }
176            else {
177                return descriptor.getName().asString();
178            }
179        }
180    
181        @NotNull
182        private static String getSimpleMangledName(@NotNull CallableMemberDescriptor descriptor) {
183            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
184    
185            MemberScope jetScope = null;
186    
187            String nameToCompare = descriptor.getName().asString();
188    
189            if (descriptor instanceof ConstructorDescriptor) {
190                nameToCompare = containingDeclaration.getName().asString();
191                containingDeclaration = containingDeclaration.getContainingDeclaration();
192            }
193    
194            if (containingDeclaration instanceof PackageFragmentDescriptor) {
195                jetScope = ((PackageFragmentDescriptor) containingDeclaration).getMemberScope();
196            }
197            else if (containingDeclaration instanceof ClassDescriptor) {
198                jetScope = ((ClassDescriptor) containingDeclaration).getDefaultType().getMemberScope();
199            }
200    
201            int counter = 0;
202    
203            if (jetScope != null) {
204                final String finalNameToCompare = nameToCompare;
205    
206                Collection<DeclarationDescriptor> declarations = DescriptorUtils.getAllDescriptors(jetScope);
207                List<CallableDescriptor> overloadedFunctions =
208                        CollectionsKt.flatMap(declarations, new Function1<DeclarationDescriptor, Iterable<? extends CallableDescriptor>>() {
209                    @Override
210                    public Iterable<? extends CallableDescriptor> invoke(DeclarationDescriptor declarationDescriptor) {
211                        if (declarationDescriptor instanceof ClassDescriptor && finalNameToCompare.equals(declarationDescriptor.getName().asString())) {
212                            ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
213                            Collection<ConstructorDescriptor> constructors = classDescriptor.getConstructors();
214    
215                            if (!DescriptorUtilsKt.hasPrimaryConstructor(classDescriptor)) {
216                                ConstructorDescriptorImpl fakePrimaryConstructor =
217                                        ConstructorDescriptorImpl.create(classDescriptor, Annotations.Companion.getEMPTY(), true, SourceElement.NO_SOURCE);
218                                return CollectionsKt.plus(constructors, fakePrimaryConstructor);
219                            }
220    
221                            return constructors;
222                        }
223    
224                        if (!(declarationDescriptor instanceof CallableMemberDescriptor)) return Collections.emptyList();
225    
226                        CallableMemberDescriptor callableMemberDescriptor = (CallableMemberDescriptor) declarationDescriptor;
227    
228                        String name = AnnotationsUtils.getNameForAnnotatedObjectWithOverrides(callableMemberDescriptor);
229    
230                        // when name == null it's mean that it's not native.
231                        if (name == null) {
232                            // skip functions without arguments, because we don't use mangling for them
233                            if (needsStableMangling(callableMemberDescriptor) && !callableMemberDescriptor.getValueParameters().isEmpty()) return Collections.emptyList();
234    
235                            // TODO add prefix for property: get_$name and set_$name
236                            name = callableMemberDescriptor.getName().asString();
237                        }
238    
239                        if (finalNameToCompare.equals(name)) return Collections.singletonList(callableMemberDescriptor);
240    
241                        return Collections.emptyList();
242                    }
243                });
244    
245                if (overloadedFunctions.size() > 1) {
246                    Collections.sort(overloadedFunctions, CALLABLE_COMPARATOR);
247                    counter = ContainerUtil.indexOfIdentity(overloadedFunctions, descriptor);
248                    assert counter >= 0;
249                }
250            }
251    
252            String name = getSuggestedName(descriptor);
253            return counter == 0 ? name : name + '_' + counter;
254        }
255    
256        private static String getArgumentTypesAsString(CallableDescriptor descriptor) {
257            StringBuilder argTypes = new StringBuilder();
258    
259            ReceiverParameterDescriptor receiverParameter = descriptor.getExtensionReceiverParameter();
260            if (receiverParameter != null) {
261                argTypes.append(DescriptorUtilsKt.getJetTypeFqName(receiverParameter.getType(), true)).append(".");
262            }
263    
264            argTypes.append(StringUtil.join(descriptor.getValueParameters(), new Function<ValueParameterDescriptor, String>() {
265                @Override
266                public String fun(ValueParameterDescriptor descriptor) {
267                    return DescriptorUtilsKt.getJetTypeFqName(descriptor.getType(), true);
268                }
269            }, ","));
270    
271            return argTypes.toString();
272        }
273    
274        @NotNull
275        public static String getStableMangledNameForDescriptor(@NotNull ClassDescriptor descriptor, @NotNull String functionName) {
276            Collection<SimpleFunctionDescriptor> functions =
277                    descriptor.getDefaultType().getMemberScope().getContributedFunctions(Name.identifier(functionName), NoLookupLocation.FROM_BACKEND);
278            assert functions.size() == 1 : "Can't select a single function: " + functionName + " in " + descriptor;
279            return getSuggestedName((DeclarationDescriptor) functions.iterator().next());
280        }
281    
282        private static class CallableComparator implements Comparator<CallableDescriptor> {
283            @Override
284            public int compare(@NotNull CallableDescriptor a, @NotNull CallableDescriptor b) {
285                // primary constructors
286                if (a instanceof ConstructorDescriptor && ((ConstructorDescriptor) a).isPrimary()) {
287                    if (!(b instanceof ConstructorDescriptor) || !((ConstructorDescriptor) b).isPrimary()) return -1;
288                }
289                else if (b instanceof ConstructorDescriptor && ((ConstructorDescriptor) b).isPrimary()) {
290                    return 1;
291                }
292    
293                // native functions
294                if (isNativeOrOverrideNative(a)) {
295                    if (!isNativeOrOverrideNative(b)) return -1;
296                }
297                else if (isNativeOrOverrideNative(b)) {
298                    return 1;
299                }
300    
301                // be visibility
302                // Actually "internal" > "private", but we want to have less number for "internal", so compare b with a instead of a with b.
303                Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
304                if (result != null && result != 0) return result;
305    
306                // by arity
307                int aArity = arity(a);
308                int bArity = arity(b);
309                if (aArity != bArity) return aArity - bArity;
310    
311                // by stringify argument types
312                String aArguments = getArgumentTypesAsString(a);
313                String bArguments = getArgumentTypesAsString(b);
314                assert aArguments != bArguments;
315    
316                return aArguments.compareTo(bArguments);
317            }
318    
319            private static int arity(CallableDescriptor descriptor) {
320                return descriptor.getValueParameters().size() + (descriptor.getExtensionReceiverParameter() == null ? 0 : 1);
321            }
322    
323            private static boolean isNativeOrOverrideNative(CallableDescriptor descriptor) {
324                if (!(descriptor instanceof CallableMemberDescriptor)) return false;
325    
326                if (AnnotationsUtils.isNativeObject(descriptor)) return true;
327    
328                Set<CallableMemberDescriptor> declarations = DescriptorUtils.getAllOverriddenDeclarations((CallableMemberDescriptor) descriptor);
329                for (CallableMemberDescriptor memberDescriptor : declarations) {
330                    if (AnnotationsUtils.isNativeObject(memberDescriptor)) return true;
331                }
332                return false;
333            }
334        }
335    }