diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventFilter.c Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,1361 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * eventFilter + * + * This module handles event filteration and the enabling/disabling + * of the corresponding events. Used for filters on JDI EventRequests + * and also internal requests. Our data is in a private hidden section + * of the HandlerNode's data. See comment for enclosing + * module eventHandler. + */ + +#include "util.h" +#include "eventFilter.h" +#include "eventFilterRestricted.h" +#include "eventHandlerRestricted.h" +#include "stepControl.h" +#include "threadControl.h" +#include "SDE.h" +#include "jvmti.h" + +typedef struct ClassFilter { + jclass clazz; +} ClassFilter; + +typedef struct LocationFilter { + jclass clazz; + jmethodID method; + jlocation location; +} LocationFilter; + +typedef struct ThreadFilter { + jthread thread; +} ThreadFilter; + +typedef struct CountFilter { + jint count; +} CountFilter; + +typedef struct ConditionalFilter { + jint exprID; +} ConditionalFilter; + +typedef struct FieldFilter { + jclass clazz; + jfieldID field; +} FieldFilter; + +typedef struct ExceptionFilter { + jclass exception; + jboolean caught; + jboolean uncaught; +} ExceptionFilter; + +typedef struct InstanceFilter { + jobject instance; +} InstanceFilter; + +typedef struct StepFilter { + jint size; + jint depth; + jthread thread; +} StepFilter; + +typedef struct MatchFilter { + char *classPattern; +} MatchFilter; + +typedef struct SourceNameFilter { + char *sourceNamePattern; +} SourceNameFilter; + +typedef struct Filter_ { + jbyte modifier; + union { + struct ClassFilter ClassOnly; + struct LocationFilter LocationOnly; + struct ThreadFilter ThreadOnly; + struct CountFilter Count; + struct ConditionalFilter Conditional; + struct FieldFilter FieldOnly; + struct ExceptionFilter ExceptionOnly; + struct InstanceFilter InstanceOnly; + struct StepFilter Step; + struct MatchFilter ClassMatch; + struct MatchFilter ClassExclude; + struct SourceNameFilter SourceNameOnly; + } u; +} Filter; + +/* The filters array is allocated to the specified filterCount. + * Theoretically, some compiler could do range checking on this + * array - so, we define it to have a ludicrously large size so + * that this range checking won't get upset. + * + * The actual allocated number of bytes is computed using the + * offset of "filters" and so is not effected by this number. + */ +#define MAX_FILTERS 10000 + +typedef struct EventFilters_ { + jint filterCount; + Filter filters[MAX_FILTERS]; +} EventFilters; + +typedef struct EventFilterPrivate_HandlerNode_ { + EventHandlerRestricted_HandlerNode not_for_us; + EventFilters ef; +} EventFilterPrivate_HandlerNode; + +/** + * The following macros extract filter info (EventFilters) from private + * data at the end of a HandlerNode + */ +#define EVENT_FILTERS(node) (&(((EventFilterPrivate_HandlerNode*)(void*)node)->ef)) +#define FILTER_COUNT(node) (EVENT_FILTERS(node)->filterCount) +#define FILTERS_ARRAY(node) (EVENT_FILTERS(node)->filters) +#define FILTER(node,index) ((FILTERS_ARRAY(node))[index]) +#define NODE_EI(node) (node->ei) + +/***** filter set-up / destruction *****/ + +/** + * Allocate a HandlerNode. + * We do it because eventHandler doesn't know how big to make it. + */ +HandlerNode * +eventFilterRestricted_alloc(jint filterCount) +{ + /*LINTED*/ + size_t size = offsetof(EventFilterPrivate_HandlerNode, ef) + + offsetof(EventFilters, filters) + + (filterCount * (int)sizeof(Filter)); + HandlerNode *node = jvmtiAllocate((jint)size); + + if (node != NULL) { + int i; + Filter *filter; + + (void)memset(node, 0, size); + + FILTER_COUNT(node) = filterCount; + + /* Initialize all modifiers + */ + for (i = 0, filter = FILTERS_ARRAY(node); + i < filterCount; + i++, filter++) { + filter->modifier = JDWP_REQUEST_NONE; + } + } + + return node; +} + +/** + * Free up global refs held by the filter. + * free things up at the JNI level if needed. + */ +static jvmtiError +clearFilters(HandlerNode *node) +{ + JNIEnv *env = getEnv(); + jint i; + jvmtiError error = JVMTI_ERROR_NONE; + Filter *filter = FILTERS_ARRAY(node); + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(ThreadOnly): + if ( filter->u.ThreadOnly.thread != NULL ) { + tossGlobalRef(env, &(filter->u.ThreadOnly.thread)); + } + break; + case JDWP_REQUEST_MODIFIER(LocationOnly): + tossGlobalRef(env, &(filter->u.LocationOnly.clazz)); + break; + case JDWP_REQUEST_MODIFIER(FieldOnly): + tossGlobalRef(env, &(filter->u.FieldOnly.clazz)); + break; + case JDWP_REQUEST_MODIFIER(ExceptionOnly): + if ( filter->u.ExceptionOnly.exception != NULL ) { + tossGlobalRef(env, &(filter->u.ExceptionOnly.exception)); + } + break; + case JDWP_REQUEST_MODIFIER(InstanceOnly): + if ( filter->u.InstanceOnly.instance != NULL ) { + tossGlobalRef(env, &(filter->u.InstanceOnly.instance)); + } + break; + case JDWP_REQUEST_MODIFIER(ClassOnly): + tossGlobalRef(env, &(filter->u.ClassOnly.clazz)); + break; + case JDWP_REQUEST_MODIFIER(ClassMatch): + jvmtiDeallocate(filter->u.ClassMatch.classPattern); + break; + case JDWP_REQUEST_MODIFIER(ClassExclude): + jvmtiDeallocate(filter->u.ClassExclude.classPattern); + break; + case JDWP_REQUEST_MODIFIER(Step): { + jthread thread = filter->u.Step.thread; + error = stepControl_endStep(thread); + if (error == JVMTI_ERROR_NONE) { + tossGlobalRef(env, &(filter->u.Step.thread)); + } + break; + } + } + } + if (error == JVMTI_ERROR_NONE) { + FILTER_COUNT(node) = 0; /* blast so we don't clear again */ + } + + return error; +} + + +/***** filtering *****/ + +/* + * Match a string against a wildcard + * string pattern. + */ +static jboolean +patternStringMatch(char *classname, const char *pattern) +{ + int pattLen; + int compLen; + char *start; + int offset; + + if ( pattern==NULL || classname==NULL ) { + return JNI_FALSE; + } + pattLen = (int)strlen(pattern); + + if ((pattern[0] != '*') && (pattern[pattLen-1] != '*')) { + /* An exact match is required when there is no *: bug 4331522 */ + return strcmp(pattern, classname) == 0; + } else { + compLen = pattLen - 1; + offset = (int)strlen(classname) - compLen; + if (offset < 0) { + return JNI_FALSE; + } else { + if (pattern[0] == '*') { + pattern++; + start = classname + offset; + } else { + start = classname; + } + return strncmp(pattern, start, compLen) == 0; + } + } +} + +static jboolean isVersionGte12x() { + jint version; + jvmtiError err = + JVMTI_FUNC_PTR(gdata->jvmti,GetVersionNumber)(gdata->jvmti, &version); + + if (err == JVMTI_ERROR_NONE) { + jint major, minor; + + major = (version & JVMTI_VERSION_MASK_MAJOR) + >> JVMTI_VERSION_SHIFT_MAJOR; + minor = (version & JVMTI_VERSION_MASK_MINOR) + >> JVMTI_VERSION_SHIFT_MINOR; + return (major > 1 || (major == 1 && minor >= 2)) ? JNI_TRUE : JNI_FALSE; + } else { + return JNI_FALSE; + } +} + +/* Return the object instance in which the event occurred */ +/* Return NULL if static or if an error occurs */ +static jobject +eventInstance(EventInfo *evinfo) +{ + jobject object = NULL; + jthread thread ; + jmethodID method ; + jint modifiers = 0; + jvmtiError error; + + static jboolean got_version = JNI_FALSE; + static jboolean is_version_gte_12x = JNI_FALSE; + + if (!got_version) { + is_version_gte_12x = isVersionGte12x(); + got_version = JNI_TRUE; + } + + switch (evinfo->ei) { + case EI_SINGLE_STEP: + case EI_BREAKPOINT: + case EI_FRAME_POP: + case EI_METHOD_ENTRY: + case EI_METHOD_EXIT: + case EI_EXCEPTION: + case EI_EXCEPTION_CATCH: + case EI_MONITOR_CONTENDED_ENTER: + case EI_MONITOR_CONTENDED_ENTERED: + case EI_MONITOR_WAIT: + case EI_MONITOR_WAITED: + thread = evinfo->thread; + method = evinfo->method; + break; + case EI_FIELD_ACCESS: + case EI_FIELD_MODIFICATION: + object = evinfo->object; + return object; + default: + return object; /* NULL */ + } + + error = methodModifiers(method, &modifiers); + + /* fail if error or static (0x8) */ + if (error == JVMTI_ERROR_NONE && thread!=NULL && (modifiers & 0x8) == 0) { + FrameNumber fnum = 0; + if (is_version_gte_12x) { + /* Use new 1.2.x function, GetLocalInstance */ + error = JVMTI_FUNC_PTR(gdata->jvmti,GetLocalInstance) + (gdata->jvmti, thread, fnum, &object); + } else { + /* get slot zero object "this" */ + error = JVMTI_FUNC_PTR(gdata->jvmti,GetLocalObject) + (gdata->jvmti, thread, fnum, 0, &object); + } + if (error != JVMTI_ERROR_NONE) { + object = NULL; + } + } + + return object; +} + +/* + * Determine if this event is interesting to this handler. + * Do so by checking each of the handler's filters. + * Return false if any of the filters fail, + * true if the handler wants this event. + * Anyone modifying this function should check + * eventFilterRestricted_passesUnloadFilter and + * eventFilter_predictFiltering as well. + * + * If shouldDelete is returned true, a count filter has expired + * and the corresponding node should be deleted. + */ +jboolean +eventFilterRestricted_passesFilter(JNIEnv *env, + char *classname, + EventInfo *evinfo, + HandlerNode *node, + jboolean *shouldDelete) +{ + jthread thread; + jclass clazz; + jmethodID method; + Filter *filter = FILTERS_ARRAY(node); + int i; + + *shouldDelete = JNI_FALSE; + thread = evinfo->thread; + clazz = evinfo->clazz; + method = evinfo->method; + + /* + * Suppress most events if they happen in debug threads + */ + if ((evinfo->ei != EI_CLASS_PREPARE) && + (evinfo->ei != EI_GC_FINISH) && + (evinfo->ei != EI_CLASS_LOAD) && + threadControl_isDebugThread(thread)) { + return JNI_FALSE; + } + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(ThreadOnly): + if (!isSameObject(env, thread, filter->u.ThreadOnly.thread)) { + return JNI_FALSE; + } + break; + + case JDWP_REQUEST_MODIFIER(ClassOnly): + /* Class filters catch events in the specified + * class and any subclass/subinterface. + */ + if (!JNI_FUNC_PTR(env,IsAssignableFrom)(env, clazz, + filter->u.ClassOnly.clazz)) { + return JNI_FALSE; + } + break; + + /* This is kinda cheating assumming the event + * fields will be in the same locations, but it is + * true now. + */ + case JDWP_REQUEST_MODIFIER(LocationOnly): + if (evinfo->method != + filter->u.LocationOnly.method || + evinfo->location != + filter->u.LocationOnly.location || + !isSameObject(env, clazz, filter->u.LocationOnly.clazz)) { + return JNI_FALSE; + } + break; + + case JDWP_REQUEST_MODIFIER(FieldOnly): + /* Field watchpoints can be triggered from the + * declared class or any subclass/subinterface. + */ + if ((evinfo->u.field_access.field != + filter->u.FieldOnly.field) || + !isSameObject(env, evinfo->u.field_access.field_clazz, + filter->u.FieldOnly.clazz)) { + return JNI_FALSE; + } + break; + + case JDWP_REQUEST_MODIFIER(ExceptionOnly): + /* do we want caught/uncaught exceptions */ + if (!((evinfo->u.exception.catch_clazz == NULL)? + filter->u.ExceptionOnly.uncaught : + filter->u.ExceptionOnly.caught)) { + return JNI_FALSE; + } + + /* do we care about exception class */ + if (filter->u.ExceptionOnly.exception != NULL) { + jclass exception = evinfo->object; + + /* do we want this exception class */ + if (!JNI_FUNC_PTR(env,IsInstanceOf)(env, exception, + filter->u.ExceptionOnly.exception)) { + return JNI_FALSE; + } + } + break; + + case JDWP_REQUEST_MODIFIER(InstanceOnly): { + jobject eventInst = eventInstance(evinfo); + jobject filterInst = filter->u.InstanceOnly.instance; + /* if no error and doesn't match, don't pass + * filter + */ + if (eventInst != NULL && + !isSameObject(env, eventInst, filterInst)) { + return JNI_FALSE; + } + break; + } + case JDWP_REQUEST_MODIFIER(Count): { + JDI_ASSERT(filter->u.Count.count > 0); + if (--filter->u.Count.count > 0) { + return JNI_FALSE; + } + *shouldDelete = JNI_TRUE; + break; + } + + case JDWP_REQUEST_MODIFIER(Conditional): +/*** + if (... filter->u.Conditional.exprID ...) { + return JNI_FALSE; + } +***/ + break; + + case JDWP_REQUEST_MODIFIER(ClassMatch): { + if (!patternStringMatch(classname, + filter->u.ClassMatch.classPattern)) { + return JNI_FALSE; + } + break; + } + + case JDWP_REQUEST_MODIFIER(ClassExclude): { + if (patternStringMatch(classname, + filter->u.ClassExclude.classPattern)) { + return JNI_FALSE; + } + break; + } + + case JDWP_REQUEST_MODIFIER(Step): + if (!isSameObject(env, thread, filter->u.Step.thread)) { + return JNI_FALSE; + } + if (!stepControl_handleStep(env, thread, clazz, method)) { + return JNI_FALSE; + } + break; + + case JDWP_REQUEST_MODIFIER(SourceNameMatch): { + char* desiredNamePattern = filter->u.SourceNameOnly.sourceNamePattern; + if (searchAllSourceNames(env, clazz, + desiredNamePattern) != 1) { + /* The name isn't in the SDE; try the sourceName in the ref + * type + */ + char *sourceName = 0; + jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,GetSourceFileName) + (gdata->jvmti, clazz, &sourceName); + if (error == JVMTI_ERROR_NONE && + sourceName != 0 && + patternStringMatch(sourceName, desiredNamePattern)) { + // got a hit - report the event + jvmtiDeallocate(sourceName); + break; + } + // We have no match, we have no source file name, + // or we got a JVM TI error. Don't report the event. + jvmtiDeallocate(sourceName); + return JNI_FALSE; + } + break; + } + + default: + EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"Invalid filter modifier"); + return JNI_FALSE; + } + } + return JNI_TRUE; +} + +/* Determine if this event is interesting to this handler. Do so + * by checking each of the handler's filters. Return false if any + * of the filters fail, true if the handler wants this event. + * Special version of filter for unloads since they don't have an + * event structure or a jclass. + * + * If shouldDelete is returned true, a count filter has expired + * and the corresponding node should be deleted. + */ +jboolean +eventFilterRestricted_passesUnloadFilter(JNIEnv *env, + char *classname, + HandlerNode *node, + jboolean *shouldDelete) +{ + Filter *filter = FILTERS_ARRAY(node); + int i; + + *shouldDelete = JNI_FALSE; + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + + case JDWP_REQUEST_MODIFIER(Count): { + JDI_ASSERT(filter->u.Count.count > 0); + if (--filter->u.Count.count > 0) { + return JNI_FALSE; + } + *shouldDelete = JNI_TRUE; + break; + } + + case JDWP_REQUEST_MODIFIER(ClassMatch): { + if (!patternStringMatch(classname, + filter->u.ClassMatch.classPattern)) { + return JNI_FALSE; + } + break; + } + + case JDWP_REQUEST_MODIFIER(ClassExclude): { + if (patternStringMatch(classname, + filter->u.ClassExclude.classPattern)) { + return JNI_FALSE; + } + break; + } + + default: + EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"Invalid filter modifier"); + return JNI_FALSE; + } + } + return JNI_TRUE; +} + +/** + * This function returns true only if it is certain that + * all events for the given node in the given stack frame will + * be filtered. It is used to optimize stepping. (If this + * function returns true the stepping algorithm does not + * have to step through every instruction in this stack frame; + * instead, it can use more efficient method entry/exit + * events. + */ +jboolean +eventFilter_predictFiltering(HandlerNode *node, jclass clazz, char *classname) +{ + JNIEnv *env; + jboolean willBeFiltered; + Filter *filter; + jboolean done; + int count; + int i; + + willBeFiltered = JNI_FALSE; + env = NULL; + filter = FILTERS_ARRAY(node); + count = FILTER_COUNT(node); + done = JNI_FALSE; + + for (i = 0; (i < count) && (!done); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(ClassOnly): + if ( env==NULL ) { + env = getEnv(); + } + if (!JNI_FUNC_PTR(env,IsAssignableFrom)(env, clazz, + filter->u.ClassOnly.clazz)) { + willBeFiltered = JNI_TRUE; + done = JNI_TRUE; + } + break; + + case JDWP_REQUEST_MODIFIER(Count): { + /* + * If preceding filters have determined that events will + * be filtered out, that is fine and we won't get here. + * However, the count must be decremented - even if + * subsequent filters will filter these events. We + * thus must end now unable to predict + */ + done = JNI_TRUE; + break; + } + + case JDWP_REQUEST_MODIFIER(ClassMatch): { + if (!patternStringMatch(classname, + filter->u.ClassMatch.classPattern)) { + willBeFiltered = JNI_TRUE; + done = JNI_TRUE; + } + break; + } + + case JDWP_REQUEST_MODIFIER(ClassExclude): { + if (patternStringMatch(classname, + filter->u.ClassExclude.classPattern)) { + willBeFiltered = JNI_TRUE; + done = JNI_TRUE; + } + break; + } + } + } + + return willBeFiltered; +} + +/** + * Determine if the given breakpoint node is in the specified class. + */ +jboolean +eventFilterRestricted_isBreakpointInClass(JNIEnv *env, jclass clazz, + HandlerNode *node) +{ + Filter *filter = FILTERS_ARRAY(node); + int i; + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(LocationOnly): + return isSameObject(env, clazz, filter->u.LocationOnly.clazz); + } + } + return JNI_TRUE; /* should never come here */ +} + +/***** filter set-up *****/ + +jvmtiError +eventFilter_setConditionalFilter(HandlerNode *node, jint index, + jint exprID) +{ + ConditionalFilter *filter = &FILTER(node, index).u.Conditional; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Conditional); + filter->exprID = exprID; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setCountFilter(HandlerNode *node, jint index, + jint count) +{ + CountFilter *filter = &FILTER(node, index).u.Count; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if (count <= 0) { + return JDWP_ERROR(INVALID_COUNT); + } else { + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Count); + filter->count = count; + return JVMTI_ERROR_NONE; + } +} + +jvmtiError +eventFilter_setThreadOnlyFilter(HandlerNode *node, jint index, + jthread thread) +{ + JNIEnv *env = getEnv(); + ThreadFilter *filter = &FILTER(node, index).u.ThreadOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if (NODE_EI(node) == EI_GC_FINISH) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + /* Create a thread ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, thread, &(filter->thread)); + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(ThreadOnly); + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setLocationOnlyFilter(HandlerNode *node, jint index, + jclass clazz, jmethodID method, + jlocation location) +{ + JNIEnv *env = getEnv(); + LocationFilter *filter = &FILTER(node, index).u.LocationOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if ((NODE_EI(node) != EI_BREAKPOINT) && + (NODE_EI(node) != EI_FIELD_ACCESS) && + (NODE_EI(node) != EI_FIELD_MODIFICATION) && + (NODE_EI(node) != EI_SINGLE_STEP) && + (NODE_EI(node) != EI_EXCEPTION)) { + + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + /* Create a class ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, clazz, &(filter->clazz)); + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(LocationOnly); + filter->method = method; + filter->location = location; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setFieldOnlyFilter(HandlerNode *node, jint index, + jclass clazz, jfieldID field) +{ + JNIEnv *env = getEnv(); + FieldFilter *filter = &FILTER(node, index).u.FieldOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if ((NODE_EI(node) != EI_FIELD_ACCESS) && + (NODE_EI(node) != EI_FIELD_MODIFICATION)) { + + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + /* Create a class ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, clazz, &(filter->clazz)); + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(FieldOnly); + filter->field = field; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setClassOnlyFilter(HandlerNode *node, jint index, + jclass clazz) +{ + JNIEnv *env = getEnv(); + ClassFilter *filter = &FILTER(node, index).u.ClassOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if ( + (NODE_EI(node) == EI_GC_FINISH) || + (NODE_EI(node) == EI_THREAD_START) || + (NODE_EI(node) == EI_THREAD_END)) { + + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + /* Create a class ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, clazz, &(filter->clazz)); + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(ClassOnly); + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setExceptionOnlyFilter(HandlerNode *node, jint index, + jclass exceptionClass, + jboolean caught, + jboolean uncaught) +{ + JNIEnv *env = getEnv(); + ExceptionFilter *filter = &FILTER(node, index).u.ExceptionOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if (NODE_EI(node) != EI_EXCEPTION) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + filter->exception = NULL; + if (exceptionClass != NULL) { + /* Create a class ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, exceptionClass, &(filter->exception)); + } + FILTER(node, index).modifier = + JDWP_REQUEST_MODIFIER(ExceptionOnly); + filter->caught = caught; + filter->uncaught = uncaught; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setInstanceOnlyFilter(HandlerNode *node, jint index, + jobject instance) +{ + JNIEnv *env = getEnv(); + InstanceFilter *filter = &FILTER(node, index).u.InstanceOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + filter->instance = NULL; + if (instance != NULL) { + /* Create an object ref that will live beyond + * the end of this call + */ + saveGlobalRef(env, instance, &(filter->instance)); + } + FILTER(node, index).modifier = + JDWP_REQUEST_MODIFIER(InstanceOnly); + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setClassMatchFilter(HandlerNode *node, jint index, + char *classPattern) +{ + MatchFilter *filter = &FILTER(node, index).u.ClassMatch; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if ( + (NODE_EI(node) == EI_THREAD_START) || + (NODE_EI(node) == EI_THREAD_END)) { + + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + FILTER(node, index).modifier = + JDWP_REQUEST_MODIFIER(ClassMatch); + filter->classPattern = classPattern; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setClassExcludeFilter(HandlerNode *node, jint index, + char *classPattern) +{ + MatchFilter *filter = &FILTER(node, index).u.ClassExclude; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if ( + (NODE_EI(node) == EI_THREAD_START) || + (NODE_EI(node) == EI_THREAD_END)) { + + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + FILTER(node, index).modifier = + JDWP_REQUEST_MODIFIER(ClassExclude); + filter->classPattern = classPattern; + return JVMTI_ERROR_NONE; +} + +jvmtiError +eventFilter_setStepFilter(HandlerNode *node, jint index, + jthread thread, jint size, jint depth) +{ + jvmtiError error; + JNIEnv *env = getEnv(); + StepFilter *filter = &FILTER(node, index).u.Step; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if (NODE_EI(node) != EI_SINGLE_STEP) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + /* Create a thread ref that will live beyond */ + /* the end of this call */ + saveGlobalRef(env, thread, &(filter->thread)); + error = stepControl_beginStep(env, filter->thread, size, depth, node); + if (error != JVMTI_ERROR_NONE) { + tossGlobalRef(env, &(filter->thread)); + return error; + } + FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Step); + filter->depth = depth; + filter->size = size; + return JVMTI_ERROR_NONE; +} + + +jvmtiError +eventFilter_setSourceNameMatchFilter(HandlerNode *node, + jint index, + char *sourceNamePattern) { + SourceNameFilter *filter = &FILTER(node, index).u.SourceNameOnly; + if (index >= FILTER_COUNT(node)) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + if (NODE_EI(node) != EI_CLASS_PREPARE) { + return AGENT_ERROR_ILLEGAL_ARGUMENT; + } + + FILTER(node, index).modifier = + JDWP_REQUEST_MODIFIER(SourceNameMatch); + filter->sourceNamePattern = sourceNamePattern; + return JVMTI_ERROR_NONE; + +} + +/***** JVMTI event enabling / disabling *****/ + +/** + * Return the Filter that is of the specified type (modifier). + * Return NULL if not found. + */ +static Filter * +findFilter(HandlerNode *node, jint modifier) +{ + int i; + Filter *filter; + for (i = 0, filter = FILTERS_ARRAY(node); + i modifier == modifier) { + return filter; + } + } + return NULL; +} + +/** + * Determine if the specified breakpoint node is in the + * same location as the LocationFilter passed in arg. + * + * This is a match function called by a + * eventHandlerRestricted_iterator invokation. + */ +static jboolean +matchBreakpoint(JNIEnv *env, HandlerNode *node, void *arg) +{ + LocationFilter *goal = (LocationFilter *)arg; + Filter *filter = FILTERS_ARRAY(node); + int i; + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(LocationOnly): { + LocationFilter *trial = &(filter->u.LocationOnly); + if (trial->method == goal->method && + trial->location == goal->location && + isSameObject(env, trial->clazz, goal->clazz)) { + return JNI_TRUE; + } + } + } + } + return JNI_FALSE; +} + +/** + * Set a breakpoint if this is the first one at this location. + */ +static jvmtiError +setBreakpoint(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + Filter *filter; + + filter = findFilter(node, JDWP_REQUEST_MODIFIER(LocationOnly)); + if (filter == NULL) { + /* bp event with no location filter */ + error = AGENT_ERROR_INTERNAL; + } else { + LocationFilter *lf = &(filter->u.LocationOnly); + + /* if this is the first handler for this + * location, set bp at JVMTI level + */ + if (!eventHandlerRestricted_iterator( + EI_BREAKPOINT, matchBreakpoint, lf)) { + LOG_LOC(("SetBreakpoint at location: method=%p,location=%d", + lf->method, (int)lf->location)); + error = JVMTI_FUNC_PTR(gdata->jvmti,SetBreakpoint) + (gdata->jvmti, lf->method, lf->location); + } + } + return error; +} + +/** + * Clear a breakpoint if this is the last one at this location. + */ +static jvmtiError +clearBreakpoint(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + Filter *filter; + + filter = findFilter(node, JDWP_REQUEST_MODIFIER(LocationOnly)); + if (filter == NULL) { + /* bp event with no location filter */ + error = AGENT_ERROR_INTERNAL; + } else { + LocationFilter *lf = &(filter->u.LocationOnly); + + /* if this is the last handler for this + * location, clear bp at JVMTI level + */ + if (!eventHandlerRestricted_iterator( + EI_BREAKPOINT, matchBreakpoint, lf)) { + LOG_LOC(("ClearBreakpoint at location: method=%p,location=%d", + lf->method, (int)lf->location)); + error = JVMTI_FUNC_PTR(gdata->jvmti,ClearBreakpoint) + (gdata->jvmti, lf->method, lf->location); + } + } + return error; +} + +/** + * Return true if a breakpoint is set at the specified location. + */ +jboolean +isBreakpointSet(jclass clazz, jmethodID method, jlocation location) +{ + LocationFilter lf; + + lf.clazz = clazz; + lf.method = method; + lf.location = location; + + return eventHandlerRestricted_iterator(EI_BREAKPOINT, + matchBreakpoint, &lf); +} + +/** + * Determine if the specified watchpoint node has the + * same field as the FieldFilter passed in arg. + * + * This is a match function called by a + * eventHandlerRestricted_iterator invokation. + */ +static jboolean +matchWatchpoint(JNIEnv *env, HandlerNode *node, void *arg) +{ + FieldFilter *goal = (FieldFilter *)arg; + Filter *filter = FILTERS_ARRAY(node); + int i; + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(FieldOnly): { + FieldFilter *trial = &(filter->u.FieldOnly); + if (trial->field == goal->field && + isSameObject(env, trial->clazz, goal->clazz)) { + return JNI_TRUE; + } + } + } + } + return JNI_FALSE; +} + +/** + * Set a watchpoint if this is the first one on this field. + */ +static jvmtiError +setWatchpoint(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + Filter *filter; + + filter = findFilter(node, JDWP_REQUEST_MODIFIER(FieldOnly)); + if (filter == NULL) { + /* event with no field filter */ + error = AGENT_ERROR_INTERNAL; + } else { + FieldFilter *ff = &(filter->u.FieldOnly); + + /* if this is the first handler for this + * field, set wp at JVMTI level + */ + if (!eventHandlerRestricted_iterator( + NODE_EI(node), matchWatchpoint, ff)) { + error = (NODE_EI(node) == EI_FIELD_ACCESS) ? + JVMTI_FUNC_PTR(gdata->jvmti,SetFieldAccessWatch) + (gdata->jvmti, ff->clazz, ff->field) : + JVMTI_FUNC_PTR(gdata->jvmti,SetFieldModificationWatch) + (gdata->jvmti, ff->clazz, ff->field); + } + } + return error; +} + +/** + * Clear a watchpoint if this is the last one on this field. + */ +static jvmtiError +clearWatchpoint(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + Filter *filter; + + filter = findFilter(node, JDWP_REQUEST_MODIFIER(FieldOnly)); + if (filter == NULL) { + /* event with no field filter */ + error = AGENT_ERROR_INTERNAL; + } else { + FieldFilter *ff = &(filter->u.FieldOnly); + + /* if this is the last handler for this + * field, clear wp at JVMTI level + */ + if (!eventHandlerRestricted_iterator( + NODE_EI(node), matchWatchpoint, ff)) { + error = (NODE_EI(node) == EI_FIELD_ACCESS) ? + JVMTI_FUNC_PTR(gdata->jvmti,ClearFieldAccessWatch) + (gdata->jvmti, ff->clazz, ff->field) : + JVMTI_FUNC_PTR(gdata->jvmti,ClearFieldModificationWatch) + (gdata->jvmti, ff->clazz, ff->field); + } + } + return error; +} + +/** + * Determine the thread this node is filtered on. + * NULL if not thread filtered. + */ +static jthread +requestThread(HandlerNode *node) +{ + int i; + Filter *filter = FILTERS_ARRAY(node); + + for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { + switch (filter->modifier) { + case JDWP_REQUEST_MODIFIER(ThreadOnly): + return filter->u.ThreadOnly.thread; + } + } + + return NULL; +} + +/** + * Determine if the specified node has a + * thread filter with the thread passed in arg. + * + * This is a match function called by a + * eventHandlerRestricted_iterator invokation. + */ +static jboolean +matchThread(JNIEnv *env, HandlerNode *node, void *arg) +{ + jthread goalThread = (jthread)arg; + jthread reqThread = requestThread(node); + + /* If the event's thread and the passed thread are the same + * (or both are NULL), we have a match. + */ + return isSameObject(env, reqThread, goalThread); +} + +/** + * Do any enabling of events (including setting breakpoints etc) + * needed to get the events requested by this handler node. + */ +static jvmtiError +enableEvents(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + + switch (NODE_EI(node)) { + /* The stepping code directly enables/disables stepping as + * necessary + */ + case EI_SINGLE_STEP: + /* Internal thread event handlers are always present + * (hardwired in the event hook), so we don't change the + * notification mode here. + */ + case EI_THREAD_START: + case EI_THREAD_END: + case EI_VM_INIT: + case EI_VM_DEATH: + case EI_CLASS_PREPARE: + case EI_GC_FINISH: + return error; + + case EI_FIELD_ACCESS: + case EI_FIELD_MODIFICATION: + error = setWatchpoint(node); + break; + + case EI_BREAKPOINT: + error = setBreakpoint(node); + break; + + default: + break; + } + + /* Don't globally enable if the above failed */ + if (error == JVMTI_ERROR_NONE) { + jthread thread = requestThread(node); + + /* If this is the first request of it's kind on this + * thread (or all threads (thread == NULL)) then enable + * these events on this thread. + */ + if (!eventHandlerRestricted_iterator( + NODE_EI(node), matchThread, thread)) { + error = threadControl_setEventMode(JVMTI_ENABLE, + NODE_EI(node), thread); + } + } + return error; +} + +/** + * Do any disabling of events (including clearing breakpoints etc) + * needed to no longer get the events requested by this handler node. + */ +static jvmtiError +disableEvents(HandlerNode *node) +{ + jvmtiError error = JVMTI_ERROR_NONE; + jvmtiError error2 = JVMTI_ERROR_NONE; + jthread thread; + + + switch (NODE_EI(node)) { + /* The stepping code directly enables/disables stepping as + * necessary + */ + case EI_SINGLE_STEP: + /* Internal thread event handlers are always present + * (hardwired in the event hook), so we don't change the + * notification mode here. + */ + case EI_THREAD_START: + case EI_THREAD_END: + case EI_VM_INIT: + case EI_VM_DEATH: + case EI_CLASS_PREPARE: + case EI_GC_FINISH: + return error; + + case EI_FIELD_ACCESS: + case EI_FIELD_MODIFICATION: + error = clearWatchpoint(node); + break; + + case EI_BREAKPOINT: + error = clearBreakpoint(node); + break; + + default: + break; + } + + thread = requestThread(node); + + /* If this is the last request of it's kind on this thread + * (or all threads (thread == NULL)) then disable these + * events on this thread. + * + * Disable even if the above caused an error + */ + if (!eventHandlerRestricted_iterator(NODE_EI(node), matchThread, thread)) { + error2 = threadControl_setEventMode(JVMTI_DISABLE, + NODE_EI(node), thread); + } + return error != JVMTI_ERROR_NONE? error : error2; +} + + +/***** filter (and event) installation and deinstallation *****/ + +/** + * Make the set of event filters that correspond with this + * node active (including enabling the corresponding events). + */ +jvmtiError +eventFilterRestricted_install(HandlerNode *node) +{ + return enableEvents(node); +} + +/** + * Make the set of event filters that correspond with this + * node inactive (including disabling the corresponding events + * and freeing resources). + */ +jvmtiError +eventFilterRestricted_deinstall(HandlerNode *node) +{ + jvmtiError error1, error2; + + error1 = disableEvents(node); + error2 = clearFilters(node); + + return error1 != JVMTI_ERROR_NONE? error1 : error2; +}