| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| /* |
| * Functions for dealing with method prototypes |
| */ |
| |
| #include "DexProto.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /* |
| * =========================================================================== |
| * String Cache |
| * =========================================================================== |
| */ |
| |
| /* |
| * Make sure that the given cache can hold a string of the given length, |
| * including the final '\0' byte. |
| */ |
| static void dexStringCacheAlloc(DexStringCache* pCache, size_t length) { |
| if (pCache->allocatedSize != 0) { |
| if (pCache->allocatedSize >= length) { |
| return; |
| } |
| free((void*) pCache->value); |
| } |
| |
| if (length <= sizeof(pCache->buffer)) { |
| pCache->value = pCache->buffer; |
| pCache->allocatedSize = 0; |
| } else { |
| pCache->value = malloc(length); |
| pCache->allocatedSize = length; |
| } |
| } |
| |
| /* |
| * Initialize the given DexStringCache. Use this function before passing |
| * one into any other function. |
| */ |
| void dexStringCacheInit(DexStringCache* pCache) { |
| pCache->value = pCache->buffer; |
| pCache->allocatedSize = 0; |
| pCache->buffer[0] = '\0'; |
| } |
| |
| /* |
| * Release the allocated contents of the given DexStringCache, if any. |
| * Use this function after your last use of a DexStringCache. |
| */ |
| void dexStringCacheRelease(DexStringCache* pCache) { |
| if (pCache->allocatedSize != 0) { |
| free((void*) pCache->value); |
| pCache->value = pCache->buffer; |
| pCache->allocatedSize = 0; |
| } |
| } |
| |
| /* |
| * If the given DexStringCache doesn't already point at the given value, |
| * make a copy of it into the cache. This always returns a writable |
| * pointer to the contents (whether or not a copy had to be made). This |
| * function is intended to be used after making a call that at least |
| * sometimes doesn't populate a DexStringCache. |
| */ |
| char* dexStringCacheEnsureCopy(DexStringCache* pCache, const char* value) { |
| if (value != pCache->value) { |
| size_t length = strlen(value) + 1; |
| dexStringCacheAlloc(pCache, length); |
| memcpy(pCache->value, value, length); |
| } |
| |
| return pCache->value; |
| } |
| |
| /* |
| * Abandon the given DexStringCache, and return a writable copy of the |
| * given value (reusing the string cache's allocation if possible). |
| * The return value must be free()d by the caller. Use this instead of |
| * dexStringCacheRelease() if you want the buffer to survive past the |
| * scope of the DexStringCache. |
| */ |
| char* dexStringCacheAbandon(DexStringCache* pCache, const char* value) { |
| if ((value == pCache->value) && (pCache->allocatedSize != 0)) { |
| char* result = pCache->value; |
| pCache->allocatedSize = 0; |
| pCache->value = pCache->buffer; |
| return result; |
| } else { |
| return strdup(value); |
| } |
| } |
| |
| |
| /* |
| * =========================================================================== |
| * Method Prototypes |
| * =========================================================================== |
| */ |
| |
| /* |
| * Return the DexProtoId from the given DexProto. The DexProto must |
| * actually refer to a DexProtoId. |
| */ |
| static inline const DexProtoId* getProtoId(const DexProto* pProto) { |
| return dexGetProtoId(pProto->dexFile, pProto->protoIdx); |
| } |
| |
| /* |
| * Get the short-form method descriptor for the given prototype. The |
| * prototype must be protoIdx-based. |
| */ |
| const char* dexProtoGetShorty(const DexProto* pProto) { |
| const DexProtoId* protoId = getProtoId(pProto); |
| |
| return dexStringById(pProto->dexFile, protoId->shortyIdx); |
| } |
| |
| /* |
| * Get the full method descriptor for the given prototype. |
| */ |
| const char* dexProtoGetMethodDescriptor(const DexProto* pProto, |
| DexStringCache* pCache) { |
| const DexFile* dexFile = pProto->dexFile; |
| const DexProtoId* protoId = getProtoId(pProto); |
| const DexTypeList* typeList = dexGetProtoParameters(dexFile, protoId); |
| size_t length = 3; // parens and terminating '\0' |
| u4 paramCount = (typeList == NULL) ? 0 : typeList->size; |
| u4 i; |
| |
| for (i = 0; i < paramCount; i++) { |
| u4 idx = dexTypeListGetIdx(typeList, i); |
| length += strlen(dexStringByTypeIdx(dexFile, idx)); |
| } |
| |
| length += strlen(dexStringByTypeIdx(dexFile, protoId->returnTypeIdx)); |
| |
| dexStringCacheAlloc(pCache, length); |
| |
| char *at = (char*) pCache->value; |
| *(at++) = '('; |
| |
| for (i = 0; i < paramCount; i++) { |
| u4 idx = dexTypeListGetIdx(typeList, i); |
| const char* desc = dexStringByTypeIdx(dexFile, idx); |
| strcpy(at, desc); |
| at += strlen(desc); |
| } |
| |
| *(at++) = ')'; |
| |
| strcpy(at, dexStringByTypeIdx(dexFile, protoId->returnTypeIdx)); |
| return pCache->value; |
| } |
| |
| /* |
| * Get a copy of the descriptor string associated with the given prototype. |
| * The returned pointer must be free()ed by the caller. |
| */ |
| char* dexProtoCopyMethodDescriptor(const DexProto* pProto) { |
| DexStringCache cache; |
| |
| dexStringCacheInit(&cache); |
| return dexStringCacheAbandon(&cache, |
| dexProtoGetMethodDescriptor(pProto, &cache)); |
| } |
| |
| /* |
| * Get the parameter descriptors for the given prototype. This is the |
| * concatenation of all the descriptors for all the parameters, in |
| * order, with no other adornment. |
| */ |
| const char* dexProtoGetParameterDescriptors(const DexProto* pProto, |
| DexStringCache* pCache) { |
| DexParameterIterator iterator; |
| size_t length = 1; /* +1 for the terminating '\0' */ |
| |
| dexParameterIteratorInit(&iterator, pProto); |
| |
| for (;;) { |
| const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); |
| if (descriptor == NULL) { |
| break; |
| } |
| |
| length += strlen(descriptor); |
| } |
| |
| dexParameterIteratorInit(&iterator, pProto); |
| |
| dexStringCacheAlloc(pCache, length); |
| char *at = (char*) pCache->value; |
| |
| for (;;) { |
| const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); |
| if (descriptor == NULL) { |
| break; |
| } |
| |
| strcpy(at, descriptor); |
| at += strlen(descriptor); |
| } |
| |
| return pCache->value; |
| } |
| |
| /* |
| * Get the type descriptor for the return type of the given prototype. |
| */ |
| const char* dexProtoGetReturnType(const DexProto* pProto) { |
| const DexProtoId* protoId = getProtoId(pProto); |
| return dexStringByTypeIdx(pProto->dexFile, protoId->returnTypeIdx); |
| } |
| |
| /* |
| * Get the parameter count of the given prototype. |
| */ |
| size_t dexProtoGetParameterCount(const DexProto* pProto) { |
| const DexProtoId* protoId = getProtoId(pProto); |
| const DexTypeList* typeList = |
| dexGetProtoParameters(pProto->dexFile, protoId); |
| return (typeList == NULL) ? 0 : typeList->size; |
| } |
| |
| /* |
| * Compute the number of parameter words (u4 units) required by the |
| * given prototype. For example, if the method takes (int, long) and |
| * returns double, this would return 3 (one for the int, two for the |
| * long, and the return type isn't relevant). |
| */ |
| int dexProtoComputeArgsSize(const DexProto* pProto) { |
| const char* shorty = dexProtoGetShorty(pProto); |
| int count = 0; |
| |
| /* Skip the return type. */ |
| shorty++; |
| |
| for (;;) { |
| switch (*(shorty++)) { |
| case '\0': { |
| return count; |
| } |
| case 'D': |
| case 'J': { |
| count += 2; |
| break; |
| } |
| default: { |
| count++; |
| break; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Common implementation for dexProtoCompare() and dexProtoCompareParameters(). |
| */ |
| static int protoCompare(const DexProto* pProto1, const DexProto* pProto2, |
| bool compareReturnType) { |
| |
| if (pProto1 == pProto2) { |
| // Easy out. |
| return 0; |
| } else { |
| const DexFile* dexFile1 = pProto1->dexFile; |
| const DexProtoId* protoId1 = getProtoId(pProto1); |
| const DexTypeList* typeList1 = |
| dexGetProtoParameters(dexFile1, protoId1); |
| int paramCount1 = (typeList1 == NULL) ? 0 : typeList1->size; |
| |
| const DexFile* dexFile2 = pProto2->dexFile; |
| const DexProtoId* protoId2 = getProtoId(pProto2); |
| const DexTypeList* typeList2 = |
| dexGetProtoParameters(dexFile2, protoId2); |
| int paramCount2 = (typeList2 == NULL) ? 0 : typeList2->size; |
| |
| if (protoId1 == protoId2) { |
| // Another easy out. |
| return 0; |
| } |
| |
| // Compare return types. |
| |
| if (compareReturnType) { |
| int result = |
| strcmp(dexStringByTypeIdx(dexFile1, protoId1->returnTypeIdx), |
| dexStringByTypeIdx(dexFile2, protoId2->returnTypeIdx)); |
| |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| // Compare parameters. |
| |
| int minParam = (paramCount1 > paramCount2) ? paramCount2 : paramCount1; |
| int i; |
| |
| for (i = 0; i < minParam; i++) { |
| u4 idx1 = dexTypeListGetIdx(typeList1, i); |
| u4 idx2 = dexTypeListGetIdx(typeList2, i); |
| int result = |
| strcmp(dexStringByTypeIdx(dexFile1, idx1), |
| dexStringByTypeIdx(dexFile2, idx2)); |
| |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| if (paramCount1 < paramCount2) { |
| return -1; |
| } else if (paramCount1 > paramCount2) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| /* |
| * Compare the two prototypes. The two prototypes are compared |
| * with the return type as the major order, then the first arguments, |
| * then second, etc. If two prototypes are identical except that one |
| * has extra arguments, then the shorter argument is considered the |
| * earlier one in sort order (similar to strcmp()). |
| */ |
| int dexProtoCompare(const DexProto* pProto1, const DexProto* pProto2) { |
| return protoCompare(pProto1, pProto2, true); |
| } |
| |
| /* |
| * Compare the two prototypes. The two prototypes are compared |
| * with the first argument as the major order, then second, etc. If two |
| * prototypes are identical except that one has extra arguments, then the |
| * shorter argument is considered the earlier one in sort order (similar |
| * to strcmp()). |
| */ |
| int dexProtoCompareParameters(const DexProto* pProto1, const DexProto* pProto2){ |
| return protoCompare(pProto1, pProto2, false); |
| } |
| |
| |
| /* |
| * Helper for dexProtoCompareToDescriptor(), which gets the return type |
| * descriptor from a method descriptor string. |
| */ |
| static const char* methodDescriptorReturnType(const char* descriptor) { |
| const char* result = strchr(descriptor, ')'); |
| |
| if (result == NULL) { |
| return NULL; |
| } |
| |
| // The return type is the character just past the ')'. |
| return result + 1; |
| } |
| |
| /* |
| * Helper for dexProtoCompareToDescriptor(), which indicates the end |
| * of an embedded argument type descriptor, which is also the |
| * beginning of the next argument type descriptor. Since this is for |
| * argument types, it doesn't accept 'V' as a valid type descriptor. |
| */ |
| static const char* methodDescriptorNextType(const char* descriptor) { |
| // Skip any array references. |
| |
| while (*descriptor == '[') { |
| descriptor++; |
| } |
| |
| switch (*descriptor) { |
| case 'B': case 'C': case 'D': case 'F': |
| case 'I': case 'J': case 'S': case 'Z': { |
| return descriptor + 1; |
| } |
| case 'L': { |
| const char* result = strchr(descriptor + 1, ';'); |
| if (result != NULL) { |
| // The type ends just past the ';'. |
| return result + 1; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Compare a prototype and a string method descriptor. The comparison |
| * is done as if the descriptor were converted to a prototype and compared |
| * with dexProtoCompare(). |
| */ |
| int dexProtoCompareToDescriptor(const DexProto* proto, |
| const char* descriptor) { |
| // First compare the return types. |
| |
| int result = strcmp(dexProtoGetReturnType(proto), |
| methodDescriptorReturnType(descriptor)); |
| |
| if (result != 0) { |
| return result; |
| } |
| |
| // The return types match, so we have to check arguments. |
| |
| DexParameterIterator iterator; |
| dexParameterIteratorInit(&iterator, proto); |
| |
| // Skip the '('. |
| assert (*descriptor == '('); |
| descriptor++; |
| |
| for (;;) { |
| const char* protoDesc = dexParameterIteratorNextDescriptor(&iterator); |
| |
| if (*descriptor == ')') { |
| // It's the end of the descriptor string. |
| if (protoDesc == NULL) { |
| // It's also the end of the prototype's arguments. |
| return 0; |
| } else { |
| // The prototype still has more arguments. |
| return 1; |
| } |
| } |
| |
| if (protoDesc == NULL) { |
| /* |
| * The prototype doesn't have arguments left, but the |
| * descriptor string does. |
| */ |
| return -1; |
| } |
| |
| // Both prototype and descriptor have arguments. Compare them. |
| |
| const char* nextDesc = methodDescriptorNextType(descriptor); |
| |
| for (;;) { |
| char c1 = *(protoDesc++); |
| char c2 = (descriptor < nextDesc) ? *(descriptor++) : '\0'; |
| |
| if (c1 < c2) { |
| // This includes the case where the proto is shorter. |
| return -1; |
| } else if (c1 > c2) { |
| // This includes the case where the desc is shorter. |
| return 1; |
| } else if (c1 == '\0') { |
| // The two types are equal in length. (c2 necessarily == '\0'.) |
| break; |
| } |
| } |
| |
| /* |
| * If we made it here, the two arguments matched, and |
| * descriptor == nextDesc. |
| */ |
| } |
| } |
| |
| |
| /* |
| * =========================================================================== |
| * Parameter Iterators |
| * =========================================================================== |
| */ |
| |
| /* |
| * Initialize the given DexParameterIterator to be at the start of the |
| * parameters of the given prototype. |
| */ |
| void dexParameterIteratorInit(DexParameterIterator* pIterator, |
| const DexProto* pProto) { |
| pIterator->proto = pProto; |
| pIterator->cursor = 0; |
| |
| pIterator->parameters = |
| dexGetProtoParameters(pProto->dexFile, getProtoId(pProto)); |
| pIterator->parameterCount = (pIterator->parameters == NULL) ? 0 |
| : pIterator->parameters->size; |
| } |
| |
| /* |
| * Get the type_id index for the next parameter, if any. This returns |
| * kDexNoIndex if the last parameter has already been consumed. |
| */ |
| u4 dexParameterIteratorNextIndex(DexParameterIterator* pIterator) { |
| int cursor = pIterator->cursor; |
| int parameterCount = pIterator->parameterCount; |
| |
| if (cursor >= parameterCount) { |
| // The iteration is complete. |
| return kDexNoIndex; |
| } else { |
| u4 idx = dexTypeListGetIdx(pIterator->parameters, cursor); |
| pIterator->cursor++; |
| return idx; |
| } |
| } |
| |
| /* |
| * Get the type descriptor for the next parameter, if any. This returns |
| * NULL if the last parameter has already been consumed. |
| */ |
| const char* dexParameterIteratorNextDescriptor( |
| DexParameterIterator* pIterator) { |
| u4 idx = dexParameterIteratorNextIndex(pIterator); |
| |
| if (idx == kDexNoIndex) { |
| return NULL; |
| } |
| |
| return dexStringByTypeIdx(pIterator->proto->dexFile, idx); |
| } |