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