/*
* 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.
*/
/*
* Dalvik bytecode structural verifier. The only public entry point
* (except for a few shared utility functions) is dvmVerifyCodeFlow().
*
* TODO: might benefit from a signature-->class lookup cache. Could avoid
* some string-peeling and wouldn't need to compute hashes.
*/
#include "Dalvik.h"
#include "analysis/Liveness.h"
#include "analysis/CodeVerify.h"
#include "analysis/Optimize.h"
#include "analysis/RegisterMap.h"
#include "libdex/DexCatch.h"
#include "libdex/InstrUtils.h"
#include <stddef.h>
/*
* We don't need to store the register data for many instructions, because
* we either only need it at branch points (for verification) or GC points
* and branches (for verification + type-precise register analysis).
*/
enum RegisterTrackingMode {
kTrackRegsBranches,
kTrackRegsGcPoints,
kTrackRegsAll
};
/*
* Set this to enable dead code scanning. This is not required, but it's
* very useful when testing changes to the verifier (to make sure we're not
* skipping over stuff) and for checking the optimized output from "dx".
* The only reason not to do it is that it slightly increases the time
* required to perform verification.
*/
#ifndef NDEBUG
# define DEAD_CODE_SCAN true
#else
# define DEAD_CODE_SCAN false
#endif
static bool gDebugVerbose = false;
#define SHOW_REG_DETAILS \
(0 | DRT_SHOW_LIVENESS /*| DRT_SHOW_REF_TYPES | DRT_SHOW_LOCALS*/)
/*
* We need an extra "pseudo register" to hold the return type briefly. It
* can be category 1 or 2, so we need two slots.
*/
#define kExtraRegs 2
#define RESULT_REGISTER(_insnRegCount) (_insnRegCount)
/*
* Big fat collection of register data.
*/
typedef struct RegisterTable {
/*
* Array of RegisterLine structs, one per address in the method. We only
* set the pointers for certain addresses, based on instruction widths
* and what we're trying to accomplish.
*/
RegisterLine* registerLines;
/*
* Number of registers we track for each instruction. This is equal
* to the method's declared "registersSize" plus kExtraRegs.
*/
size_t insnRegCountPlus;
/*
* Storage for a register line we're currently working on.
*/
RegisterLine workLine;
/*
* Storage for a register line we're saving for later.
*/
RegisterLine savedLine;
/*
* A single large alloc, with all of the storage needed for RegisterLine
* data (RegType array, MonitorEntries array, monitor stack).
*/
void* lineAlloc;
} RegisterTable;
/* fwd */
#ifndef NDEBUG
static void checkMergeTab();
#endif
static bool isInitMethod(const Method* meth);
static RegType getInvocationThis(const RegisterLine* registerLine,\
const DecodedInstruction* pDecInsn, VerifyError* pFailure);
static void verifyRegisterType(RegisterLine* registerLine, \
u4 vsrc, RegType checkType, VerifyError* pFailure);
static bool doCodeVerification(VerifierData* vdata, RegisterTable* regTable);
static bool verifyInstruction(const Method* meth, InsnFlags* insnFlags,\
RegisterTable* regTable, int insnIdx, UninitInstanceMap* uninitMap,
int* pStartGuess);
static ClassObject* findCommonSuperclass(ClassObject* c1, ClassObject* c2);
static void dumpRegTypes(const VerifierData* vdata, \
const RegisterLine* registerLine, int addr, const char* addrName,
const UninitInstanceMap* uninitMap, int displayFlags);
/* bit values for dumpRegTypes() "displayFlags" */
enum {
DRT_SIMPLE = 0,
DRT_SHOW_REF_TYPES = 0x01,
DRT_SHOW_LOCALS = 0x02,
DRT_SHOW_LIVENESS = 0x04,
};
/*
* ===========================================================================
* RegType and UninitInstanceMap utility functions
* ===========================================================================
*/
#define __ kRegTypeUnknown
#define _U kRegTypeUninit
#define _X kRegTypeConflict
#define _0 kRegTypeZero
#define _1 kRegTypeOne
#define _Z kRegTypeBoolean
#define _y kRegTypeConstPosByte
#define _Y kRegTypeConstByte
#define _h kRegTypeConstPosShort
#define _H kRegTypeConstShort
#define _c kRegTypeConstChar
#define _i kRegTypeConstInteger
#define _b kRegTypePosByte
#define _B kRegTypeByte
#define _s kRegTypePosShort
#define _S kRegTypeShort
#define _C kRegTypeChar
#define _I kRegTypeInteger
#define _F kRegTypeFloat
#define _N kRegTypeConstLo
#define _n kRegTypeConstHi
#define _J kRegTypeLongLo
#define _j kRegTypeLongHi
#define _D kRegTypeDoubleLo
#define _d kRegTypeDoubleHi
/*
* Merge result table for primitive values. The table is symmetric along
* the diagonal.
*
* Note that 32-bit int/float do not merge into 64-bit long/double. This
* is a register merge, not a widening conversion. Only the "implicit"
* widening within a category, e.g. byte to short, is allowed.
*
* Dalvik does not draw a distinction between int and float, but we enforce
* that once a value is used as int, it can't be used as float, and vice
* versa. We do not allow free exchange between 32-bit int/float and 64-bit
* long/double.
*
* Note that Uninit+Uninit=Uninit. This holds true because we only
* use this when the RegType value is exactly equal to kRegTypeUninit, which
* can only happen for the zeroeth entry in the table.
*
* "Unknown" never merges with anything known. The only time a register
* transitions from "unknown" to "known" is when we're executing code
* for the first time, and we handle that with a simple copy.
*/
const char gDvmMergeTab[kRegTypeMAX][kRegTypeMAX] =
{
/* chk: _ U X 0 1 Z y Y h H c i b B s S C I F N n J j D d */
{ /*_*/ __,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X },
{ /*U*/ _X,_U,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X },
{ /*X*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X },
{ /*0*/ _X,_X,_X,_0,_Z,_Z,_y,_Y,_h,_H,_c,_i,_b,_B,_s,_S,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*1*/ _X,_X,_X,_Z,_1,_Z,_y,_Y,_h,_H,_c,_i,_b,_B,_s,_S,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*Z*/ _X,_X,_X,_Z,_Z,_Z,_y,_Y,_h,_H,_c,_i,_b,_B,_s,_S,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*y*/ _X,_X,_X,_y,_y,_y,_y,_Y,_h,_H,_c,_i,_b,_B,_s,_S,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*Y*/ _X,_X,_X,_Y,_Y,_Y,_Y,_Y,_h,_H,_c,_i,_B,_B,_S,_S,_I,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*h*/ _X,_X,_X,_h,_h,_h,_h,_h,_h,_H,_c,_i,_s,_S,_s,_S,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*H*/ _X,_X,_X,_H,_H,_H,_H,_H,_H,_H,_c,_i,_S,_S,_S,_S,_I,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*c*/ _X,_X,_X,_c,_c,_c,_c,_c,_c,_c,_c,_i,_C,_I,_C,_I,_C,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*i*/ _X,_X,_X,_i,_i,_i,_i,_i,_i,_i,_i,_i,_I,_I,_I,_I,_I,_I,_F,_X,_X,_X,_X,_X,_X },
{ /*b*/ _X,_X,_X,_b,_b,_b,_b,_B,_s,_S,_C,_I,_b,_B,_s,_S,_C,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*B*/ _X,_X,_X,_B,_B,_B,_B,_B,_S,_S,_I,_I,_B,_B,_S,_S,_I,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*s*/ _X,_X,_X,_s,_s,_s,_s,_S,_s,_S,_C,_I,_s,_S,_s,_S,_C,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*S*/ _X,_X,_X,_S,_S,_S,_S,_S,_S,_S,_I,_I,_S,_S,_S,_S,_I,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*C*/ _X,_X,_X,_C,_C,_C,_C,_I,_C,_I,_C,_I,_C,_I,_C,_I,_C,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*I*/ _X,_X,_X,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_I,_X,_X,_X,_X,_X,_X,_X },
{ /*F*/ _X,_X,_X,_F,_F,_F,_F,_F,_F,_F,_F,_F,_X,_X,_X,_X,_X,_X,_F,_X,_X,_X,_X,_X,_X },
{ /*N*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_N,_X,_J,_X,_D,_X },
{ /*n*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_n,_X,_j,_X,_d },
{ /*J*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_J,_X,_J,_X,_X,_X },
{ /*j*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_j,_X,_j,_X,_X },
{ /*D*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_D,_X,_X,_X,_D,_X },
{ /*d*/ _X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_X,_d,_X,_X,_X,_d },
};
#undef __
#undef _U
#undef _X
#undef _0
#undef _1
#undef _Z
#undef _y
#undef _Y
#undef _h
#undef _H
#undef _c
#undef _i
#undef _b
#undef _B
#undef _s
#undef _S
#undef _C
#undef _I
#undef _F
#undef _N
#undef _n
#undef _J
#undef _j
#undef _D
#undef _d
#ifndef NDEBUG
/*
* Verify symmetry in the conversion table.
*/
static void checkMergeTab()
{
int i, j;
for (i = 0; i < kRegTypeMAX; i++) {
for (j = i; j < kRegTypeMAX; j++) {
if (gDvmMergeTab[i][j] != gDvmMergeTab[j][i]) {
ALOGE("Symmetry violation: %d,%d vs %d,%d", i, j, j, i);
dvmAbort();
}
}
}
}
#endif
/*
* Determine whether we can convert "srcType" to "checkType", where
* "checkType" is one of the category-1 non-reference types.
*
* Constant derived types may become floats, but other values may not.
*/
static bool canConvertTo1nr(RegType srcType, RegType checkType)
{
static const char convTab
[kRegType1nrEND-kRegType1nrSTART+1][kRegType1nrEND-kRegType1nrSTART+1] =
{
/* chk: 0 1 Z y Y h H c i b B s S C I F */
{ /*0*/ 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ /*1*/ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ /*Z*/ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ /*y*/ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ /*Y*/ 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1 },
{ /*h*/ 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1 },
{ /*H*/ 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1 },
{ /*c*/ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1 },
{ /*i*/ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1 },
{ /*b*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0 },
{ /*B*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0 },
{ /*s*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 },
{ /*S*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 },
{ /*C*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 },
{ /*I*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
{ /*F*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
};
assert(checkType >= kRegType1nrSTART && checkType <= kRegType1nrEND);
#if 0
if (checkType < kRegType1nrSTART || checkType > kRegType1nrEND) {
LOG_VFY("Unexpected checkType %d (srcType=%d)", checkType, srcType);
assert(false);
return false;
}
#endif
//printf("convTab[%d][%d] = %d\n", srcType, checkType,
// convTab[srcType-kRegType1nrSTART][checkType-kRegType1nrSTART]);
if (srcType >= kRegType1nrSTART && srcType <= kRegType1nrEND)
return (bool) convTab[srcType-kRegType1nrSTART][checkType-kRegType1nrSTART];
return false;
}
/*
* Determine whether the category-2 types are compatible.
*/
static bool canConvertTo2(RegType srcType, RegType checkType)
{
return ((srcType == kRegTypeConstLo || srcType == checkType) &&
(checkType == kRegTypeLongLo || checkType == kRegTypeDoubleLo));
}
/*
* Determine whether or not "instrType" and "targetType" are compatible,
* for purposes of getting or setting a value in a field or array. The
* idea is that an instruction with a category 1nr type (say, aget-short
* or iput-boolean) is accessing a static field, instance field, or array
* entry, and we want to make sure sure that the operation is legal.
*
* At a minimum, source and destination must have the same width. We
* further refine this to assert that "short" and "char" are not
* compatible, because the sign-extension is different on the "get"
* operations.
*
* We're not considering the actual contents of the register, so we'll
* never get "pseudo-types" like kRegTypeZero or kRegTypePosShort. We
* could get kRegTypeUnknown in "targetType" if a field or array class
* lookup failed. Category 2 types and references are checked elsewhere.
*/
static bool checkFieldArrayStore1nr(RegType instrType, RegType targetType)
{
return (instrType == targetType);
}
/*
* Convert a VM PrimitiveType enum value to the equivalent RegType value.
*/
static RegType primitiveTypeToRegType(PrimitiveType primType)
{
switch (primType) {
case PRIM_BOOLEAN: return kRegTypeBoolean;
case PRIM_BYTE: return kRegTypeByte;
case PRIM_SHORT: return kRegTypeShort;
case PRIM_CHAR: return kRegTypeChar;
case PRIM_INT: return kRegTypeInteger;
case PRIM_LONG: return kRegTypeLongLo;
case PRIM_FLOAT: return kRegTypeFloat;
case PRIM_DOUBLE: return kRegTypeDoubleLo;
case PRIM_VOID:
default: {
assert(false);
return kRegTypeUnknown;
}
}
}
/*
* Convert a const derived RegType to the equivalent non-const RegType value.
* Does nothing if the argument type isn't const derived.
*/
static RegType constTypeToRegType(RegType constType)
{
switch (constType) {
case kRegTypeConstPosByte: return kRegTypePosByte;
case kRegTypeConstByte: return kRegTypeByte;
case kRegTypeConstPosShort: return kRegTypePosShort;
case kRegTypeConstShort: return kRegTypeShort;
case kRegTypeConstChar: return kRegTypeChar;
case kRegTypeConstInteger: return kRegTypeInteger;
default: {
return constType;
}
}
}
/*
* Given a 32-bit constant, return the most-restricted RegType enum entry
* that can hold the value. The types used here indicate the value came
* from a const instruction, and may not correctly represent the real type
* of the value. Upon use, a constant derived type is updated with the
* type from the use, which will be unambiguous.
*/
static char determineCat1Const(s4 value)
{
if (value < -32768)
return kRegTypeConstInteger;
else if (value < -128)
return kRegTypeConstShort;
else if (value < 0)
return kRegTypeConstByte;
else if (value == 0)
return kRegTypeZero;
else if (value == 1)
return kRegTypeOne;
else if (value < 128)
return kRegTypeConstPosByte;
else if (value < 32768)
return kRegTypeConstPosShort;
else if (value < 65536)
return kRegTypeConstChar;
else
return kRegTypeConstInteger;
}
/*
* Create a new uninitialized instance map.
*
* The map is allocated and populated with address entries. The addresses
* appear in ascending order to allow binary searching.
*
* Very few methods have 10 or more new-instance instructions; the
* majority have 0 or 1. Occasionally a static initializer will have 200+.
*
* TODO: merge this into the static pass or initRegisterTable; want to
* avoid walking through the instructions yet again just to set up this table
*/
UninitInstanceMap* dvmCreateUninitInstanceMap(const Method* meth,
const InsnFlags* insnFlags, int newInstanceCount)
{
const int insnsSize = dvmGetMethodInsnsSize(meth);
const u2* insns = meth->insns;
UninitInstanceMap* uninitMap;
bool isInit = false;
int idx, addr;
if (isInitMethod(meth)) {
newInstanceCount++;
isInit = true;
}
/*
* Allocate the header and map as a single unit.
*
* TODO: consider having a static instance so we can avoid allocations.
* I don't think the verifier is guaranteed to be single-threaded when
* running in the VM (rather than dexopt), so that must be taken into
* account.
*/
int size = offsetof(UninitInstanceMap, map) +
newInstanceCount * sizeof(uninitMap->map[0]);
uninitMap = (UninitInstanceMap*)calloc(1, size);
if (uninitMap == NULL)
return NULL;
uninitMap->numEntries = newInstanceCount;
idx = 0;
if (isInit) {
uninitMap->map[idx++].addr = kUninitThisArgAddr;
}
/*
* Run through and find the new-instance instructions.
*/
for (addr = 0; addr < insnsSize; /**/) {
int width = dvmInsnGetWidth(insnFlags, addr);
Opcode opcode = dexOpcodeFromCodeUnit(*insns);
if (opcode == OP_NEW_INSTANCE)
uninitMap->map[idx++].addr = addr;
addr += width;
insns += width;
}
assert(idx == newInstanceCount);
return uninitMap;
}
/*
* Free the map.
*/
void dvmFreeUninitInstanceMap(UninitInstanceMap* uninitMap)
{
free(uninitMap);
}
/*
* Set the class object associated with the instruction at "addr".
*
* Returns the map slot index, or -1 if the address isn't listed in the map
* (shouldn't happen) or if a class is already associated with the address
* (bad bytecode).
*
* Entries, once set, do not change -- a given address can only allocate
* one type of object.
*/
static int setUninitInstance(UninitInstanceMap* uninitMap, int addr,
ClassObject* clazz)
{
int idx;
assert(clazz != NULL);
#ifdef VERIFIER_STATS
gDvm.verifierStats.uninitSearches++;
#endif
/* TODO: binary search when numEntries > 8 */
for (idx = uninitMap->numEntries - 1; idx >= 0; idx--) {
if (uninitMap->map[idx].addr == addr) {
if (uninitMap->map[idx].clazz != NULL &&
uninitMap->map[idx].clazz != clazz)
{
LOG_VFY("VFY: addr %d already set to %p, not setting to %p",
addr, uninitMap->map[idx].clazz, clazz);
return -1; // already set to something else??
}
uninitMap->map[idx].clazz = clazz;
return idx;
}
}
LOG_VFY("VFY: addr %d not found in uninit map", addr);
assert(false); // shouldn't happen
return -1;
}
/*
* Get the class object at the specified index.
*/
static ClassObject* getUninitInstance(const UninitInstanceMap* uninitMap,
int idx)
{
assert(idx >= 0 && idx < uninitMap->numEntries);
return uninitMap->map[idx].clazz;
}
/* determine if "type" is actually an object reference (init/uninit/zero) */
static inline bool regTypeIsReference(RegType type) {
return (type > kRegTypeMAX || type == kRegTypeUninit ||
type == kRegTypeZero);
}
/* determine if "type" is an uninitialized object reference */
static inline bool regTypeIsUninitReference(RegType type) {
return ((type & kRegTypeUninitMask) == kRegTypeUninit);
}
/* convert the initialized reference "type" to a ClassObject pointer */
/* (does not expect uninit ref types or "zero") */
static ClassObject* regTypeInitializedReferenceToClass(RegType type)
{
assert(regTypeIsReference(type) && type != kRegTypeZero);
if ((type & 0x01) == 0) {
return (ClassObject*) type;
} else {
//LOG_VFY("VFY: attempted to use uninitialized reference");
return NULL;
}
}
/* extract the index into the uninitialized instance map table */
static inline int regTypeToUninitIndex(RegType type) {
assert(regTypeIsUninitReference(type));
return (type & ~kRegTypeUninitMask) >> kRegTypeUninitShift;
}
/* convert the reference "type" to a ClassObject pointer */
static ClassObject* regTypeReferenceToClass(RegType type,
const UninitInstanceMap* uninitMap)
{
assert(regTypeIsReference(type) && type != kRegTypeZero);
if (regTypeIsUninitReference(type)) {
assert(uninitMap != NULL);
return getUninitInstance(uninitMap, regTypeToUninitIndex(type));
} else {
return (ClassObject*) type;
}
}
/* convert the ClassObject pointer to an (initialized) register type */
static inline RegType regTypeFromClass(ClassObject* clazz) {
return (u4) clazz;
}
/* return the RegType for the uninitialized reference in slot "uidx" */
static RegType regTypeFromUninitIndex(int uidx) {
return (u4) (kRegTypeUninit | (uidx << kRegTypeUninitShift));
}
/*
* ===========================================================================
* Signature operations
* ===========================================================================
*/
/*
* Is this method a constructor?
*/
static bool isInitMethod(const Method* meth)
{
return (*meth->name == '<' && strcmp(meth->name+1, "init>") == 0);
}
/*
* Is this method a class initializer?
*/
#if 0
static bool isClassInitMethod(const Method* meth)
{
return (*meth->name == '<' && strcmp(meth->name+1, "clinit>") == 0);
}
#endif
/*
* Look up a class reference given as a simple string descriptor.
*
* If we can't find it, return a generic substitute when possible.
*/
static ClassObject* lookupClassByDescriptor(const Method* meth,
const char* pDescriptor, VerifyError* pFailure)
{
/*
* The javac compiler occasionally puts references to nonexistent
* classes in signatures. For example, if you have a non-static
* inner class with no constructor, the compiler provides
* a private <init> for you. Constructing the class
* requires <init>(parent), but the outer class can't call
* that because the method is private. So the compiler
* generates a package-scope <init>(parent,bogus) method that
* just calls the regular <init> (the "bogus" part being necessary
* to distinguish the signature of the synthetic method).
* Treating the bogus class as an instance of java.lang.Object
* allows the verifier to process the class successfully.
*/
//ALOGI("Looking up '%s'", typeStr);
ClassObject* clazz;
clazz = dvmFindClassNoInit(pDescriptor, meth->clazz->classLoader);
if (clazz == NULL) {
dvmClearOptException(dvmThreadSelf());
if (strchr(pDescriptor, '$') != NULL) {
ALOGV("VFY: unable to find class referenced in signature (%s)",
pDescriptor);
} else {
LOG_VFY("VFY: unable to find class referenced in signature (%s)",
pDescriptor);
}
if (pDescriptor[0] == '[') {
/* We are looking at an array descriptor. */
/*
* There should never be a problem loading primitive arrays.
*/
if (pDescriptor[1] != 'L' && pDescriptor[1] != '[') {
LOG_VFY("VFY: invalid char in signature in '%s'",
pDescriptor);
*pFailure = VERIFY_ERROR_GENERIC;
}
/*
* Try to continue with base array type. This will let
* us pass basic stuff (e.g. get array len) that wouldn't
* fly with an Object. This is NOT correct if the
* missing type is a primitive array, but we should never
* have a problem loading those. (I'm not convinced this
* is correct or even useful. Just use Object here?)
*/
clazz = dvmFindClassNoInit("[Ljava/lang/Object;",
meth->clazz->classLoader);
} else if (pDescriptor[0] == 'L') {
/*
* We are looking at a non-array reference descriptor;
* try to continue with base reference type.
*/
clazz = gDvm.classJavaLangObject;
} else {
/* We are looking at a primitive type. */
LOG_VFY("VFY: invalid char in signature in '%s'", pDescriptor);
*pFailure = VERIFY_ERROR_GENERIC;
}
if (clazz == NULL) {
*pFailure = VERIFY_ERROR_GENERIC;
}
}
if (dvmIsPrimitiveClass(clazz)) {
LOG_VFY("VFY: invalid use of primitive type '%s'", pDescriptor);
*pFailure = VERIFY_ERROR_GENERIC;
clazz = NULL;
}
return clazz;
}
/*
* Look up a class reference in a signature. Could be an arg or the
* return value.
*
* Advances "*pSig" to the last character in the signature (that is, to
* the ';').
*
* NOTE: this is also expected to verify the signature.
*/
static ClassObject* lookupSignatureClass(const Method* meth, const char** pSig,
VerifyError* pFailure)
{
const char* sig = *pSig;
const char* endp = sig;
assert(sig != NULL && *sig == 'L');
while (*++endp != ';' && *endp != '\0')
;
if (*endp != ';') {
LOG_VFY("VFY: bad signature component '%s' (missing ';')", sig);
*pFailure = VERIFY_ERROR_GENERIC;
return NULL;
}
endp++; /* Advance past the ';'. */
int typeLen = endp - sig;
char typeStr[typeLen+1]; /* +1 for the '\0' */
memcpy(typeStr, sig, typeLen);
typeStr[typeLen] = '\0';
*pSig = endp - 1; /* - 1 so that *pSig points at, not past, the ';' */
return lookupClassByDescriptor(meth, typeStr, pFailure);
}
/*
* Look up an array class reference in a signature. Could be an arg or the
* return value.
*
* Advances "*pSig" to the last character in the signature.
*
* NOTE: this is also expected to verify the signature.
*/
static ClassObject* lookupSignatureArrayClass(const Method* meth,
const char** pSig, VerifyError* pFailure)
{
const char* sig = *pSig;
const char* endp = sig;
assert(sig != NULL && *sig == '[');
/* find the end */
while (*++endp == '[' && *endp != '\0')
;
if (*endp == 'L') {
while (*++endp != ';' && *endp != '\0')
;
if (*endp != ';') {
LOG_VFY("VFY: bad signature component '%s' (missing ';')", sig);
*pFailure = VERIFY_ERROR_GENERIC;
return NULL;
}
}
int typeLen = endp - sig +1;
char typeStr[typeLen+1];
memcpy(typeStr, sig, typeLen);
typeStr[typeLen] = '\0';
*pSig = endp;
return lookupClassByDescriptor(meth, typeStr, pFailure);
}
/*
* Set the register types for the first instruction in the method based on
* the method signature.
*
* This has the side-effect of validating the signature.
*
* Returns "true" on success.
*/
static bool setTypesFromSignature(const Method* meth, RegType* regTypes,
UninitInstanceMap* uninitMap)
{
DexParameterIterator iterator;
int actualArgs, expectedArgs, argStart;
VerifyError failure = VERIFY_ERROR_NONE;
const char* descriptor;
dexParameterIteratorInit(&iterator, &meth->prototype);
argStart = meth->registersSize - meth->insSize;
expectedArgs = meth->insSize; /* long/double count as two */
actualArgs = 0;
assert(argStart >= 0); /* should have been verified earlier */
/*
* Include the "this" pointer.
*/
if (!dvmIsStaticMethod(meth)) {
/*
* If this is a constructor for a class other than java.lang.Object,
* mark the first ("this") argument as uninitialized. This restricts
* field access until the superclass constructor is called.
*/
if (isInitMethod(meth) && meth->clazz != gDvm.classJavaLangObject) {
int uidx = setUninitInstance(uninitMap, kUninitThisArgAddr,
meth->clazz);
assert(uidx == 0);
regTypes[argStart + actualArgs] = regTypeFromUninitIndex(uidx);
} else {
regTypes[argStart + actualArgs] = regTypeFromClass(meth->clazz);
}
actualArgs++;
}
for (;;) {
descriptor = dexParameterIteratorNextDescriptor(&iterator);
if (descriptor == NULL) {
break;
}
if (actualArgs >= expectedArgs) {
LOG_VFY("VFY: expected %d args, found more (%s)",
expectedArgs, descriptor);
goto bad_sig;
}
switch (*descriptor) {
case 'L':
case '[':
/*
* We assume that reference arguments are initialized. The
* only way it could be otherwise (assuming the caller was
* verified) is if the current method is <init>, but in that
* case it's effectively considered initialized the instant
* we reach here (in the sense that we can return without
* doing anything or call virtual methods).
*/
{
ClassObject* clazz =
lookupClassByDescriptor(meth, descriptor, &failure);
if (!VERIFY_OK(failure))
goto bad_sig;
regTypes[argStart + actualArgs] = regTypeFromClass(clazz);
}
actualArgs++;
break;
case 'Z':
regTypes[argStart + actualArgs] = kRegTypeBoolean;
actualArgs++;
break;
case 'C':
regTypes[argStart + actualArgs] = kRegTypeChar;
actualArgs++;
break;
case 'B':
regTypes[argStart + actualArgs] = kRegTypeByte;
actualArgs++;
break;
case 'I':
regTypes[argStart + actualArgs] = kRegTypeInteger;
actualArgs++;
break;
case 'S':
regTypes[argStart + actualArgs] = kRegTypeShort;
actualArgs++;
break;
case 'F':
regTypes[argStart + actualArgs] = kRegTypeFloat;
actualArgs++;
break;
case 'D':
regTypes[argStart + actualArgs] = kRegTypeDoubleLo;
regTypes[argStart + actualArgs +1] = kRegTypeDoubleHi;
actualArgs += 2;
break;
case 'J':
regTypes[argStart + actualArgs] = kRegTypeLongLo;
regTypes[argStart + actualArgs +1] = kRegTypeLongHi;
actualArgs += 2;
break;
default:
LOG_VFY("VFY: unexpected signature type char '%c'", *descriptor);
goto bad_sig;
}
}
if (actualArgs != expectedArgs) {
LOG_VFY("VFY: expected %d args, found %d", expectedArgs, actualArgs);
goto bad_sig;
}
descriptor = dexProtoGetReturnType(&meth->prototype);
/*
* Validate return type. We don't do the type lookup; just want to make
* sure that it has the right format. Only major difference from the
* method argument format is that 'V' is supported.
*/
switch (*descriptor) {
case 'I':
case 'C':
case 'S':
case 'B':
case 'Z':
case 'V':
case 'F':
case 'D':
case 'J':
if (*(descriptor+1) != '\0')
goto bad_sig;
break;
case '[':
/* single/multi, object/primitive */
while (*++descriptor == '[')
;
if (*descriptor == 'L') {
while (*++descriptor != ';' && *descriptor != '\0')
;
if (*descriptor != ';')
goto bad_sig;
} else {
if (*(descriptor+1) != '\0')
goto bad_sig;
}
break;
case 'L':
/* could be more thorough here, but shouldn't be required */
while (*++descriptor != ';' && *descriptor != '\0')
;
if (*descriptor != ';')
goto bad_sig;
break;
default:
goto bad_sig;
}
return true;
//fail:
// LOG_VFY_METH(meth, "VFY: bad sig");
// return false;
bad_sig:
{
char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
LOG_VFY("VFY: bad signature '%s' for %s.%s",
desc, meth->clazz->descriptor, meth->name);
free(desc);
}
return false;
}
/*
* Return the register type for the method. We can't just use the
* already-computed DalvikJniReturnType, because if it's a reference type
* we need to do the class lookup.
*
* Returned references are assumed to be initialized.
*
* Returns kRegTypeUnknown for "void".
*/
static RegType getMethodReturnType(const Method* meth)
{
RegType type;
const char* descriptor = dexProtoGetReturnType(&meth->prototype);
switch (*descriptor) {
case 'I':
type = kRegTypeInteger;
break;
case 'C':
type = kRegTypeChar;
break;
case 'S':
type = kRegTypeShort;
break;
case 'B':
type = kRegTypeByte;
break;
case 'Z':
type = kRegTypeBoolean;
break;
case 'V':
type = kRegTypeUnknown;
break;
case 'F':
type = kRegTypeFloat;
break;
case 'D':
type = kRegTypeDoubleLo;
break;
case 'J':
type = kRegTypeLongLo;
break;
case 'L':
case '[':
{
VerifyError failure = VERIFY_ERROR_NONE;
ClassObject* clazz =
lookupClassByDescriptor(meth, descriptor, &failure);
assert(VERIFY_OK(failure));
type = regTypeFromClass(clazz);
}
break;
default:
/* we verified signature return type earlier, so this is impossible */
assert(false);
type = kRegTypeConflict;
break;
}
return type;
}
/*
* Convert a single-character signature value (i.e. a primitive type) to
* the corresponding RegType. This is intended for access to object fields
* holding primitive types.
*
* Returns kRegTypeUnknown for objects, arrays, and void.
*/
static RegType primSigCharToRegType(char sigChar)
{
RegType type;
switch (sigChar) {
case 'I':
type = kRegTypeInteger;
break;
case 'C':
type = kRegTypeChar;
break;
case 'S':
type = kRegTypeShort;
break;
case 'B':
type = kRegTypeByte;
break;
case 'Z':
type = kRegTypeBoolean;
break;
case 'F':
type = kRegTypeFloat;
break;
case 'D':
type = kRegTypeDoubleLo;
break;
case 'J':
type = kRegTypeLongLo;
break;
case 'V':
case 'L':
case '[':
type = kRegTypeUnknown;
break;
default:
assert(false);
type = kRegTypeUnknown;
break;
}
return type;
}
/*
* See if the method matches the MethodType.
*/
static bool isCorrectInvokeKind(MethodType methodType, Method* resMethod)
{
switch (methodType) {
case METHOD_DIRECT:
return dvmIsDirectMethod(resMethod);
case METHOD_STATIC:
return dvmIsStaticMethod(resMethod);
case METHOD_VIRTUAL:
case METHOD_INTERFACE:
return !dvmIsDirectMethod(resMethod);
default:
return false;
}
}
/*
* Verify the arguments to a method. We're executing in "method", making
* a call to the method reference in vB.
*
* If this is a "direct" invoke, we allow calls to <init>. For calls to
* <init>, the first argument may be an uninitialized reference. Otherwise,
* calls to anything starting with '<' will be rejected, as will any
* uninitialized reference arguments.
*
* For non-static method calls, this will verify that the method call is
* appropriate for the "this" argument.
*
* The method reference is in vBBBB. The "isRange" parameter determines
* whether we use 0-4 "args" values or a range of registers defined by
* vAA and vCCCC.
*
* Widening conversions on integers and references are allowed, but
* narrowing conversions are not.
*
* Returns the resolved method on success, NULL on failure (with *pFailure
* set appropriately).
*/
static Method* verifyInvocationArgs(const Method* meth,
RegisterLine* registerLine, const int insnRegCount,
const DecodedInstruction* pDecInsn, UninitInstanceMap* uninitMap,
MethodType methodType, bool isRange, bool isSuper, VerifyError* pFailure)
{
Method* resMethod;
char* sigOriginal = NULL;
const char* sig;
int expectedArgs;
int actualArgs;
/*
* Resolve the method. This could be an abstract or concrete method
* depending on what sort of call we're making.
*/
if (methodType == METHOD_INTERFACE) {
resMethod = dvmOptResolveInterfaceMethod(meth->clazz, pDecInsn->vB);
} else {
resMethod = dvmOptResolveMethod(meth->clazz, pDecInsn->vB, methodType,
pFailure);
}
if (resMethod == NULL) {
/* failed; print a meaningful failure message */
DexFile* pDexFile = meth->clazz->pDvmDex->pDexFile;
const DexMethodId* pMethodId = dexGetMethodId(pDexFile, pDecInsn->vB);
const char* methodName = dexStringById(pDexFile, pMethodId->nameIdx);
char* methodDesc = dexCopyDescriptorFromMethodId(pDexFile, pMethodId);
const char* classDescriptor = dexStringByTypeIdx(pDexFile, pMethodId->classIdx);
if (!gDvm.optimizing) {
std::string dotMissingClass =
dvmHumanReadableDescriptor(classDescriptor);
std::string dotMethClass =
dvmHumanReadableDescriptor(meth->clazz->descriptor);
ALOGI("Could not find method %s.%s, referenced from method %s.%s",
dotMissingClass.c_str(), methodName,
dotMethClass.c_str(), meth->name);
}
LOG_VFY("VFY: unable to resolve %s method %u: %s.%s %s",
dvmMethodTypeStr(methodType), pDecInsn->vB,
classDescriptor, methodName, methodDesc);
free(methodDesc);
if (VERIFY_OK(*pFailure)) /* not set for interface resolve */
*pFailure = VERIFY_ERROR_NO_METHOD;
goto fail;
}
/*
* Only time you can explicitly call a method starting with '<' is when
* making a "direct" invocation on "<init>". There are additional
* restrictions but we don't enforce them here.
*/
if (resMethod->name[0] == '<') {
if (methodType != METHOD_DIRECT || !isInitMethod(resMethod)) {
LOG_VFY("VFY: invalid call to %s.%s",
resMethod->clazz->descriptor, resMethod->name);
goto bad_sig;
}
}
/*
* See if the method type implied by the invoke instruction matches the
* access flags for the target method.
*/
if (!isCorrectInvokeKind(methodType, resMethod)) {
LOG_VFY("VFY: invoke type does not match method type of %s.%s",
resMethod->clazz->descriptor, resMethod->name);
goto fail;
}
/*
* If we're using invoke-super(method), make sure that the executing
* method's class' superclass has a vtable entry for the target method.
*/
if (isSuper) {
assert(methodType == METHOD_VIRTUAL);
ClassObject* super = meth->clazz->super;
if (super == NULL || resMethod->methodIndex > super->vtableCount) {
char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
LOG_VFY("VFY: invalid invoke-super from %s.%s to super %s.%s %s",
meth->clazz->descriptor, meth->name,
(super == NULL) ? "-" : super->descriptor,
resMethod->name, desc);
free(desc);
*pFailure = VERIFY_ERROR_NO_METHOD;
goto fail;
}
}
/*
* We use vAA as our expected arg count, rather than resMethod->insSize,
* because we need to match the call to the signature. Also, we might
* might be calling through an abstract method definition (which doesn't
* have register count values).
*/
sigOriginal = dexProtoCopyMethodDescriptor(&resMethod->prototype);
sig = sigOriginal;
expectedArgs = pDecInsn->vA;
actualArgs = 0;
/* caught by static verifier */
assert(isRange || expectedArgs <= 5);
if (expectedArgs > meth->outsSize) {
LOG_VFY("VFY: invalid arg count (%d) exceeds outsSize (%d)",
expectedArgs, meth->outsSize);
goto fail;
}
if (*sig++ != '(')
goto bad_sig;
/*
* Check the "this" argument, which must be an instance of the class
* that declared the method. For an interface class, we don't do the
* full interface merge, so we can't do a rigorous check here (which
* is okay since we have to do it at runtime).
*/
if (!dvmIsStaticMethod(resMethod)) {
ClassObject* actualThisRef;
RegType actualArgType;
actualArgType = getInvocationThis(registerLine, pDecInsn, pFailure);
if (!VERIFY_OK(*pFailure))
goto fail;
if (regTypeIsUninitReference(actualArgType) && resMethod->name[0] != '<')
{
LOG_VFY("VFY: 'this' arg must be initialized");
goto fail;
}
if (methodType != METHOD_INTERFACE && actualArgType != kRegTypeZero) {
actualThisRef = regTypeReferenceToClass(actualArgType, uninitMap);
if (!dvmInstanceof(actualThisRef, resMethod->clazz)) {
LOG_VFY("VFY: 'this' arg '%s' not instance of '%s'",
actualThisRef->descriptor,
resMethod->clazz->descriptor);
goto fail;
}
}
actualArgs++;
}
/*
* Process the target method's signature. This signature may or may not
* have been verified, so we can't assume it's properly formed.
*/
while (*sig != '\0' && *sig != ')') {
if (actualArgs >= expectedArgs) {
LOG_VFY("VFY: expected %d args, found more (%c)",
expectedArgs, *sig);
goto bad_sig;
}
u4 getReg;
if (isRange)
getReg = pDecInsn->vC + actualArgs;
else
getReg = pDecInsn->arg[actualArgs];
switch (*sig) {
case 'L':
{
ClassObject* clazz = lookupSignatureClass(meth, &sig, pFailure);
if (!VERIFY_OK(*pFailure))
goto bad_sig;
verifyRegisterType(registerLine, getReg,
regTypeFromClass(clazz), pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: bad arg %d (into %s)",
actualArgs, clazz->descriptor);
goto bad_sig;
}
}
actualArgs++;
break;
case '[':
{
ClassObject* clazz =
lookupSignatureArrayClass(meth, &sig, pFailure);
if (!VERIFY_OK(*pFailure))
goto bad_sig;
verifyRegisterType(registerLine, getReg,
regTypeFromClass(clazz), pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: bad arg %d (into %s)",
actualArgs, clazz->descriptor);
goto bad_sig;
}
}
actualArgs++;
break;
case 'Z':
verifyRegisterType(registerLine, getReg, kRegTypeBoolean, pFailure);
actualArgs++;
break;
case 'C':
verifyRegisterType(registerLine, getReg, kRegTypeChar, pFailure);
actualArgs++;
break;
case 'B':
verifyRegisterType(registerLine, getReg, kRegTypeByte, pFailure);
actualArgs++;
break;
case 'I':
verifyRegisterType(registerLine, getReg, kRegTypeInteger, pFailure);
actualArgs++;
break;
case 'S':
verifyRegisterType(registerLine, getReg, kRegTypeShort, pFailure);
actualArgs++;
break;
case 'F':
verifyRegisterType(registerLine, getReg, kRegTypeFloat, pFailure);
actualArgs++;
break;
case 'D':
verifyRegisterType(registerLine, getReg, kRegTypeDoubleLo, pFailure);
actualArgs += 2;
break;
case 'J':
verifyRegisterType(registerLine, getReg, kRegTypeLongLo, pFailure);
actualArgs += 2;
break;
default:
LOG_VFY("VFY: invocation target: bad signature type char '%c'",
*sig);
goto bad_sig;
}
sig++;
}
if (*sig != ')') {
char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
LOG_VFY("VFY: invocation target: bad signature '%s'", desc);
free(desc);
goto bad_sig;
}
if (actualArgs != expectedArgs) {
LOG_VFY("VFY: expected %d args, found %d", expectedArgs, actualArgs);
goto bad_sig;
}
free(sigOriginal);
return resMethod;
bad_sig:
if (resMethod != NULL) {
char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
LOG_VFY("VFY: rejecting call to %s.%s %s",
resMethod->clazz->descriptor, resMethod->name, desc);
free(desc);
}
fail:
free(sigOriginal);
if (*pFailure == VERIFY_ERROR_NONE)
*pFailure = VERIFY_ERROR_GENERIC;
return NULL;
}
/*
* Get the class object for the type of data stored in a field. This isn't
* stored in the Field struct, so we have to recover it from the signature.
*
* This only works for reference types. Don't call this for primitive types.
*
* If we can't find the class, we return java.lang.Object, so that
* verification can continue if a field is only accessed in trivial ways.
*/
static ClassObject* getFieldClass(const Method* meth, const Field* field)
{
ClassObject* fieldClass;
const char* signature = field->signature;
if ((*signature == 'L') || (*signature == '[')) {
fieldClass = dvmFindClassNoInit(signature,
meth->clazz->classLoader);
} else {
return NULL;
}
if (fieldClass == NULL) {
dvmClearOptException(dvmThreadSelf());
ALOGV("VFY: unable to find class '%s' for field %s.%s, trying Object",
field->signature, meth->clazz->descriptor, field->name);
fieldClass = gDvm.classJavaLangObject;
} else {
assert(!dvmIsPrimitiveClass(fieldClass));
}
return fieldClass;
}
/*
* ===========================================================================
* Register operations
* ===========================================================================
*/
/*
* Get the type of register N.
*
* The register index was validated during the static pass, so we don't
* need to check it here.
*/
static inline RegType getRegisterType(const RegisterLine* registerLine, u4 vsrc)
{
return registerLine->regTypes[vsrc];
}
/*
* Get the value from a register, and cast it to a ClassObject. Sets
* "*pFailure" if something fails.
*
* This fails if the register holds an uninitialized class.
*
* If the register holds kRegTypeZero, this returns a NULL pointer.
*/
static ClassObject* getClassFromRegister(const RegisterLine* registerLine,
u4 vsrc, VerifyError* pFailure)
{
ClassObject* clazz = NULL;
RegType type;
/* get the element type of the array held in vsrc */
type = getRegisterType(registerLine, vsrc);
/* if "always zero", we allow it to fail at runtime */
if (type == kRegTypeZero)
goto bail;
if (!regTypeIsReference(type)) {
LOG_VFY("VFY: tried to get class from non-ref register v%d (type=%d)",
vsrc, type);
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
if (regTypeIsUninitReference(type)) {
LOG_VFY("VFY: register %u holds uninitialized reference", vsrc);
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
clazz = regTypeInitializedReferenceToClass(type);
bail:
return clazz;
}
/*
* Get the "this" pointer from a non-static method invocation. This
* returns the RegType so the caller can decide whether it needs the
* reference to be initialized or not. (Can also return kRegTypeZero
* if the reference can only be zero at this point.)
*
* The argument count is in vA, and the first argument is in vC, for both
* "simple" and "range" versions. We just need to make sure vA is >= 1
* and then return vC.
*/
static RegType getInvocationThis(const RegisterLine* registerLine,
const DecodedInstruction* pDecInsn, VerifyError* pFailure)
{
RegType thisType = kRegTypeUnknown;
if (pDecInsn->vA < 1) {
LOG_VFY("VFY: invoke lacks 'this'");
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
/* get the element type of the array held in vsrc */
thisType = getRegisterType(registerLine, pDecInsn->vC);
if (!regTypeIsReference(thisType)) {
LOG_VFY("VFY: tried to get class from non-ref register v%d (type=%d)",
pDecInsn->vC, thisType);
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
bail:
return thisType;
}
/*
* Set the type of register N, verifying that the register is valid. If
* "newType" is the "Lo" part of a 64-bit value, register N+1 will be
* set to "newType+1".
*
* The register index was validated during the static pass, so we don't
* need to check it here.
*
* TODO: clear mon stack bits
*/
static void setRegisterType(RegisterLine* registerLine, u4 vdst,
RegType newType)
{
RegType* insnRegs = registerLine->regTypes;
switch (newType) {
case kRegTypeUnknown:
case kRegTypeBoolean:
case kRegTypeOne:
case kRegTypeConstByte:
case kRegTypeConstPosByte:
case kRegTypeConstShort:
case kRegTypeConstPosShort:
case kRegTypeConstChar:
case kRegTypeConstInteger:
case kRegTypeByte:
case kRegTypePosByte:
case kRegTypeShort:
case kRegTypePosShort:
case kRegTypeChar:
case kRegTypeInteger:
case kRegTypeFloat:
case kRegTypeZero:
case kRegTypeUninit:
insnRegs[vdst] = newType;
break;
case kRegTypeConstLo:
case kRegTypeLongLo:
case kRegTypeDoubleLo:
insnRegs[vdst] = newType;
insnRegs[vdst+1] = newType+1;
break;
case kRegTypeConstHi:
case kRegTypeLongHi:
case kRegTypeDoubleHi:
/* should never set these explicitly */
ALOGE("BUG: explicit set of high register type");
dvmAbort();
break;
default:
/* can't switch for ref types, so we check explicitly */
if (regTypeIsReference(newType)) {
insnRegs[vdst] = newType;
/*
* In most circumstances we won't see a reference to a primitive
* class here (e.g. "D"), since that would mean the object in the
* register is actually a primitive type. It can happen as the
* result of an assumed-successful check-cast instruction in
* which the second argument refers to a primitive class. (In
* practice, such an instruction will always throw an exception.)
*
* This is not an issue for instructions like const-class, where
* the object in the register is a java.lang.Class instance.
*/
break;
}
/* bad type - fall through */
case kRegTypeConflict: // should only be set during a merge
ALOGE("BUG: set register to unknown type %d", newType);
dvmAbort();
break;
}
/*
* Clear the monitor entry bits for this register.
*/
if (registerLine->monitorEntries != NULL)
registerLine->monitorEntries[vdst] = 0;
}
/*
* Verify that the contents of the specified register have the specified
* type (or can be converted to it through an implicit widening conversion).
*
* This will modify the type of the source register if it was originally
* derived from a constant to prevent mixing of int/float and long/double.
*
* If "vsrc" is a reference, both it and the "vsrc" register must be
* initialized ("vsrc" may be Zero). This will verify that the value in
* the register is an instance of checkType, or if checkType is an
* interface, verify that the register implements checkType.
*/
static void verifyRegisterType(RegisterLine* registerLine, u4 vsrc,
RegType checkType, VerifyError* pFailure)
{
const RegType* insnRegs = registerLine->regTypes;
RegType srcType = insnRegs[vsrc];
//ALOGD("check-reg v%u = %d", vsrc, checkType);
switch (checkType) {
case kRegTypeFloat:
case kRegTypeBoolean:
case kRegTypePosByte:
case kRegTypeByte:
case kRegTypePosShort:
case kRegTypeShort:
case kRegTypeChar:
case kRegTypeInteger:
if (!canConvertTo1nr(srcType, checkType)) {
LOG_VFY("VFY: register1 v%u type %d, wanted %d",
vsrc, srcType, checkType);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
/* Update type if result is float */
if (checkType == kRegTypeFloat) {
setRegisterType(registerLine, vsrc, checkType);
} else {
/* Update const type to actual type after use */
setRegisterType(registerLine, vsrc, constTypeToRegType(srcType));
}
break;
case kRegTypeLongLo:
case kRegTypeDoubleLo:
if (insnRegs[vsrc+1] != srcType+1) {
LOG_VFY("VFY: register2 v%u-%u values %d,%d",
vsrc, vsrc+1, insnRegs[vsrc], insnRegs[vsrc+1]);
*pFailure = VERIFY_ERROR_GENERIC;
break;
} else if (!canConvertTo2(srcType, checkType)) {
LOG_VFY("VFY: register2 v%u type %d, wanted %d",
vsrc, srcType, checkType);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
/* Update type if source is from const */
if (srcType == kRegTypeConstLo) {
setRegisterType(registerLine, vsrc, checkType);
}
break;
case kRegTypeConstLo:
case kRegTypeConstHi:
case kRegTypeLongHi:
case kRegTypeDoubleHi:
case kRegTypeZero:
case kRegTypeOne:
case kRegTypeUnknown:
case kRegTypeConflict:
/* should never be checking for these explicitly */
assert(false);
*pFailure = VERIFY_ERROR_GENERIC;
return;
case kRegTypeUninit:
default:
/* make sure checkType is initialized reference */
if (!regTypeIsReference(checkType)) {
LOG_VFY("VFY: unexpected check type %d", checkType);
assert(false);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
if (regTypeIsUninitReference(checkType)) {
LOG_VFY("VFY: uninitialized ref not expected as reg check");
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
/* make sure srcType is initialized reference or always-NULL */
if (!regTypeIsReference(srcType)) {
LOG_VFY("VFY: register1 v%u type %d, wanted ref", vsrc, srcType);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
if (regTypeIsUninitReference(srcType)) {
LOG_VFY("VFY: register1 v%u holds uninitialized ref", vsrc);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
/* if the register isn't Zero, make sure it's an instance of check */
if (srcType != kRegTypeZero) {
ClassObject* srcClass = regTypeInitializedReferenceToClass(srcType);
ClassObject* checkClass = regTypeInitializedReferenceToClass(checkType);
assert(srcClass != NULL);
assert(checkClass != NULL);
if (dvmIsInterfaceClass(checkClass)) {
/*
* All objects implement all interfaces as far as the
* verifier is concerned. The runtime has to sort it out.
* See comments above findCommonSuperclass.
*/
/*
if (srcClass != checkClass &&
!dvmImplements(srcClass, checkClass))
{
LOG_VFY("VFY: %s does not implement %s",
srcClass->descriptor, checkClass->descriptor);
*pFailure = VERIFY_ERROR_GENERIC;
}
*/
} else {
if (!dvmInstanceof(srcClass, checkClass)) {
LOG_VFY("VFY: %s is not instance of %s",
srcClass->descriptor, checkClass->descriptor);
*pFailure = VERIFY_ERROR_GENERIC;
}
}
}
break;
}
}
/*
* Set the type of the "result" register.
*/
static void setResultRegisterType(RegisterLine* registerLine,
const int insnRegCount, RegType newType)
{
setRegisterType(registerLine, RESULT_REGISTER(insnRegCount), newType);
}
/*
* Update all registers holding "uninitType" to instead hold the
* corresponding initialized reference type. This is called when an
* appropriate <init> method is invoked -- all copies of the reference
* must be marked as initialized.
*/
static void markRefsAsInitialized(RegisterLine* registerLine, int insnRegCount,
UninitInstanceMap* uninitMap, RegType uninitType, VerifyError* pFailure)
{
RegType* insnRegs = registerLine->regTypes;
ClassObject* clazz;
RegType initType;
int i, changed;
clazz = getUninitInstance(uninitMap, regTypeToUninitIndex(uninitType));
if (clazz == NULL) {
ALOGE("VFY: unable to find type=%#x (idx=%d)",
uninitType, regTypeToUninitIndex(uninitType));
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
initType = regTypeFromClass(clazz);
changed = 0;
for (i = 0; i < insnRegCount; i++) {
if (insnRegs[i] == uninitType) {
insnRegs[i] = initType;
changed++;
}
}
//ALOGD("VFY: marked %d registers as initialized", changed);
assert(changed > 0);
return;
}
/*
* We're creating a new instance of class C at address A. Any registers
* holding instances previously created at address A must be initialized
* by now. If not, we mark them as "conflict" to prevent them from being
* used (otherwise, markRefsAsInitialized would mark the old ones and the
* new ones at the same time).
*/
static void markUninitRefsAsInvalid(RegisterLine* registerLine,
int insnRegCount, UninitInstanceMap* uninitMap, RegType uninitType)
{
RegType* insnRegs = registerLine->regTypes;
int i, changed;
changed = 0;
for (i = 0; i < insnRegCount; i++) {
if (insnRegs[i] == uninitType) {
insnRegs[i] = kRegTypeConflict;
if (registerLine->monitorEntries != NULL)
registerLine->monitorEntries[i] = 0;
changed++;
}
}
//if (changed)
// ALOGD("VFY: marked %d uninitialized registers as invalid", changed);
}
/*
* Find the register line for the specified instruction in the current method.
*/
static inline RegisterLine* getRegisterLine(const RegisterTable* regTable,
int insnIdx)
{
return ®Table->registerLines[insnIdx];
}
/*
* Copy a register line.
*/
static inline void copyRegisterLine(RegisterLine* dst, const RegisterLine* src,
size_t numRegs)
{
memcpy(dst->regTypes, src->regTypes, numRegs * sizeof(RegType));
assert((src->monitorEntries == NULL && dst->monitorEntries == NULL) ||
(src->monitorEntries != NULL && dst->monitorEntries != NULL));
if (dst->monitorEntries != NULL) {
assert(dst->monitorStack != NULL);
memcpy(dst->monitorEntries, src->monitorEntries,
numRegs * sizeof(MonitorEntries));
memcpy(dst->monitorStack, src->monitorStack,
kMaxMonitorStackDepth * sizeof(u4));
dst->monitorStackTop = src->monitorStackTop;
}
}
/*
* Copy a register line into the table.
*/
static inline void copyLineToTable(RegisterTable* regTable, int insnIdx,
const RegisterLine* src)
{
RegisterLine* dst = getRegisterLine(regTable, insnIdx);
assert(dst->regTypes != NULL);
copyRegisterLine(dst, src, regTable->insnRegCountPlus);
}
/*
* Copy a register line out of the table.
*/
static inline void copyLineFromTable(RegisterLine* dst,
const RegisterTable* regTable, int insnIdx)
{
RegisterLine* src = getRegisterLine(regTable, insnIdx);
assert(src->regTypes != NULL);
copyRegisterLine(dst, src, regTable->insnRegCountPlus);
}
#ifndef NDEBUG
/*
* Compare two register lines. Returns 0 if they match.
*
* Using this for a sort is unwise, since the value can change based on
* machine endianness.
*/
static inline int compareLineToTable(const RegisterTable* regTable,
int insnIdx, const RegisterLine* line2)
{
const RegisterLine* line1 = getRegisterLine(regTable, insnIdx);
if (line1->monitorEntries != NULL) {
int result;
if (line2->monitorEntries == NULL)
return 1;
result = memcmp(line1->monitorEntries, line2->monitorEntries,
regTable->insnRegCountPlus * sizeof(MonitorEntries));
if (result != 0) {
LOG_VFY("monitorEntries mismatch");
return result;
}
result = line1->monitorStackTop - line2->monitorStackTop;
if (result != 0) {
LOG_VFY("monitorStackTop mismatch");
return result;
}
result = memcmp(line1->monitorStack, line2->monitorStack,
line1->monitorStackTop);
if (result != 0) {
LOG_VFY("monitorStack mismatch");
return result;
}
}
return memcmp(line1->regTypes, line2->regTypes,
regTable->insnRegCountPlus * sizeof(RegType));
}
#endif
/*
* Register type categories, for type checking.
*
* The spec says category 1 includes boolean, byte, char, short, int, float,
* reference, and returnAddress. Category 2 includes long and double.
*
* We treat object references separately, so we have "category1nr". We
* don't support jsr/ret, so there is no "returnAddress" type.
*/
enum TypeCategory {
kTypeCategoryUnknown = 0,
kTypeCategory1nr, // boolean, byte, char, short, int, float
kTypeCategory2, // long, double
kTypeCategoryRef, // object reference
};
/*
* See if "type" matches "cat". All we're really looking for here is that
* we're not mixing and matching 32-bit and 64-bit quantities, and we're
* not mixing references with numerics. (For example, the arguments to
* "a < b" could be integers of different sizes, but they must both be
* integers. Dalvik is less specific about int vs. float, so we treat them
* as equivalent here.)
*
* For category 2 values, "type" must be the "low" half of the value.
*
* Sets "*pFailure" if something looks wrong.
*/
static void checkTypeCategory(RegType type, TypeCategory cat,
VerifyError* pFailure)
{
switch (cat) {
case kTypeCategory1nr:
switch (type) {
case kRegTypeZero:
case kRegTypeOne:
case kRegTypeBoolean:
case kRegTypeConstPosByte:
case kRegTypeConstByte:
case kRegTypeConstPosShort:
case kRegTypeConstShort:
case kRegTypeConstChar:
case kRegTypeConstInteger:
case kRegTypePosByte:
case kRegTypeByte:
case kRegTypePosShort:
case kRegTypeShort:
case kRegTypeChar:
case kRegTypeInteger:
case kRegTypeFloat:
break;
default:
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
break;
case kTypeCategory2:
switch (type) {
case kRegTypeConstLo:
case kRegTypeLongLo:
case kRegTypeDoubleLo:
break;
default:
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
break;
case kTypeCategoryRef:
if (type != kRegTypeZero && !regTypeIsReference(type))
*pFailure = VERIFY_ERROR_GENERIC;
break;
default:
assert(false);
*pFailure = VERIFY_ERROR_GENERIC;
break;
}
}
/*
* For a category 2 register pair, verify that "typeh" is the appropriate
* high part for "typel".
*
* Does not verify that "typel" is in fact the low part of a 64-bit
* register pair.
*/
static void checkWidePair(RegType typel, RegType typeh, VerifyError* pFailure)
{
if ((typeh != typel+1))
*pFailure = VERIFY_ERROR_GENERIC;
}
/*
* Implement category-1 "move" instructions. Copy a 32-bit value from
* "vsrc" to "vdst".
*/
static void copyRegister1(RegisterLine* registerLine, u4 vdst, u4 vsrc,
TypeCategory cat, VerifyError* pFailure)
{
assert(cat == kTypeCategory1nr || cat == kTypeCategoryRef);
RegType type = getRegisterType(registerLine, vsrc);
checkTypeCategory(type, cat, pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: copy1 v%u<-v%u type=%d cat=%d", vdst, vsrc, type, cat);
} else {
setRegisterType(registerLine, vdst, type);
if (cat == kTypeCategoryRef && registerLine->monitorEntries != NULL) {
registerLine->monitorEntries[vdst] =
registerLine->monitorEntries[vsrc];
}
}
}
/*
* Implement category-2 "move" instructions. Copy a 64-bit value from
* "vsrc" to "vdst". This copies both halves of the register.
*/
static void copyRegister2(RegisterLine* registerLine, u4 vdst, u4 vsrc,
VerifyError* pFailure)
{
RegType typel = getRegisterType(registerLine, vsrc);
RegType typeh = getRegisterType(registerLine, vsrc+1);
checkTypeCategory(typel, kTypeCategory2, pFailure);
checkWidePair(typel, typeh, pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: copy2 v%u<-v%u type=%d/%d", vdst, vsrc, typel, typeh);
} else {
setRegisterType(registerLine, vdst, typel);
/* target monitor stack bits will be cleared */
}
}
/*
* Implement "move-result". Copy the category-1 value from the result
* register to another register, and reset the result register.
*/
static void copyResultRegister1(RegisterLine* registerLine,
const int insnRegCount, u4 vdst, TypeCategory cat, VerifyError* pFailure)
{
RegType type;
u4 vsrc;
assert(vdst < (u4) insnRegCount);
vsrc = RESULT_REGISTER(insnRegCount);
type = getRegisterType(registerLine, vsrc);
checkTypeCategory(type, cat, pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: copyRes1 v%u<-v%u cat=%d type=%d",
vdst, vsrc, cat, type);
} else {
setRegisterType(registerLine, vdst, type);
setRegisterType(registerLine, vsrc, kRegTypeUnknown);
/* target monitor stack bits will be cleared */
}
}
/*
* Implement "move-result-wide". Copy the category-2 value from the result
* register to another register, and reset the result register.
*/
static void copyResultRegister2(RegisterLine* registerLine,
const int insnRegCount, u4 vdst, VerifyError* pFailure)
{
RegType typel, typeh;
u4 vsrc;
assert(vdst < (u4) insnRegCount);
vsrc = RESULT_REGISTER(insnRegCount);
typel = getRegisterType(registerLine, vsrc);
typeh = getRegisterType(registerLine, vsrc+1);
checkTypeCategory(typel, kTypeCategory2, pFailure);
checkWidePair(typel, typeh, pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: copyRes2 v%u<-v%u type=%d/%d",
vdst, vsrc, typel, typeh);
} else {
setRegisterType(registerLine, vdst, typel);
setRegisterType(registerLine, vsrc, kRegTypeUnknown);
setRegisterType(registerLine, vsrc+1, kRegTypeUnknown);
/* target monitor stack bits will be cleared */
}
}
/*
* Verify types for a simple two-register instruction (e.g. "neg-int").
* "dstType" is stored into vA, and "srcType" is verified against vB.
*/
static void checkUnop(RegisterLine* registerLine, DecodedInstruction* pDecInsn,
RegType dstType, RegType srcType, VerifyError* pFailure)
{
verifyRegisterType(registerLine, pDecInsn->vB, srcType, pFailure);
setRegisterType(registerLine, pDecInsn->vA, dstType);
}
/*
* We're performing an operation like "and-int/2addr" that can be
* performed on booleans as well as integers. We get no indication of
* boolean-ness, but we can infer it from the types of the arguments.
*
* Assumes we've already validated reg1/reg2.
*
* TODO: consider generalizing this. The key principle is that the
* result of a bitwise operation can only be as wide as the widest of
* the operands. You can safely AND/OR/XOR two chars together and know
* you still have a char, so it's reasonable for the compiler or "dx"
* to skip the int-to-char instruction. (We need to do this for boolean
* because there is no int-to-boolean operation.)
*
* Returns true if both args are Boolean, Zero, or One.
*/
static bool upcastBooleanOp(RegisterLine* registerLine, u4 reg1, u4 reg2)
{
RegType type1, type2;
type1 = getRegisterType(registerLine, reg1);
type2 = getRegisterType(registerLine, reg2);
if ((type1 == kRegTypeBoolean || type1 == kRegTypeZero ||
type1 == kRegTypeOne) &&
(type2 == kRegTypeBoolean || type2 == kRegTypeZero ||
type2 == kRegTypeOne))
{
return true;
}
return false;
}
/*
* Verify types for A two-register instruction with a literal constant
* (e.g. "add-int/lit8"). "dstType" is stored into vA, and "srcType" is
* verified against vB.
*
* If "checkBooleanOp" is set, we use the constant value in vC.
*/
static void checkLitop(RegisterLine* registerLine, DecodedInstruction* pDecInsn,
RegType dstType, RegType srcType, bool checkBooleanOp,
VerifyError* pFailure)
{
verifyRegisterType(registerLine, pDecInsn->vB, srcType, pFailure);
if (VERIFY_OK(*pFailure) && checkBooleanOp) {
assert(dstType == kRegTypeInteger);
/* check vB with the call, then check the constant manually */
if (upcastBooleanOp(registerLine, pDecInsn->vB, pDecInsn->vB)
&& (pDecInsn->vC == 0 || pDecInsn->vC == 1))
{
dstType = kRegTypeBoolean;
}
}
setRegisterType(registerLine, pDecInsn->vA, dstType);
}
/*
* Verify types for a simple three-register instruction (e.g. "add-int").
* "dstType" is stored into vA, and "srcType1"/"srcType2" are verified
* against vB/vC.
*/
static void checkBinop(RegisterLine* registerLine, DecodedInstruction* pDecInsn,
RegType dstType, RegType srcType1, RegType srcType2, bool checkBooleanOp,
VerifyError* pFailure)
{
verifyRegisterType(registerLine, pDecInsn->vB, srcType1, pFailure);
verifyRegisterType(registerLine, pDecInsn->vC, srcType2, pFailure);
if (VERIFY_OK(*pFailure) && checkBooleanOp) {
assert(dstType == kRegTypeInteger);
if (upcastBooleanOp(registerLine, pDecInsn->vB, pDecInsn->vC))
dstType = kRegTypeBoolean;
}
setRegisterType(registerLine, pDecInsn->vA, dstType);
}
/*
* Verify types for a binary "2addr" operation. "srcType1"/"srcType2"
* are verified against vA/vB, then "dstType" is stored into vA.
*/
static void checkBinop2addr(RegisterLine* registerLine,
DecodedInstruction* pDecInsn, RegType dstType, RegType srcType1,
RegType srcType2, bool checkBooleanOp, VerifyError* pFailure)
{
verifyRegisterType(registerLine, pDecInsn->vA, srcType1, pFailure);
verifyRegisterType(registerLine, pDecInsn->vB, srcType2, pFailure);
if (VERIFY_OK(*pFailure) && checkBooleanOp) {
assert(dstType == kRegTypeInteger);
if (upcastBooleanOp(registerLine, pDecInsn->vA, pDecInsn->vB))
dstType = kRegTypeBoolean;
}
setRegisterType(registerLine, pDecInsn->vA, dstType);
}
/*
* Treat right-shifting as a narrowing conversion when possible.
*
* For example, right-shifting an int 24 times results in a value that can
* be treated as a byte.
*
* Things get interesting when contemplating sign extension. Right-
* shifting an integer by 16 yields a value that can be represented in a
* "short" but not a "char", but an unsigned right shift by 16 yields a
* value that belongs in a char rather than a short. (Consider what would
* happen if the result of the shift were cast to a char or short and then
* cast back to an int. If sign extension, or the lack thereof, causes
* a change in the 32-bit representation, then the conversion was lossy.)
*
* A signed right shift by 17 on an integer results in a short. An unsigned
* right shfit by 17 on an integer results in a posshort, which can be
* assigned to a short or a char.
*
* An unsigned right shift on a short can actually expand the result into
* a 32-bit integer. For example, 0xfffff123 >>> 8 becomes 0x00fffff1,
* which can't be represented in anything smaller than an int.
*
* javac does not generate code that takes advantage of this, but some
* of the code optimizers do. It's generally a peephole optimization
* that replaces a particular sequence, e.g. (bipush 24, ishr, i2b) is
* replaced by (bipush 24, ishr). Knowing that shifting a short 8 times
* to the right yields a byte is really more than we need to handle the
* code that's out there, but support is not much more complex than just
* handling integer.
*
* Right-shifting never yields a boolean value.
*
* Returns the new register type.
*/
static RegType adjustForRightShift(RegisterLine* registerLine, int reg,
unsigned int shiftCount, bool isUnsignedShift, VerifyError* pFailure)
{
RegType srcType = getRegisterType(registerLine, reg);
RegType newType;
/* convert const derived types to their actual types */
srcType = constTypeToRegType(srcType);
/* no-op */
if (shiftCount == 0)
return srcType;
/* safe defaults */
if (isUnsignedShift)
newType = kRegTypeInteger;
else
newType = srcType;
if (shiftCount >= 32) {
LOG_VFY("Got unexpectedly large shift count %u", shiftCount);
/* fail? */
return newType;
}
switch (srcType) {
case kRegTypeInteger: /* 32-bit signed value */
if (isUnsignedShift) {
if (shiftCount > 24)
newType = kRegTypePosByte;
else if (shiftCount >= 16)
newType = kRegTypeChar;
} else {
if (shiftCount >= 24)
newType = kRegTypeByte;
else if (shiftCount >= 16)
newType = kRegTypeShort;
}
break;
case kRegTypeShort: /* 16-bit signed value */
if (isUnsignedShift) {
/* default (kRegTypeInteger) is correct */
} else {
if (shiftCount >= 8)
newType = kRegTypeByte;
}
break;
case kRegTypePosShort: /* 15-bit unsigned value */
if (shiftCount >= 8)
newType = kRegTypePosByte;
break;
case kRegTypeChar: /* 16-bit unsigned value */
if (shiftCount > 8)
newType = kRegTypePosByte;
break;
case kRegTypeByte: /* 8-bit signed value */
/* defaults (u=kRegTypeInteger / s=srcType) are correct */
break;
case kRegTypePosByte: /* 7-bit unsigned value */
/* always use newType=srcType */
newType = srcType;
break;
case kRegTypeZero: /* 1-bit unsigned value */
case kRegTypeOne:
case kRegTypeBoolean:
/* unnecessary? */
newType = kRegTypeZero;
break;
default:
/* long, double, references; shouldn't be here! */
assert(false);
break;
}
if (newType != srcType) {
LOGVV("narrowing: %d(%d) --> %d to %d",
shiftCount, isUnsignedShift, srcType, newType);
} else {
LOGVV("not narrowed: %d(%d) --> %d",
shiftCount, isUnsignedShift, srcType);
}
return newType;
}
/*
* ===========================================================================
* Register merge
* ===========================================================================
*/
/*
* Compute the "class depth" of a class. This is the distance from the
* class to the top of the tree, chasing superclass links. java.lang.Object
* has a class depth of 0.
*/
static int getClassDepth(ClassObject* clazz)
{
int depth = 0;
while (clazz->super != NULL) {
clazz = clazz->super;
depth++;
}
return depth;
}
/*
* Given two classes, walk up the superclass tree to find a common
* ancestor. (Called from findCommonSuperclass().)
*
* TODO: consider caching the class depth in the class object so we don't
* have to search for it here.
*/
static ClassObject* digForSuperclass(ClassObject* c1, ClassObject* c2)
{
int depth1, depth2;
depth1 = getClassDepth(c1);
depth2 = getClassDepth(c2);
if (gDebugVerbose) {
LOGVV("COMMON: %s(%d) + %s(%d)",
c1->descriptor, depth1, c2->descriptor, depth2);
}
/* pull the deepest one up */
if (depth1 > depth2) {
while (depth1 > depth2) {
c1 = c1->super;
depth1--;
}
} else {
while (depth2 > depth1) {
c2 = c2->super;
depth2--;
}
}
/* walk up in lock-step */
while (c1 != c2) {
c1 = c1->super;
c2 = c2->super;
assert(c1 != NULL && c2 != NULL);
}
if (gDebugVerbose) {
LOGVV(" : --> %s", c1->descriptor);
}
return c1;
}
/*
* Merge two array classes. We can't use the general "walk up to the
* superclass" merge because the superclass of an array is always Object.
* We want String[] + Integer[] = Object[]. This works for higher dimensions
* as well, e.g. String[][] + Integer[][] = Object[][].
*
* If Foo1 and Foo2 are subclasses of Foo, Foo1[] + Foo2[] = Foo[].
*
* If Class implements Type, Class[] + Type[] = Type[].
*
* If the dimensions don't match, we want to convert to an array of Object
* with the least dimension, e.g. String[][] + String[][][][] = Object[][].
*
* Arrays of primitive types effectively have one less dimension when
* merging. int[] + float[] = Object, int[] + String[] = Object,
* int[][] + float[][] = Object[], int[][] + String[] = Object[]. (The
* only time this function doesn't return an array class is when one of
* the arguments is a 1-dimensional primitive array.)
*
* This gets a little awkward because we may have to ask the VM to create
* a new array type with the appropriate element and dimensions. However, we
* shouldn't be doing this often.
*/
static ClassObject* findCommonArraySuperclass(ClassObject* c1, ClassObject* c2)
{
ClassObject* arrayClass = NULL;
ClassObject* commonElem;
int arrayDim1, arrayDim2;
int i, numDims;
bool hasPrimitive = false;
arrayDim1 = c1->arrayDim;
arrayDim2 = c2->arrayDim;
assert(c1->arrayDim > 0);
assert(c2->arrayDim > 0);
if (dvmIsPrimitiveClass(c1->elementClass)) {
arrayDim1--;
hasPrimitive = true;
}
if (dvmIsPrimitiveClass(c2->elementClass)) {
arrayDim2--;
hasPrimitive = true;
}
if (!hasPrimitive && arrayDim1 == arrayDim2) {
/*
* Two arrays of reference types with equal dimensions. Try to
* find a good match.
*/
commonElem = findCommonSuperclass(c1->elementClass, c2->elementClass);
numDims = arrayDim1;
} else {
/*
* Mismatched array depths and/or array(s) of primitives. We want
* Object, or an Object array with appropriate dimensions.
*
* We initialize arrayClass to Object here, because it's possible
* for us to set numDims=0.
*/
if (arrayDim1 < arrayDim2)
numDims = arrayDim1;
else
numDims = arrayDim2;
arrayClass = commonElem = c1->super; // == java.lang.Object
}
/*
* Find an appropriately-dimensioned array class. This is easiest
* to do iteratively, using the array class found by the current round
* as the element type for the next round.
*/
for (i = 0; i < numDims; i++) {
arrayClass = dvmFindArrayClassForElement(commonElem);
commonElem = arrayClass;
}
assert(arrayClass != NULL);
LOGVV("ArrayMerge '%s' + '%s' --> '%s'",
c1->descriptor, c2->descriptor, arrayClass->descriptor);
return arrayClass;
}
/*
* Find the first common superclass of the two classes. We're not
* interested in common interfaces.
*
* The easiest way to do this for concrete classes is to compute the "class
* depth" of each, move up toward the root of the deepest one until they're
* at the same depth, then walk both up to the root until they match.
*
* If both classes are arrays, we need to merge based on array depth and
* element type.
*
* If one class is an interface, we check to see if the other class/interface
* (or one of its predecessors) implements the interface. If so, we return
* the interface; otherwise, we return Object.
*
* NOTE: we continue the tradition of "lazy interface handling". To wit,
* suppose we have three classes:
* One implements Fancy, Free
* Two implements Fancy, Free
* Three implements Free
* where Fancy and Free are unrelated interfaces. The code requires us
* to merge One into Two. Ideally we'd use a common interface, which
* gives us a choice between Fancy and Free, and no guidance on which to
* use. If we use Free, we'll be okay when Three gets merged in, but if
* we choose Fancy, we're hosed. The "ideal" solution is to create a
* set of common interfaces and carry that around, merging further references
* into it. This is a pain. The easy solution is to simply boil them
* down to Objects and let the runtime invokeinterface call fail, which
* is what we do.
*/
static ClassObject* findCommonSuperclass(ClassObject* c1, ClassObject* c2)
{
assert(!dvmIsPrimitiveClass(c1) && !dvmIsPrimitiveClass(c2));
if (c1 == c2)
return c1;
if (dvmIsInterfaceClass(c1) && dvmImplements(c2, c1)) {
if (gDebugVerbose)
LOGVV("COMMON/I1: %s + %s --> %s",
c1->descriptor, c2->descriptor, c1->descriptor);
return c1;
}
if (dvmIsInterfaceClass(c2) && dvmImplements(c1, c2)) {
if (gDebugVerbose)
LOGVV("COMMON/I2: %s + %s --> %s",
c1->descriptor, c2->descriptor, c2->descriptor);
return c2;
}
if (dvmIsArrayClass(c1) && dvmIsArrayClass(c2)) {
return findCommonArraySuperclass(c1, c2);
}
return digForSuperclass(c1, c2);
}
/*
* Merge two RegType values.
*
* Sets "*pChanged" to "true" if the result doesn't match "type1".
*/
static RegType mergeTypes(RegType type1, RegType type2, bool* pChanged)
{
RegType result;
/*
* Check for trivial case so we don't have to hit memory.
*/
if (type1 == type2)
return type1;
/*
* Use the table if we can, and reject any attempts to merge something
* from the table with a reference type.
*
* Uninitialized references are composed of the enum ORed with an
* index value. The uninitialized table entry at index zero *will*
* show up as a simple kRegTypeUninit value. Since this cannot be
* merged with anything but itself, the rules do the right thing.
*/
if (type1 < kRegTypeMAX) {
if (type2 < kRegTypeMAX) {
result = gDvmMergeTab[type1][type2];
} else {
/* simple + reference == conflict, usually */
if (type1 == kRegTypeZero)
result = type2;
else
result = kRegTypeConflict;
}
} else {
if (type2 < kRegTypeMAX) {
/* reference + simple == conflict, usually */
if (type2 == kRegTypeZero)
result = type1;
else
result = kRegTypeConflict;
} else {
/* merging two references */
if (regTypeIsUninitReference(type1) ||
regTypeIsUninitReference(type2))
{
/* can't merge uninit with anything but self */
result = kRegTypeConflict;
} else {
ClassObject* clazz1 = regTypeInitializedReferenceToClass(type1);
ClassObject* clazz2 = regTypeInitializedReferenceToClass(type2);
ClassObject* mergedClass;
mergedClass = findCommonSuperclass(clazz1, clazz2);
assert(mergedClass != NULL);
result = regTypeFromClass(mergedClass);
}
}
}
if (result != type1)
*pChanged = true;
return result;
}
/*
* Merge the bits that indicate which monitor entry addresses on the stack
* are associated with this register.
*
* The merge is a simple bitwise AND.
*
* Sets "*pChanged" to "true" if the result doesn't match "ents1".
*/
static MonitorEntries mergeMonitorEntries(MonitorEntries ents1,
MonitorEntries ents2, bool* pChanged)
{
MonitorEntries result = ents1 & ents2;
if (result != ents1)
*pChanged = true;
return result;
}
/*
* Control can transfer to "nextInsn".
*
* Merge the registers from "workLine" into "regTable" at "nextInsn", and
* set the "changed" flag on the target address if any of the registers
* has changed.
*
* Returns "false" if we detect mis-matched monitor stacks.
*/
static bool updateRegisters(const Method* meth, InsnFlags* insnFlags,
RegisterTable* regTable, int nextInsn, const RegisterLine* workLine)
{
const size_t insnRegCountPlus = regTable->insnRegCountPlus;
assert(workLine != NULL);
const RegType* workRegs = workLine->regTypes;
if (!dvmInsnIsVisitedOrChanged(insnFlags, nextInsn)) {
/*
* We haven't processed this instruction before, and we haven't
* touched the registers here, so there's nothing to "merge". Copy
* the registers over and mark it as changed. (This is the only
* way a register can transition out of "unknown", so this is not
* just an optimization.)
*/
LOGVV("COPY into 0x%04x", nextInsn);
copyLineToTable(regTable, nextInsn, workLine);
dvmInsnSetChanged(insnFlags, nextInsn, true);
#ifdef VERIFIER_STATS
gDvm.verifierStats.copyRegCount++;
#endif
} else {
if (gDebugVerbose) {
LOGVV("MERGE into 0x%04x", nextInsn);
//dumpRegTypes(vdata, targetRegs, 0, "targ", NULL, 0);
//dumpRegTypes(vdata, workRegs, 0, "work", NULL, 0);
}
/* merge registers, set Changed only if different */
RegisterLine* targetLine = getRegisterLine(regTable, nextInsn);
RegType* targetRegs = targetLine->regTypes;
MonitorEntries* workMonEnts = workLine->monitorEntries;
MonitorEntries* targetMonEnts = targetLine->monitorEntries;
bool changed = false;
unsigned int idx;
assert(targetRegs != NULL);
if (targetMonEnts != NULL) {
/*
* Monitor stacks must be identical.
*/
if (targetLine->monitorStackTop != workLine->monitorStackTop) {
LOG_VFY_METH(meth,
"VFY: mismatched stack depth %d vs. %d at 0x%04x",
targetLine->monitorStackTop, workLine->monitorStackTop,
nextInsn);
return false;
}
if (memcmp(targetLine->monitorStack, workLine->monitorStack,
targetLine->monitorStackTop * sizeof(u4)) != 0)
{
LOG_VFY_METH(meth, "VFY: mismatched monitor stacks at 0x%04x",
nextInsn);
return false;
}
}
for (idx = 0; idx < insnRegCountPlus; idx++) {
targetRegs[idx] =
mergeTypes(targetRegs[idx], workRegs[idx], &changed);
if (targetMonEnts != NULL) {
targetMonEnts[idx] = mergeMonitorEntries(targetMonEnts[idx],
workMonEnts[idx], &changed);
}
}
if (gDebugVerbose) {
//ALOGI(" RESULT (changed=%d)", changed);
//dumpRegTypes(vdata, targetRegs, 0, "rslt", NULL, 0);
}
#ifdef VERIFIER_STATS
gDvm.verifierStats.mergeRegCount++;
if (changed)
gDvm.verifierStats.mergeRegChanged++;
#endif
if (changed)
dvmInsnSetChanged(insnFlags, nextInsn, true);
}
return true;
}
/*
* ===========================================================================
* Utility functions
* ===========================================================================
*/
/*
* Look up an instance field, specified by "fieldIdx", that is going to be
* accessed in object "objType". This resolves the field and then verifies
* that the class containing the field is an instance of the reference in
* "objType".
*
* It is possible for "objType" to be kRegTypeZero, meaning that we might
* have a null reference. This is a runtime problem, so we allow it,
* skipping some of the type checks.
*
* In general, "objType" must be an initialized reference. However, we
* allow it to be uninitialized if this is an "<init>" method and the field
* is declared within the "objType" class.
*
* Returns an InstField on success, returns NULL and sets "*pFailure"
* on failure.
*/
static InstField* getInstField(const Method* meth,
const UninitInstanceMap* uninitMap, RegType objType, int fieldIdx,
VerifyError* pFailure)
{
InstField* instField = NULL;
ClassObject* objClass;
bool mustBeLocal = false;
if (!regTypeIsReference(objType)) {
LOG_VFY("VFY: attempt to access field in non-reference type %d",
objType);
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
instField = dvmOptResolveInstField(meth->clazz, fieldIdx, pFailure);
if (instField == NULL) {
LOG_VFY("VFY: unable to resolve instance field %u", fieldIdx);
assert(!VERIFY_OK(*pFailure));
goto bail;
}
if (objType == kRegTypeZero)
goto bail;
/*
* Access to fields in uninitialized objects is allowed if this is
* the <init> method for the object and the field in question is
* declared by this class.
*/
objClass = regTypeReferenceToClass(objType, uninitMap);
assert(objClass != NULL);
if (regTypeIsUninitReference(objType)) {
if (!isInitMethod(meth) || meth->clazz != objClass) {
LOG_VFY("VFY: attempt to access field via uninitialized ref");
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
mustBeLocal = true;
}
if (!dvmInstanceof(objClass, instField->clazz)) {
LOG_VFY("VFY: invalid field access (field %s.%s, through %s ref)",
instField->clazz->descriptor, instField->name,
objClass->descriptor);
*pFailure = VERIFY_ERROR_NO_FIELD;
goto bail;
}
if (mustBeLocal) {
/* for uninit ref, make sure it's defined by this class, not super */
if (instField < objClass->ifields ||
instField >= objClass->ifields + objClass->ifieldCount)
{
LOG_VFY("VFY: invalid constructor field access (field %s in %s)",
instField->name, objClass->descriptor);
*pFailure = VERIFY_ERROR_GENERIC;
goto bail;
}
}
bail:
return instField;
}
/*
* Look up a static field.
*
* Returns a StaticField on success, returns NULL and sets "*pFailure"
* on failure.
*/
static StaticField* getStaticField(const Method* meth, int fieldIdx,
VerifyError* pFailure)
{
StaticField* staticField;
staticField = dvmOptResolveStaticField(meth->clazz, fieldIdx, pFailure);
if (staticField == NULL) {
DexFile* pDexFile = meth->clazz->pDvmDex->pDexFile;
const DexFieldId* pFieldId;
pFieldId = dexGetFieldId(pDexFile, fieldIdx);
LOG_VFY("VFY: unable to resolve static field %u (%s) in %s", fieldIdx,
dexStringById(pDexFile, pFieldId->nameIdx),
dexStringByTypeIdx(pDexFile, pFieldId->classIdx));
assert(!VERIFY_OK(*pFailure));
goto bail;
}
bail:
return staticField;
}
/*
* If "field" is marked "final", make sure this is the either <clinit>
* or <init> as appropriate.
*
* Sets "*pFailure" on failure.
*/
static void checkFinalFieldAccess(const Method* meth, const Field* field,
VerifyError* pFailure)
{
if (!dvmIsFinalField(field))
return;
/* make sure we're in the same class */
if (meth->clazz != field->clazz) {
LOG_VFY_METH(meth, "VFY: can't modify final field %s.%s",
field->clazz->descriptor, field->name);
*pFailure = VERIFY_ERROR_ACCESS_FIELD;
return;
}
/*
* The VM spec descriptions of putfield and putstatic say that
* IllegalAccessError is only thrown when the instructions appear
* outside the declaring class. Our earlier attempts to restrict
* final field modification to constructors are, therefore, wrong.
*/
#if 0
/* make sure we're in the right kind of constructor */
if (dvmIsStaticField(field)) {
if (!isClassInitMethod(meth)) {
LOG_VFY_METH(meth,
"VFY: can't modify final static field outside <clinit>");
*pFailure = VERIFY_ERROR_GENERIC;
}
} else {
if (!isInitMethod(meth)) {
LOG_VFY_METH(meth,
"VFY: can't modify final field outside <init>");
*pFailure = VERIFY_ERROR_GENERIC;
}
}
#endif
}
/*
* Make sure that the register type is suitable for use as an array index.
*
* Sets "*pFailure" if not.
*/
static void checkArrayIndexType(const Method* meth, RegType regType,
VerifyError* pFailure)
{
if (VERIFY_OK(*pFailure)) {
/*
* The 1nr types are interchangeable at this level. However,
* check that a float is not used as the index.
*/
checkTypeCategory(regType, kTypeCategory1nr, pFailure);
if (regType == kRegTypeFloat) {
*pFailure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(*pFailure)) {
LOG_VFY_METH(meth, "Invalid reg type for array index (%d)",
regType);
}
}
}
/*
* Check constraints on constructor return. Specifically, make sure that
* the "this" argument got initialized.
*
* The "this" argument to <init> uses code offset kUninitThisArgAddr, which
* puts it at the start of the list in slot 0. If we see a register with
* an uninitialized slot 0 reference, we know it somehow didn't get
* initialized.
*
* Returns "true" if all is well.
*/
static bool checkConstructorReturn(const Method* meth,
const RegisterLine* registerLine, const int insnRegCount)
{
const RegType* insnRegs = registerLine->regTypes;
int i;
if (!isInitMethod(meth))
return true;
RegType uninitThis = regTypeFromUninitIndex(kUninitThisArgSlot);
for (i = 0; i < insnRegCount; i++) {
if (insnRegs[i] == uninitThis) {
LOG_VFY("VFY: <init> returning without calling superclass init");
return false;
}
}
return true;
}
/*
* Verify that the target instruction is not "move-exception". It's important
* that the only way to execute a move-exception is as the first instruction
* of an exception handler.
*
* Returns "true" if all is well, "false" if the target instruction is
* move-exception.
*/
static bool checkMoveException(const Method* meth, int insnIdx,
const char* logNote)
{
assert(insnIdx >= 0 && insnIdx < (int)dvmGetMethodInsnsSize(meth));
if ((meth->insns[insnIdx] & 0xff) == OP_MOVE_EXCEPTION) {
LOG_VFY("VFY: invalid use of move-exception");
return false;
}
return true;
}
/*
* For the "move-exception" instruction at "insnIdx", which must be at an
* exception handler address, determine the first common superclass of
* all exceptions that can land here. (For javac output, we're probably
* looking at multiple spans of bytecode covered by one "try" that lands
* at an exception-specific "catch", but in general the handler could be
* shared for multiple exceptions.)
*
* Returns NULL if no matching exception handler can be found, or if the
* exception is not a subclass of Throwable.
*/
static ClassObject* getCaughtExceptionType(const Method* meth, int insnIdx,
VerifyError* pFailure)
{
VerifyError localFailure;
const DexCode* pCode;
DexFile* pDexFile;
ClassObject* commonSuper = NULL;
u4 handlersSize;
u4 offset;
u4 i;
pDexFile = meth->clazz->pDvmDex->pDexFile;
pCode = dvmGetMethodCode(meth);
if (pCode->triesSize != 0) {
handlersSize = dexGetHandlersSize(pCode);
offset = dexGetFirstHandlerOffset(pCode);
} else {
handlersSize = 0;
offset = 0;
}
for (i = 0; i < handlersSize; i++) {
DexCatchIterator iterator;
dexCatchIteratorInit(&iterator, pCode, offset);
for (;;) {
const DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
if (handler == NULL) {
break;
}
if (handler->address == (u4) insnIdx) {
ClassObject* clazz;
if (handler->typeIdx == kDexNoIndex)
clazz = gDvm.exThrowable;
else
clazz = dvmOptResolveClass(meth->clazz, handler->typeIdx,
&localFailure);
if (clazz == NULL) {
LOG_VFY("VFY: unable to resolve exception class %u (%s)",
handler->typeIdx,
dexStringByTypeIdx(pDexFile, handler->typeIdx));
/* TODO: do we want to keep going? If we don't fail
* this we run the risk of having a non-Throwable
* introduced at runtime. However, that won't pass
* an instanceof test, so is essentially harmless. */
} else {
if (commonSuper == NULL)
commonSuper = clazz;
else
commonSuper = findCommonSuperclass(clazz, commonSuper);
}
}
}
offset = dexCatchIteratorGetEndOffset(&iterator, pCode);
}
if (commonSuper == NULL) {
/* no catch blocks, or no catches with classes we can find */
LOG_VFY_METH(meth,
"VFY: unable to find exception handler at addr %#x", insnIdx);
*pFailure = VERIFY_ERROR_GENERIC;
} else {
// TODO: verify the class is an instance of Throwable?
}
return commonSuper;
}
/*
* Helper for initRegisterTable.
*
* Returns an updated copy of "storage".
*/
static u1* assignLineStorage(u1* storage, RegisterLine* line,
bool trackMonitors, size_t regTypeSize, size_t monEntSize, size_t stackSize)
{
line->regTypes = (RegType*) storage;
storage += regTypeSize;
if (trackMonitors) {
line->monitorEntries = (MonitorEntries*) storage;
storage += monEntSize;
line->monitorStack = (u4*) storage;
storage += stackSize;
assert(line->monitorStackTop == 0);
}
return storage;
}
/*
* Initialize the RegisterTable.
*
* Every instruction address can have a different set of information about
* what's in which register, but for verification purposes we only need to
* store it at branch target addresses (because we merge into that).
*
* By zeroing out the regType storage we are effectively initializing the
* register information to kRegTypeUnknown.
*
* We jump through some hoops here to minimize the total number of
* allocations we have to perform per method verified.
*/
static bool initRegisterTable(const VerifierData* vdata,
RegisterTable* regTable, RegisterTrackingMode trackRegsFor)
{
const Method* meth = vdata->method;
const int insnsSize = vdata->insnsSize;
const InsnFlags* insnFlags = vdata->insnFlags;
const int kExtraLines = 2; /* workLine, savedLine */
int i;
/*
* Every address gets a RegisterLine struct. This is wasteful, but
* not so much that it's worth chasing through an extra level of
* indirection.
*/
regTable->insnRegCountPlus = meth->registersSize + kExtraRegs;
regTable->registerLines =
(RegisterLine*) calloc(insnsSize, sizeof(RegisterLine));
if (regTable->registerLines == NULL)
return false;
assert(insnsSize > 0);
/*
* Count up the number of "interesting" instructions.
*
* "All" means "every address that holds the start of an instruction".
* "Branches" and "GcPoints" mean just those addresses.
*
* "GcPoints" fills about half the addresses, "Branches" about 15%.
*/
int interestingCount = kExtraLines;
for (i = 0; i < insnsSize; i++) {
bool interesting;
switch (trackRegsFor) {
case kTrackRegsAll:
interesting = dvmInsnIsOpcode(insnFlags, i);
break;
case kTrackRegsGcPoints:
interesting = dvmInsnIsGcPoint(insnFlags, i) ||
dvmInsnIsBranchTarget(insnFlags, i);
break;
case kTrackRegsBranches:
interesting = dvmInsnIsBranchTarget(insnFlags, i);
break;
default:
dvmAbort();
return false;
}
if (interesting)
interestingCount++;
/* count instructions, for display only */
//if (dvmInsnIsOpcode(insnFlags, i))
// insnCount++;
}
/*
* Allocate storage for the register type arrays.
* TODO: set trackMonitors based on global config option
*/
size_t regTypeSize = regTable->insnRegCountPlus * sizeof(RegType);
size_t monEntSize = regTable->insnRegCountPlus * sizeof(MonitorEntries);
size_t stackSize = kMaxMonitorStackDepth * sizeof(u4);
bool trackMonitors;
if (gDvm.monitorVerification) {
trackMonitors = (vdata->monitorEnterCount != 0);
} else {
trackMonitors = false;
}
size_t spacePerEntry = regTypeSize +
(trackMonitors ? monEntSize + stackSize : 0);
regTable->lineAlloc = calloc(interestingCount, spacePerEntry);
if (regTable->lineAlloc == NULL)
return false;
#ifdef VERIFIER_STATS
size_t totalSpace = interestingCount * spacePerEntry +
insnsSize * sizeof(RegisterLine);
if (gDvm.verifierStats.biggestAlloc < totalSpace)
gDvm.verifierStats.biggestAlloc = totalSpace;
#endif
/*
* Populate the sparse register line table.
*
* There is a RegisterLine associated with every address, but not
* every RegisterLine has non-NULL pointers to storage for its fields.
*/
u1* storage = (u1*)regTable->lineAlloc;
for (i = 0; i < insnsSize; i++) {
bool interesting;
switch (trackRegsFor) {
case kTrackRegsAll:
interesting = dvmInsnIsOpcode(insnFlags, i);
break;
case kTrackRegsGcPoints:
interesting = dvmInsnIsGcPoint(insnFlags, i) ||
dvmInsnIsBranchTarget(insnFlags, i);
break;
case kTrackRegsBranches:
interesting = dvmInsnIsBranchTarget(insnFlags, i);
break;
default:
dvmAbort();
return false;
}
if (interesting) {
storage = assignLineStorage(storage, ®Table->registerLines[i],
trackMonitors, regTypeSize, monEntSize, stackSize);
}
}
/*
* Grab storage for our "temporary" register lines.
*/
storage = assignLineStorage(storage, ®Table->workLine,
trackMonitors, regTypeSize, monEntSize, stackSize);
storage = assignLineStorage(storage, ®Table->savedLine,
trackMonitors, regTypeSize, monEntSize, stackSize);
//ALOGD("Tracking registers for [%d], total %d in %d units",
// trackRegsFor, interestingCount-kExtraLines, insnsSize);
assert(storage - (u1*)regTable->lineAlloc ==
(int) (interestingCount * spacePerEntry));
assert(regTable->registerLines[0].regTypes != NULL);
return true;
}
/*
* Free up any "hairy" structures associated with register lines.
*/
static void freeRegisterLineInnards(VerifierData* vdata)
{
unsigned int idx;
if (vdata->registerLines == NULL)
return;
for (idx = 0; idx < vdata->insnsSize; idx++) {
BitVector* liveRegs = vdata->registerLines[idx].liveRegs;
if (liveRegs != NULL)
dvmFreeBitVector(liveRegs);
}
}
/*
* Verify that the arguments in a filled-new-array instruction are valid.
*
* "resClass" is the class refered to by pDecInsn->vB.
*/
static void verifyFilledNewArrayRegs(const Method* meth,
RegisterLine* registerLine, const DecodedInstruction* pDecInsn,
ClassObject* resClass, bool isRange, VerifyError* pFailure)
{
u4 argCount = pDecInsn->vA;
RegType expectedType;
PrimitiveType elemType;
unsigned int ui;
assert(dvmIsArrayClass(resClass));
elemType = resClass->elementClass->primitiveType;
if (elemType == PRIM_NOT) {
expectedType = regTypeFromClass(resClass->elementClass);
} else {
expectedType = primitiveTypeToRegType(elemType);
}
//ALOGI("filled-new-array: %s -> %d", resClass->descriptor, expectedType);
/*
* Verify each register. If "argCount" is bad, verifyRegisterType()
* will run off the end of the list and fail. It's legal, if silly,
* for argCount to be zero.
*/
for (ui = 0; ui < argCount; ui++) {
u4 getReg;
if (isRange)
getReg = pDecInsn->vC + ui;
else
getReg = pDecInsn->arg[ui];
verifyRegisterType(registerLine, getReg, expectedType, pFailure);
if (!VERIFY_OK(*pFailure)) {
LOG_VFY("VFY: filled-new-array arg %u(%u) not valid", ui, getReg);
return;
}
}
}
/*
* Replace an instruction with "throw-verification-error". This allows us to
* defer error reporting until the code path is first used.
*
* This is expected to be called during "just in time" verification, not
* from within dexopt. (Verification failures in dexopt will result in
* postponement of verification to first use of the class.)
*
* The throw-verification-error instruction requires two code units. Some
* of the replaced instructions require three; the third code unit will
* receive a "nop". The instruction's length will be left unchanged
* in "insnFlags".
*
* The VM postpones setting of debugger breakpoints in unverified classes,
* so there should be no clashes with the debugger.
*
* Returns "true" on success.
*/
static bool replaceFailingInstruction(const Method* meth, InsnFlags* insnFlags,
int insnIdx, VerifyError failure)
{
VerifyErrorRefType refType;
u2* oldInsns = (u2*) meth->insns + insnIdx;
int width;
if (gDvm.optimizing)
ALOGD("Weird: RFI during dexopt?");
/*
* Generate the new instruction out of the old.
*
* First, make sure this is an instruction we're expecting to stomp on.
*/
Opcode opcode = dexOpcodeFromCodeUnit(*oldInsns);
switch (opcode) {
case OP_CONST_CLASS: // insn[1] == class ref, 2 bytes
case OP_CHECK_CAST:
case OP_INSTANCE_OF:
case OP_NEW_INSTANCE:
case OP_NEW_ARRAY:
case OP_FILLED_NEW_ARRAY: // insn[1] == class ref, 3 bytes
case OP_FILLED_NEW_ARRAY_RANGE:
refType = VERIFY_ERROR_REF_CLASS;
break;
case OP_IGET: // insn[1] == field ref, 2 bytes
case OP_IGET_BOOLEAN:
case OP_IGET_BYTE:
case OP_IGET_CHAR:
case OP_IGET_SHORT:
case OP_IGET_WIDE:
case OP_IGET_OBJECT:
case OP_IPUT:
case OP_IPUT_BOOLEAN:
case OP_IPUT_BYTE:
case OP_IPUT_CHAR:
case OP_IPUT_SHORT:
case OP_IPUT_WIDE:
case OP_IPUT_OBJECT:
case OP_SGET:
case OP_SGET_BOOLEAN:
case OP_SGET_BYTE:
case OP_SGET_CHAR:
case OP_SGET_SHORT:
case OP_SGET_WIDE:
case OP_SGET_OBJECT:
case OP_SPUT:
case OP_SPUT_BOOLEAN:
case OP_SPUT_BYTE:
case OP_SPUT_CHAR:
case OP_SPUT_SHORT:
case OP_SPUT_WIDE:
case OP_SPUT_OBJECT:
refType = VERIFY_ERROR_REF_FIELD;
break;
case OP_INVOKE_VIRTUAL: // insn[1] == method ref, 3 bytes
case OP_INVOKE_VIRTUAL_RANGE:
case OP_INVOKE_SUPER:
case OP_INVOKE_SUPER_RANGE:
case OP_INVOKE_DIRECT:
case OP_INVOKE_DIRECT_RANGE:
case OP_INVOKE_STATIC:
case OP_INVOKE_STATIC_RANGE:
case OP_INVOKE_INTERFACE:
case OP_INVOKE_INTERFACE_RANGE:
refType = VERIFY_ERROR_REF_METHOD;
break;
default:
/* could handle this in a generic way, but this is probably safer */
LOG_VFY("GLITCH: verifier asked to replace opcode 0x%02x", opcode);
return false;
}
assert((dexGetFlagsFromOpcode(opcode) & kInstrCanThrow) != 0);
/* write a NOP over the third code unit, if necessary */
width = dvmInsnGetWidth(insnFlags, insnIdx);
switch (width) {
case 2:
case 4:
/* nothing to do */
break;
case 3:
dvmUpdateCodeUnit(meth, oldInsns+2, OP_NOP);
break;
default:
/* whoops */
ALOGE("ERROR: stomped a %d-unit instruction with a verifier error",
width);
dvmAbort();
}
/* encode the opcode, with the failure code in the high byte */
assert(width == 2 || width == 3);
u2 newVal = OP_THROW_VERIFICATION_ERROR |
(failure << 8) | (refType << (8 + kVerifyErrorRefTypeShift));
dvmUpdateCodeUnit(meth, oldInsns, newVal);
return true;
}
/*
* Handle a monitor-enter instruction.
*/
void handleMonitorEnter(RegisterLine* workLine, u4 regIdx, u4 insnIdx,
VerifyError* pFailure)
{
if (!regTypeIsReference(getRegisterType(workLine, regIdx))) {
LOG_VFY("VFY: monitor-enter on non-object");
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
if (workLine->monitorEntries == NULL) {
/* should only be true if monitor verification is disabled */
assert(!gDvm.monitorVerification);
return;
}
if (workLine->monitorStackTop == kMaxMonitorStackDepth) {
LOG_VFY("VFY: monitor-enter stack overflow (%d)",
kMaxMonitorStackDepth);
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
/*
* Push an entry on the stack, and set a bit in the register flags to
* indicate that it's associated with this register.
*/
workLine->monitorEntries[regIdx] |= 1 << workLine->monitorStackTop;
workLine->monitorStack[workLine->monitorStackTop++] = insnIdx;
}
/*
* Handle a monitor-exit instruction.
*/
void handleMonitorExit(RegisterLine* workLine, u4 regIdx, u4 insnIdx,
VerifyError* pFailure)
{
if (!regTypeIsReference(getRegisterType(workLine, regIdx))) {
LOG_VFY("VFY: monitor-exit on non-object");
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
if (workLine->monitorEntries == NULL) {
/* should only be true if monitor verification is disabled */
assert(!gDvm.monitorVerification);
return;
}
if (workLine->monitorStackTop == 0) {
LOG_VFY("VFY: monitor-exit stack underflow");
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
/*
* Confirm that the entry at the top of the stack is associated with
* the register. Pop the top entry off.
*/
workLine->monitorStackTop--;
#ifdef BUG_3215458_FIXED
/*
* TODO: This code can safely be enabled if know we are working on
* a dex file of format version 036 or later. (That is, we'll need to
* add a check for the version number.)
*/
if ((workLine->monitorEntries[regIdx] & (1 << workLine->monitorStackTop))
== 0)
{
LOG_VFY("VFY: monitor-exit bit %d not set: addr=0x%04x (bits[%d]=%#x)",
workLine->monitorStackTop, insnIdx, regIdx,
workLine->monitorEntries[regIdx]);
*pFailure = VERIFY_ERROR_GENERIC;
return;
}
#endif
workLine->monitorStack[workLine->monitorStackTop] = 0;
/*
* Clear the bit from the register flags.
*/
workLine->monitorEntries[regIdx] &= ~(1 << workLine->monitorStackTop);
}
/*
* ===========================================================================
* Entry point and driver loop
* ===========================================================================
*/
/*
* One-time preparation.
*/
static void verifyPrep()
{
#ifndef NDEBUG
/* only need to do this if the table was updated */
checkMergeTab();
#endif
}
/*
* Entry point for the detailed code-flow analysis of a single method.
*/
bool dvmVerifyCodeFlow(VerifierData* vdata)
{
bool result = false;
const Method* meth = vdata->method;
const int insnsSize = vdata->insnsSize;
const bool generateRegisterMap = gDvm.generateRegisterMaps;
RegisterTable regTable;
memset(®Table, 0, sizeof(regTable));
#ifdef VERIFIER_STATS
gDvm.verifierStats.methodsExamined++;
if (vdata->monitorEnterCount)
gDvm.verifierStats.monEnterMethods++;
#endif
/* TODO: move this elsewhere -- we don't need to do this for every method */
verifyPrep();
if (meth->registersSize * insnsSize > 4*1024*1024) {
LOG_VFY_METH(meth,
"VFY: warning: method is huge (regs=%d insnsSize=%d)",
meth->registersSize, insnsSize);
/* might be bogus data, might be some huge generated method */
}
/*
* Create register lists, and initialize them to "Unknown". If we're
* also going to create the register map, we need to retain the
* register lists for a larger set of addresses.
*/
if (!initRegisterTable(vdata, ®Table,
generateRegisterMap ? kTrackRegsGcPoints : kTrackRegsBranches))
goto bail;
vdata->registerLines = regTable.registerLines;
/*
* Perform liveness analysis.
*
* We can do this before or after the main verifier pass. The choice
* affects whether or not we see the effects of verifier instruction
* changes, i.e. substitution of throw-verification-error.
*
* In practice the ordering doesn't really matter, because T-V-E
* just prunes "can continue", creating regions of dead code (with
* corresponding register map data that will never be used).
*/
if (generateRegisterMap &&
gDvm.registerMapMode == kRegisterMapModeLivePrecise)
{
/*
* Compute basic blocks and predecessor lists.
*/
if (!dvmComputeVfyBasicBlocks(vdata))
goto bail;
/*
* Compute liveness.
*/
if (!dvmComputeLiveness(vdata))
goto bail;
}
/*
* Initialize the types of the registers that correspond to the
* method arguments. We can determine this from the method signature.
*/
if (!setTypesFromSignature(meth, regTable.registerLines[0].regTypes,
vdata->uninitMap))
goto bail;
/*
* Run the verifier.
*/
if (!doCodeVerification(vdata, ®Table))
goto bail;
/*
* Generate a register map.
*/
if (generateRegisterMap) {
RegisterMap* pMap = dvmGenerateRegisterMapV(vdata);
if (pMap != NULL) {
/*
* Tuck it into the Method struct. It will either get used
* directly or, if we're in dexopt, will be packed up and
* appended to the DEX file.
*/
dvmSetRegisterMap((Method*)meth, pMap);
}
}
/*
* Success.
*/
result = true;
bail:
freeRegisterLineInnards(vdata);
free(regTable.registerLines);
free(regTable.lineAlloc);
return result;
}
/*
* Grind through the instructions.
*
* The basic strategy is as outlined in v3 4.11.1.2: set the "changed" bit
* on the first instruction, process it (setting additional "changed" bits),
* and repeat until there are no more.
*
* v3 4.11.1.1
* - (N/A) operand stack is always the same size
* - operand stack [registers] contain the correct types of values
* - local variables [registers] contain the correct types of values
* - methods are invoked with the appropriate arguments
* - fields are assigned using values of appropriate types
* - opcodes have the correct type values in operand registers
* - there is never an uninitialized class instance in a local variable in
* code protected by an exception handler (operand stack is okay, because
* the operand stack is discarded when an exception is thrown) [can't
* know what's a local var w/o the debug info -- should fall out of
* register typing]
*
* v3 4.11.1.2
* - execution cannot fall off the end of the code
*
* (We also do many of the items described in the "static checks" sections,
* because it's easier to do them here.)
*
* We need an array of RegType values, one per register, for every
* instruction. If the method uses monitor-enter, we need extra data
* for every register, and a stack for every "interesting" instruction.
* In theory this could become quite large -- up to several megabytes for
* a monster function.
*
* NOTE:
* The spec forbids backward branches when there's an uninitialized reference
* in a register. The idea is to prevent something like this:
* loop:
* move r1, r0
* new-instance r0, MyClass
* ...
* if-eq rN, loop // once
* initialize r0
*
* This leaves us with two different instances, both allocated by the
* same instruction, but only one is initialized. The scheme outlined in
* v3 4.11.1.4 wouldn't catch this, so they work around it by preventing
* backward branches. We achieve identical results without restricting
* code reordering by specifying that you can't execute the new-instance
* instruction if a register contains an uninitialized instance created
* by that same instrutcion.
*/
static bool doCodeVerification(VerifierData* vdata, RegisterTable* regTable)
{
const Method* meth = vdata->method;
InsnFlags* insnFlags = vdata->insnFlags;
UninitInstanceMap* uninitMap = vdata->uninitMap;
const int insnsSize = dvmGetMethodInsnsSize(meth);
bool result = false;
bool debugVerbose = false;
int insnIdx, startGuess;
/*
* Begin by marking the first instruction as "changed".
*/
dvmInsnSetChanged(insnFlags, 0, true);
if (dvmWantVerboseVerification(meth)) {
IF_ALOGI() {
char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
ALOGI("Now verifying: %s.%s %s (ins=%d regs=%d)",
meth->clazz->descriptor, meth->name, desc,
meth->insSize, meth->registersSize);
ALOGI(" ------ [0 4 8 12 16 20 24 28 32 36");
free(desc);
}
debugVerbose = true;
gDebugVerbose = true;
} else {
gDebugVerbose = false;
}
startGuess = 0;
/*
* Continue until no instructions are marked "changed".
*/
while (true) {
/*
* Find the first marked one. Use "startGuess" as a way to find
* one quickly.
*/
for (insnIdx = startGuess; insnIdx < insnsSize; insnIdx++) {
if (dvmInsnIsChanged(insnFlags, insnIdx))
break;
}
if (insnIdx == insnsSize) {
if (startGuess != 0) {
/* try again, starting from the top */
startGuess = 0;
continue;
} else {
/* all flags are clear */
break;
}
}
/*
* We carry the working set of registers from instruction to
* instruction. If this address can be the target of a branch
* (or throw) instruction, or if we're skipping around chasing
* "changed" flags, we need to load the set of registers from
* the table.
*
* Because we always prefer to continue on to the next instruction,
* we should never have a situation where we have a stray
* "changed" flag set on an instruction that isn't a branch target.
*/
if (dvmInsnIsBranchTarget(insnFlags, insnIdx)) {
RegisterLine* workLine = ®Table->workLine;
copyLineFromTable(workLine, regTable, insnIdx);
} else {
#ifndef NDEBUG
/*
* Sanity check: retrieve the stored register line (assuming
* a full table) and make sure it actually matches.
*/
RegisterLine* registerLine = getRegisterLine(regTable, insnIdx);
if (registerLine->regTypes != NULL &&
compareLineToTable(regTable, insnIdx, ®Table->workLine) != 0)
{
char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
LOG_VFY("HUH? workLine diverged in %s.%s %s",
meth->clazz->descriptor, meth->name, desc);
free(desc);
dumpRegTypes(vdata, registerLine, 0, "work",
uninitMap, DRT_SHOW_REF_TYPES | DRT_SHOW_LOCALS);
dumpRegTypes(vdata, registerLine, 0, "insn",
uninitMap, DRT_SHOW_REF_TYPES | DRT_SHOW_LOCALS);
}
#endif
}
if (debugVerbose) {
dumpRegTypes(vdata, ®Table->workLine, insnIdx,
NULL, uninitMap, SHOW_REG_DETAILS);
}
//ALOGI("process %s.%s %s %d",
// meth->clazz->descriptor, meth->name, meth->descriptor, insnIdx);
if (!verifyInstruction(meth, insnFlags, regTable, insnIdx,
uninitMap, &startGuess))
{
//ALOGD("+++ %s bailing at %d", meth->name, insnIdx);
goto bail;
}
/*
* Clear "changed" and mark as visited.
*/
dvmInsnSetVisited(insnFlags, insnIdx, true);
dvmInsnSetChanged(insnFlags, insnIdx, false);
}
if (DEAD_CODE_SCAN && !IS_METHOD_FLAG_SET(meth, METHOD_ISWRITABLE)) {
/*
* Scan for dead code. There's nothing "evil" about dead code
* (besides the wasted space), but it indicates a flaw somewhere
* down the line, possibly in the verifier.
*
* If we've substituted "always throw" instructions into the stream,
* we are almost certainly going to have some dead code.
*/
int deadStart = -1;
for (insnIdx = 0; insnIdx < insnsSize;
insnIdx += dvmInsnGetWidth(insnFlags, insnIdx))
{
/*
* Switch-statement data doesn't get "visited" by scanner. It
* may or may not be preceded by a padding NOP (for alignment).
*/
int instr = meth->insns[insnIdx];
if (instr == kPackedSwitchSignature ||
instr == kSparseSwitchSignature ||
instr == kArrayDataSignature ||
(instr == OP_NOP && (insnIdx + 1 < insnsSize) &&
(meth->insns[insnIdx+1] == kPackedSwitchSignature ||
meth->insns[insnIdx+1] == kSparseSwitchSignature ||
meth->insns[insnIdx+1] == kArrayDataSignature)))
{
dvmInsnSetVisited(insnFlags, insnIdx, true);
}
if (!dvmInsnIsVisited(insnFlags, insnIdx)) {
if (deadStart < 0)
deadStart = insnIdx;
} else if (deadStart >= 0) {
IF_ALOGD() {
char* desc =
dexProtoCopyMethodDescriptor(&meth->prototype);
ALOGD("VFY: dead code 0x%04x-%04x in %s.%s %s",
deadStart, insnIdx-1,
meth->clazz->descriptor, meth->name, desc);
free(desc);
}
deadStart = -1;
}
}
if (deadStart >= 0) {
IF_ALOGD() {
char* desc = dexProtoCopyMethodDescriptor(&meth->prototype);
ALOGD("VFY: dead code 0x%04x-%04x in %s.%s %s",
deadStart, insnIdx-1,
meth->clazz->descriptor, meth->name, desc);
free(desc);
}
}
}
result = true;
bail:
return result;
}
/*
* Perform verification for a single instruction.
*
* This requires fully decoding the instruction to determine the effect
* it has on registers.
*
* Finds zero or more following instructions and sets the "changed" flag
* if execution at that point needs to be (re-)evaluated. Register changes
* are merged into "regTypes" at the target addresses. Does not set or
* clear any other flags in "insnFlags".
*
* This may alter meth->insns if we need to replace an instruction with
* throw-verification-error.
*/
static bool verifyInstruction(const Method* meth, InsnFlags* insnFlags,
RegisterTable* regTable, int insnIdx, UninitInstanceMap* uninitMap,
int* pStartGuess)
{
const int insnsSize = dvmGetMethodInsnsSize(meth);
const u2* insns = meth->insns + insnIdx;
bool result = false;
#ifdef VERIFIER_STATS
if (dvmInsnIsVisited(insnFlags, insnIdx)) {
gDvm.verifierStats.instrsReexamined++;
} else {
gDvm.verifierStats.instrsExamined++;
}
#endif
/*
* Once we finish decoding the instruction, we need to figure out where
* we can go from here. There are three possible ways to transfer
* control to another statement:
*
* (1) Continue to the next instruction. Applies to all but
* unconditional branches, method returns, and exception throws.
* (2) Branch to one or more possible locations. Applies to branches
* and switch statements.
* (3) Exception handlers. Applies to any instruction that can
* throw an exception that is handled by an encompassing "try"
* block.
*
* We can also return, in which case there is no successor instruction
* from this point.
*
* The behavior can be determined from the OpcodeFlags.
*/
RegisterLine* workLine = ®Table->workLine;
const DexFile* pDexFile = meth->clazz->pDvmDex->pDexFile;
ClassObject* resClass;
s4 branchTarget = 0;
const int insnRegCount = meth->registersSize;
RegType tmpType;
DecodedInstruction decInsn;
bool justSetResult = false;
VerifyError failure = VERIFY_ERROR_NONE;
#ifndef NDEBUG
memset(&decInsn, 0x81, sizeof(decInsn));
#endif
dexDecodeInstruction(insns, &decInsn);
int nextFlags = dexGetFlagsFromOpcode(decInsn.opcode);
/*
* Make a copy of the previous register state. If the instruction
* can throw an exception, we will copy/merge this into the "catch"
* address rather than workLine, because we don't want the result
* from the "successful" code path (e.g. a check-cast that "improves"
* a type) to be visible to the exception handler.
*/
if ((nextFlags & kInstrCanThrow) != 0 && dvmInsnIsInTry(insnFlags, insnIdx))
{
copyRegisterLine(®Table->savedLine, workLine,
regTable->insnRegCountPlus);
} else {
#ifndef NDEBUG
memset(regTable->savedLine.regTypes, 0xdd,
regTable->insnRegCountPlus * sizeof(RegType));
#endif
}
switch (decInsn.opcode) {
case OP_NOP:
/*
* A "pure" NOP has no effect on anything. Data tables start with
* a signature that looks like a NOP; if we see one of these in
* the course of executing code then we have a problem.
*/
if (decInsn.vA != 0) {
LOG_VFY("VFY: encountered data table in instruction stream");
failure = VERIFY_ERROR_GENERIC;
}
break;
case OP_MOVE:
case OP_MOVE_FROM16:
case OP_MOVE_16:
copyRegister1(workLine, decInsn.vA, decInsn.vB, kTypeCategory1nr,
&failure);
break;
case OP_MOVE_WIDE:
case OP_MOVE_WIDE_FROM16:
case OP_MOVE_WIDE_16:
copyRegister2(workLine, decInsn.vA, decInsn.vB, &failure);
break;
case OP_MOVE_OBJECT:
case OP_MOVE_OBJECT_FROM16:
case OP_MOVE_OBJECT_16:
copyRegister1(workLine, decInsn.vA, decInsn.vB, kTypeCategoryRef,
&failure);
break;
/*
* The move-result instructions copy data out of a "pseudo-register"
* with the results from the last method invocation. In practice we
* might want to hold the result in an actual CPU register, so the
* Dalvik spec requires that these only appear immediately after an
* invoke or filled-new-array.
*
* These calls invalidate the "result" register. (This is now
* redundant with the reset done below, but it can make the debug info
* easier to read in some cases.)
*/
case OP_MOVE_RESULT:
copyResultRegister1(workLine, insnRegCount, decInsn.vA,
kTypeCategory1nr, &failure);
break;
case OP_MOVE_RESULT_WIDE:
copyResultRegister2(workLine, insnRegCount, decInsn.vA, &failure);
break;
case OP_MOVE_RESULT_OBJECT:
copyResultRegister1(workLine, insnRegCount, decInsn.vA,
kTypeCategoryRef, &failure);
break;
case OP_MOVE_EXCEPTION:
/*
* This statement can only appear as the first instruction in an
* exception handler (though not all exception handlers need to
* have one of these). We verify that as part of extracting the
* exception type from the catch block list.
*
* "resClass" will hold the closest common superclass of all
* exceptions that can be handled here.
*/
resClass = getCaughtExceptionType(meth, insnIdx, &failure);
if (resClass == NULL) {
assert(!VERIFY_OK(failure));
} else {
setRegisterType(workLine, decInsn.vA, regTypeFromClass(resClass));
}
break;
case OP_RETURN_VOID:
if (!checkConstructorReturn(meth, workLine, insnRegCount)) {
failure = VERIFY_ERROR_GENERIC;
} else if (getMethodReturnType(meth) != kRegTypeUnknown) {
LOG_VFY("VFY: return-void not expected");
failure = VERIFY_ERROR_GENERIC;
}
break;
case OP_RETURN:
if (!checkConstructorReturn(meth, workLine, insnRegCount)) {
failure = VERIFY_ERROR_GENERIC;
} else {
/* check the method signature */
RegType returnType = getMethodReturnType(meth);
checkTypeCategory(returnType, kTypeCategory1nr, &failure);
if (!VERIFY_OK(failure))
LOG_VFY("VFY: return-1nr not expected");
/*
* javac generates synthetic functions that write byte values
* into boolean fields. Also, it may use integer values for
* boolean, byte, short, and character return types.
*/
RegType srcType = getRegisterType(workLine, decInsn.vA);
if ((returnType == kRegTypeBoolean && srcType == kRegTypeByte) ||
((returnType == kRegTypeBoolean || returnType == kRegTypeByte ||
returnType == kRegTypeShort || returnType == kRegTypeChar) &&
srcType == kRegTypeInteger))
returnType = srcType;
/* check the register contents */
verifyRegisterType(workLine, decInsn.vA, returnType, &failure);
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: return-1nr on invalid register v%d",
decInsn.vA);
}
}
break;
case OP_RETURN_WIDE:
if (!checkConstructorReturn(meth, workLine, insnRegCount)) {
failure = VERIFY_ERROR_GENERIC;
} else {
RegType returnType;
/* check the method signature */
returnType = getMethodReturnType(meth);
checkTypeCategory(returnType, kTypeCategory2, &failure);
if (!VERIFY_OK(failure))
LOG_VFY("VFY: return-wide not expected");
/* check the register contents */
verifyRegisterType(workLine, decInsn.vA, returnType, &failure);
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: return-wide on invalid register pair v%d",
decInsn.vA);
}
}
break;
case OP_RETURN_OBJECT:
if (!checkConstructorReturn(meth, workLine, insnRegCount)) {
failure = VERIFY_ERROR_GENERIC;
} else {
RegType returnType = getMethodReturnType(meth);
checkTypeCategory(returnType, kTypeCategoryRef, &failure);
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: return-object not expected");
break;
}
/* returnType is the *expected* return type, not register value */
assert(returnType != kRegTypeZero);
assert(!regTypeIsUninitReference(returnType));
/*
* Verify that the reference in vAA is an instance of the type
* in "returnType". The Zero type is allowed here. If the
* method is declared to return an interface, then any
* initialized reference is acceptable.
*
* Note getClassFromRegister fails if the register holds an
* uninitialized reference, so we do not allow them to be
* returned.
*/
ClassObject* declClass;
declClass = regTypeInitializedReferenceToClass(returnType);
resClass = getClassFromRegister(workLine, decInsn.vA, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
if (!dvmIsInterfaceClass(declClass) &&
!dvmInstanceof(resClass, declClass))
{
LOG_VFY("VFY: returning %s (cl=%p), declared %s (cl=%p)",
resClass->descriptor, resClass->classLoader,
declClass->descriptor, declClass->classLoader);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
}
break;
case OP_CONST_4:
case OP_CONST_16:
case OP_CONST:
/* could be boolean, int, float, or a null reference */
setRegisterType(workLine, decInsn.vA,
determineCat1Const((s4)decInsn.vB));
break;
case OP_CONST_HIGH16:
/* could be boolean, int, float, or a null reference */
setRegisterType(workLine, decInsn.vA,
determineCat1Const((s4) decInsn.vB << 16));
break;
case OP_CONST_WIDE_16:
case OP_CONST_WIDE_32:
case OP_CONST_WIDE:
case OP_CONST_WIDE_HIGH16:
/* could be long or double; resolved upon use */
setRegisterType(workLine, decInsn.vA, kRegTypeConstLo);
break;
case OP_CONST_STRING:
case OP_CONST_STRING_JUMBO:
assert(gDvm.classJavaLangString != NULL);
setRegisterType(workLine, decInsn.vA,
regTypeFromClass(gDvm.classJavaLangString));
break;
case OP_CONST_CLASS:
assert(gDvm.classJavaLangClass != NULL);
/* make sure we can resolve the class; access check is important */
resClass = dvmOptResolveClass(meth->clazz, decInsn.vB, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vB);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve const-class %d (%s) in %s",
decInsn.vB, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else {
setRegisterType(workLine, decInsn.vA,
regTypeFromClass(gDvm.classJavaLangClass));
}
break;
case OP_MONITOR_ENTER:
handleMonitorEnter(workLine, decInsn.vA, insnIdx, &failure);
break;
case OP_MONITOR_EXIT:
/*
* monitor-exit instructions are odd. They can throw exceptions,
* but when they do they act as if they succeeded and the PC is
* pointing to the following instruction. (This behavior goes back
* to the need to handle asynchronous exceptions, a now-deprecated
* feature that Dalvik doesn't support.)
*
* In practice we don't need to worry about this. The only
* exceptions that can be thrown from monitor-exit are for a
* null reference and -exit without a matching -enter. If the
* structured locking checks are working, the former would have
* failed on the -enter instruction, and the latter is impossible.
*
* This is fortunate, because issue 3221411 prevents us from
* chasing the "can throw" path when monitor verification is
* enabled. If we can fully verify the locking we can ignore
* some catch blocks (which will show up as "dead" code when
* we skip them here); if we can't, then the code path could be
* "live" so we still need to check it.
*/
if (workLine->monitorEntries != NULL)
nextFlags &= ~kInstrCanThrow;
handleMonitorExit(workLine, decInsn.vA, insnIdx, &failure);
break;
case OP_CHECK_CAST:
/*
* If this instruction succeeds, we will promote register vA to
* the type in vB. (This could be a demotion -- not expected, so
* we don't try to address it.)
*
* If it fails, an exception is thrown, which we deal with later
* by ignoring the update to decInsn.vA when branching to a handler.
*/
resClass = dvmOptResolveClass(meth->clazz, decInsn.vB, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vB);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve check-cast %d (%s) in %s",
decInsn.vB, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else {
RegType origType;
origType = getRegisterType(workLine, decInsn.vA);
if (!regTypeIsReference(origType)) {
LOG_VFY("VFY: check-cast on non-reference in v%u",decInsn.vA);
failure = VERIFY_ERROR_GENERIC;
break;
}
setRegisterType(workLine, decInsn.vA, regTypeFromClass(resClass));
}
break;
case OP_INSTANCE_OF:
/* make sure we're checking a reference type */
tmpType = getRegisterType(workLine, decInsn.vB);
if (!regTypeIsReference(tmpType)) {
LOG_VFY("VFY: vB not a reference (%d)", tmpType);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* make sure we can resolve the class; access check is important */
resClass = dvmOptResolveClass(meth->clazz, decInsn.vC, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vC);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve instanceof %d (%s) in %s",
decInsn.vC, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else {
/* result is boolean */
setRegisterType(workLine, decInsn.vA, kRegTypeBoolean);
}
break;
case OP_ARRAY_LENGTH:
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL && !dvmIsArrayClass(resClass)) {
LOG_VFY("VFY: array-length on non-array");
failure = VERIFY_ERROR_GENERIC;
break;
}
setRegisterType(workLine, decInsn.vA, kRegTypeInteger);
break;
case OP_NEW_INSTANCE:
resClass = dvmOptResolveClass(meth->clazz, decInsn.vB, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vB);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve new-instance %d (%s) in %s",
decInsn.vB, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else {
RegType uninitType;
/* can't create an instance of an interface or abstract class */
if (dvmIsAbstractClass(resClass) || dvmIsInterfaceClass(resClass)) {
LOG_VFY("VFY: new-instance on interface or abstract class %s",
resClass->descriptor);
failure = VERIFY_ERROR_INSTANTIATION;
break;
}
/* add resolved class to uninit map if not already there */
int uidx = setUninitInstance(uninitMap, insnIdx, resClass);
assert(uidx >= 0);
uninitType = regTypeFromUninitIndex(uidx);
/*
* Any registers holding previous allocations from this address
* that have not yet been initialized must be marked invalid.
*/
markUninitRefsAsInvalid(workLine, insnRegCount, uninitMap,
uninitType);
/* add the new uninitialized reference to the register ste */
setRegisterType(workLine, decInsn.vA, uninitType);
}
break;
case OP_NEW_ARRAY:
resClass = dvmOptResolveClass(meth->clazz, decInsn.vC, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vC);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve new-array %d (%s) in %s",
decInsn.vC, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else if (!dvmIsArrayClass(resClass)) {
LOG_VFY("VFY: new-array on non-array class");
failure = VERIFY_ERROR_GENERIC;
} else {
/* make sure "size" register is valid type */
verifyRegisterType(workLine, decInsn.vB, kRegTypeInteger, &failure);
/* set register type to array class */
setRegisterType(workLine, decInsn.vA, regTypeFromClass(resClass));
}
break;
case OP_FILLED_NEW_ARRAY:
case OP_FILLED_NEW_ARRAY_RANGE:
resClass = dvmOptResolveClass(meth->clazz, decInsn.vB, &failure);
if (resClass == NULL) {
const char* badClassDesc = dexStringByTypeIdx(pDexFile, decInsn.vB);
dvmLogUnableToResolveClass(badClassDesc, meth);
LOG_VFY("VFY: unable to resolve filled-array %d (%s) in %s",
decInsn.vB, badClassDesc, meth->clazz->descriptor);
assert(failure != VERIFY_ERROR_GENERIC);
} else if (!dvmIsArrayClass(resClass)) {
LOG_VFY("VFY: filled-new-array on non-array class");
failure = VERIFY_ERROR_GENERIC;
} else {
bool isRange = (decInsn.opcode == OP_FILLED_NEW_ARRAY_RANGE);
/* check the arguments to the instruction */
verifyFilledNewArrayRegs(meth, workLine, &decInsn,
resClass, isRange, &failure);
/* filled-array result goes into "result" register */
setResultRegisterType(workLine, insnRegCount,
regTypeFromClass(resClass));
justSetResult = true;
}
break;
case OP_CMPL_FLOAT:
case OP_CMPG_FLOAT:
verifyRegisterType(workLine, decInsn.vB, kRegTypeFloat, &failure);
verifyRegisterType(workLine, decInsn.vC, kRegTypeFloat, &failure);
setRegisterType(workLine, decInsn.vA, kRegTypeBoolean);
break;
case OP_CMPL_DOUBLE:
case OP_CMPG_DOUBLE:
verifyRegisterType(workLine, decInsn.vB, kRegTypeDoubleLo, &failure);
verifyRegisterType(workLine, decInsn.vC, kRegTypeDoubleLo, &failure);
setRegisterType(workLine, decInsn.vA, kRegTypeBoolean);
break;
case OP_CMP_LONG:
verifyRegisterType(workLine, decInsn.vB, kRegTypeLongLo, &failure);
verifyRegisterType(workLine, decInsn.vC, kRegTypeLongLo, &failure);
setRegisterType(workLine, decInsn.vA, kRegTypeBoolean);
break;
case OP_THROW:
resClass = getClassFromRegister(workLine, decInsn.vA, &failure);
if (VERIFY_OK(failure) && resClass != NULL) {
if (!dvmInstanceof(resClass, gDvm.exThrowable)) {
LOG_VFY("VFY: thrown class %s not instanceof Throwable",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
}
}
break;
case OP_GOTO:
case OP_GOTO_16:
case OP_GOTO_32:
/* no effect on or use of registers */
break;
case OP_PACKED_SWITCH:
case OP_SPARSE_SWITCH:
/* verify that vAA is an integer, or can be converted to one */
verifyRegisterType(workLine, decInsn.vA, kRegTypeInteger, &failure);
break;
case OP_FILL_ARRAY_DATA:
{
RegType valueType;
const u2 *arrayData;
u2 elemWidth;
/* Similar to the verification done for APUT */
resClass = getClassFromRegister(workLine, decInsn.vA, &failure);
if (!VERIFY_OK(failure))
break;
/* resClass can be null if the reg type is Zero */
if (resClass == NULL)
break;
if (!dvmIsArrayClass(resClass) || resClass->arrayDim != 1 ||
resClass->elementClass->primitiveType == PRIM_NOT ||
resClass->elementClass->primitiveType == PRIM_VOID)
{
LOG_VFY("VFY: invalid fill-array-data on %s",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
valueType = primitiveTypeToRegType(
resClass->elementClass->primitiveType);
assert(valueType != kRegTypeUnknown);
/*
* Now verify if the element width in the table matches the element
* width declared in the array
*/
arrayData = insns + (insns[1] | (((s4)insns[2]) << 16));
if (arrayData[0] != kArrayDataSignature) {
LOG_VFY("VFY: invalid magic for array-data");
failure = VERIFY_ERROR_GENERIC;
break;
}
switch (resClass->elementClass->primitiveType) {
case PRIM_BOOLEAN:
case PRIM_BYTE:
elemWidth = 1;
break;
case PRIM_CHAR:
case PRIM_SHORT:
elemWidth = 2;
break;
case PRIM_FLOAT:
case PRIM_INT:
elemWidth = 4;
break;
case PRIM_DOUBLE:
case PRIM_LONG:
elemWidth = 8;
break;
default:
elemWidth = 0;
break;
}
/*
* Since we don't compress the data in Dex, expect to see equal
* width of data stored in the table and expected from the array
* class.
*/
if (arrayData[1] != elemWidth) {
LOG_VFY("VFY: array-data size mismatch (%d vs %d)",
arrayData[1], elemWidth);
failure = VERIFY_ERROR_GENERIC;
}
}
break;
case OP_IF_EQ:
case OP_IF_NE:
{
RegType type1, type2;
type1 = getRegisterType(workLine, decInsn.vA);
type2 = getRegisterType(workLine, decInsn.vB);
/* both references? */
if (regTypeIsReference(type1) && regTypeIsReference(type2))
break;
/* both category-1nr? */
checkTypeCategory(type1, kTypeCategory1nr, &failure);
checkTypeCategory(type2, kTypeCategory1nr, &failure);
if (type1 == kRegTypeFloat || type2 == kRegTypeFloat) {
failure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: args to if-eq/if-ne must both be refs or cat1");
break;
}
}
break;
case OP_IF_LT:
case OP_IF_GE:
case OP_IF_GT:
case OP_IF_LE:
tmpType = getRegisterType(workLine, decInsn.vA);
checkTypeCategory(tmpType, kTypeCategory1nr, &failure);
if (tmpType == kRegTypeFloat) {
failure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: args to 'if' must be cat-1nr and not float");
break;
}
tmpType = getRegisterType(workLine, decInsn.vB);
checkTypeCategory(tmpType, kTypeCategory1nr, &failure);
if (tmpType == kRegTypeFloat) {
failure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(failure)) {
LOG_VFY("VFY: args to 'if' must be cat-1nr and not float");
break;
}
break;
case OP_IF_EQZ:
case OP_IF_NEZ:
tmpType = getRegisterType(workLine, decInsn.vA);
if (regTypeIsReference(tmpType))
break;
checkTypeCategory(tmpType, kTypeCategory1nr, &failure);
if (tmpType == kRegTypeFloat) {
failure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(failure))
LOG_VFY("VFY: expected non-float cat-1 arg to if");
break;
case OP_IF_LTZ:
case OP_IF_GEZ:
case OP_IF_GTZ:
case OP_IF_LEZ:
tmpType = getRegisterType(workLine, decInsn.vA);
checkTypeCategory(tmpType, kTypeCategory1nr, &failure);
if (tmpType == kRegTypeFloat) {
failure = VERIFY_ERROR_GENERIC;
}
if (!VERIFY_OK(failure))
LOG_VFY("VFY: expected non-float cat-1 arg to if");
break;
case OP_AGET:
tmpType = kRegTypeInteger;
goto aget_1nr_common;
case OP_AGET_BOOLEAN:
tmpType = kRegTypeBoolean;
goto aget_1nr_common;
case OP_AGET_BYTE:
tmpType = kRegTypeByte;
goto aget_1nr_common;
case OP_AGET_CHAR:
tmpType = kRegTypeChar;
goto aget_1nr_common;
case OP_AGET_SHORT:
tmpType = kRegTypeShort;
goto aget_1nr_common;
aget_1nr_common:
{
RegType srcType, indexType;
indexType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, indexType, &failure);
if (!VERIFY_OK(failure))
break;
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
/* verify the class */
if (!dvmIsArrayClass(resClass) || resClass->arrayDim != 1 ||
resClass->elementClass->primitiveType == PRIM_NOT)
{
LOG_VFY("VFY: invalid aget-1nr target %s",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* make sure array type matches instruction */
srcType = primitiveTypeToRegType(
resClass->elementClass->primitiveType);
/* correct if float */
if (srcType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
if (!checkFieldArrayStore1nr(tmpType, srcType)) {
LOG_VFY("VFY: invalid aget-1nr, array type=%d with"
" inst type=%d (on %s)",
srcType, tmpType, resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
} else {
/*
* Null array ref; this code path will fail at runtime. Label
* result as zero to allow it to remain mergeable.
*/
tmpType = kRegTypeZero;
}
setRegisterType(workLine, decInsn.vA, tmpType);
}
break;
case OP_AGET_WIDE:
{
RegType dstType, indexType;
indexType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, indexType, &failure);
if (!VERIFY_OK(failure))
break;
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
/* verify the class */
if (!dvmIsArrayClass(resClass) || resClass->arrayDim != 1 ||
resClass->elementClass->primitiveType == PRIM_NOT)
{
LOG_VFY("VFY: invalid aget-wide target %s",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* try to refine "dstType" */
switch (resClass->elementClass->primitiveType) {
case PRIM_LONG:
dstType = kRegTypeLongLo;
break;
case PRIM_DOUBLE:
dstType = kRegTypeDoubleLo;
break;
default:
LOG_VFY("VFY: invalid aget-wide on %s",
resClass->descriptor);
dstType = kRegTypeUnknown;
failure = VERIFY_ERROR_GENERIC;
break;
}
} else {
/*
* Null array ref; this code path will fail at runtime. We
* know this is either long or double, so label it const.
*/
dstType = kRegTypeConstLo;
}
setRegisterType(workLine, decInsn.vA, dstType);
}
break;
case OP_AGET_OBJECT:
{
RegType dstType, indexType;
indexType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, indexType, &failure);
if (!VERIFY_OK(failure))
break;
/* get the class of the array we're pulling an object from */
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
ClassObject* elementClass;
assert(resClass != NULL);
if (!dvmIsArrayClass(resClass)) {
LOG_VFY("VFY: aget-object on non-array class");
failure = VERIFY_ERROR_GENERIC;
break;
}
assert(resClass->elementClass != NULL);
/*
* Find the element class. resClass->elementClass indicates
* the basic type, which won't be what we want for a
* multi-dimensional array.
*/
if (resClass->descriptor[1] == '[') {
assert(resClass->arrayDim > 1);
elementClass = dvmFindArrayClass(&resClass->descriptor[1],
resClass->classLoader);
} else if (resClass->descriptor[1] == 'L') {
assert(resClass->arrayDim == 1);
elementClass = resClass->elementClass;
} else {
LOG_VFY("VFY: aget-object on non-ref array class (%s)",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
dstType = regTypeFromClass(elementClass);
} else {
/*
* The array reference is NULL, so the current code path will
* throw an exception. For proper merging with later code
* paths, and correct handling of "if-eqz" tests on the
* result of the array get, we want to treat this as a null
* reference.
*/
dstType = kRegTypeZero;
}
setRegisterType(workLine, decInsn.vA, dstType);
}
break;
case OP_APUT:
tmpType = kRegTypeInteger;
goto aput_1nr_common;
case OP_APUT_BOOLEAN:
tmpType = kRegTypeBoolean;
goto aput_1nr_common;
case OP_APUT_BYTE:
tmpType = kRegTypeByte;
goto aput_1nr_common;
case OP_APUT_CHAR:
tmpType = kRegTypeChar;
goto aput_1nr_common;
case OP_APUT_SHORT:
tmpType = kRegTypeShort;
goto aput_1nr_common;
aput_1nr_common:
{
RegType srcType, dstType, indexType;
indexType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, indexType, &failure);
if (!VERIFY_OK(failure))
break;
srcType = getRegisterType(workLine, decInsn.vA);
/* correct if float */
if (srcType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
/* make sure the source register has the correct type */
if (!canConvertTo1nr(srcType, tmpType)) {
LOG_VFY("VFY: invalid reg type %d on aput instr (need %d)",
srcType, tmpType);
failure = VERIFY_ERROR_GENERIC;
break;
}
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
/* resClass can be null if the reg type is Zero */
if (resClass == NULL)
break;
if (!dvmIsArrayClass(resClass) || resClass->arrayDim != 1 ||
resClass->elementClass->primitiveType == PRIM_NOT)
{
LOG_VFY("VFY: invalid aput-1nr on %s", resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* verify that instruction matches array */
dstType = primitiveTypeToRegType(
resClass->elementClass->primitiveType);
/* correct if float */
if (dstType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
verifyRegisterType(workLine, decInsn.vA, dstType, &failure);
if (dstType == kRegTypeUnknown ||
!checkFieldArrayStore1nr(tmpType, dstType)) {
LOG_VFY("VFY: invalid aput-1nr on %s (inst=%d dst=%d)",
resClass->descriptor, tmpType, dstType);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
break;
case OP_APUT_WIDE:
tmpType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, tmpType, &failure);
if (!VERIFY_OK(failure))
break;
resClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
/* verify the class and try to refine "dstType" */
if (!dvmIsArrayClass(resClass) || resClass->arrayDim != 1 ||
resClass->elementClass->primitiveType == PRIM_NOT)
{
LOG_VFY("VFY: invalid aput-wide on %s",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
switch (resClass->elementClass->primitiveType) {
case PRIM_LONG:
verifyRegisterType(workLine, decInsn.vA, kRegTypeLongLo, &failure);
break;
case PRIM_DOUBLE:
verifyRegisterType(workLine, decInsn.vA, kRegTypeDoubleLo, &failure);
break;
default:
LOG_VFY("VFY: invalid aput-wide on %s",
resClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
break;
case OP_APUT_OBJECT:
tmpType = getRegisterType(workLine, decInsn.vC);
checkArrayIndexType(meth, tmpType, &failure);
if (!VERIFY_OK(failure))
break;
/* get the ref we're storing; Zero is okay, Uninit is not */
resClass = getClassFromRegister(workLine, decInsn.vA, &failure);
if (!VERIFY_OK(failure))
break;
if (resClass != NULL) {
ClassObject* arrayClass;
ClassObject* elementClass;
/*
* Get the array class. If the array ref is null, we won't
* have type information (and we'll crash at runtime with a
* null pointer exception).
*/
arrayClass = getClassFromRegister(workLine, decInsn.vB, &failure);
if (arrayClass != NULL) {
/* see if the array holds a compatible type */
if (!dvmIsArrayClass(arrayClass)) {
LOG_VFY("VFY: invalid aput-object on %s",
arrayClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
/*
* Find the element class. resClass->elementClass indicates
* the basic type, which won't be what we want for a
* multi-dimensional array.
*
* All we want to check here is that the element type is a
* reference class. We *don't* check instanceof here, because
* you can still put a String into a String[] after the latter
* has been cast to an Object[].
*/
if (arrayClass->descriptor[1] == '[') {
assert(arrayClass->arrayDim > 1);
elementClass = dvmFindArrayClass(&arrayClass->descriptor[1],
arrayClass->classLoader);
} else {
assert(arrayClass->arrayDim == 1);
elementClass = arrayClass->elementClass;
}
if (elementClass->primitiveType != PRIM_NOT) {
LOG_VFY("VFY: invalid aput-object of %s into %s",
resClass->descriptor, arrayClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
}
break;
case OP_IGET:
tmpType = kRegTypeInteger;
goto iget_1nr_common;
case OP_IGET_BOOLEAN:
tmpType = kRegTypeBoolean;
goto iget_1nr_common;
case OP_IGET_BYTE:
tmpType = kRegTypeByte;
goto iget_1nr_common;
case OP_IGET_CHAR:
tmpType = kRegTypeChar;
goto iget_1nr_common;
case OP_IGET_SHORT:
tmpType = kRegTypeShort;
goto iget_1nr_common;
iget_1nr_common:
{
InstField* instField;
RegType objType, fieldType;
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
/* make sure the field's type is compatible with expectation */
fieldType = primSigCharToRegType(instField->signature[0]);
/* correct if float */
if (fieldType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
if (fieldType == kRegTypeUnknown ||
!checkFieldArrayStore1nr(tmpType, fieldType))
{
LOG_VFY("VFY: invalid iget-1nr of %s.%s (inst=%d field=%d)",
instField->clazz->descriptor,
instField->name, tmpType, fieldType);
failure = VERIFY_ERROR_GENERIC;
break;
}
setRegisterType(workLine, decInsn.vA, tmpType);
}
break;
case OP_IGET_WIDE:
{
RegType dstType;
InstField* instField;
RegType objType;
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
/* check the type, which should be prim */
switch (instField->signature[0]) {
case 'D':
dstType = kRegTypeDoubleLo;
break;
case 'J':
dstType = kRegTypeLongLo;
break;
default:
LOG_VFY("VFY: invalid iget-wide of %s.%s",
instField->clazz->descriptor,
instField->name);
dstType = kRegTypeUnknown;
failure = VERIFY_ERROR_GENERIC;
break;
}
if (VERIFY_OK(failure)) {
setRegisterType(workLine, decInsn.vA, dstType);
}
}
break;
case OP_IGET_OBJECT:
{
ClassObject* fieldClass;
InstField* instField;
RegType objType;
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
fieldClass = getFieldClass(meth, instField);
if (fieldClass == NULL) {
/* class not found or primitive type */
LOG_VFY("VFY: unable to recover field class from '%s'",
instField->signature);
failure = VERIFY_ERROR_GENERIC;
break;
}
if (VERIFY_OK(failure)) {
assert(!dvmIsPrimitiveClass(fieldClass));
setRegisterType(workLine, decInsn.vA,
regTypeFromClass(fieldClass));
}
}
break;
case OP_IPUT:
tmpType = kRegTypeInteger;
goto iput_1nr_common;
case OP_IPUT_BOOLEAN:
tmpType = kRegTypeBoolean;
goto iput_1nr_common;
case OP_IPUT_BYTE:
tmpType = kRegTypeByte;
goto iput_1nr_common;
case OP_IPUT_CHAR:
tmpType = kRegTypeChar;
goto iput_1nr_common;
case OP_IPUT_SHORT:
tmpType = kRegTypeShort;
goto iput_1nr_common;
iput_1nr_common:
{
RegType srcType, fieldType, objType;
InstField* instField;
srcType = getRegisterType(workLine, decInsn.vA);
/*
* javac generates synthetic functions that write byte values
* into boolean fields.
*/
if (tmpType == kRegTypeBoolean && srcType == kRegTypeByte)
tmpType = kRegTypeByte;
/* correct if float */
if (srcType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
/* make sure the source register has the correct type */
if (!canConvertTo1nr(srcType, tmpType)) {
LOG_VFY("VFY: invalid reg type %d on iput instr (need %d)",
srcType, tmpType);
failure = VERIFY_ERROR_GENERIC;
break;
}
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, instField, &failure);
if (!VERIFY_OK(failure))
break;
/* get type of field we're storing into */
fieldType = primSigCharToRegType(instField->signature[0]);
/* correct if float */
if (fieldType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
if (fieldType == kRegTypeBoolean && srcType == kRegTypeByte)
fieldType = kRegTypeByte;
verifyRegisterType(workLine, decInsn.vA, fieldType, &failure);
if (fieldType == kRegTypeUnknown ||
!checkFieldArrayStore1nr(tmpType, fieldType))
{
LOG_VFY("VFY: invalid iput-1nr of %s.%s (inst=%d field=%d)",
instField->clazz->descriptor,
instField->name, tmpType, fieldType);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
break;
case OP_IPUT_WIDE:
tmpType = getRegisterType(workLine, decInsn.vA);
{
RegType typeHi = getRegisterType(workLine, decInsn.vA + 1);
checkTypeCategory(tmpType, kTypeCategory2, &failure);
checkWidePair(tmpType, typeHi, &failure);
}
if (!VERIFY_OK(failure))
break;
InstField* instField;
RegType objType;
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, instField, &failure);
if (!VERIFY_OK(failure))
break;
/* check the type, which should be prim */
switch (instField->signature[0]) {
case 'D':
verifyRegisterType(workLine, decInsn.vA, kRegTypeDoubleLo, &failure);
break;
case 'J':
verifyRegisterType(workLine, decInsn.vA, kRegTypeLongLo, &failure);
break;
default:
LOG_VFY("VFY: invalid iput-wide of %s.%s",
instField->clazz->descriptor,
instField->name);
failure = VERIFY_ERROR_GENERIC;
break;
}
break;
case OP_IPUT_OBJECT:
{
ClassObject* fieldClass;
ClassObject* valueClass;
InstField* instField;
RegType objType, valueType;
objType = getRegisterType(workLine, decInsn.vB);
instField = getInstField(meth, uninitMap, objType, decInsn.vC,
&failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, instField, &failure);
if (!VERIFY_OK(failure))
break;
fieldClass = getFieldClass(meth, instField);
if (fieldClass == NULL) {
LOG_VFY("VFY: unable to recover field class from '%s'",
instField->signature);
failure = VERIFY_ERROR_GENERIC;
break;
}
valueType = getRegisterType(workLine, decInsn.vA);
if (!regTypeIsReference(valueType)) {
LOG_VFY("VFY: storing non-ref v%d into ref field '%s' (%s)",
decInsn.vA, instField->name,
fieldClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
if (valueType != kRegTypeZero) {
valueClass = regTypeInitializedReferenceToClass(valueType);
if (valueClass == NULL) {
LOG_VFY("VFY: storing uninit ref v%d into ref field",
decInsn.vA);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* allow if field is any interface or field is base class */
if (!dvmIsInterfaceClass(fieldClass) &&
!dvmInstanceof(valueClass, fieldClass))
{
LOG_VFY("VFY: storing type '%s' into field type '%s' (%s.%s)",
valueClass->descriptor, fieldClass->descriptor,
instField->clazz->descriptor,
instField->name);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
}
break;
case OP_SGET:
tmpType = kRegTypeInteger;
goto sget_1nr_common;
case OP_SGET_BOOLEAN:
tmpType = kRegTypeBoolean;
goto sget_1nr_common;
case OP_SGET_BYTE:
tmpType = kRegTypeByte;
goto sget_1nr_common;
case OP_SGET_CHAR:
tmpType = kRegTypeChar;
goto sget_1nr_common;
case OP_SGET_SHORT:
tmpType = kRegTypeShort;
goto sget_1nr_common;
sget_1nr_common:
{
StaticField* staticField;
RegType fieldType;
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
/*
* Make sure the field's type is compatible with expectation.
* We can get ourselves into trouble if we mix & match loads
* and stores with different widths, so rather than just checking
* "canConvertTo1nr" we require that the field types have equal
* widths.
*/
fieldType = primSigCharToRegType(staticField->signature[0]);
/* correct if float */
if (fieldType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
if (!checkFieldArrayStore1nr(tmpType, fieldType)) {
LOG_VFY("VFY: invalid sget-1nr of %s.%s (inst=%d actual=%d)",
staticField->clazz->descriptor,
staticField->name, tmpType, fieldType);
failure = VERIFY_ERROR_GENERIC;
break;
}
setRegisterType(workLine, decInsn.vA, tmpType);
}
break;
case OP_SGET_WIDE:
{
StaticField* staticField;
RegType dstType;
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
/* check the type, which should be prim */
switch (staticField->signature[0]) {
case 'D':
dstType = kRegTypeDoubleLo;
break;
case 'J':
dstType = kRegTypeLongLo;
break;
default:
LOG_VFY("VFY: invalid sget-wide of %s.%s",
staticField->clazz->descriptor,
staticField->name);
dstType = kRegTypeUnknown;
failure = VERIFY_ERROR_GENERIC;
break;
}
if (VERIFY_OK(failure)) {
setRegisterType(workLine, decInsn.vA, dstType);
}
}
break;
case OP_SGET_OBJECT:
{
StaticField* staticField;
ClassObject* fieldClass;
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
fieldClass = getFieldClass(meth, staticField);
if (fieldClass == NULL) {
LOG_VFY("VFY: unable to recover field class from '%s'",
staticField->signature);
failure = VERIFY_ERROR_GENERIC;
break;
}
if (dvmIsPrimitiveClass(fieldClass)) {
LOG_VFY("VFY: attempt to get prim field with sget-object");
failure = VERIFY_ERROR_GENERIC;
break;
}
setRegisterType(workLine, decInsn.vA, regTypeFromClass(fieldClass));
}
break;
case OP_SPUT:
tmpType = kRegTypeInteger;
goto sput_1nr_common;
case OP_SPUT_BOOLEAN:
tmpType = kRegTypeBoolean;
goto sput_1nr_common;
case OP_SPUT_BYTE:
tmpType = kRegTypeByte;
goto sput_1nr_common;
case OP_SPUT_CHAR:
tmpType = kRegTypeChar;
goto sput_1nr_common;
case OP_SPUT_SHORT:
tmpType = kRegTypeShort;
goto sput_1nr_common;
sput_1nr_common:
{
RegType srcType, fieldType;
StaticField* staticField;
srcType = getRegisterType(workLine, decInsn.vA);
/*
* javac generates synthetic functions that write byte values
* into boolean fields.
*/
if (tmpType == kRegTypeBoolean && srcType == kRegTypeByte)
tmpType = kRegTypeByte;
/* correct if float */
if (srcType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
/* make sure the source register has the correct type */
if (!canConvertTo1nr(srcType, tmpType)) {
LOG_VFY("VFY: invalid reg type %d on sput instr (need %d)",
srcType, tmpType);
failure = VERIFY_ERROR_GENERIC;
break;
}
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, staticField, &failure);
if (!VERIFY_OK(failure))
break;
/*
* Get type of field we're storing into. We know that the
* contents of the register match the instruction, but we also
* need to ensure that the instruction matches the field type.
* Using e.g. sput-short to write into a 32-bit integer field
* can lead to trouble if we do 16-bit writes.
*/
fieldType = primSigCharToRegType(staticField->signature[0]);
/* correct if float */
if (fieldType == kRegTypeFloat && tmpType == kRegTypeInteger)
tmpType = kRegTypeFloat;
if (fieldType == kRegTypeBoolean && srcType == kRegTypeByte)
fieldType = kRegTypeByte;
verifyRegisterType(workLine, decInsn.vA, fieldType, &failure);
if (fieldType == kRegTypeUnknown ||
!checkFieldArrayStore1nr(tmpType, fieldType)) {
LOG_VFY("VFY: invalid sput-1nr of %s.%s (inst=%d actual=%d)",
staticField->clazz->descriptor,
staticField->name, tmpType, fieldType);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
break;
case OP_SPUT_WIDE:
tmpType = getRegisterType(workLine, decInsn.vA);
{
RegType typeHi = getRegisterType(workLine, decInsn.vA + 1);
checkTypeCategory(tmpType, kTypeCategory2, &failure);
checkWidePair(tmpType, typeHi, &failure);
}
if (!VERIFY_OK(failure))
break;
StaticField* staticField;
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, staticField, &failure);
if (!VERIFY_OK(failure))
break;
/* check the type, which should be prim */
switch (staticField->signature[0]) {
case 'D':
verifyRegisterType(workLine, decInsn.vA, kRegTypeDoubleLo, &failure);
break;
case 'J':
verifyRegisterType(workLine, decInsn.vA, kRegTypeLongLo, &failure);
break;
default:
LOG_VFY("VFY: invalid sput-wide of %s.%s",
staticField->clazz->descriptor,
staticField->name);
failure = VERIFY_ERROR_GENERIC;
break;
}
break;
case OP_SPUT_OBJECT:
{
ClassObject* fieldClass;
ClassObject* valueClass;
StaticField* staticField;
RegType valueType;
staticField = getStaticField(meth, decInsn.vB, &failure);
if (!VERIFY_OK(failure))
break;
checkFinalFieldAccess(meth, staticField, &failure);
if (!VERIFY_OK(failure))
break;
fieldClass = getFieldClass(meth, staticField);
if (fieldClass == NULL) {
LOG_VFY("VFY: unable to recover field class from '%s'",
staticField->signature);
failure = VERIFY_ERROR_GENERIC;
break;
}
valueType = getRegisterType(workLine, decInsn.vA);
if (!regTypeIsReference(valueType)) {
LOG_VFY("VFY: storing non-ref v%d into ref field '%s' (%s)",
decInsn.vA, staticField->name,
fieldClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
if (valueType != kRegTypeZero) {
valueClass = regTypeInitializedReferenceToClass(valueType);
if (valueClass == NULL) {
LOG_VFY("VFY: storing uninit ref v%d into ref field",
decInsn.vA);
failure = VERIFY_ERROR_GENERIC;
break;
}
/* allow if field is any interface or field is base class */
if (!dvmIsInterfaceClass(fieldClass) &&
!dvmInstanceof(valueClass, fieldClass))
{
LOG_VFY("VFY: storing type '%s' into field type '%s' (%s.%s)",
valueClass->descriptor, fieldClass->descriptor,
staticField->clazz->descriptor,
staticField->name);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
}
break;
case OP_INVOKE_VIRTUAL:
case OP_INVOKE_VIRTUAL_RANGE:
case OP_INVOKE_SUPER:
case OP_INVOKE_SUPER_RANGE:
{
Method* calledMethod;
RegType returnType;
bool isRange;
bool isSuper;
isRange = (decInsn.opcode == OP_INVOKE_VIRTUAL_RANGE ||
decInsn.opcode == OP_INVOKE_SUPER_RANGE);
isSuper = (decInsn.opcode == OP_INVOKE_SUPER ||
decInsn.opcode == OP_INVOKE_SUPER_RANGE);
calledMethod = verifyInvocationArgs(meth, workLine, insnRegCount,
&decInsn, uninitMap, METHOD_VIRTUAL, isRange,
isSuper, &failure);
if (!VERIFY_OK(failure))
break;
returnType = getMethodReturnType(calledMethod);
setResultRegisterType(workLine, insnRegCount, returnType);
justSetResult = true;
}
break;
case OP_INVOKE_DIRECT:
case OP_INVOKE_DIRECT_RANGE:
{
RegType returnType;
Method* calledMethod;
bool isRange;
isRange = (decInsn.opcode == OP_INVOKE_DIRECT_RANGE);
calledMethod = verifyInvocationArgs(meth, workLine, insnRegCount,
&decInsn, uninitMap, METHOD_DIRECT, isRange,
false, &failure);
if (!VERIFY_OK(failure))
break;
/*
* Some additional checks when calling <init>. We know from
* the invocation arg check that the "this" argument is an
* instance of calledMethod->clazz. Now we further restrict
* that to require that calledMethod->clazz is the same as
* this->clazz or this->super, allowing the latter only if
* the "this" argument is the same as the "this" argument to
* this method (which implies that we're in <init> ourselves).
*/
if (isInitMethod(calledMethod)) {
RegType thisType;
thisType = getInvocationThis(workLine, &decInsn, &failure);
if (!VERIFY_OK(failure))
break;
/* no null refs allowed (?) */
if (thisType == kRegTypeZero) {
LOG_VFY("VFY: unable to initialize null ref");
failure = VERIFY_ERROR_GENERIC;
break;
}
ClassObject* thisClass;
thisClass = regTypeReferenceToClass(thisType, uninitMap);
assert(thisClass != NULL);
/* must be in same class or in superclass */
if (calledMethod->clazz == thisClass->super) {
if (thisClass != meth->clazz) {
LOG_VFY("VFY: invoke-direct <init> on super only "
"allowed for 'this' in <init>");
failure = VERIFY_ERROR_GENERIC;
break;
}
} else if (calledMethod->clazz != thisClass) {
LOG_VFY("VFY: invoke-direct <init> must be on current "
"class or super");
failure = VERIFY_ERROR_GENERIC;
break;
}
/* arg must be an uninitialized reference */
if (!regTypeIsUninitReference(thisType)) {
LOG_VFY("VFY: can only initialize the uninitialized");
failure = VERIFY_ERROR_GENERIC;
break;
}
/*
* Replace the uninitialized reference with an initialized
* one, and clear the entry in the uninit map. We need to
* do this for all registers that have the same object
* instance in them, not just the "this" register.
*/
markRefsAsInitialized(workLine, insnRegCount, uninitMap,
thisType, &failure);
if (!VERIFY_OK(failure))
break;
}
returnType = getMethodReturnType(calledMethod);
setResultRegisterType(workLine, insnRegCount, returnType);
justSetResult = true;
}
break;
case OP_INVOKE_STATIC:
case OP_INVOKE_STATIC_RANGE:
{
RegType returnType;
Method* calledMethod;
bool isRange;
isRange = (decInsn.opcode == OP_INVOKE_STATIC_RANGE);
calledMethod = verifyInvocationArgs(meth, workLine, insnRegCount,
&decInsn, uninitMap, METHOD_STATIC, isRange,
false, &failure);
if (!VERIFY_OK(failure))
break;
returnType = getMethodReturnType(calledMethod);
setResultRegisterType(workLine, insnRegCount, returnType);
justSetResult = true;
}
break;
case OP_INVOKE_INTERFACE:
case OP_INVOKE_INTERFACE_RANGE:
{
RegType /*thisType,*/ returnType;
Method* absMethod;
bool isRange;
isRange = (decInsn.opcode == OP_INVOKE_INTERFACE_RANGE);
absMethod = verifyInvocationArgs(meth, workLine, insnRegCount,
&decInsn, uninitMap, METHOD_INTERFACE, isRange,
false, &failure);
if (!VERIFY_OK(failure))
break;
#if 0 /* can't do this here, fails on dalvik test 052-verifier-fun */
/*
* Get the type of the "this" arg, which should always be an
* interface class. Because we don't do a full merge on
* interface classes, this might have reduced to Object.
*/
thisType = getInvocationThis(workLine, &decInsn, &failure);
if (!VERIFY_OK(failure))
break;
if (thisType == kRegTypeZero) {
/* null pointer always passes (and always fails at runtime) */
} else {
ClassObject* thisClass;
thisClass = regTypeInitializedReferenceToClass(thisType);
if (thisClass == NULL) {
LOG_VFY("VFY: interface call on uninitialized");
failure = VERIFY_ERROR_GENERIC;
break;
}
/*
* Either "thisClass" needs to be the interface class that
* defined absMethod, or absMethod's class needs to be one
* of the interfaces implemented by "thisClass". (Or, if
* we couldn't complete the merge, this will be Object.)
*/
if (thisClass != absMethod->clazz &&
thisClass != gDvm.classJavaLangObject &&
!dvmImplements(thisClass, absMethod->clazz))
{
LOG_VFY("VFY: unable to match absMethod '%s' with %s interfaces",
absMethod->name, thisClass->descriptor);
failure = VERIFY_ERROR_GENERIC;
break;
}
}
#endif
/*
* We don't have an object instance, so we can't find the
* concrete method. However, all of the type information is
* in the abstract method, so we're good.
*/
returnType = getMethodReturnType(absMethod);
setResultRegisterType(workLine, insnRegCount, returnType);
justSetResult = true;
}
break;
case OP_NEG_INT:
case OP_NOT_INT:
checkUnop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, &failure);
break;
case OP_NEG_LONG:
case OP_NOT_LONG:
checkUnop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeLongLo, &failure);
break;
case OP_NEG_FLOAT:
checkUnop(workLine, &decInsn,
kRegTypeFloat, kRegTypeFloat, &failure);
break;
case OP_NEG_DOUBLE:
checkUnop(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeDoubleLo, &failure);
break;
case OP_INT_TO_LONG:
checkUnop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeInteger, &failure);
break;
case OP_INT_TO_FLOAT:
checkUnop(workLine, &decInsn,
kRegTypeFloat, kRegTypeInteger, &failure);
break;
case OP_INT_TO_DOUBLE:
checkUnop(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeInteger, &failure);
break;
case OP_LONG_TO_INT:
checkUnop(workLine, &decInsn,
kRegTypeInteger, kRegTypeLongLo, &failure);
break;
case OP_LONG_TO_FLOAT:
checkUnop(workLine, &decInsn,
kRegTypeFloat, kRegTypeLongLo, &failure);
break;
case OP_LONG_TO_DOUBLE:
checkUnop(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeLongLo, &failure);
break;
case OP_FLOAT_TO_INT:
checkUnop(workLine, &decInsn,
kRegTypeInteger, kRegTypeFloat, &failure);
break;
case OP_FLOAT_TO_LONG:
checkUnop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeFloat, &failure);
break;
case OP_FLOAT_TO_DOUBLE:
checkUnop(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeFloat, &failure);
break;
case OP_DOUBLE_TO_INT:
checkUnop(workLine, &decInsn,
kRegTypeInteger, kRegTypeDoubleLo, &failure);
break;
case OP_DOUBLE_TO_LONG:
checkUnop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeDoubleLo, &failure);
break;
case OP_DOUBLE_TO_FLOAT:
checkUnop(workLine, &decInsn,
kRegTypeFloat, kRegTypeDoubleLo, &failure);
break;
case OP_INT_TO_BYTE:
checkUnop(workLine, &decInsn,
kRegTypeByte, kRegTypeInteger, &failure);
break;
case OP_INT_TO_CHAR:
checkUnop(workLine, &decInsn,
kRegTypeChar, kRegTypeInteger, &failure);
break;
case OP_INT_TO_SHORT:
checkUnop(workLine, &decInsn,
kRegTypeShort, kRegTypeInteger, &failure);
break;
case OP_ADD_INT:
case OP_SUB_INT:
case OP_MUL_INT:
case OP_REM_INT:
case OP_DIV_INT:
case OP_SHL_INT:
case OP_SHR_INT:
case OP_USHR_INT:
checkBinop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, kRegTypeInteger, false, &failure);
break;
case OP_AND_INT:
case OP_OR_INT:
case OP_XOR_INT:
checkBinop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, kRegTypeInteger, true, &failure);
break;
case OP_ADD_LONG:
case OP_SUB_LONG:
case OP_MUL_LONG:
case OP_DIV_LONG:
case OP_REM_LONG:
case OP_AND_LONG:
case OP_OR_LONG:
case OP_XOR_LONG:
checkBinop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeLongLo, kRegTypeLongLo, false, &failure);
break;
case OP_SHL_LONG:
case OP_SHR_LONG:
case OP_USHR_LONG:
/* shift distance is Int, making these different from other binops */
checkBinop(workLine, &decInsn,
kRegTypeLongLo, kRegTypeLongLo, kRegTypeInteger, false, &failure);
break;
case OP_ADD_FLOAT:
case OP_SUB_FLOAT:
case OP_MUL_FLOAT:
case OP_DIV_FLOAT:
case OP_REM_FLOAT:
checkBinop(workLine, &decInsn,
kRegTypeFloat, kRegTypeFloat, kRegTypeFloat, false, &failure);
break;
case OP_ADD_DOUBLE:
case OP_SUB_DOUBLE:
case OP_MUL_DOUBLE:
case OP_DIV_DOUBLE:
case OP_REM_DOUBLE:
checkBinop(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeDoubleLo, kRegTypeDoubleLo, false,
&failure);
break;
case OP_ADD_INT_2ADDR:
case OP_SUB_INT_2ADDR:
case OP_MUL_INT_2ADDR:
case OP_REM_INT_2ADDR:
case OP_SHL_INT_2ADDR:
case OP_SHR_INT_2ADDR:
case OP_USHR_INT_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, kRegTypeInteger, false, &failure);
break;
case OP_AND_INT_2ADDR:
case OP_OR_INT_2ADDR:
case OP_XOR_INT_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, kRegTypeInteger, true, &failure);
break;
case OP_DIV_INT_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, kRegTypeInteger, false, &failure);
break;
case OP_ADD_LONG_2ADDR:
case OP_SUB_LONG_2ADDR:
case OP_MUL_LONG_2ADDR:
case OP_DIV_LONG_2ADDR:
case OP_REM_LONG_2ADDR:
case OP_AND_LONG_2ADDR:
case OP_OR_LONG_2ADDR:
case OP_XOR_LONG_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeLongLo, kRegTypeLongLo, kRegTypeLongLo, false, &failure);
break;
case OP_SHL_LONG_2ADDR:
case OP_SHR_LONG_2ADDR:
case OP_USHR_LONG_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeLongLo, kRegTypeLongLo, kRegTypeInteger, false, &failure);
break;
case OP_ADD_FLOAT_2ADDR:
case OP_SUB_FLOAT_2ADDR:
case OP_MUL_FLOAT_2ADDR:
case OP_DIV_FLOAT_2ADDR:
case OP_REM_FLOAT_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeFloat, kRegTypeFloat, kRegTypeFloat, false, &failure);
break;
case OP_ADD_DOUBLE_2ADDR:
case OP_SUB_DOUBLE_2ADDR:
case OP_MUL_DOUBLE_2ADDR:
case OP_DIV_DOUBLE_2ADDR:
case OP_REM_DOUBLE_2ADDR:
checkBinop2addr(workLine, &decInsn,
kRegTypeDoubleLo, kRegTypeDoubleLo, kRegTypeDoubleLo, false,
&failure);
break;
case OP_ADD_INT_LIT16:
case OP_RSUB_INT:
case OP_MUL_INT_LIT16:
case OP_DIV_INT_LIT16:
case OP_REM_INT_LIT16:
checkLitop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, false, &failure);
break;
case OP_AND_INT_LIT16:
case OP_OR_INT_LIT16:
case OP_XOR_INT_LIT16:
checkLitop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, true, &failure);
break;
case OP_ADD_INT_LIT8:
case OP_RSUB_INT_LIT8:
case OP_MUL_INT_LIT8:
case OP_DIV_INT_LIT8:
case OP_REM_INT_LIT8:
case OP_SHL_INT_LIT8:
checkLitop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, false, &failure);
break;
case OP_SHR_INT_LIT8:
tmpType = adjustForRightShift(workLine,
decInsn.vB, decInsn.vC, false, &failure);
checkLitop(workLine, &decInsn,
tmpType, kRegTypeInteger, false, &failure);
break;
case OP_USHR_INT_LIT8:
tmpType = adjustForRightShift(workLine,
decInsn.vB, decInsn.vC, true, &failure);
checkLitop(workLine, &decInsn,
tmpType, kRegTypeInteger, false, &failure);
break;
case OP_AND_INT_LIT8:
case OP_OR_INT_LIT8:
case OP_XOR_INT_LIT8:
checkLitop(workLine, &decInsn,
kRegTypeInteger, kRegTypeInteger, true, &failure);
break;
/*
* This falls into the general category of "optimized" instructions,
* which don't generally appear during verification. Because it's
* inserted in the course of verification, we can expect to see it here.
*/
case OP_THROW_VERIFICATION_ERROR:
break;
/*
* Verifying "quickened" instructions is tricky, because we have
* discarded the original field/method information. The byte offsets
* and vtable indices only have meaning in the context of an object
* instance.
*
* If a piece of code declares a local reference variable, assigns
* null to it, and then issues a virtual method call on it, we
* cannot evaluate the method call during verification. This situation
* isn't hard to handle, since we know the call will always result in an
* NPE, and the arguments and return value don't matter. Any code that
* depends on the result of the method call is inaccessible, so the
* fact that we can't fully verify anything that comes after the bad
* call is not a problem.
*
* We must also consider the case of multiple code paths, only some of
* which involve a null reference. We can completely verify the method
* if we sidestep the results of executing with a null reference.
* For example, if on the first pass through the code we try to do a
* virtual method invocation through a null ref, we have to skip the
* method checks and have the method return a "wildcard" type (which
* merges with anything to become that other thing). The move-result
* will tell us if it's a reference, single-word numeric, or double-word
* value. We continue to perform the verification, and at the end of
* the function any invocations that were never fully exercised are
* marked as null-only.
*
* We would do something similar for the field accesses. The field's
* type, once known, can be used to recover the width of short integers.
* If the object reference was null, the field-get returns the "wildcard"
* type, which is acceptable for any operation.
*/
case OP_EXECUTE_INLINE:
case OP_EXECUTE_INLINE_RANGE:
case OP_IGET_QUICK:
case OP_IGET_WIDE_QUICK:
case OP_IGET_OBJECT_QUICK:
case OP_IPUT_QUICK:
case OP_IPUT_WIDE_QUICK:
case OP_IPUT_OBJECT_QUICK:
case OP_INVOKE_VIRTUAL_QUICK:
case OP_INVOKE_VIRTUAL_QUICK_RANGE:
case OP_INVOKE_SUPER_QUICK:
case OP_INVOKE_SUPER_QUICK_RANGE:
/* fall through to failure */
/*
* These instructions are equivalent (from the verifier's point of view)
* to the original form. The change was made for correctness rather
* than improved performance (except for invoke-object-init, which
* provides both). The substitution takes place after verification
* completes, though, so we don't expect to see them here.
*/
case OP_INVOKE_OBJECT_INIT_RANGE:
case OP_RETURN_VOID_BARRIER:
case OP_IGET_VOLATILE:
case OP_IGET_WIDE_VOLATILE:
case OP_IGET_OBJECT_VOLATILE:
case OP_IPUT_VOLATILE:
case OP_IPUT_WIDE_VOLATILE:
case OP_IPUT_OBJECT_VOLATILE:
case OP_SGET_VOLATILE:
case OP_SGET_WIDE_VOLATILE:
case OP_SGET_OBJECT_VOLATILE:
case OP_SPUT_VOLATILE:
case OP_SPUT_WIDE_VOLATILE:
case OP_SPUT_OBJECT_VOLATILE:
/* fall through to failure */
/* these should never appear during verification */
case OP_UNUSED_3E:
case OP_UNUSED_3F:
case OP_UNUSED_40:
case OP_UNUSED_41:
case OP_UNUSED_42:
case OP_UNUSED_43:
case OP_UNUSED_73:
case OP_UNUSED_79:
case OP_UNUSED_7A:
case OP_BREAKPOINT:
case OP_UNUSED_FF:
failure = VERIFY_ERROR_GENERIC;
break;
/*
* DO NOT add a "default" clause here. Without it the compiler will
* complain if an instruction is missing (which is desirable).
*/
}
if (!VERIFY_OK(failure)) {
if (failure == VERIFY_ERROR_GENERIC || gDvm.optimizing) {
/* immediate failure, reject class */
LOG_VFY_METH(meth, "VFY: rejecting opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
goto bail;
} else {
/* replace opcode and continue on */
ALOGD("VFY: replacing opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
if (!replaceFailingInstruction(meth, insnFlags, insnIdx, failure)) {
LOG_VFY_METH(meth, "VFY: rejecting opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
goto bail;
}
/* IMPORTANT: meth->insns may have been changed */
insns = meth->insns + insnIdx;
/* continue on as if we just handled a throw-verification-error */
failure = VERIFY_ERROR_NONE;
nextFlags = kInstrCanThrow;
}
}
/*
* If we didn't just set the result register, clear it out. This
* ensures that you can only use "move-result" immediately after the
* result is set. (We could check this statically, but it's not
* expensive and it makes our debugging output cleaner.)
*/
if (!justSetResult) {
int reg = RESULT_REGISTER(insnRegCount);
setRegisterType(workLine, reg, kRegTypeUnknown);
setRegisterType(workLine, reg+1, kRegTypeUnknown);
}
/*
* Handle "continue". Tag the next consecutive instruction.
*/
if ((nextFlags & kInstrCanContinue) != 0) {
int insnWidth = dvmInsnGetWidth(insnFlags, insnIdx);
if (insnIdx+insnWidth >= insnsSize) {
LOG_VFY_METH(meth,
"VFY: execution can walk off end of code area (from %#x)",
insnIdx);
goto bail;
}
/*
* The only way to get to a move-exception instruction is to get
* thrown there. Make sure the next instruction isn't one.
*/
if (!checkMoveException(meth, insnIdx+insnWidth, "next"))
goto bail;
if (getRegisterLine(regTable, insnIdx+insnWidth)->regTypes != NULL) {
/*
* Merge registers into what we have for the next instruction,
* and set the "changed" flag if needed.
*/
if (!updateRegisters(meth, insnFlags, regTable, insnIdx+insnWidth,
workLine))
goto bail;
} else {
/*
* We're not recording register data for the next instruction,
* so we don't know what the prior state was. We have to
* assume that something has changed and re-evaluate it.
*/
dvmInsnSetChanged(insnFlags, insnIdx+insnWidth, true);
}
}
/*
* Handle "branch". Tag the branch target.
*
* NOTE: instructions like OP_EQZ provide information about the state
* of the register when the branch is taken or not taken. For example,
* somebody could get a reference field, check it for zero, and if the
* branch is taken immediately store that register in a boolean field
* since the value is known to be zero. We do not currently account for
* that, and will reject the code.
*
* TODO: avoid re-fetching the branch target
*/
if ((nextFlags & kInstrCanBranch) != 0) {
bool isConditional;
if (!dvmGetBranchOffset(meth, insnFlags, insnIdx, &branchTarget,
&isConditional))
{
/* should never happen after static verification */
LOG_VFY_METH(meth, "VFY: bad branch at %d", insnIdx);
goto bail;
}
assert(isConditional || (nextFlags & kInstrCanContinue) == 0);
assert(!isConditional || (nextFlags & kInstrCanContinue) != 0);
if (!checkMoveException(meth, insnIdx+branchTarget, "branch"))
goto bail;
/* update branch target, set "changed" if appropriate */
if (!updateRegisters(meth, insnFlags, regTable, insnIdx+branchTarget,
workLine))
goto bail;
}
/*
* Handle "switch". Tag all possible branch targets.
*
* We've already verified that the table is structurally sound, so we
* just need to walk through and tag the targets.
*/
if ((nextFlags & kInstrCanSwitch) != 0) {
int offsetToSwitch = insns[1] | (((s4)insns[2]) << 16);
const u2* switchInsns = insns + offsetToSwitch;
int switchCount = switchInsns[1];
int offsetToTargets, targ;
if ((*insns & 0xff) == OP_PACKED_SWITCH) {
/* 0=sig, 1=count, 2/3=firstKey */
offsetToTargets = 4;
} else {
/* 0=sig, 1=count, 2..count*2 = keys */
assert((*insns & 0xff) == OP_SPARSE_SWITCH);
offsetToTargets = 2 + 2*switchCount;
}
/* verify each switch target */
for (targ = 0; targ < switchCount; targ++) {
int offset, absOffset;
/* offsets are 32-bit, and only partly endian-swapped */
offset = switchInsns[offsetToTargets + targ*2] |
(((s4) switchInsns[offsetToTargets + targ*2 +1]) << 16);
absOffset = insnIdx + offset;
assert(absOffset >= 0 && absOffset < insnsSize);
if (!checkMoveException(meth, absOffset, "switch"))
goto bail;
if (!updateRegisters(meth, insnFlags, regTable, absOffset,
workLine))
goto bail;
}
}
/*
* Handle instructions that can throw and that are sitting in a
* "try" block. (If they're not in a "try" block when they throw,
* control transfers out of the method.)
*/
if ((nextFlags & kInstrCanThrow) != 0 && dvmInsnIsInTry(insnFlags, insnIdx))
{
const DexCode* pCode = dvmGetMethodCode(meth);
DexCatchIterator iterator;
bool hasCatchAll = false;
if (dexFindCatchHandler(&iterator, pCode, insnIdx)) {
for (;;) {
DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
if (handler == NULL) {
break;
}
if (handler->typeIdx == kDexNoIndex)
hasCatchAll = true;
/*
* Merge registers into the "catch" block. We want to
* use the "savedRegs" rather than "workRegs", because
* at runtime the exception will be thrown before the
* instruction modifies any registers.
*/
if (!updateRegisters(meth, insnFlags, regTable,
handler->address, ®Table->savedLine))
goto bail;
}
}
/*
* If the monitor stack depth is nonzero, there must be a "catch all"
* handler for this instruction. This does apply to monitor-exit
* because of async exception handling.
*/
if (workLine->monitorStackTop != 0 && !hasCatchAll) {
/*
* The state in workLine reflects the post-execution state.
* If the current instruction is a monitor-enter and the monitor
* stack was empty, we don't need a catch-all (if it throws,
* it will do so before grabbing the lock).
*/
if (!(decInsn.opcode == OP_MONITOR_ENTER &&
workLine->monitorStackTop == 1))
{
LOG_VFY_METH(meth,
"VFY: no catch-all for instruction at 0x%04x", insnIdx);
goto bail;
}
}
}
/*
* If we're returning from the method, make sure our monitor stack
* is empty.
*/
if ((nextFlags & kInstrCanReturn) != 0 && workLine->monitorStackTop != 0) {
LOG_VFY_METH(meth, "VFY: return with stack depth=%d at 0x%04x",
workLine->monitorStackTop, insnIdx);
goto bail;
}
/*
* Update startGuess. Advance to the next instruction of that's
* possible, otherwise use the branch target if one was found. If
* neither of those exists we're in a return or throw; leave startGuess
* alone and let the caller sort it out.
*/
if ((nextFlags & kInstrCanContinue) != 0) {
*pStartGuess = insnIdx + dvmInsnGetWidth(insnFlags, insnIdx);
} else if ((nextFlags & kInstrCanBranch) != 0) {
/* we're still okay if branchTarget is zero */
*pStartGuess = insnIdx + branchTarget;
}
assert(*pStartGuess >= 0 && *pStartGuess < insnsSize &&
dvmInsnGetWidth(insnFlags, *pStartGuess) != 0);
result = true;
bail:
return result;
}
/*
* callback function used in dumpRegTypes to print local vars
* valid at a given address.
*/
static void logLocalsCb(void *cnxt, u2 reg, u4 startAddress, u4 endAddress,
const char *name, const char *descriptor,
const char *signature)
{
int addr = *((int *)cnxt);
if (addr >= (int) startAddress && addr < (int) endAddress)
{
ALOGI(" %2d: '%s' %s", reg, name, descriptor);
}
}
/*
* Dump the register types for the specifed address to the log file.
*/
static void dumpRegTypes(const VerifierData* vdata,
const RegisterLine* registerLine, int addr, const char* addrName,
const UninitInstanceMap* uninitMap, int displayFlags)
{
const Method* meth = vdata->method;
const InsnFlags* insnFlags = vdata->insnFlags;
const RegType* addrRegs = registerLine->regTypes;
int regCount = meth->registersSize;
int fullRegCount = regCount + kExtraRegs;
bool branchTarget = dvmInsnIsBranchTarget(insnFlags, addr);
int i;
assert(addr >= 0 && addr < (int) dvmGetMethodInsnsSize(meth));
int regCharSize = fullRegCount + (fullRegCount-1)/4 + 2 +1;
char regChars[regCharSize +1];
memset(regChars, ' ', regCharSize);
regChars[0] = '[';
if (regCount == 0)
regChars[1] = ']';
else
regChars[1 + (regCount-1) + (regCount-1)/4 +1] = ']';
regChars[regCharSize] = '\0';
for (i = 0; i < regCount + kExtraRegs; i++) {
char tch;
switch (addrRegs[i]) {
case kRegTypeUnknown: tch = '.'; break;
case kRegTypeConflict: tch = 'X'; break;
case kRegTypeZero: tch = '0'; break;
case kRegTypeOne: tch = '1'; break;
case kRegTypeBoolean: tch = 'Z'; break;
case kRegTypeConstPosByte: tch = 'y'; break;
case kRegTypeConstByte: tch = 'Y'; break;
case kRegTypeConstPosShort: tch = 'h'; break;
case kRegTypeConstShort: tch = 'H'; break;
case kRegTypeConstChar: tch = 'c'; break;
case kRegTypeConstInteger: tch = 'i'; break;
case kRegTypePosByte: tch = 'b'; break;
case kRegTypeByte: tch = 'B'; break;
case kRegTypePosShort: tch = 's'; break;
case kRegTypeShort: tch = 'S'; break;
case kRegTypeChar: tch = 'C'; break;
case kRegTypeInteger: tch = 'I'; break;
case kRegTypeFloat: tch = 'F'; break;
case kRegTypeConstLo: tch = 'N'; break;
case kRegTypeConstHi: tch = 'n'; break;
case kRegTypeLongLo: tch = 'J'; break;
case kRegTypeLongHi: tch = 'j'; break;
case kRegTypeDoubleLo: tch = 'D'; break;
case kRegTypeDoubleHi: tch = 'd'; break;
default:
if (regTypeIsReference(addrRegs[i])) {
if (regTypeIsUninitReference(addrRegs[i]))
tch = 'U';
else
tch = 'L';
} else {
tch = '*';
assert(false);
}
break;
}
if (i < regCount)
regChars[1 + i + (i/4)] = tch;
else
regChars[1 + i + (i/4) + 2] = tch;
}
if (addr == 0 && addrName != NULL) {
ALOGI("%c%s %s mst=%d", branchTarget ? '>' : ' ',
addrName, regChars, registerLine->monitorStackTop);
} else {
ALOGI("%c0x%04x %s mst=%d", branchTarget ? '>' : ' ',
addr, regChars, registerLine->monitorStackTop);
}
if (displayFlags & DRT_SHOW_LIVENESS) {
/*
* We can't use registerLine->liveRegs because it might be the
* "work line" rather than the copy from RegisterTable.
*/
BitVector* liveRegs = vdata->registerLines[addr].liveRegs;
if (liveRegs != NULL) {
char liveChars[regCharSize + 1];
memset(liveChars, ' ', regCharSize);
liveChars[regCharSize] = '\0';
for (i = 0; i < regCount; i++) {
bool isLive = dvmIsBitSet(liveRegs, i);
liveChars[i + 1 + (i / 4)] = isLive ? '+' : '-';
}
ALOGI(" %s", liveChars);
} else {
ALOGI(" %c", '#');
}
}
if (displayFlags & DRT_SHOW_REF_TYPES) {
for (i = 0; i < regCount + kExtraRegs; i++) {
if (regTypeIsReference(addrRegs[i]) && addrRegs[i] != kRegTypeZero)
{
ClassObject* clazz = regTypeReferenceToClass(addrRegs[i], uninitMap);
assert(dvmIsHeapAddress((Object*)clazz));
if (i < regCount) {
ALOGI(" %2d: 0x%08x %s%s",
i, addrRegs[i],
regTypeIsUninitReference(addrRegs[i]) ? "[U]" : "",
clazz->descriptor);
} else {
ALOGI(" RS: 0x%08x %s%s",
addrRegs[i],
regTypeIsUninitReference(addrRegs[i]) ? "[U]" : "",
clazz->descriptor);
}
}
}
}
if (displayFlags & DRT_SHOW_LOCALS) {
dexDecodeDebugInfo(meth->clazz->pDvmDex->pDexFile,
dvmGetMethodCode(meth),
meth->clazz->descriptor,
meth->prototype.protoIdx,
meth->accessFlags,
NULL, logLocalsCb, &addr);
}
}