/* * 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); }