1 /*
   2  * Copyright (c) 2017, Google and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 #include <stdio.h>
  25 #include <string.h>
  26 #include "jvmti.h"
  27 
  28 #ifdef __cplusplus
  29 extern "C" {
  30 #endif
  31 
  32 #ifndef JNI_ENV_ARG
  33 
  34 #ifdef __cplusplus
  35 #define JNI_ENV_ARG(x, y) y
  36 #define JNI_ENV_PTR(x) x
  37 #else
  38 #define JNI_ENV_ARG(x,y) x, y
  39 #define JNI_ENV_PTR(x) (*x)
  40 #endif
  41 
  42 #endif
  43 
  44 #define PASSED 0
  45 #define FAILED 2
  46 
  47 #define MAX_TRACES 400
  48 
  49 static const char *EXC_CNAME = "java/lang/Exception";
  50 static jvmtiEnv *jvmti = NULL;
  51 
  52 static int check_error(jvmtiError err, const char* s) {
  53   if (err != JVMTI_ERROR_NONE) {
  54     printf("  ## %s error: %d\n", s, err);
  55     return 1;
  56   }
  57   return 0;
  58 }
  59 
  60 static int check_capability_error(jvmtiError err, const char* s) {
  61   if (err != JVMTI_ERROR_NONE) {
  62     if (err == JVMTI_ERROR_MUST_POSSESS_CAPABILITY) {
  63       return 0;
  64     }
  65     printf("  ## %s error: %d\n", s, err);
  66     return 1;
  67   }
  68   return 1;
  69 }
  70 
  71 static
  72 jint throw_exc(JNIEnv *env, char *msg) {
  73   jclass exc_class = JNI_ENV_PTR(env)->FindClass(JNI_ENV_ARG(env, EXC_CNAME));
  74 
  75   if (exc_class == NULL) {
  76     printf("throw_exc: Error in FindClass(env, %s)\n", EXC_CNAME);
  77     return -1;
  78   }
  79   return JNI_ENV_PTR(env)->ThrowNew(JNI_ENV_ARG(env, exc_class), msg);
  80 }
  81 
  82 static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved);
  83 
  84 JNIEXPORT
  85 jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
  86   return Agent_Initialize(jvm, options, reserved);
  87 }
  88 
  89 JNIEXPORT
  90 jint JNICALL Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) {
  91   return Agent_Initialize(jvm, options, reserved);
  92 }
  93 
  94 JNIEXPORT
  95 jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
  96   return JNI_VERSION_1_8;
  97 }
  98 
  99 JNIEXPORT void JNICALL OnVMInit(jvmtiEnv *jvmti, JNIEnv *jni_env, jthread thread) {
 100 }
 101 
 102 JNIEXPORT void JNICALL OnClassLoad(jvmtiEnv *jvmti_env, JNIEnv *jni_env,
 103                                               jthread thread, jclass klass) {
 104   // NOP.
 105 }
 106 
 107 JNIEXPORT void JNICALL OnClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env,
 108                                       jthread thread, jclass klass) {
 109   // We need to do this to "prime the pump", as it were -- make sure
 110   // that all of the methodIDs have been initialized internally, for
 111   // AsyncGetCallTrace.
 112   jint method_count;
 113   jmethodID *methods = 0;
 114   jvmtiError err = (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods);
 115   if ((err != JVMTI_ERROR_NONE) && (err != JVMTI_ERROR_CLASS_NOT_PREPARED)) {
 116     // JVMTI_ERROR_CLASS_NOT_PREPARED is okay because some classes may
 117     // be loaded but not prepared at this point.
 118     throw_exc(jni_env, "Failed to create method IDs for methods in class\n");
 119   }
 120 }
 121 
 122 static
 123 jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
 124   jint res;
 125 
 126   res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti),
 127                                  JVMTI_VERSION_9);
 128   if (res != JNI_OK || jvmti == NULL) {
 129     printf("    Error: wrong result of a valid call to GetEnv!\n");
 130     return JNI_ERR;
 131   }
 132 
 133   jvmtiEventCallbacks callbacks;
 134   memset(&callbacks, 0, sizeof(callbacks));
 135 
 136   callbacks.VMInit = &OnVMInit;
 137   callbacks.ClassLoad = &OnClassLoad;
 138   callbacks.ClassPrepare = &OnClassPrepare;
 139 
 140   jvmtiCapabilities caps;
 141   memset(&caps, 0, sizeof(caps));
 142   // Get line numbers, sample heap, and filename for the test.
 143   caps.can_get_line_numbers = 1;
 144   caps.can_sample_heap= 1;
 145   caps.can_get_source_file_name = 1;
 146   if (check_error((*jvmti)->AddCapabilities(jvmti, &caps),
 147                   "Add capabilities\n")){
 148     return JNI_ERR;
 149   }
 150 
 151   if (check_error((*jvmti)->SetEventCallbacks(jvmti, &callbacks,
 152                                               sizeof(jvmtiEventCallbacks)),
 153                   " Set Event Callbacks")) {
 154     return JNI_ERR;
 155   }
 156   if (check_error((*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
 157                                                      JVMTI_EVENT_VM_INIT, NULL),
 158                   "Set Event for VM Init")) {
 159     return JNI_ERR;
 160   }
 161   if (check_error((*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
 162                                                      JVMTI_EVENT_CLASS_LOAD, NULL),
 163                   "Set Event for Class Load")) {
 164     return JNI_ERR;
 165   }
 166   if (check_error( (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
 167                                                       JVMTI_EVENT_CLASS_PREPARE, NULL),
 168                   "Set Event for Class Prepare")) {
 169     return JNI_ERR;
 170   }
 171 
 172   return JNI_OK;
 173 }
 174 
 175 // Given a method and a location, this method gets the line number.
 176 // Kind of expensive, comparatively.
 177 static
 178 jint get_line_number(jvmtiEnv *jvmti, jmethodID method, jlocation location) {
 179   // The location is -1 if the bci isn't known or -3 for a native method.
 180   if (location == -1 || location == -3) {
 181     return -1;
 182   }
 183 
 184   // Read the line number table.
 185   jvmtiLineNumberEntry *table_ptr = 0;
 186   jint line_number_table_entries;
 187   int jvmti_error = (*jvmti)->GetLineNumberTable(jvmti, method,
 188                                                  &line_number_table_entries,
 189                                                  &table_ptr);
 190 
 191   if (JVMTI_ERROR_NONE != jvmti_error) {
 192     return -1;
 193   }
 194   if (line_number_table_entries <= 0) {
 195     return -1;
 196   }
 197   if (line_number_table_entries == 1) {
 198     return table_ptr[0].line_number;
 199   }
 200 
 201   // Go through all the line numbers...
 202   jint last_location = table_ptr[0].start_location;
 203   int l;
 204   for (l = 1; l < line_number_table_entries; l++) {
 205     // ... and if you see one that is in the right place for your
 206     // location, you've found the line number!
 207     if ((location < table_ptr[l].start_location) &&
 208         (location >= last_location)) {
 209       return table_ptr[l - 1].line_number;
 210     }
 211     last_location = table_ptr[l].start_location;
 212   }
 213 
 214   if (location >= last_location) {
 215     return table_ptr[line_number_table_entries - 1].line_number;
 216   } else {
 217     return -1;
 218   }
 219 }
 220 
 221 typedef struct _ExpectedContentFrame {
 222   const char *name;
 223   const char *signature;
 224   const char *file_name;
 225   int line_number;
 226 } ExpectedContentFrame;
 227 
 228 static jint check_sample_content(JNIEnv *env,
 229                                  jvmtiStackTrace *trace,
 230                                  ExpectedContentFrame *expected,
 231                                  int expected_count) {
 232   int i;
 233 
 234   if (expected_count > trace->frame_count) {
 235     return 0;
 236   }
 237 
 238   for (i = 0; i < expected_count; i++) {
 239     // Get basic information out of the trace.
 240     int bci = trace->frames[i].location;
 241     jmethodID methodid = trace->frames[i].method;
 242     char *name = NULL, *signature = NULL, *file_name = NULL;
 243 
 244     if (bci < 0) {
 245       return 0;
 246     }
 247 
 248     // Transform into usable information.
 249     int line_number = get_line_number(jvmti, methodid, bci);
 250     (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0);
 251 
 252     jclass declaring_class;
 253     if (JVMTI_ERROR_NONE !=
 254         (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) {
 255       return 0;
 256     }
 257 
 258     jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class,
 259                                                  &file_name);
 260     if (err != JVMTI_ERROR_NONE) {
 261       return 0;
 262     }
 263 
 264     // Compare now, none should be NULL.
 265     if (name == NULL) {
 266       return 0;
 267     }
 268 
 269     if (file_name == NULL) {
 270       return 0;
 271     }
 272 
 273     if (signature == NULL) {
 274       return 0;
 275     }
 276 
 277     if (strcmp(name, expected[i].name) ||
 278         strcmp(signature, expected[i].signature) ||
 279         strcmp(file_name, expected[i].file_name) ||
 280         line_number != expected[i].line_number) {
 281       return 0;
 282     }
 283   }
 284 
 285   return 1;
 286 }
 287 
 288 static jint compare_samples(JNIEnv* env, jvmtiStackTrace* traces, int trace_count,
 289                             ExpectedContentFrame* expected_content, size_t size) {
 290   // We expect the code to record correctly the bci, retrieve the line
 291   // number, have the right method and the class name of the first frames.
 292   int i;
 293   for (i = 0; i < trace_count; i++) {
 294     jvmtiStackTrace *trace = traces + i;
 295     if (check_sample_content(env, trace, expected_content, size)) {
 296       // At least one frame matched what we were looking for.
 297       return 1;
 298     }
 299   }
 300 
 301   return 0;
 302 }
 303 
 304 static jint check_samples(JNIEnv* env, ExpectedContentFrame* expected,
 305                           size_t size,
 306                           jvmtiError (*const get_traces)(jvmtiEnv*, jvmtiStackTraces*)) {
 307   jvmtiStackTraces traces;
 308   jvmtiError error = get_traces(jvmti, &traces);
 309 
 310   if (error != JVMTI_ERROR_NONE) {
 311     return 0;
 312   }
 313 
 314   int result = compare_samples(env, traces.stack_traces, traces.trace_count,
 315                             expected, size);
 316   (*jvmti)->ReleaseTraces(jvmti, &traces);
 317   return result;
 318 }
 319 
 320 static jint frames_exist_live(JNIEnv* env, ExpectedContentFrame* expected,
 321                             size_t size) {
 322   return check_samples(env, expected, size, (*jvmti)->GetLiveTraces);
 323 }
 324 
 325 static jint frames_exist_recent(JNIEnv* env, ExpectedContentFrame* expected,
 326                               size_t size) {
 327   return check_samples(env, expected, size, (*jvmti)->GetGarbageTraces);
 328 }
 329 
 330 static jint frames_exist_frequent(JNIEnv* env, ExpectedContentFrame* expected,
 331                               size_t size) {
 332   return check_samples(env, expected, size, (*jvmti)->GetFrequentGarbageTraces);
 333 }
 334 
 335 // Static native API for various tests.
 336 static void fill_native_frames(JNIEnv* env, jobjectArray frames,
 337                                ExpectedContentFrame* native_frames, size_t size) {
 338   size_t i;
 339   for(i = 0; i < size; i++) {
 340     jobject obj = (*env)->GetObjectArrayElement(env, frames, i);
 341     jclass frame_class = (*env)->GetObjectClass(env, obj);
 342     jfieldID line_number_field_id = (*env)->GetFieldID(env, frame_class, "lineNumber", "I");
 343     int line_number = (*env)->GetIntField(env, obj, line_number_field_id);
 344 
 345     jfieldID string_id = (*env)->GetFieldID(env, frame_class, "method", "Ljava/lang/String;");
 346     jstring string_object = (jstring) (*env)->GetObjectField(env, obj, string_id);
 347     const char* method = (*env)->GetStringUTFChars(env, string_object, 0);
 348 
 349     string_id = (*env)->GetFieldID(env, frame_class, "fileName", "Ljava/lang/String;");
 350     string_object = (jstring) (*env)->GetObjectField(env, obj, string_id);
 351     const char* file_name = (*env)->GetStringUTFChars(env, string_object, 0);
 352 
 353     string_id = (*env)->GetFieldID(env, frame_class, "signature", "Ljava/lang/String;");
 354     string_object = (jstring) (*env)->GetObjectField(env, obj, string_id);
 355     const char* signature= (*env)->GetStringUTFChars(env, string_object, 0);
 356 
 357     native_frames[i].name = method;
 358     native_frames[i].file_name = file_name;
 359     native_frames[i].signature = signature;
 360     native_frames[i].line_number = line_number;
 361   }
 362 }
 363 
 364 static jint checkAnd(JNIEnv *env, jobjectArray frames, int live, int recent,
 365                      int frequent) {
 366   jobject loader = NULL;
 367 
 368   if (frames == NULL) {
 369     return 0;
 370   }
 371 
 372   // Start by transforming the frames into a C-friendly structure.
 373   jsize size = (*env)->GetArrayLength(env, frames);
 374   ExpectedContentFrame native_frames[size];
 375   fill_native_frames(env, frames, native_frames, size);
 376 
 377   if (jvmti == NULL) {
 378     throw_exc(env, "JVMTI client was not properly loaded!\n");
 379     return 0;
 380   }
 381 
 382   int result = 1;
 383 
 384   if (live) {
 385     result = frames_exist_live(env, native_frames, size);
 386   }
 387 
 388   if (recent) {
 389     result = result &&
 390         frames_exist_recent(env, native_frames, size);
 391   }
 392 
 393   if (frequent) {
 394     result = result &&
 395         frames_exist_frequent(env, native_frames, size);
 396   }
 397 
 398   return result;
 399 }
 400 
 401 static jint checkOr(JNIEnv *env, jobjectArray frames, int live, int recent,
 402                     int frequent) {
 403   jobject loader = NULL;
 404 
 405   if (frames == NULL) {
 406     return 0;
 407   }
 408 
 409   // Start by transforming the frames into a C-friendly structure.
 410   jsize size = (*env)->GetArrayLength(env, frames);
 411   ExpectedContentFrame native_frames[size];
 412   fill_native_frames(env, frames, native_frames, size);
 413 
 414   if (jvmti == NULL) {
 415     throw_exc(env, "JVMTI client was not properly loaded!\n");
 416     return 0;
 417   }
 418 
 419   int result = 0;
 420 
 421   if (live) {
 422     result = frames_exist_live(env, native_frames, size);
 423   }
 424 
 425   if (recent) {
 426     result = result ||
 427         frames_exist_recent(env, native_frames, size);
 428   }
 429 
 430   if (frequent) {
 431     result = result ||
 432         frames_exist_frequent(env, native_frames, size);
 433   }
 434 
 435   return result;
 436 }
 437 
 438 static jint checkAll(JNIEnv *env, jobjectArray frames) {
 439   return checkAnd(env, frames, 1, 1, 1);
 440 }
 441 
 442 static jint checkNone(JNIEnv *env, jobjectArray frames) {
 443   jobject loader = NULL;
 444 
 445   if (frames == NULL) {
 446     return 0;
 447   }
 448 
 449   // Start by transforming the frames into a C-friendly structure.
 450   jsize size = (*env)->GetArrayLength(env, frames);
 451   ExpectedContentFrame native_frames[size];
 452   fill_native_frames(env, frames, native_frames, size);
 453 
 454   if (jvmti == NULL) {
 455     throw_exc(env, "JVMTI client was not properly loaded!\n");
 456     return 0;
 457   }
 458 
 459   if ((!frames_exist_live(env, native_frames, size)) &&
 460       (!frames_exist_recent(env, native_frames, size)) &&
 461       (!frames_exist_frequent(env, native_frames, size))) {
 462     return 1;
 463   }
 464   return 0;
 465 }
 466 
 467 static void enable_sampling() {
 468   check_error((*jvmti)->StartHeapSampling(jvmti, 1<<19, MAX_TRACES),
 469               "Start Heap Sampling");
 470 }
 471 
 472 static void disable_sampling() {
 473   check_error((*jvmti)->StopHeapSampling(jvmti), "Stop Heap Sampling");
 474 }
 475 
 476 // HeapMonitorTest JNI.
 477 JNIEXPORT jint JNICALL
 478 Java_MyPackage_HeapMonitorTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 479   // We want the frames in each part.
 480   if (!checkAll(env, frames)) {
 481     return FAILED;
 482   }
 483   return PASSED;
 484 }
 485 
 486 JNIEXPORT void JNICALL
 487 Java_MyPackage_HeapMonitorTest_enableSampling(JNIEnv *env, jclass cls) {
 488   enable_sampling();
 489 }
 490 
 491 // HeapMonitorOnOffTest JNI.
 492 JNIEXPORT jint JNICALL
 493 Java_MyPackage_HeapMonitorOnOffTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 494   // We want the frames in each part.
 495   if (!checkAll(env, frames)) {
 496     return FAILED;
 497   }
 498   return PASSED;
 499 }
 500 
 501 JNIEXPORT jint JNICALL
 502 Java_MyPackage_HeapMonitorOnOffTest_checkWipeOut(JNIEnv *env, jclass cls, jobjectArray frames) {
 503   // We want the frames in none of the parts.
 504   if (!checkNone(env, frames)) {
 505     return FAILED;
 506   }
 507   return PASSED;
 508 }
 509 
 510 JNIEXPORT void JNICALL
 511 Java_MyPackage_HeapMonitorOnOffTest_enableSampling(JNIEnv *env, jclass cls) {
 512   enable_sampling();
 513 }
 514 
 515 JNIEXPORT void JNICALL
 516 Java_MyPackage_HeapMonitorOnOffTest_disableSampling(JNIEnv *env, jclass cls) {
 517   disable_sampling();
 518 }
 519 
 520 // HeapMonitorRecentTest JNI.
 521 JNIEXPORT jint JNICALL
 522 Java_MyPackage_HeapMonitorRecentTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 523   // We want the frames in each part.
 524   if (!checkAll(env, frames)) {
 525     return FAILED;
 526   }
 527   return PASSED;
 528 }
 529 
 530 JNIEXPORT jint JNICALL
 531 Java_MyPackage_HeapMonitorRecentTest_checkLiveOrRecentFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 532   if (checkOr(env, frames, 1, 1, 0)) {
 533     return FAILED;
 534   }
 535   return PASSED;
 536 }
 537 
 538 JNIEXPORT jint JNICALL
 539 Java_MyPackage_HeapMonitorRecentTest_checkLiveAndRecentFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 540   if (checkAnd(env, frames, 1, 1, 0)) {
 541     return FAILED;
 542   }
 543   return PASSED;
 544 }
 545 
 546 JNIEXPORT void JNICALL
 547 Java_MyPackage_HeapMonitorRecentTest_enableSampling(JNIEnv *env, jclass cls) {
 548   enable_sampling();
 549 }
 550 
 551 // HeapMonitorFrequentTest JNI.
 552 JNIEXPORT jint JNICALL
 553 Java_MyPackage_HeapMonitorFrequentTest_checkFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 554   // We want the frames in each part.
 555   if (!checkAll(env, frames)) {
 556     return FAILED;
 557   }
 558   return PASSED;
 559 }
 560 
 561 JNIEXPORT jint JNICALL
 562 Java_MyPackage_HeapMonitorFrequentTest_checkFrequentFrames(JNIEnv *env, jclass cls, jobjectArray frames) {
 563   if (checkAnd(env, frames, 0, 0, 1)) {
 564     return PASSED;
 565   }
 566   return FAILED;
 567 }
 568 
 569 JNIEXPORT void JNICALL
 570 Java_MyPackage_HeapMonitorFrequentTest_enableSampling(JNIEnv *env, jclass cls) {
 571   enable_sampling();
 572 }
 573 
 574 JNIEXPORT jboolean JNICALL
 575 Java_MyPackage_HeapMonitorNoCapabilityTest_allSamplingMethodsFail(JNIEnv *env, jclass cls) {
 576   jvmtiCapabilities caps;
 577   memset(&caps, 0, sizeof(caps));
 578   caps.can_sample_heap= 1;
 579   if (check_error((*jvmti)->RelinquishCapabilities(jvmti, &caps),
 580                   "Add capabilities\n")){
 581     return 0;
 582   }
 583 
 584   if (check_capability_error((*jvmti)->StartHeapSampling(jvmti, 1<<19,
 585                                                          MAX_TRACES),
 586                              "Start Heap Sampling")) {
 587     return 0;
 588   }
 589 
 590   if (check_capability_error((*jvmti)->StopHeapSampling(jvmti),
 591                              "Stop Heap Sampling")) {
 592     return 0;
 593   }
 594 
 595   if (check_capability_error((*jvmti)->ReleaseTraces(jvmti, NULL),
 596                              "Release Traces")) {
 597     return 0;
 598   }
 599 
 600   if (check_capability_error((*jvmti)->GetHeapSamplingStats(jvmti, NULL),
 601                              "Get Heap Sampling Stats")) {
 602     return 0;
 603   }
 604 
 605   if (check_capability_error((*jvmti)->GetGarbageTraces(jvmti, NULL),
 606                              "Get Garbage Traces")) {
 607     return 0;
 608   }
 609 
 610   if (check_capability_error((*jvmti)->GetFrequentGarbageTraces(jvmti, NULL),
 611                              "Get Frequent Garbage Traces")) {
 612     return 0;
 613   }
 614 
 615   if (check_capability_error((*jvmti)->GetLiveTraces(jvmti, NULL),
 616                              "Get Live Traces")) {
 617     return 0;
 618   }
 619 
 620   // Calling enable sampling should fail now.
 621   return 1;
 622 }
 623 
 624 JNIEXPORT void JNICALL
 625 Java_MyPackage_HeapMonitorStatTest_enableSampling(JNIEnv *env, jclass cls) {
 626   enable_sampling();
 627 }
 628 
 629 JNIEXPORT jint JNICALL
 630 Java_MyPackage_HeapMonitorStatTest_statsNull(JNIEnv *env, jclass cls) {
 631   jvmtiHeapSamplingStats stats;
 632   check_error((*jvmti)->GetHeapSamplingStats(jvmti, &stats),
 633               "Heap Sampling Statistics");
 634 
 635   jvmtiHeapSamplingStats zero;
 636   memset(&zero, 0, sizeof(zero));
 637   return memcmp(&stats, &zero, sizeof(zero)) == 0;
 638 }
 639 
 640 #ifdef __cplusplus
 641 }
 642 #endif