Commit 72c10d8617c799432e28dabf1679b1a6f5245c02

Authored by Jay Berkenbilt
1 parent 3340dbe9

C API: overhaul error handling

* Handle error conditions that occur when using the object handle
  interfaces. In the past, some exceptions were not correctly
  converted to errors or warnings.
* Add more detailed information to qpdf-c.h
* Make it possible to work more explicitly with uninitialized objects
ChangeLog
  1 +2021-12-10 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * C API: Overhaul how errors are handle the C API's object handle
  4 + interfaces. Clarify documentation regarding object accessors and
  5 + how type errors and warnings are handled. Many cases that used to
  6 + crash code that used the C API can now be trapped and will be
  7 + written stderr if not trapped. The new method
  8 + qpdf_register_oh_error_handler can be used to specifically handle
  9 + errors that occur when accessing object handles. See qpdf-c.h for
  10 + details.
  11 +
  12 + * C API: Add qpdf_oh_new_uninitialized to explicitly create
  13 + uninitialized object handles.
  14 +
  15 + * Add new error code qpdf_e_object that is used for exceptions
  16 + (including warnings) that are caused by using QPDFObjectHandle
  17 + methods on object handles of the wrong type.
  18 +
1 19 2021-12-02 Jay Berkenbilt <ejb@ql.org>
2 20  
3 21 * C API: Add qpdf_oh_is_initialized.
... ...
include/qpdf/qpdf-c.h
... ... @@ -62,22 +62,27 @@
62 62 * string was just returned.
63 63 *
64 64 * Many functions defined here merely set parameters and therefore
65   - * never return error conditions. Functions that may cause PDF
66   - * files to be read or written may return error conditions. Such
67   - * functions return an error code. If there were no errors or
68   - * warnings, they return QPDF_SUCCESS. If there were warnings,
69   - * the return value has the QPDF_WARNINGS bit set. If there
70   - * errors, the QPDF_ERRORS bit is set. In other words, if there
71   - * are both warnings and errors, then the return status will be
72   - * QPDF_WARNINGS | QPDF_ERRORS. You may also call the
  65 + * never return error conditions. Functions that access or return
  66 + * qpdf_oh object handles may generate warnings but have no way to
  67 + * return errors, but the errors may be checked afterwards or
  68 + * handled using a registered handler. This is discussed in more
  69 + * detail in the section on object handling. Functions that may
  70 + * cause PDF files to be read or written may return error
  71 + * conditions. Such functions return an error code. If there were
  72 + * no errors or warnings, they return QPDF_SUCCESS. If there were
  73 + * warnings, the return value has the QPDF_WARNINGS bit set. If
  74 + * there were errors, the QPDF_ERRORS bit is set. In other words,
  75 + * if there are both warnings and errors, then the return status
  76 + * will be QPDF_WARNINGS | QPDF_ERRORS. You may also call the
73 77 * qpdf_more_warnings and qpdf_more_errors functions to test
74   - * whether there are unseen warning or error conditions. By
  78 + * whether there are unseen warning or error conditions. By
75 79 * default, warnings are written to stderr when detected, but this
76   - * behavior can be suppressed. In all cases, errors and warnings
  80 + * behavior can be suppressed. In all cases, errors and warnings
77 81 * may be retrieved by calling qpdf_next_warning and
78   - * qpdf_next_error. All exceptions thrown by the C++ interface
79   - * are caught and converted into error messages by the C
80   - * interface.
  82 + * qpdf_get_error. All exceptions thrown by the C++ interface are
  83 + * caught and converted into error messages by the C interface.
  84 + * Any exceptions to this are qpdf bugs and should be reported at
  85 + * https://github.com/qpdf/qpdf/issues/new.
81 86 *
82 87 * Most functions defined here have obvious counterparts that are
83 88 * methods to either QPDF or QPDFWriter. Please see comments in
... ... @@ -550,13 +555,51 @@ extern &quot;C&quot; {
550 555 * handle, the object is safely part of the dictionary or array.
551 556 * Similarly, any other object handle refering to the object remains
552 557 * valid. Explicitly releasing an object handle is essentially the
553   - * same as letting a QPDFObjectHandle go out of scope in the C++ API.
  558 + * same as letting a QPDFObjectHandle go out of scope in the C++
  559 + * API.
  560 + *
  561 + * Important note about error handling:
  562 + *
  563 + * While many of the functions that operate on the QPDF object
  564 + * return error codes, the qpdf_oh functions return values such as
  565 + * object handles or data. They have no way to return error codes.
  566 + * If they generate warnings, the warnings are handled using the
  567 + * error/warning handling functions described above. If the
  568 + * underlying C++ call throws an exception, the error handler
  569 + * registered with qpdf_register_oh_error_handler() will be
  570 + * called. If no handler is registered, the exception is written
  571 + * to STDERR. In either case, a sensible fallback value is
  572 + * returned (0 for numbers, QPDF_FALSE for booleans, "" for
  573 + * strings, or a null object). It is sensible for a C program to
  574 + * use setjmp and longjmp with this error handler since the C++
  575 + * code has raised an exception, but you can also just set a flag
  576 + * and check it after each call.
  577 + *
  578 + * All conditions under which exceptions would be thrown by object
  579 + * accessors are caused by programmer error or major problems such
  580 + * as running out of memory or not being able to read the input
  581 + * file. If they are ever caused by invalid data in the PDF file,
  582 + * it is a bug in qpdf, which should be reported at
  583 + * https://github.com/qpdf/qpdf/issues/new.
554 584 */
555 585  
556 586 /* For examples of using this API, see examples/pdf-c-objects.c */
557 587  
558 588 typedef unsigned int qpdf_oh;
559 589  
  590 + /* If an exception is thrown by the C++ code when any of the
  591 + * qpdf_oh functions are called, the registered handle_error
  592 + * function will be called. The value passed to data will be
  593 + * passed along to the error handler function. If any errors occur
  594 + * and no error handler is accessed, a single warning will be
  595 + * issued, and the error will be written to stderr.
  596 + */
  597 + QPDF_DLL
  598 + void qpdf_register_oh_error_handler(
  599 + qpdf_data qpdf,
  600 + void (*handle_error)(qpdf_data qpdf, qpdf_error error, void* data),
  601 + void* data);
  602 +
560 603 /* Releasing objects -- see comments above. These functions have no
561 604 * equivalent in the C++ API.
562 605 */
... ... @@ -659,7 +702,7 @@ extern &quot;C&quot; {
659 702 /* The memory returned by qpdf_oh_dict_next_key is owned by
660 703 * qpdf_data. It is good until the next call to
661 704 * qpdf_oh_dict_next_key with the same qpdf_data object. Calling
662   - * the method again, even with a different dict, invalidates
  705 + * the function again, even with a different dict, invalidates
663 706 * previous return values.
664 707 */
665 708 QPDF_DLL
... ... @@ -677,6 +720,8 @@ extern &quot;C&quot; {
677 720 qpdf_data data, qpdf_oh oh, char const* key);
678 721  
679 722 QPDF_DLL
  723 + qpdf_oh qpdf_oh_new_uninitialized(qpdf_data qpdf);
  724 + QPDF_DLL
680 725 qpdf_oh qpdf_oh_new_null(qpdf_data data);
681 726 QPDF_DLL
682 727 qpdf_oh qpdf_oh_new_bool(qpdf_data data, QPDF_BOOL value);
... ...
libqpdf/qpdf-c.cc
... ... @@ -41,6 +41,9 @@ struct _qpdf_data
41 41 PointerHolder<Buffer> output_buffer;
42 42  
43 43 // QPDFObjectHandle support
  44 + void (*oh_error_handler)(qpdf_data, qpdf_error, void*);
  45 + void* oh_error_handler_data;
  46 + bool default_oh_error_handler_called;
44 47 std::map<qpdf_oh, PointerHolder<QPDFObjectHandle>> oh_cache;
45 48 qpdf_oh next_oh;
46 49 std::set<std::string> cur_iter_dict_keys;
... ... @@ -48,8 +51,32 @@ struct _qpdf_data
48 51 std::string cur_dict_key;
49 52 };
50 53  
  54 +static void default_oh_error_handler(qpdf_data qpdf, qpdf_error e, void* data)
  55 +{
  56 + bool* called = reinterpret_cast<bool*>(data);
  57 + if (called != nullptr)
  58 + {
  59 + QTC::TC("qpdf", "qpdf-c warn about oh error", *called ? 0 : 1);
  60 + if (! *called)
  61 + {
  62 + qpdf->warnings.push_back(
  63 + QPDFExc(
  64 + qpdf_e_internal,
  65 + qpdf->qpdf->getFilename(),
  66 + "", 0,
  67 + "C API object handle accessor errors occurred,"
  68 + " and the application did not define an error handler"));
  69 + *called = true;
  70 + }
  71 + }
  72 + std::cerr << e->exc->what() << std::endl;
  73 +}
  74 +
51 75 _qpdf_data::_qpdf_data() :
52 76 write_memory(false),
  77 + oh_error_handler(default_oh_error_handler),
  78 + oh_error_handler_data(&this->default_oh_error_handler_called),
  79 + default_oh_error_handler_called(false),
53 80 next_oh(0)
54 81 {
55 82 }
... ... @@ -170,6 +197,13 @@ void qpdf_cleanup(qpdf_data* qpdf)
170 197 {
171 198 QTC::TC("qpdf", "qpdf-c called qpdf_cleanup");
172 199 qpdf_oh_release_all(*qpdf);
  200 + if ((*qpdf)->error.getPointer())
  201 + {
  202 + QTC::TC("qpdf", "qpdf-c cleanup warned about unhandled error");
  203 + std::cerr << "WARNING: application did not handle error: "
  204 + << (*qpdf)->error->what() << std::endl;
  205 +
  206 + }
173 207 delete *qpdf;
174 208 *qpdf = 0;
175 209 }
... ... @@ -841,6 +875,38 @@ QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf)
841 875 return status;
842 876 }
843 877  
  878 +void qpdf_register_oh_error_handler(
  879 + qpdf_data qpdf,
  880 + void (*handle_error)(qpdf_data qpdf, qpdf_error error, void* data),
  881 + void* data)
  882 +{
  883 + QTC::TC("qpdf", "qpdf-c registered oh error handler");
  884 + qpdf->oh_error_handler = handle_error;
  885 + qpdf->oh_error_handler_data = data;
  886 +}
  887 +
  888 +template<class RET>
  889 +static RET trap_oh_errors(
  890 + qpdf_data qpdf,
  891 + std::function<RET()> fallback,
  892 + std::function<RET(qpdf_data)> fn)
  893 +{
  894 + // Note: fallback is a function so we don't have to evaluate it
  895 + // unless needed. This is important because sometimes the fallback
  896 + // creates an object.
  897 + RET ret;
  898 + QPDF_ERROR_CODE status = trap_errors(qpdf, [&ret, &fn] (qpdf_data q) {
  899 + ret = fn(q);
  900 + });
  901 + if (status & QPDF_ERRORS)
  902 + {
  903 + (*qpdf->oh_error_handler)(
  904 + qpdf, qpdf_get_error(qpdf), qpdf->oh_error_handler_data);
  905 + return fallback();
  906 + }
  907 + return ret;
  908 +}
  909 +
844 910 static qpdf_oh
845 911 new_object(qpdf_data qpdf, QPDFObjectHandle const& qoh)
846 912 {
... ... @@ -867,310 +933,367 @@ void qpdf_oh_release_all(qpdf_data qpdf)
867 933 qpdf->oh_cache.clear();
868 934 }
869 935  
  936 +template <class T>
  937 +static std::function<T()> return_T(T const& r)
  938 +{
  939 + return [&r]() { return r; };
  940 +}
  941 +
  942 +static QPDF_BOOL return_false()
  943 +{
  944 + return QPDF_FALSE;
  945 +}
  946 +
  947 +static std::function<qpdf_oh()> return_uninitialized(qpdf_data qpdf)
  948 +{
  949 + return [qpdf]() { return qpdf_oh_new_uninitialized(qpdf); };
  950 +}
  951 +
  952 +static std::function<qpdf_oh()> return_null(qpdf_data qpdf)
  953 +{
  954 + return [qpdf]() { return qpdf_oh_new_null(qpdf); };
  955 +}
  956 +
870 957 qpdf_oh qpdf_get_trailer(qpdf_data qpdf)
871 958 {
872 959 QTC::TC("qpdf", "qpdf-c called qpdf_get_trailer");
873   - return new_object(qpdf, qpdf->qpdf->getTrailer());
  960 + return trap_oh_errors<qpdf_oh>(
  961 + qpdf, return_uninitialized(qpdf), [] (qpdf_data q) {
  962 + return new_object(q, q->qpdf->getTrailer());
  963 + });
874 964 }
875 965  
876 966 qpdf_oh qpdf_get_root(qpdf_data qpdf)
877 967 {
878 968 QTC::TC("qpdf", "qpdf-c called qpdf_get_root");
879   - return new_object(qpdf, qpdf->qpdf->getRoot());
880   -}
881   -
882   -static bool
883   -qpdf_oh_valid_internal(qpdf_data qpdf, qpdf_oh oh)
884   -{
885   - auto i = qpdf->oh_cache.find(oh);
886   - bool result = ((i != qpdf->oh_cache.end()) &&
887   - (i->second).getPointer());
888   - if (! result)
889   - {
890   - QTC::TC("qpdf", "qpdf-c invalid object handle");
891   - qpdf->warnings.push_back(
892   - QPDFExc(
893   - qpdf_e_damaged_pdf,
894   - qpdf->qpdf->getFilename(),
895   - std::string("C API object handle ") +
896   - QUtil::uint_to_string(oh),
897   - 0, "attempted access to unknown object handle"));
898   - }
899   - return result;
  969 + return trap_oh_errors<qpdf_oh>(
  970 + qpdf, return_uninitialized(qpdf), [] (qpdf_data q) {
  971 + return new_object(q, q->qpdf->getRoot());
  972 + });
  973 +}
  974 +
  975 +template<class RET>
  976 +static RET do_with_oh(
  977 + qpdf_data qpdf, qpdf_oh oh,
  978 + std::function<RET()> fallback,
  979 + std::function<RET(QPDFObjectHandle&)> fn)
  980 +{
  981 + return trap_oh_errors<RET>(
  982 + qpdf, fallback, [&fn, &oh](qpdf_data q) {
  983 + auto i = q->oh_cache.find(oh);
  984 + bool result = ((i != q->oh_cache.end()) &&
  985 + (i->second).getPointer());
  986 + if (! result)
  987 + {
  988 + QTC::TC("qpdf", "qpdf-c invalid object handle");
  989 + throw QPDFExc(
  990 + qpdf_e_internal,
  991 + q->qpdf->getFilename(),
  992 + std::string("C API object handle ") +
  993 + QUtil::uint_to_string(oh),
  994 + 0, "attempted access to unknown object handle");
  995 + }
  996 + return fn(*(q->oh_cache[oh]));
  997 + });
  998 +}
  999 +
  1000 +static void do_with_oh_void(
  1001 + qpdf_data qpdf, qpdf_oh oh,
  1002 + std::function<void(QPDFObjectHandle&)> fn)
  1003 +{
  1004 + do_with_oh<bool>(
  1005 + qpdf, oh, return_T<bool>(false), [&fn](QPDFObjectHandle& o) {
  1006 + fn(o);
  1007 + return true; // unused
  1008 + });
900 1009 }
901 1010  
902 1011 QPDF_BOOL qpdf_oh_is_initialized(qpdf_data qpdf, qpdf_oh oh)
903 1012 {
904 1013 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_initialized");
905   - return (qpdf_oh_valid_internal(qpdf, oh) &&
906   - qpdf->oh_cache[oh]->isInitialized());
  1014 + return do_with_oh<QPDF_BOOL>(
  1015 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1016 + return o.isInitialized();
  1017 + });
907 1018 }
908 1019  
909 1020 QPDF_BOOL qpdf_oh_is_bool(qpdf_data qpdf, qpdf_oh oh)
910 1021 {
911 1022 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_bool");
912   - return (qpdf_oh_valid_internal(qpdf, oh) &&
913   - qpdf->oh_cache[oh]->isBool());
  1023 + return do_with_oh<QPDF_BOOL>(
  1024 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1025 + return o.isBool();
  1026 + });
914 1027 }
915 1028  
916 1029 QPDF_BOOL qpdf_oh_is_null(qpdf_data qpdf, qpdf_oh oh)
917 1030 {
918 1031 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_null");
919   - return (qpdf_oh_valid_internal(qpdf, oh) &&
920   - qpdf->oh_cache[oh]->isNull());
  1032 + return do_with_oh<QPDF_BOOL>(
  1033 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1034 + return o.isNull();
  1035 + });
921 1036 }
922 1037  
923 1038 QPDF_BOOL qpdf_oh_is_integer(qpdf_data qpdf, qpdf_oh oh)
924 1039 {
925 1040 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_integer");
926   - return (qpdf_oh_valid_internal(qpdf, oh) &&
927   - qpdf->oh_cache[oh]->isInteger());
  1041 + return do_with_oh<QPDF_BOOL>(
  1042 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1043 + return o.isInteger();
  1044 + });
928 1045 }
929 1046  
930 1047 QPDF_BOOL qpdf_oh_is_real(qpdf_data qpdf, qpdf_oh oh)
931 1048 {
932 1049 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_real");
933   - return (qpdf_oh_valid_internal(qpdf, oh) &&
934   - qpdf->oh_cache[oh]->isReal());
  1050 + return do_with_oh<QPDF_BOOL>(
  1051 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1052 + return o.isReal();
  1053 + });
935 1054 }
936 1055  
937 1056 QPDF_BOOL qpdf_oh_is_name(qpdf_data qpdf, qpdf_oh oh)
938 1057 {
939 1058 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_name");
940   - return (qpdf_oh_valid_internal(qpdf, oh) &&
941   - qpdf->oh_cache[oh]->isName());
  1059 + return do_with_oh<QPDF_BOOL>(
  1060 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1061 + return o.isName();
  1062 + });
942 1063 }
943 1064  
944 1065 QPDF_BOOL qpdf_oh_is_string(qpdf_data qpdf, qpdf_oh oh)
945 1066 {
946 1067 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_string");
947   - return (qpdf_oh_valid_internal(qpdf, oh) &&
948   - qpdf->oh_cache[oh]->isString());
  1068 + return do_with_oh<QPDF_BOOL>(
  1069 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1070 + return o.isString();
  1071 + });
949 1072 }
950 1073  
951 1074 QPDF_BOOL qpdf_oh_is_operator(qpdf_data qpdf, qpdf_oh oh)
952 1075 {
953 1076 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_operator");
954   - return (qpdf_oh_valid_internal(qpdf, oh) &&
955   - qpdf->oh_cache[oh]->isOperator());
  1077 + return do_with_oh<QPDF_BOOL>(
  1078 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1079 + return o.isOperator();
  1080 + });
956 1081 }
957 1082  
958 1083 QPDF_BOOL qpdf_oh_is_inline_image(qpdf_data qpdf, qpdf_oh oh)
959 1084 {
960 1085 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_inline_image");
961   - return (qpdf_oh_valid_internal(qpdf, oh) &&
962   - qpdf->oh_cache[oh]->isInlineImage());
  1086 + return do_with_oh<QPDF_BOOL>(
  1087 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1088 + return o.isInlineImage();
  1089 + });
963 1090 }
964 1091  
965 1092 QPDF_BOOL qpdf_oh_is_array(qpdf_data qpdf, qpdf_oh oh)
966 1093 {
967 1094 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_array");
968   - return (qpdf_oh_valid_internal(qpdf, oh) &&
969   - qpdf->oh_cache[oh]->isArray());
  1095 + return do_with_oh<QPDF_BOOL>(
  1096 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1097 + return o.isArray();
  1098 + });
970 1099 }
971 1100  
972 1101 QPDF_BOOL qpdf_oh_is_dictionary(qpdf_data qpdf, qpdf_oh oh)
973 1102 {
974 1103 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_dictionary");
975   - return (qpdf_oh_valid_internal(qpdf, oh) &&
976   - qpdf->oh_cache[oh]->isDictionary());
  1104 + return do_with_oh<QPDF_BOOL>(
  1105 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1106 + return o.isDictionary();
  1107 + });
977 1108 }
978 1109  
979 1110 QPDF_BOOL qpdf_oh_is_stream(qpdf_data qpdf, qpdf_oh oh)
980 1111 {
981 1112 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_stream");
982   - return (qpdf_oh_valid_internal(qpdf, oh) &&
983   - qpdf->oh_cache[oh]->isStream());
  1113 + return do_with_oh<QPDF_BOOL>(
  1114 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1115 + return o.isStream();
  1116 + });
984 1117 }
985 1118  
986 1119 QPDF_BOOL qpdf_oh_is_indirect(qpdf_data qpdf, qpdf_oh oh)
987 1120 {
988 1121 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_indirect");
989   - return (qpdf_oh_valid_internal(qpdf, oh) &&
990   - qpdf->oh_cache[oh]->isIndirect());
  1122 + return do_with_oh<QPDF_BOOL>(
  1123 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1124 + return o.isIndirect();
  1125 + });
991 1126 }
992 1127  
993 1128 QPDF_BOOL qpdf_oh_is_scalar(qpdf_data qpdf, qpdf_oh oh)
994 1129 {
995 1130 QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_scalar");
996   - return (qpdf_oh_valid_internal(qpdf, oh) &&
997   - qpdf->oh_cache[oh]->isScalar());
  1131 + return do_with_oh<QPDF_BOOL>(
  1132 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1133 + return o.isScalar();
  1134 + });
  1135 +}
  1136 +
  1137 +QPDF_BOOL qpdf_oh_is_number(qpdf_data qpdf, qpdf_oh oh)
  1138 +{
  1139 + return do_with_oh<QPDF_BOOL>(
  1140 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1141 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_number");
  1142 + return o.isNumber();
  1143 + });
998 1144 }
999 1145  
1000 1146 qpdf_oh qpdf_oh_wrap_in_array(qpdf_data qpdf, qpdf_oh oh)
1001 1147 {
1002   - if (! qpdf_oh_valid_internal(qpdf, oh))
1003   - {
1004   - return qpdf_oh_new_array(qpdf);
1005   - }
1006   - auto qoh = qpdf->oh_cache[oh];
1007   - if (qoh->isArray())
1008   - {
1009   - QTC::TC("qpdf", "qpdf-c array to wrap_in_array");
1010   - return new_object(qpdf, *qoh);
1011   - }
1012   - else
1013   - {
1014   - QTC::TC("qpdf", "qpdf-c non-array to wrap_in_array");
1015   - return new_object(qpdf,
1016   - QPDFObjectHandle::newArray(
1017   - std::vector<QPDFObjectHandle>{
1018   - *qpdf->oh_cache[oh]}));
1019   - }
  1148 + return do_with_oh<qpdf_oh>(
  1149 + qpdf, oh,
  1150 + [&qpdf](){ return qpdf_oh_new_array(qpdf); },
  1151 + [&qpdf](QPDFObjectHandle& qoh) {
  1152 + if (qoh.isArray())
  1153 + {
  1154 + QTC::TC("qpdf", "qpdf-c array to wrap_in_array");
  1155 + return new_object(qpdf, qoh);
  1156 + }
  1157 + else
  1158 + {
  1159 + QTC::TC("qpdf", "qpdf-c non-array to wrap_in_array");
  1160 + return new_object(qpdf,
  1161 + QPDFObjectHandle::newArray(
  1162 + std::vector<QPDFObjectHandle>{qoh}));
  1163 + }
  1164 + });
1020 1165 }
1021 1166  
1022 1167 qpdf_oh qpdf_oh_parse(qpdf_data qpdf, char const* object_str)
1023 1168 {
1024 1169 QTC::TC("qpdf", "qpdf-c called qpdf_oh_parse");
1025   - return new_object(qpdf, QPDFObjectHandle::parse(object_str));
  1170 + return trap_oh_errors<qpdf_oh>(
  1171 + qpdf, return_uninitialized(qpdf), [&object_str] (qpdf_data q) {
  1172 + return new_object(q, QPDFObjectHandle::parse(object_str));
  1173 + });
1026 1174 }
1027 1175  
1028 1176 QPDF_BOOL qpdf_oh_get_bool_value(qpdf_data qpdf, qpdf_oh oh)
1029 1177 {
1030   - if (! qpdf_oh_valid_internal(qpdf, oh))
1031   - {
1032   - return QPDF_FALSE;
1033   - }
1034   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_bool_value");
1035   - return qpdf->oh_cache[oh]->getBoolValue();
  1178 + return do_with_oh<QPDF_BOOL>(
  1179 + qpdf, oh, return_false, [](QPDFObjectHandle& o) {
  1180 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_bool_value");
  1181 + return o.getBoolValue();
  1182 + });
1036 1183 }
1037 1184  
1038 1185 long long qpdf_oh_get_int_value(qpdf_data qpdf, qpdf_oh oh)
1039 1186 {
1040   - if (! qpdf_oh_valid_internal(qpdf, oh))
1041   - {
1042   - return 0LL;
1043   - }
1044   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value");
1045   - return qpdf->oh_cache[oh]->getIntValue();
  1187 + return do_with_oh<long long>(
  1188 + qpdf, oh, return_T<long long>(0LL), [](QPDFObjectHandle& o) {
  1189 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value");
  1190 + return o.getIntValue();
  1191 + });
1046 1192 }
1047 1193  
1048 1194 int qpdf_oh_get_int_value_as_int(qpdf_data qpdf, qpdf_oh oh)
1049 1195 {
1050   - if (! qpdf_oh_valid_internal(qpdf, oh))
1051   - {
1052   - return 0;
1053   - }
1054   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value_as_int");
1055   - return qpdf->oh_cache[oh]->getIntValueAsInt();
  1196 + return do_with_oh<int>(
  1197 + qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
  1198 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value_as_int");
  1199 + return o.getIntValueAsInt();
  1200 + });
1056 1201 }
1057 1202  
1058 1203 unsigned long long qpdf_oh_get_uint_value(qpdf_data qpdf, qpdf_oh oh)
1059 1204 {
1060   - if (! qpdf_oh_valid_internal(qpdf, oh))
1061   - {
1062   - return 0ULL;
1063   - }
1064   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value");
1065   - return qpdf->oh_cache[oh]->getUIntValue();
  1205 + return do_with_oh<unsigned long long>(
  1206 + qpdf, oh, return_T<unsigned long long>(0ULL), [](QPDFObjectHandle& o) {
  1207 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value");
  1208 + return o.getUIntValue();
  1209 + });
1066 1210 }
1067 1211  
1068 1212 unsigned int qpdf_oh_get_uint_value_as_uint(qpdf_data qpdf, qpdf_oh oh)
1069 1213 {
1070   - if (! qpdf_oh_valid_internal(qpdf, oh))
1071   - {
1072   - return 0U;
1073   - }
1074   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value_as_uint");
1075   - return qpdf->oh_cache[oh]->getUIntValueAsUInt();
  1214 + return do_with_oh<unsigned int>(
  1215 + qpdf, oh, return_T<unsigned int>(0U), [](QPDFObjectHandle& o) {
  1216 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value_as_uint");
  1217 + return o.getUIntValueAsUInt();
  1218 + });
1076 1219 }
1077 1220  
1078 1221 char const* qpdf_oh_get_real_value(qpdf_data qpdf, qpdf_oh oh)
1079 1222 {
1080   - if (! qpdf_oh_valid_internal(qpdf, oh))
1081   - {
1082   - return "";
1083   - }
1084   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_real_value");
1085   - qpdf->tmp_string = qpdf->oh_cache[oh]->getRealValue();
1086   - return qpdf->tmp_string.c_str();
1087   -}
1088   -
1089   -QPDF_BOOL qpdf_oh_is_number(qpdf_data qpdf, qpdf_oh oh)
1090   -{
1091   - if (! qpdf_oh_valid_internal(qpdf, oh))
1092   - {
1093   - return QPDF_FALSE;
1094   - }
1095   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_number");
1096   - return qpdf->oh_cache[oh]->isNumber();
  1223 + return do_with_oh<char const*>(
  1224 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1225 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_real_value");
  1226 + qpdf->tmp_string = o.getRealValue();
  1227 + return qpdf->tmp_string.c_str();
  1228 + });
1097 1229 }
1098 1230  
1099 1231 double qpdf_oh_get_numeric_value(qpdf_data qpdf, qpdf_oh oh)
1100 1232 {
1101   - if (! qpdf_oh_valid_internal(qpdf, oh))
1102   - {
1103   - return 0.0;
1104   - }
1105   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_numeric_value");
1106   - return qpdf->oh_cache[oh]->getNumericValue();
  1233 + return do_with_oh<double>(
  1234 + qpdf, oh, return_T<double>(0.0), [](QPDFObjectHandle& o) {
  1235 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_numeric_value");
  1236 + return o.getNumericValue();
  1237 + });
1107 1238 }
1108 1239  
1109 1240 char const* qpdf_oh_get_name(qpdf_data qpdf, qpdf_oh oh)
1110 1241 {
1111   - if (! qpdf_oh_valid_internal(qpdf, oh))
1112   - {
1113   - return "";
1114   - }
1115   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_name");
1116   - qpdf->tmp_string = qpdf->oh_cache[oh]->getName();
1117   - return qpdf->tmp_string.c_str();
  1242 + return do_with_oh<char const*>(
  1243 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1244 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_name");
  1245 + qpdf->tmp_string = o.getName();
  1246 + return qpdf->tmp_string.c_str();
  1247 + });
1118 1248 }
1119 1249  
1120 1250 char const* qpdf_oh_get_string_value(qpdf_data qpdf, qpdf_oh oh)
1121 1251 {
1122   - if (! qpdf_oh_valid_internal(qpdf, oh))
1123   - {
1124   - return "";
1125   - }
1126   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_string_value");
1127   - qpdf->tmp_string = qpdf->oh_cache[oh]->getStringValue();
1128   - return qpdf->tmp_string.c_str();
  1252 + return do_with_oh<char const*>(
  1253 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1254 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_string_value");
  1255 + qpdf->tmp_string = o.getStringValue();
  1256 + return qpdf->tmp_string.c_str();
  1257 + });
1129 1258 }
1130 1259  
1131 1260 char const* qpdf_oh_get_utf8_value(qpdf_data qpdf, qpdf_oh oh)
1132 1261 {
1133   - if (! qpdf_oh_valid_internal(qpdf, oh))
1134   - {
1135   - return "";
1136   - }
1137   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_utf8_value");
1138   - qpdf->tmp_string = qpdf->oh_cache[oh]->getUTF8Value();
1139   - return qpdf->tmp_string.c_str();
  1262 + return do_with_oh<char const*>(
  1263 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1264 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_utf8_value");
  1265 + qpdf->tmp_string = o.getUTF8Value();
  1266 + return qpdf->tmp_string.c_str();
  1267 + });
1140 1268 }
1141 1269  
1142 1270 int qpdf_oh_get_array_n_items(qpdf_data qpdf, qpdf_oh oh)
1143 1271 {
1144   - if (! qpdf_oh_valid_internal(qpdf, oh))
1145   - {
1146   - return 0;
1147   - }
1148   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_n_items");
1149   - return qpdf->oh_cache[oh]->getArrayNItems();
  1272 + return do_with_oh<int>(
  1273 + qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
  1274 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_n_items");
  1275 + return o.getArrayNItems();
  1276 + });
1150 1277 }
1151 1278  
1152 1279 qpdf_oh qpdf_oh_get_array_item(qpdf_data qpdf, qpdf_oh oh, int n)
1153 1280 {
1154   - if (! qpdf_oh_valid_internal(qpdf, oh))
1155   - {
1156   - return qpdf_oh_new_null(qpdf);
1157   - }
1158   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_item");
1159   - return new_object(qpdf, qpdf->oh_cache[oh]->getArrayItem(n));
  1281 + return do_with_oh<qpdf_oh>(
  1282 + qpdf, oh, return_null(qpdf), [&qpdf, &n](QPDFObjectHandle& o) {
  1283 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_item");
  1284 + return new_object(qpdf, o.getArrayItem(n));
  1285 + });
1160 1286 }
1161 1287  
1162 1288 void qpdf_oh_begin_dict_key_iter(qpdf_data qpdf, qpdf_oh oh)
1163 1289 {
1164   - if (qpdf_oh_valid_internal(qpdf, oh) &&
1165   - qpdf_oh_is_dictionary(qpdf, oh))
1166   - {
1167   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_begin_dict_key_iter");
1168   - qpdf->cur_iter_dict_keys = qpdf->oh_cache[oh]->getKeys();
1169   - }
1170   - else
1171   - {
1172   - qpdf->cur_iter_dict_keys = {};
1173   - }
  1290 + qpdf->cur_iter_dict_keys = do_with_oh<std::set<std::string>>(
  1291 + qpdf, oh,
  1292 + [](){ return std::set<std::string>(); },
  1293 + [](QPDFObjectHandle& o) {
  1294 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_begin_dict_key_iter");
  1295 + return o.getKeys();
  1296 + });
1174 1297 qpdf->dict_iter = qpdf->cur_iter_dict_keys.begin();
1175 1298 }
1176 1299  
... ... @@ -1197,32 +1320,35 @@ char const* qpdf_oh_dict_next_key(qpdf_data qpdf)
1197 1320  
1198 1321 QPDF_BOOL qpdf_oh_has_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
1199 1322 {
1200   - if (! qpdf_oh_valid_internal(qpdf, oh))
1201   - {
1202   - return QPDF_FALSE;
1203   - }
1204   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_has_key");
1205   - return qpdf->oh_cache[oh]->hasKey(key);
  1323 + return do_with_oh<QPDF_BOOL>(
  1324 + qpdf, oh, return_false, [&key](QPDFObjectHandle& o) {
  1325 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_has_key");
  1326 + return o.hasKey(key);
  1327 + });
1206 1328 }
1207 1329  
1208 1330 qpdf_oh qpdf_oh_get_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
1209 1331 {
1210   - if (! qpdf_oh_valid_internal(qpdf, oh))
1211   - {
1212   - return qpdf_oh_new_null(qpdf);
1213   - }
1214   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_key");
1215   - return new_object(qpdf, qpdf->oh_cache[oh]->getKey(key));
  1332 + return do_with_oh<qpdf_oh>(
  1333 + qpdf, oh, return_null(qpdf), [&qpdf, &key](QPDFObjectHandle& o) {
  1334 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_key");
  1335 + return new_object(qpdf, o.getKey(key));
  1336 + });
1216 1337 }
1217 1338  
1218 1339 QPDF_BOOL qpdf_oh_is_or_has_name(qpdf_data qpdf, qpdf_oh oh, char const* key)
1219 1340 {
1220   - if (! qpdf_oh_valid_internal(qpdf, oh))
1221   - {
1222   - return QPDF_FALSE;
1223   - }
1224   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_or_has_name");
1225   - return qpdf->oh_cache[oh]->isOrHasName(key);
  1341 + return do_with_oh<QPDF_BOOL>(
  1342 + qpdf, oh, return_false, [&key](QPDFObjectHandle& o) {
  1343 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_or_has_name");
  1344 + return o.isOrHasName(key);
  1345 + });
  1346 +}
  1347 +
  1348 +qpdf_oh qpdf_oh_new_uninitialized(qpdf_data qpdf)
  1349 +{
  1350 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_new_uninitialized");
  1351 + return new_object(qpdf, QPDFObjectHandle());
1226 1352 }
1227 1353  
1228 1354 qpdf_oh qpdf_oh_new_null(qpdf_data qpdf)
... ... @@ -1288,156 +1414,143 @@ qpdf_oh qpdf_oh_new_dictionary(qpdf_data qpdf)
1288 1414  
1289 1415 void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh)
1290 1416 {
1291   - if (qpdf_oh_valid_internal(qpdf, oh))
1292   - {
1293   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_make_direct");
1294   - qpdf->oh_cache[oh]->makeDirect();
1295   - }
  1417 + do_with_oh_void(
  1418 + qpdf, oh, [](QPDFObjectHandle& o) {
  1419 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_make_direct");
  1420 + o.makeDirect();
  1421 + });
1296 1422 }
1297 1423  
1298 1424 static QPDFObjectHandle
1299 1425 qpdf_oh_item_internal(qpdf_data qpdf, qpdf_oh item)
1300 1426 {
1301   - if (qpdf_oh_valid_internal(qpdf, item))
1302   - {
1303   - return *(qpdf->oh_cache[item]);
1304   - }
1305   - else
1306   - {
1307   - return QPDFObjectHandle::newNull();
1308   - }
  1427 + return do_with_oh<QPDFObjectHandle>(
  1428 + qpdf, item,
  1429 + [](){return QPDFObjectHandle::newNull();},
  1430 + [](QPDFObjectHandle& o) {
  1431 + return o;
  1432 + });
1309 1433 }
1310 1434  
1311 1435 void qpdf_oh_set_array_item(qpdf_data qpdf, qpdf_oh oh,
1312 1436 int at, qpdf_oh item)
1313 1437 {
1314   - if (qpdf_oh_is_array(qpdf, oh))
1315   - {
1316   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_set_array_item");
1317   - qpdf->oh_cache[oh]->setArrayItem(
1318   - at, qpdf_oh_item_internal(qpdf, item));
1319   - }
  1438 + do_with_oh_void(
  1439 + qpdf, oh, [&qpdf, &at, &item](QPDFObjectHandle& o) {
  1440 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_set_array_item");
  1441 + o.setArrayItem(at, qpdf_oh_item_internal(qpdf, item));
  1442 + });
1320 1443 }
1321 1444  
1322 1445 void qpdf_oh_insert_item(qpdf_data qpdf, qpdf_oh oh, int at, qpdf_oh item)
1323 1446 {
1324   - if (qpdf_oh_is_array(qpdf, oh))
1325   - {
1326   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_insert_item");
1327   - qpdf->oh_cache[oh]->insertItem(
1328   - at, qpdf_oh_item_internal(qpdf, item));
1329   - }
  1447 + do_with_oh_void(
  1448 + qpdf, oh, [&qpdf, &at, &item](QPDFObjectHandle& o) {
  1449 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_insert_item");
  1450 + o.insertItem(at, qpdf_oh_item_internal(qpdf, item));
  1451 + });
1330 1452 }
1331 1453  
1332 1454 void qpdf_oh_append_item(qpdf_data qpdf, qpdf_oh oh, qpdf_oh item)
1333 1455 {
1334   - if (qpdf_oh_is_array(qpdf, oh))
1335   - {
1336   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_append_item");
1337   - qpdf->oh_cache[oh]->appendItem(
1338   - qpdf_oh_item_internal(qpdf, item));
1339   - }
  1456 + do_with_oh_void(
  1457 + qpdf, oh, [&qpdf, &item](QPDFObjectHandle& o) {
  1458 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_append_item");
  1459 + o.appendItem(qpdf_oh_item_internal(qpdf, item));
  1460 + });
1340 1461 }
1341 1462  
1342 1463 void qpdf_oh_erase_item(qpdf_data qpdf, qpdf_oh oh, int at)
1343 1464 {
1344   - if (qpdf_oh_is_array(qpdf, oh))
1345   - {
1346   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_erase_item");
1347   - qpdf->oh_cache[oh]->eraseItem(at);
1348   - }
  1465 + do_with_oh_void(
  1466 + qpdf, oh, [&at](QPDFObjectHandle& o) {
  1467 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_erase_item");
  1468 + o.eraseItem(at);
  1469 + });
1349 1470 }
1350 1471  
1351 1472 void qpdf_oh_replace_key(qpdf_data qpdf, qpdf_oh oh,
1352 1473 char const* key, qpdf_oh item)
1353 1474 {
1354   - if (qpdf_oh_is_dictionary(qpdf, oh))
1355   - {
1356   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_key");
1357   - qpdf->oh_cache[oh]->replaceKey(
1358   - key, qpdf_oh_item_internal(qpdf, item));
1359   - }
  1475 + do_with_oh_void(
  1476 + qpdf, oh, [&qpdf, &key, &item](QPDFObjectHandle& o) {
  1477 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_key");
  1478 + o.replaceKey(key, qpdf_oh_item_internal(qpdf, item));
  1479 + });
1360 1480 }
1361 1481  
1362 1482 void qpdf_oh_remove_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
1363 1483 {
1364   - if (qpdf_oh_is_dictionary(qpdf, oh))
1365   - {
1366   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_remove_key");
1367   - qpdf->oh_cache[oh]->removeKey(key);
1368   - }
  1484 + do_with_oh_void(
  1485 + qpdf, oh, [&key](QPDFObjectHandle& o) {
  1486 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_remove_key");
  1487 + o.removeKey(key);
  1488 + });
1369 1489 }
1370 1490  
1371 1491 void qpdf_oh_replace_or_remove_key(qpdf_data qpdf, qpdf_oh oh,
1372 1492 char const* key, qpdf_oh item)
1373 1493 {
1374   - if (qpdf_oh_is_dictionary(qpdf, oh))
1375   - {
1376   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_or_remove_key");
1377   - qpdf->oh_cache[oh]->replaceOrRemoveKey(
1378   - key, qpdf_oh_item_internal(qpdf, item));
1379   - }
  1494 + do_with_oh_void(
  1495 + qpdf, oh, [&qpdf, &key, &item](QPDFObjectHandle& o) {
  1496 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_or_remove_key");
  1497 + o.replaceOrRemoveKey(key, qpdf_oh_item_internal(qpdf, item));
  1498 + });
1380 1499 }
1381 1500  
1382 1501 qpdf_oh qpdf_oh_get_dict(qpdf_data qpdf, qpdf_oh oh)
1383 1502 {
1384   - if (! qpdf_oh_valid_internal(qpdf, oh))
1385   - {
1386   - return qpdf_oh_new_null(qpdf);
1387   - }
1388   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_dict");
1389   - return new_object(qpdf, qpdf->oh_cache[oh]->getDict());
  1503 + return do_with_oh<qpdf_oh>(
  1504 + qpdf, oh, return_null(qpdf), [&qpdf](QPDFObjectHandle& o) {
  1505 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_dict");
  1506 + return new_object(qpdf, o.getDict());
  1507 + });
1390 1508 }
1391 1509  
1392 1510 int qpdf_oh_get_object_id(qpdf_data qpdf, qpdf_oh oh)
1393 1511 {
1394   - if (! qpdf_oh_valid_internal(qpdf, oh))
1395   - {
1396   - return 0;
1397   - }
1398   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_object_id");
1399   - return qpdf->oh_cache[oh]->getObjectID();
  1512 + return do_with_oh<int>(
  1513 + qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
  1514 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_object_id");
  1515 + return o.getObjectID();
  1516 + });
1400 1517 }
1401 1518  
1402 1519 int qpdf_oh_get_generation(qpdf_data qpdf, qpdf_oh oh)
1403 1520 {
1404   - if (! qpdf_oh_valid_internal(qpdf, oh))
1405   - {
1406   - return 0;
1407   - }
1408   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_generation");
1409   - return qpdf->oh_cache[oh]->getGeneration();
  1521 + return do_with_oh<int>(
  1522 + qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
  1523 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_generation");
  1524 + return o.getGeneration();
  1525 + });
1410 1526 }
1411 1527  
1412 1528 char const* qpdf_oh_unparse(qpdf_data qpdf, qpdf_oh oh)
1413 1529 {
1414   - if (! qpdf_oh_valid_internal(qpdf, oh))
1415   - {
1416   - return "";
1417   - }
1418   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse");
1419   - qpdf->tmp_string = qpdf->oh_cache[oh]->unparse();
1420   - return qpdf->tmp_string.c_str();
  1530 + return do_with_oh<char const*>(
  1531 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1532 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse");
  1533 + qpdf->tmp_string = o.unparse();
  1534 + return qpdf->tmp_string.c_str();
  1535 + });
1421 1536 }
1422 1537  
1423 1538 char const* qpdf_oh_unparse_resolved(qpdf_data qpdf, qpdf_oh oh)
1424 1539 {
1425   - if (! qpdf_oh_valid_internal(qpdf, oh))
1426   - {
1427   - return "";
1428   - }
1429   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_resolved");
1430   - qpdf->tmp_string = qpdf->oh_cache[oh]->unparseResolved();
1431   - return qpdf->tmp_string.c_str();
  1540 + return do_with_oh<char const*>(
  1541 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1542 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_resolved");
  1543 + qpdf->tmp_string = o.unparseResolved();
  1544 + return qpdf->tmp_string.c_str();
  1545 + });
1432 1546 }
1433 1547  
1434 1548 char const* qpdf_oh_unparse_binary(qpdf_data qpdf, qpdf_oh oh)
1435 1549 {
1436   - if (! qpdf_oh_valid_internal(qpdf, oh))
1437   - {
1438   - return "";
1439   - }
1440   - QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_binary");
1441   - qpdf->tmp_string = qpdf->oh_cache[oh]->unparseBinary();
1442   - return qpdf->tmp_string.c_str();
  1550 + return do_with_oh<char const*>(
  1551 + qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
  1552 + QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_binary");
  1553 + qpdf->tmp_string = o.unparseBinary();
  1554 + return qpdf->tmp_string.c_str();
  1555 + });
1443 1556 }
... ...
manual/qpdf-manual.xml
... ... @@ -5231,6 +5231,19 @@ print &quot;\n&quot;;
5231 5231 </listitem>
5232 5232 <listitem>
5233 5233 <para>
  5234 + Overhaul error handling for the object handle functions in
  5235 + the C API. See comments in the &ldquo;Object handling&rdquo;
  5236 + section of <filename>include/qpdf/qpdf-c.h</filename> for
  5237 + details. In particular, exceptions thrown by the underlying
  5238 + C++ code when calling object accessors are caught and
  5239 + converted into errors. The errors can be trapped by
  5240 + registering an error handler with
  5241 + <function>qpdf_register_oh_error_handler</function> or will
  5242 + be written to stderr if no handler is registered.
  5243 + </para>
  5244 + </listitem>
  5245 + <listitem>
  5246 + <para>
5234 5247 Add <function>qpdf_get_last_string_length</function> to the
5235 5248 C API to get the length of the last string that was
5236 5249 returned. This is needed to handle strings that contain
... ... @@ -5239,9 +5252,9 @@ print &quot;\n&quot;;
5239 5252 </listitem>
5240 5253 <listitem>
5241 5254 <para>
5242   - Add <function>qpdf_oh_is_initialized</function> to the
5243   - C API. While you can't directly create uninitialized objects
5244   - from the C API, you still have to be able to detect them.
  5255 + Add <function>qpdf_oh_is_initialized</function> and
  5256 + <function>qpdf_oh_new_uninitialized</function> to the C API
  5257 + to make it possible to work with uninitialized objects.
5245 5258 </para>
5246 5259 </listitem>
5247 5260 <listitem>
... ...
qpdf/qpdf-ctest.c
... ... @@ -35,28 +35,29 @@ static FILE* safe_fopen(char const* filename, char const* mode)
35 35 return f;
36 36 }
37 37  
38   -static void report_errors()
  38 +static void print_error(char const* label, qpdf_data qpdf, qpdf_error e)
39 39 {
40 40 #define POS_FMT " pos : " LL_FMT "\n"
  41 + printf("%s: %s\n", label, qpdf_get_error_full_text(qpdf, e));
  42 + printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
  43 + printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
  44 + printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
  45 + printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
  46 +}
  47 +
  48 +static void report_errors()
  49 +{
41 50 qpdf_error e = 0;
42 51 while (qpdf_more_warnings(qpdf))
43 52 {
44 53 e = qpdf_next_warning(qpdf);
45   - printf("warning: %s\n", qpdf_get_error_full_text(qpdf, e));
46   - printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
47   - printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
48   - printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
49   - printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
  54 + print_error("warning", qpdf, e);
50 55 }
51 56 if (qpdf_has_error(qpdf))
52 57 {
53 58 e = qpdf_get_error(qpdf);
54 59 assert(qpdf_has_error(qpdf) == QPDF_FALSE);
55   - printf("error: %s\n", qpdf_get_error_full_text(qpdf, e));
56   - printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
57   - printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
58   - printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
59   - printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
  60 + print_error("error", qpdf, e);
60 61 }
61 62 else
62 63 {
... ... @@ -72,6 +73,16 @@ static void report_errors()
72 73 }
73 74 }
74 75  
  76 +static void handle_oh_error(qpdf_data qpdf, qpdf_error error, void* data)
  77 +{
  78 + char const* label = "oh error";
  79 + if (data)
  80 + {
  81 + label = *((char const**)data);
  82 + }
  83 + print_error(label, qpdf, error);
  84 +}
  85 +
75 86 static void read_file_into_memory(char const* filename,
76 87 char** buf, unsigned long* size)
77 88 {
... ... @@ -615,8 +626,11 @@ static void test24(char const* infile,
615 626 */
616 627 qpdf_oh_replace_key(qpdf, resources, "/ProcSet", procset);
617 628  
618   - /* Release and access to exercise warnings and to show that write
619   - * still works after releasing.
  629 + /* Release and access to exercise handling of object handle errors
  630 + * and to show that write still works after releasing. This test
  631 + * uses the default oh error handler, so messages get written to
  632 + * stderr. The warning about using the default error handler only
  633 + * appears once.
620 634 */
621 635 qpdf_oh_release(qpdf, page1);
622 636 contents = qpdf_oh_get_key(qpdf, page1, "/Contents");
... ... @@ -791,6 +805,82 @@ static void test28(char const* infile,
791 805 }
792 806 }
793 807  
  808 +static void test29(char const* infile,
  809 + char const* password,
  810 + char const* outfile,
  811 + char const* outfile2)
  812 +{
  813 + /* Trap exceptions thrown by object accessors. Type mismatches are
  814 + * errors rather than warnings when they don't have an owning QPDF
  815 + * object.
  816 + */
  817 + char const* label = "oh error";
  818 + qpdf_register_oh_error_handler(qpdf, handle_oh_error, (void*)&label);
  819 +
  820 + /* get_root fails when we have no trailer */
  821 + label = "get root";
  822 + qpdf_oh root = qpdf_get_root(qpdf);
  823 + assert(root != 0);
  824 + assert(! qpdf_oh_is_initialized(qpdf, root));
  825 +
  826 + label = "bad parse";
  827 + assert(! qpdf_oh_is_initialized(qpdf, qpdf_oh_parse(qpdf, "[oops")));
  828 + report_errors();
  829 +
  830 + label = "type mismatch";
  831 + assert(qpdf_oh_get_int_value_as_int(
  832 + qpdf, qpdf_oh_new_string(qpdf, "x")) == 0);
  833 + qpdf_oh int_oh = qpdf_oh_new_integer(qpdf, 12);
  834 + assert(strlen(qpdf_oh_get_string_value(qpdf, int_oh)) == 0);
  835 +
  836 + // This doesn't test every possible error flow, but it tests each
  837 + // way of handling errors in the library code.
  838 + label = "array type mismatch";
  839 + assert(qpdf_oh_get_array_n_items(qpdf, int_oh) == 0);
  840 + assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_array_item(qpdf, int_oh, 3)));
  841 + label = "append to non-array";
  842 + qpdf_oh_append_item(qpdf, int_oh, qpdf_oh_new_null(qpdf));
  843 + qpdf_oh array = qpdf_oh_new_array(qpdf);
  844 + label = "array bounds";
  845 + assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_array_item(qpdf, array, 3)));
  846 +
  847 + label = "dictionary iter type mismatch";
  848 + qpdf_oh_begin_dict_key_iter(qpdf, int_oh);
  849 + assert(qpdf_oh_dict_more_keys(qpdf) == QPDF_FALSE);
  850 + label = "dictionary type mismatch";
  851 + assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_key(qpdf, int_oh, "potato")));
  852 + assert(qpdf_oh_has_key(qpdf, int_oh, "potato") == QPDF_FALSE);
  853 +
  854 + report_errors();
  855 +}
  856 +
  857 +static void test30(char const* infile,
  858 + char const* password,
  859 + char const* outfile,
  860 + char const* outfile2)
  861 +{
  862 + assert(qpdf_read(qpdf, infile, password) & QPDF_ERRORS);
  863 + /* Fail to handle error */
  864 +}
  865 +
  866 +static void test31(char const* infile,
  867 + char const* password,
  868 + char const* outfile,
  869 + char const* outfile2)
  870 +{
  871 + /* Make sure type warnings have a specific error code. This test
  872 + * case is designed for minimal.pdf.
  873 + */
  874 + qpdf_read(qpdf, infile, password);
  875 + qpdf_oh trailer = qpdf_get_trailer(qpdf);
  876 + assert(qpdf_oh_get_int_value(qpdf, trailer) == 0LL);
  877 + assert(! qpdf_has_error(qpdf));
  878 + assert(qpdf_more_warnings(qpdf));
  879 + qpdf_error e = qpdf_next_warning(qpdf);
  880 + assert(qpdf_get_error_code(qpdf, e) == qpdf_e_object);
  881 + report_errors();
  882 +}
  883 +
794 884 int main(int argc, char* argv[])
795 885 {
796 886 char* p = 0;
... ... @@ -859,6 +949,9 @@ int main(int argc, char* argv[])
859 949 (n == 26) ? test26 :
860 950 (n == 27) ? test27 :
861 951 (n == 28) ? test28 :
  952 + (n == 29) ? test29 :
  953 + (n == 30) ? test30 :
  954 + (n == 31) ? test31 :
862 955 0);
863 956  
864 957 if (fn == 0)
... ...
qpdf/qpdf.testcov
... ... @@ -602,3 +602,7 @@ QPDFObjectHandle check ownership 0
602 602 qpdf weak crypto warning 0
603 603 qpdf-c called qpdf_oh_is_initialized 0
604 604 qpdf-c registered progress reporter 0
  605 +qpdf-c called qpdf_oh_new_uninitialized 0
  606 +qpdf-c warn about oh error 1
  607 +qpdf-c registered oh error handler 0
  608 +qpdf-c cleanup warned about unhandled error 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -4812,7 +4812,7 @@ foreach my $i (@c_check_types)
4812 4812 show_ntests();
4813 4813 # ----------
4814 4814 $td->notify("--- C API Object Handle ---");
4815   -$n_tests += 7;
  4815 +$n_tests += 10;
4816 4816  
4817 4817 $td->runtest("C check object handles",
4818 4818 {$td->COMMAND => "qpdf-ctest 24 minimal.pdf '' a.pdf"},
... ... @@ -4843,6 +4843,18 @@ $td-&gt;runtest(&quot;C wrap and clone objects&quot;,
4843 4843 {$td->COMMAND => "qpdf-ctest 28 minimal.pdf '' ''"},
4844 4844 {$td->STRING => "", $td->EXIT_STATUS => 0},
4845 4845 $td->NORMALIZE_NEWLINES);
  4846 +$td->runtest("C object handle errors",
  4847 + {$td->COMMAND => "qpdf-ctest 29 minimal.pdf '' ''"},
  4848 + {$td->FILE => "c-oh-errors.out", $td->EXIT_STATUS => 0},
  4849 + $td->NORMALIZE_NEWLINES);
  4850 +$td->runtest("C unhandled error warning",
  4851 + {$td->COMMAND => "qpdf-ctest 30 bad1.pdf '' ''"},
  4852 + {$td->FILE => "c-unhandled-error.out", $td->EXIT_STATUS => 0},
  4853 + $td->NORMALIZE_NEWLINES);
  4854 +$td->runtest("C type mismatch warning",
  4855 + {$td->COMMAND => "qpdf-ctest 31 minimal.pdf '' ''"},
  4856 + {$td->FILE => "c-type-warning.out", $td->EXIT_STATUS => 0},
  4857 + $td->NORMALIZE_NEWLINES);
4846 4858  
4847 4859 show_ntests();
4848 4860 # ----------
... ...
qpdf/qtest/qpdf/c-object-handles.out
... ... @@ -7,18 +7,11 @@ item 0: 0 0.00
7 7 item 1: 0 0.00
8 8 item 2: 612 612.00
9 9 item 3: 792 792.00
10   -warning: minimal.pdf (C API object handle 6): attempted access to unknown object handle
11   - code: 5
  10 +minimal.pdf (C API object handle 6): attempted access to unknown object handle
  11 +minimal.pdf (C API object handle 9): attempted access to unknown object handle
  12 +minimal.pdf (C API object handle 9): attempted access to unknown object handle
  13 +warning: minimal.pdf: C API object handle accessor errors occurred, and the application did not define an error handler
  14 + code: 1
12 15 file: minimal.pdf
13 16 pos : 0
14   - text: attempted access to unknown object handle
15   -warning: minimal.pdf (C API object handle 9): attempted access to unknown object handle
16   - code: 5
17   - file: minimal.pdf
18   - pos : 0
19   - text: attempted access to unknown object handle
20   -warning: minimal.pdf (C API object handle 9): attempted access to unknown object handle
21   - code: 5
22   - file: minimal.pdf
23   - pos : 0
24   - text: attempted access to unknown object handle
  17 + text: C API object handle accessor errors occurred, and the application did not define an error handler
... ...
qpdf/qtest/qpdf/c-oh-errors.out 0 โ†’ 100644
  1 +get root: attempted to dereference an uninitialized QPDFObjectHandle
  2 + code: 1
  3 + file:
  4 + pos : 0
  5 + text: attempted to dereference an uninitialized QPDFObjectHandle
  6 +bad parse: parsed object (offset 1): unknown token while reading object; treating as string
  7 + code: 5
  8 + file: parsed object
  9 + pos : 1
  10 + text: unknown token while reading object; treating as string
  11 +type mismatch: operation for integer attempted on object of type string: returning 0
  12 + code: 7
  13 + file:
  14 + pos : 0
  15 + text: operation for integer attempted on object of type string: returning 0
  16 +type mismatch: operation for string attempted on object of type integer: returning empty string
  17 + code: 7
  18 + file:
  19 + pos : 0
  20 + text: operation for string attempted on object of type integer: returning empty string
  21 +array type mismatch: operation for array attempted on object of type integer: treating as empty
  22 + code: 7
  23 + file:
  24 + pos : 0
  25 + text: operation for array attempted on object of type integer: treating as empty
  26 +array type mismatch: operation for array attempted on object of type integer: returning null
  27 + code: 7
  28 + file:
  29 + pos : 0
  30 + text: operation for array attempted on object of type integer: returning null
  31 +append to non-array: operation for array attempted on object of type integer: ignoring attempt to append item
  32 + code: 7
  33 + file:
  34 + pos : 0
  35 + text: operation for array attempted on object of type integer: ignoring attempt to append item
  36 +array bounds: returning null for out of bounds array access
  37 + code: 7
  38 + file:
  39 + pos : 0
  40 + text: returning null for out of bounds array access
  41 +dictionary iter type mismatch: operation for dictionary attempted on object of type integer: treating as empty
  42 + code: 7
  43 + file:
  44 + pos : 0
  45 + text: operation for dictionary attempted on object of type integer: treating as empty
  46 +dictionary type mismatch: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval
  47 + code: 7
  48 + file:
  49 + pos : 0
  50 + text: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval
  51 +dictionary type mismatch: operation for dictionary attempted on object of type integer: returning false for a key containment request
  52 + code: 7
  53 + file:
  54 + pos : 0
  55 + text: operation for dictionary attempted on object of type integer: returning false for a key containment request
... ...
qpdf/qtest/qpdf/c-type-warning.out 0 โ†’ 100644
  1 +WARNING: minimal.pdf, trailer at offset 715: operation for integer attempted on object of type dictionary: returning 0
... ...
qpdf/qtest/qpdf/c-unhandled-error.out 0 โ†’ 100644
  1 +WARNING: bad1.pdf: can't find PDF header
  2 +WARNING: bad1.pdf: file is damaged
  3 +WARNING: bad1.pdf: can't find startxref
  4 +WARNING: bad1.pdf: Attempting to reconstruct cross-reference table
  5 +WARNING: application did not handle error: bad1.pdf: unable to find trailer dictionary while recovering damaged file
... ...