1 /* 2 * Input Visitor 3 * 4 * Copyright (C) 2012-2017 Red Hat, Inc. 5 * Copyright IBM, Corp. 2011 6 * 7 * Authors: 8 * Anthony Liguori <aliguori@us.ibm.com> 9 * 10 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. 11 * See the COPYING.LIB file in the top-level directory. 12 * 13 */ 14 15 #include "qemu/osdep.h" 16 #include <math.h> 17 #include "qapi/compat-policy.h" 18 #include "qapi/error.h" 19 #include "qapi/qobject-input-visitor.h" 20 #include "qapi/visitor-impl.h" 21 #include "qemu/queue.h" 22 #include "qapi/qmp/qjson.h" 23 #include "qapi/qmp/qbool.h" 24 #include "qapi/qmp/qdict.h" 25 #include "qapi/qmp/qerror.h" 26 #include "qapi/qmp/qlist.h" 27 #include "qapi/qmp/qnull.h" 28 #include "qapi/qmp/qnum.h" 29 #include "qapi/qmp/qstring.h" 30 #include "qemu/cutils.h" 31 #include "qemu/option.h" 32 33 typedef struct StackObject { 34 const char *name; /* Name of @obj in its parent, if any */ 35 QObject *obj; /* QDict or QList being visited */ 36 void *qapi; /* sanity check that caller uses same pointer */ 37 38 GHashTable *h; /* If @obj is QDict: unvisited keys */ 39 const QListEntry *entry; /* If @obj is QList: unvisited tail */ 40 unsigned index; /* If @obj is QList: list index of @entry */ 41 42 QSLIST_ENTRY(StackObject) node; /* parent */ 43 } StackObject; 44 45 struct QObjectInputVisitor { 46 Visitor visitor; 47 48 /* Root of visit at visitor creation. */ 49 QObject *root; 50 bool keyval; /* Assume @root made with keyval_parse() */ 51 52 /* Stack of objects being visited (all entries will be either 53 * QDict or QList). */ 54 QSLIST_HEAD(, StackObject) stack; 55 56 GString *errname; /* Accumulator for full_name() */ 57 }; 58 59 static QObjectInputVisitor *to_qiv(Visitor *v) 60 { 61 return container_of(v, QObjectInputVisitor, visitor); 62 } 63 64 /* 65 * Find the full name of something @qiv is currently visiting. 66 * @qiv is visiting something named @name in the stack of containers 67 * @qiv->stack. 68 * If @n is zero, return its full name. 69 * If @n is positive, return the full name of the @n-th container 70 * counting from the top. The stack of containers must have at least 71 * @n elements. 72 * The returned string is valid until the next full_name_nth(@v) or 73 * destruction of @v. 74 */ 75 static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name, 76 int n) 77 { 78 StackObject *so; 79 char buf[32]; 80 81 if (qiv->errname) { 82 g_string_truncate(qiv->errname, 0); 83 } else { 84 qiv->errname = g_string_new(""); 85 } 86 87 QSLIST_FOREACH(so , &qiv->stack, node) { 88 if (n) { 89 n--; 90 } else if (qobject_type(so->obj) == QTYPE_QDICT) { 91 g_string_prepend(qiv->errname, name ?: "<anonymous>"); 92 g_string_prepend_c(qiv->errname, '.'); 93 } else { 94 snprintf(buf, sizeof(buf), 95 qiv->keyval ? ".%u" : "[%u]", 96 so->index); 97 g_string_prepend(qiv->errname, buf); 98 } 99 name = so->name; 100 } 101 assert(!n); 102 103 if (name) { 104 g_string_prepend(qiv->errname, name); 105 } else if (qiv->errname->str[0] == '.') { 106 g_string_erase(qiv->errname, 0, 1); 107 } else if (!qiv->errname->str[0]) { 108 return "<anonymous>"; 109 } 110 111 return qiv->errname->str; 112 } 113 114 static const char *full_name(QObjectInputVisitor *qiv, const char *name) 115 { 116 return full_name_nth(qiv, name, 0); 117 } 118 119 static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, 120 const char *name, 121 bool consume) 122 { 123 StackObject *tos; 124 QObject *qobj; 125 QObject *ret; 126 127 if (QSLIST_EMPTY(&qiv->stack)) { 128 /* Starting at root, name is ignored. */ 129 assert(qiv->root); 130 return qiv->root; 131 } 132 133 /* We are in a container; find the next element. */ 134 tos = QSLIST_FIRST(&qiv->stack); 135 qobj = tos->obj; 136 assert(qobj); 137 138 if (qobject_type(qobj) == QTYPE_QDICT) { 139 assert(name); 140 ret = qdict_get(qobject_to(QDict, qobj), name); 141 if (tos->h && consume && ret) { 142 bool removed = g_hash_table_remove(tos->h, name); 143 assert(removed); 144 } 145 } else { 146 assert(qobject_type(qobj) == QTYPE_QLIST); 147 assert(!name); 148 if (tos->entry) { 149 ret = qlist_entry_obj(tos->entry); 150 if (consume) { 151 tos->entry = qlist_next(tos->entry); 152 } 153 } else { 154 ret = NULL; 155 } 156 if (consume) { 157 tos->index++; 158 } 159 } 160 161 return ret; 162 } 163 164 static QObject *qobject_input_get_object(QObjectInputVisitor *qiv, 165 const char *name, 166 bool consume, Error **errp) 167 { 168 QObject *obj = qobject_input_try_get_object(qiv, name, consume); 169 170 if (!obj) { 171 error_setg(errp, QERR_MISSING_PARAMETER, full_name(qiv, name)); 172 } 173 return obj; 174 } 175 176 static const char *qobject_input_get_keyval(QObjectInputVisitor *qiv, 177 const char *name, 178 Error **errp) 179 { 180 QObject *qobj; 181 QString *qstr; 182 183 qobj = qobject_input_get_object(qiv, name, true, errp); 184 if (!qobj) { 185 return NULL; 186 } 187 188 qstr = qobject_to(QString, qobj); 189 if (!qstr) { 190 switch (qobject_type(qobj)) { 191 case QTYPE_QDICT: 192 case QTYPE_QLIST: 193 error_setg(errp, "Parameters '%s.*' are unexpected", 194 full_name(qiv, name)); 195 return NULL; 196 default: 197 /* Non-string scalar (should this be an assertion?) */ 198 error_setg(errp, "Internal error: parameter %s invalid", 199 full_name(qiv, name)); 200 return NULL; 201 } 202 } 203 204 return qstring_get_str(qstr); 205 } 206 207 static const QListEntry *qobject_input_push(QObjectInputVisitor *qiv, 208 const char *name, 209 QObject *obj, void *qapi) 210 { 211 GHashTable *h; 212 StackObject *tos = g_new0(StackObject, 1); 213 QDict *qdict = qobject_to(QDict, obj); 214 QList *qlist = qobject_to(QList, obj); 215 const QDictEntry *entry; 216 217 assert(obj); 218 tos->name = name; 219 tos->obj = obj; 220 tos->qapi = qapi; 221 222 if (qdict) { 223 h = g_hash_table_new(g_str_hash, g_str_equal); 224 for (entry = qdict_first(qdict); 225 entry; 226 entry = qdict_next(qdict, entry)) { 227 g_hash_table_insert(h, (void *)qdict_entry_key(entry), NULL); 228 } 229 tos->h = h; 230 } else { 231 assert(qlist); 232 tos->entry = qlist_first(qlist); 233 tos->index = -1; 234 } 235 236 QSLIST_INSERT_HEAD(&qiv->stack, tos, node); 237 return tos->entry; 238 } 239 240 241 static bool qobject_input_check_struct(Visitor *v, Error **errp) 242 { 243 QObjectInputVisitor *qiv = to_qiv(v); 244 StackObject *tos = QSLIST_FIRST(&qiv->stack); 245 GHashTableIter iter; 246 const char *key; 247 248 assert(tos && !tos->entry); 249 250 g_hash_table_iter_init(&iter, tos->h); 251 if (g_hash_table_iter_next(&iter, (void **)&key, NULL)) { 252 error_setg(errp, "Parameter '%s' is unexpected", 253 full_name(qiv, key)); 254 return false; 255 } 256 return true; 257 } 258 259 static void qobject_input_stack_object_free(StackObject *tos) 260 { 261 if (tos->h) { 262 g_hash_table_unref(tos->h); 263 } 264 265 g_free(tos); 266 } 267 268 static void qobject_input_pop(Visitor *v, void **obj) 269 { 270 QObjectInputVisitor *qiv = to_qiv(v); 271 StackObject *tos = QSLIST_FIRST(&qiv->stack); 272 273 assert(tos && tos->qapi == obj); 274 QSLIST_REMOVE_HEAD(&qiv->stack, node); 275 qobject_input_stack_object_free(tos); 276 } 277 278 static bool qobject_input_start_struct(Visitor *v, const char *name, void **obj, 279 size_t size, Error **errp) 280 { 281 QObjectInputVisitor *qiv = to_qiv(v); 282 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 283 284 if (obj) { 285 *obj = NULL; 286 } 287 if (!qobj) { 288 return false; 289 } 290 if (qobject_type(qobj) != QTYPE_QDICT) { 291 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 292 full_name(qiv, name), "object"); 293 return false; 294 } 295 296 qobject_input_push(qiv, name, qobj, obj); 297 298 if (obj) { 299 *obj = g_malloc0(size); 300 } 301 return true; 302 } 303 304 static void qobject_input_end_struct(Visitor *v, void **obj) 305 { 306 QObjectInputVisitor *qiv = to_qiv(v); 307 StackObject *tos = QSLIST_FIRST(&qiv->stack); 308 309 assert(qobject_type(tos->obj) == QTYPE_QDICT && tos->h); 310 qobject_input_pop(v, obj); 311 } 312 313 314 static bool qobject_input_start_list(Visitor *v, const char *name, 315 GenericList **list, size_t size, 316 Error **errp) 317 { 318 QObjectInputVisitor *qiv = to_qiv(v); 319 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 320 const QListEntry *entry; 321 322 if (list) { 323 *list = NULL; 324 } 325 if (!qobj) { 326 return false; 327 } 328 if (qobject_type(qobj) != QTYPE_QLIST) { 329 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 330 full_name(qiv, name), "array"); 331 return false; 332 } 333 334 entry = qobject_input_push(qiv, name, qobj, list); 335 if (entry && list) { 336 *list = g_malloc0(size); 337 } 338 return true; 339 } 340 341 static GenericList *qobject_input_next_list(Visitor *v, GenericList *tail, 342 size_t size) 343 { 344 QObjectInputVisitor *qiv = to_qiv(v); 345 StackObject *tos = QSLIST_FIRST(&qiv->stack); 346 347 assert(tos && qobject_to(QList, tos->obj)); 348 349 if (!tos->entry) { 350 return NULL; 351 } 352 tail->next = g_malloc0(size); 353 return tail->next; 354 } 355 356 static bool qobject_input_check_list(Visitor *v, Error **errp) 357 { 358 QObjectInputVisitor *qiv = to_qiv(v); 359 StackObject *tos = QSLIST_FIRST(&qiv->stack); 360 361 assert(tos && qobject_to(QList, tos->obj)); 362 363 if (tos->entry) { 364 error_setg(errp, "Only %u list elements expected in %s", 365 tos->index + 1, full_name_nth(qiv, NULL, 1)); 366 return false; 367 } 368 return true; 369 } 370 371 static void qobject_input_end_list(Visitor *v, void **obj) 372 { 373 QObjectInputVisitor *qiv = to_qiv(v); 374 StackObject *tos = QSLIST_FIRST(&qiv->stack); 375 376 assert(qobject_type(tos->obj) == QTYPE_QLIST && !tos->h); 377 qobject_input_pop(v, obj); 378 } 379 380 static bool qobject_input_start_alternate(Visitor *v, const char *name, 381 GenericAlternate **obj, size_t size, 382 Error **errp) 383 { 384 QObjectInputVisitor *qiv = to_qiv(v); 385 QObject *qobj = qobject_input_get_object(qiv, name, false, errp); 386 387 if (!qobj) { 388 *obj = NULL; 389 return false; 390 } 391 *obj = g_malloc0(size); 392 (*obj)->type = qobject_type(qobj); 393 return true; 394 } 395 396 static bool qobject_input_type_int64(Visitor *v, const char *name, int64_t *obj, 397 Error **errp) 398 { 399 QObjectInputVisitor *qiv = to_qiv(v); 400 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 401 QNum *qnum; 402 403 if (!qobj) { 404 return false; 405 } 406 qnum = qobject_to(QNum, qobj); 407 if (!qnum || !qnum_get_try_int(qnum, obj)) { 408 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 409 full_name(qiv, name), "integer"); 410 return false; 411 } 412 return true; 413 } 414 415 static bool qobject_input_type_int64_keyval(Visitor *v, const char *name, 416 int64_t *obj, Error **errp) 417 { 418 QObjectInputVisitor *qiv = to_qiv(v); 419 const char *str = qobject_input_get_keyval(qiv, name, errp); 420 421 if (!str) { 422 return false; 423 } 424 425 if (qemu_strtoi64(str, NULL, 0, obj) < 0) { 426 /* TODO report -ERANGE more nicely */ 427 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, 428 full_name(qiv, name), "integer"); 429 return false; 430 } 431 return true; 432 } 433 434 static bool qobject_input_type_uint64(Visitor *v, const char *name, 435 uint64_t *obj, Error **errp) 436 { 437 QObjectInputVisitor *qiv = to_qiv(v); 438 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 439 QNum *qnum; 440 int64_t val; 441 442 if (!qobj) { 443 return false; 444 } 445 qnum = qobject_to(QNum, qobj); 446 if (!qnum) { 447 goto err; 448 } 449 450 if (qnum_get_try_uint(qnum, obj)) { 451 return true; 452 } 453 454 /* Need to accept negative values for backward compatibility */ 455 if (qnum_get_try_int(qnum, &val)) { 456 *obj = val; 457 return true; 458 } 459 460 err: 461 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, 462 full_name(qiv, name), "uint64"); 463 return false; 464 } 465 466 static bool qobject_input_type_uint64_keyval(Visitor *v, const char *name, 467 uint64_t *obj, Error **errp) 468 { 469 QObjectInputVisitor *qiv = to_qiv(v); 470 const char *str = qobject_input_get_keyval(qiv, name, errp); 471 472 if (!str) { 473 return false; 474 } 475 476 if (qemu_strtou64(str, NULL, 0, obj) < 0) { 477 /* TODO report -ERANGE more nicely */ 478 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, 479 full_name(qiv, name), "integer"); 480 return false; 481 } 482 return true; 483 } 484 485 static bool qobject_input_type_bool(Visitor *v, const char *name, bool *obj, 486 Error **errp) 487 { 488 QObjectInputVisitor *qiv = to_qiv(v); 489 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 490 QBool *qbool; 491 492 if (!qobj) { 493 return false; 494 } 495 qbool = qobject_to(QBool, qobj); 496 if (!qbool) { 497 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 498 full_name(qiv, name), "boolean"); 499 return false; 500 } 501 502 *obj = qbool_get_bool(qbool); 503 return true; 504 } 505 506 static bool qobject_input_type_bool_keyval(Visitor *v, const char *name, 507 bool *obj, Error **errp) 508 { 509 QObjectInputVisitor *qiv = to_qiv(v); 510 const char *str = qobject_input_get_keyval(qiv, name, errp); 511 512 if (!str) { 513 return false; 514 } 515 516 if (!qapi_bool_parse(name, str, obj, NULL)) { 517 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, 518 full_name(qiv, name), "'on' or 'off'"); 519 return false; 520 } 521 return true; 522 } 523 524 static bool qobject_input_type_str(Visitor *v, const char *name, char **obj, 525 Error **errp) 526 { 527 QObjectInputVisitor *qiv = to_qiv(v); 528 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 529 QString *qstr; 530 531 *obj = NULL; 532 if (!qobj) { 533 return false; 534 } 535 qstr = qobject_to(QString, qobj); 536 if (!qstr) { 537 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 538 full_name(qiv, name), "string"); 539 return false; 540 } 541 542 *obj = g_strdup(qstring_get_str(qstr)); 543 return true; 544 } 545 546 static bool qobject_input_type_str_keyval(Visitor *v, const char *name, 547 char **obj, Error **errp) 548 { 549 QObjectInputVisitor *qiv = to_qiv(v); 550 const char *str = qobject_input_get_keyval(qiv, name, errp); 551 552 *obj = g_strdup(str); 553 return !!str; 554 } 555 556 static bool qobject_input_type_number(Visitor *v, const char *name, double *obj, 557 Error **errp) 558 { 559 QObjectInputVisitor *qiv = to_qiv(v); 560 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 561 QNum *qnum; 562 563 if (!qobj) { 564 return false; 565 } 566 qnum = qobject_to(QNum, qobj); 567 if (!qnum) { 568 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 569 full_name(qiv, name), "number"); 570 return false; 571 } 572 573 *obj = qnum_get_double(qnum); 574 return true; 575 } 576 577 static bool qobject_input_type_number_keyval(Visitor *v, const char *name, 578 double *obj, Error **errp) 579 { 580 QObjectInputVisitor *qiv = to_qiv(v); 581 const char *str = qobject_input_get_keyval(qiv, name, errp); 582 double val; 583 584 if (!str) { 585 return false; 586 } 587 588 if (qemu_strtod_finite(str, NULL, &val)) { 589 /* TODO report -ERANGE more nicely */ 590 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 591 full_name(qiv, name), "number"); 592 return false; 593 } 594 595 *obj = val; 596 return true; 597 } 598 599 static bool qobject_input_type_any(Visitor *v, const char *name, QObject **obj, 600 Error **errp) 601 { 602 QObjectInputVisitor *qiv = to_qiv(v); 603 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 604 605 *obj = NULL; 606 if (!qobj) { 607 return false; 608 } 609 610 *obj = qobject_ref(qobj); 611 return true; 612 } 613 614 static bool qobject_input_type_null(Visitor *v, const char *name, 615 QNull **obj, Error **errp) 616 { 617 QObjectInputVisitor *qiv = to_qiv(v); 618 QObject *qobj = qobject_input_get_object(qiv, name, true, errp); 619 620 *obj = NULL; 621 if (!qobj) { 622 return false; 623 } 624 625 if (qobject_type(qobj) != QTYPE_QNULL) { 626 error_setg(errp, QERR_INVALID_PARAMETER_TYPE, 627 full_name(qiv, name), "null"); 628 return false; 629 } 630 *obj = qnull(); 631 return true; 632 } 633 634 static bool qobject_input_type_size_keyval(Visitor *v, const char *name, 635 uint64_t *obj, Error **errp) 636 { 637 QObjectInputVisitor *qiv = to_qiv(v); 638 const char *str = qobject_input_get_keyval(qiv, name, errp); 639 640 if (!str) { 641 return false; 642 } 643 644 if (qemu_strtosz(str, NULL, obj) < 0) { 645 /* TODO report -ERANGE more nicely */ 646 error_setg(errp, QERR_INVALID_PARAMETER_VALUE, 647 full_name(qiv, name), "size"); 648 return false; 649 } 650 return true; 651 } 652 653 static void qobject_input_optional(Visitor *v, const char *name, bool *present) 654 { 655 QObjectInputVisitor *qiv = to_qiv(v); 656 QObject *qobj = qobject_input_try_get_object(qiv, name, false); 657 658 if (!qobj) { 659 *present = false; 660 return; 661 } 662 663 *present = true; 664 } 665 666 static bool qobject_input_policy_reject(Visitor *v, const char *name, 667 unsigned special_features, 668 Error **errp) 669 { 670 return !compat_policy_input_ok(special_features, &v->compat_policy, 671 ERROR_CLASS_GENERIC_ERROR, 672 "parameter", name, errp); 673 } 674 675 static void qobject_input_free(Visitor *v) 676 { 677 QObjectInputVisitor *qiv = to_qiv(v); 678 679 while (!QSLIST_EMPTY(&qiv->stack)) { 680 StackObject *tos = QSLIST_FIRST(&qiv->stack); 681 682 QSLIST_REMOVE_HEAD(&qiv->stack, node); 683 qobject_input_stack_object_free(tos); 684 } 685 686 qobject_unref(qiv->root); 687 if (qiv->errname) { 688 g_string_free(qiv->errname, TRUE); 689 } 690 g_free(qiv); 691 } 692 693 static QObjectInputVisitor *qobject_input_visitor_base_new(QObject *obj) 694 { 695 QObjectInputVisitor *v = g_malloc0(sizeof(*v)); 696 697 assert(obj); 698 699 v->visitor.type = VISITOR_INPUT; 700 v->visitor.start_struct = qobject_input_start_struct; 701 v->visitor.check_struct = qobject_input_check_struct; 702 v->visitor.end_struct = qobject_input_end_struct; 703 v->visitor.start_list = qobject_input_start_list; 704 v->visitor.next_list = qobject_input_next_list; 705 v->visitor.check_list = qobject_input_check_list; 706 v->visitor.end_list = qobject_input_end_list; 707 v->visitor.start_alternate = qobject_input_start_alternate; 708 v->visitor.optional = qobject_input_optional; 709 v->visitor.policy_reject = qobject_input_policy_reject; 710 v->visitor.free = qobject_input_free; 711 712 v->root = qobject_ref(obj); 713 714 return v; 715 } 716 717 Visitor *qobject_input_visitor_new(QObject *obj) 718 { 719 QObjectInputVisitor *v = qobject_input_visitor_base_new(obj); 720 721 v->visitor.type_int64 = qobject_input_type_int64; 722 v->visitor.type_uint64 = qobject_input_type_uint64; 723 v->visitor.type_bool = qobject_input_type_bool; 724 v->visitor.type_str = qobject_input_type_str; 725 v->visitor.type_number = qobject_input_type_number; 726 v->visitor.type_any = qobject_input_type_any; 727 v->visitor.type_null = qobject_input_type_null; 728 729 return &v->visitor; 730 } 731 732 Visitor *qobject_input_visitor_new_keyval(QObject *obj) 733 { 734 QObjectInputVisitor *v = qobject_input_visitor_base_new(obj); 735 736 v->visitor.type_int64 = qobject_input_type_int64_keyval; 737 v->visitor.type_uint64 = qobject_input_type_uint64_keyval; 738 v->visitor.type_bool = qobject_input_type_bool_keyval; 739 v->visitor.type_str = qobject_input_type_str_keyval; 740 v->visitor.type_number = qobject_input_type_number_keyval; 741 v->visitor.type_any = qobject_input_type_any; 742 v->visitor.type_null = qobject_input_type_null; 743 v->visitor.type_size = qobject_input_type_size_keyval; 744 v->keyval = true; 745 746 return &v->visitor; 747 } 748 749 Visitor *qobject_input_visitor_new_str(const char *str, 750 const char *implied_key, 751 Error **errp) 752 { 753 bool is_json = str[0] == '{'; 754 QObject *obj; 755 QDict *args; 756 Visitor *v; 757 758 if (is_json) { 759 obj = qobject_from_json(str, errp); 760 if (!obj) { 761 return NULL; 762 } 763 args = qobject_to(QDict, obj); 764 assert(args); 765 v = qobject_input_visitor_new(QOBJECT(args)); 766 } else { 767 args = keyval_parse(str, implied_key, NULL, errp); 768 if (!args) { 769 return NULL; 770 } 771 v = qobject_input_visitor_new_keyval(QOBJECT(args)); 772 } 773 qobject_unref(args); 774 775 return v; 776 } 777