1 /*
2 * nsfError.c --
3 *
4 * Error reporting functions for the Next Scripting Framework.
5 *
6 * Copyright (C) 1999-2018 Gustaf Neumann (a, b)
7 * Copyright (C) 1999-2007 Uwe Zdun (a, b)
8 * Copyright (C) 2011-2016 Stefan Sobernig (b)
9 *
10 * (a) University of Essen
11 * Specification of Software Systems
12 * Altendorferstrasse 97-101
13 * D-45143 Essen, Germany
14 *
15 * (b) Vienna University of Economics and Business
16 * Institute of Information Systems and New Media
17 * A-1020, Welthandelsplatz 1
18 * Vienna, Austria
19 *
20 * This work is licensed under the MIT License https://www.opensource.org/licenses/MIT
21 *
22 * Copyright:
23 *
24 * Permission is hereby granted, free of charge, to any person obtaining a
25 * copy of this software and associated documentation files (the "Software"),
26 * to deal in the Software without restriction, including without limitation
27 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
28 * and/or sell copies of the Software, and to permit persons to whom the
29 * Software is furnished to do so, subject to the following conditions:
30 *
31 * The above copyright notice and this permission notice shall be included in
32 * all copies or substantial portions of the Software.
33 *
34 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
39 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
40 * DEALINGS IN THE SOFTWARE.
41 *
42 */
43
44 #include "nsfInt.h"
45
46 /* function prototypes */
47 Tcl_Obj *NsfParamDefsSyntax(
48 Tcl_Interp *interp, Nsf_Param const *paramsPtr,
49 NsfObject *contextObject, const char *pattern
50 ) nonnull(1) nonnull(2) returns_nonnull;
51
52
53 /*
54 *----------------------------------------------------------------------
55 *
56 * NsfDStringVPrintf --
57 *
58 * Appends a formatted value to a Tcl_DString. This function
59 * continues until having allocated sufficient memory.
60 *
61 * Note: The current implementation assumes C99 compliant
62 * implementations of vs*printf() for all runtimes other than
63 * MSVC. For MSVC, the pre-C99 vs*printf() implementations are
64 * explicitly set by Tcl internals (see tclInt.h). For
65 * MinGW/MinGW-w64, __USE_MINGW_ANSI_STDIO must be set (see
66 * nsfInt.h).
67 *
68 * Results:
69 * None.
70 *
71 * Side effects:
72 * None.
73 *
74 *----------------------------------------------------------------------
75 */
76
77 void
NsfDStringVPrintf(Tcl_DString * dsPtr,const char * fmt,va_list argPtr)78 NsfDStringVPrintf(Tcl_DString *dsPtr, const char *fmt, va_list argPtr) {
79 int result, avail, offset;
80 va_list argPtrCopy;
81 bool failure;
82
83 /*
84 * Tcl_DStringLength returns the current length *without* the
85 * terminating character (NTC).
86 */
87 offset = Tcl_DStringLength(dsPtr);
88
89 #if defined(_MSC_VER)
90 /*
91 * Pre-C99: currently free storage, excluding NTC
92 */
93 avail = dsPtr->spaceAvl - offset - 1;
94 #else
95 /*
96 * C99: currently free storage, including NTC
97 */
98 avail = dsPtr->spaceAvl - offset;
99 #endif
100
101 /*
102 * 1) Copy va_list so that the caller's copy is untouched.
103 * 2) Run vsnprintf() eagerly.
104 */
105 va_copy(argPtrCopy, argPtr);
106 result = vsnprintf(dsPtr->string + offset, (size_t)avail, fmt, argPtrCopy);
107 va_end(argPtrCopy);
108
109 #if defined(_MSC_VER)
110 /*
111 * vs*printf() in pre-C99 runtimes (MSVC up to VS13, VS15 and newer
112 * in backward-compat mode) return -1 upon overflowing the buffer.
113 *
114 * Note: Tcl via tclInt.h precludes the use of pre-C99 mode even in
115 * VS15 and newer (vsnprintf points to backwards compatible, pre-C99
116 * _vsnprintf).
117 */
118 failure = (result == -1);
119 #else
120 /*
121 * vs*printf() in C99 compliant runtimes (GCC, CLANG, MSVC in VS15
122 * and newer, MinGW/MinGW-w64 with __USE_MINGW_ANSI_STDIO) returns
123 * the number of chars to be written if the buffer would be
124 * sufficiently large (excluding NTC, the terminating null
125 * character). A return value of -1 signals an encoding error.
126 */
127 assert(result > -1); /* no encoding error */
128 failure = (result >= (int)avail);
129 #endif
130
131 if (likely(! failure)) {
132 /*
133 * vsnprintf() copied all content, adjust the Tcl_DString length.
134 */
135 Tcl_DStringSetLength(dsPtr, offset + result);
136
137 } else {
138 int addedStringLength;
139 /*
140 * vsnprintf() could not copy all content, content was truncated.
141 * Determine the required length (for MSVC), adjust the Tcl_DString
142 * size, and copy again.
143 */
144
145 #if defined(_MSC_VER)
146 va_copy(argPtrCopy, argPtr);
147 addedStringLength = _vscprintf(fmt, argPtrCopy);
148 va_end(argPtrCopy);
149 #else
150 addedStringLength = result;
151 #endif
152
153 Tcl_DStringSetLength(dsPtr, offset + addedStringLength);
154
155 #if defined(_MSC_VER)
156 /*
157 * Pre-C99: currently free storage, excluding NTC
158 */
159 avail = dsPtr->spaceAvl - offset - 1;
160 #else
161 /*
162 * C99: currently free storage, including NTC
163 */
164 avail = dsPtr->spaceAvl - offset;
165 #endif
166
167 va_copy(argPtrCopy, argPtr);
168 result = vsnprintf(dsPtr->string + offset, (size_t)avail, fmt, argPtrCopy);
169 va_end(argPtrCopy);
170
171 #if defined(_MSC_VER)
172 failure = (result == -1);
173 #else
174 failure = (result == -1 || result >= avail);
175 #endif
176
177 #if defined(NDEBUG)
178 if (unlikely(failure)) {
179 Tcl_Panic("writing string-formatting output to a dynamic Tcl string failed");
180 }
181 #endif
182 assert(! failure);
183 }
184 }
185
186 /*
187 *----------------------------------------------------------------------
188 * Nsf_DStringPrintf --
189 *
190 * Append a sequence of values using a format string.
191 *
192 * Results:
193 * Pointer to the current string value.
194 *
195 * Side effects:
196 * None.
197 *
198 *----------------------------------------------------------------------
199 */
200
201 void
Nsf_DStringPrintf(Tcl_DString * dsPtr,const char * fmt,...)202 Nsf_DStringPrintf(Tcl_DString *dsPtr, const char *fmt, ...)
203 {
204 va_list ap;
205
206 nonnull_assert(dsPtr != NULL);
207 nonnull_assert(fmt != NULL);
208
209 va_start(ap, fmt);
210 NsfDStringVPrintf(dsPtr, fmt, ap);
211 va_end(ap);
212 }
213
214 /*
215 *----------------------------------------------------------------------
216 *
217 * NsfDStringArgv --
218 *
219 * Appends argument vector to an initialized Tcl_DString.
220 *
221 * Results:
222 * None.
223 *
224 * Side effects:
225 * None.
226 *
227 *----------------------------------------------------------------------
228 */
229 void
NsfDStringArgv(Tcl_DString * dsPtr,int objc,Tcl_Obj * const objv[])230 NsfDStringArgv(
231 Tcl_DString *dsPtr,
232 int objc, Tcl_Obj *const objv[]
233 ) {
234 nonnull_assert(dsPtr != NULL);
235 nonnull_assert(objv != NULL);
236
237 if (objc > 0) {
238 int i;
239 Tcl_DStringAppendElement(dsPtr, NsfMethodName(objv[0]));
240 for (i = 1; i < objc; i++) {
241 Tcl_DStringAppendElement(dsPtr, ObjStr(objv[i]));
242 }
243 }
244 }
245
246
247 /*
248 *----------------------------------------------------------------------
249 *
250 * NsfPrintError --
251 *
252 * Produce a formatted error message with a printf-like semantics.
253 *
254 * Results:
255 * TCL_ERROR
256 *
257 * Side effects:
258 * Sets the result message.
259 *
260 *----------------------------------------------------------------------
261 */
262 int
NsfPrintError(Tcl_Interp * interp,const char * fmt,...)263 NsfPrintError(Tcl_Interp *interp, const char *fmt, ...) {
264 va_list ap;
265 Tcl_DString ds;
266
267 Tcl_DStringInit(&ds);
268
269 va_start(ap, fmt);
270 NsfDStringVPrintf(&ds, fmt, ap);
271 va_end(ap);
272
273 Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)));
274 Tcl_DStringFree(&ds);
275
276 return TCL_ERROR;
277 }
278
279 /*
280 *----------------------------------------------------------------------
281 *
282 * NsfErrInProc --
283 *
284 * Produce a general error message when an error occurs in a
285 * scripted NSF method.
286 *
287 * Results:
288 * TCL_ERROR
289 *
290 * Side effects:
291 * Sets the result message.
292 *
293 *----------------------------------------------------------------------
294 */
295 int
NsfErrInProc(Tcl_Interp * interp,Tcl_Obj * objName,Tcl_Obj * clName,const char * procName)296 NsfErrInProc(
297 Tcl_Interp *interp,
298 Tcl_Obj *objName,
299 Tcl_Obj *clName,
300 const char *procName
301 ) {
302 Tcl_DString errMsg;
303 const char *cName, *space;
304
305 Tcl_DStringInit(&errMsg);
306 Tcl_DStringAppend(&errMsg, "\n ", -1);
307 if (clName != NULL) {
308 cName = ObjStr(clName);
309 space = " ";
310 } else {
311 cName = "";
312 space = "";
313 }
314 Tcl_DStringAppend(&errMsg, ObjStr(objName), -1);
315 Tcl_DStringAppend(&errMsg, space, -1);
316 Tcl_DStringAppend(&errMsg, cName, -1);
317 Tcl_DStringAppend(&errMsg, "->", 2);
318 Tcl_DStringAppend(&errMsg, procName, -1);
319 Tcl_AddErrorInfo (interp, Tcl_DStringValue(&errMsg));
320 Tcl_DStringFree(&errMsg);
321 return TCL_ERROR;
322 }
323
324 /*
325 *----------------------------------------------------------------------
326 *
327 * NsfObjWrongArgs --
328 *
329 * Produce a general error message when an NSF method is called with
330 * an invalid argument list (wrong number of arguments).
331 *
332 * Results:
333 * TCL_ERROR
334 *
335 * Side effects:
336 * Sets the result message.
337 *
338 *----------------------------------------------------------------------
339 */
340 int
NsfObjWrongArgs(Tcl_Interp * interp,const char * msg,Tcl_Obj * cmdNameObj,Tcl_Obj * methodPathObj,const char * arglist)341 NsfObjWrongArgs(
342 Tcl_Interp *interp,
343 const char *msg, Tcl_Obj *cmdNameObj,
344 Tcl_Obj *methodPathObj, const char *arglist
345 ) {
346 bool need_space = NSF_FALSE;
347 Tcl_DString ds;
348
349 nonnull_assert(interp != NULL);
350 nonnull_assert(msg != NULL);
351
352 Tcl_DStringInit(&ds);
353
354 Nsf_DStringPrintf(&ds, "%s should be \"", msg);
355 if (cmdNameObj != NULL) {
356 Tcl_DStringAppend(&ds, ObjStr(cmdNameObj), -1);
357 need_space = NSF_TRUE;
358 }
359
360 if (methodPathObj != NULL) {
361 if (need_space) {
362 Tcl_DStringAppend(&ds, " ", 1);
363 }
364
365 INCR_REF_COUNT(methodPathObj);
366 Tcl_DStringAppend(&ds, ObjStr(methodPathObj), -1);
367 DECR_REF_COUNT(methodPathObj);
368
369 need_space = NSF_TRUE;
370 }
371 if (arglist != NULL) {
372 if (need_space) {
373 Tcl_DStringAppend(&ds, " ", 1);
374 }
375 Tcl_DStringAppend(&ds, arglist, -1);
376 }
377 Tcl_DStringAppend(&ds, "\"", 1);
378
379 Tcl_SetObjResult(interp, Tcl_NewStringObj(ds.string, ds.length));
380 Tcl_DStringFree(&ds);
381
382 return TCL_ERROR;
383 }
384
385
386 /*
387 *----------------------------------------------------------------------
388 *
389 * NsfArgumentError --
390 *
391 * Produce a wrong-number-of-arguments error based on a parameter
392 * definition.
393 *
394 * Results:
395 * TCL_ERROR
396 *
397 * Side effects:
398 * Sets the result message.
399 *
400 *----------------------------------------------------------------------
401 */
402 int
NsfArgumentError(Tcl_Interp * interp,const char * errorMsg,Nsf_Param const * paramPtr,Tcl_Obj * cmdNameObj,Tcl_Obj * methodPathObj)403 NsfArgumentError(
404 Tcl_Interp *interp,
405 const char *errorMsg, Nsf_Param const *paramPtr,
406 Tcl_Obj *cmdNameObj, Tcl_Obj *methodPathObj
407 ) {
408 Tcl_Obj *argStringObj = NsfParamDefsSyntax(interp, paramPtr, NULL, NULL);
409
410 nonnull_assert(interp != NULL);
411 nonnull_assert(errorMsg != NULL);
412 nonnull_assert(paramPtr != NULL);
413
414 NsfObjWrongArgs(interp, errorMsg, cmdNameObj, methodPathObj, ObjStr(argStringObj));
415 DECR_REF_COUNT2("paramDefsObj", argStringObj);
416
417 return TCL_ERROR;
418 }
419
420 /*
421 *----------------------------------------------------------------------
422 *
423 * NsfUnexpectedArgumentError --
424 *
425 * Produce an error message on an unexpected argument (most likely,
426 * too many arguments)
427 *
428 * Results:
429 * TCL_ERROR
430 *
431 * Side effects:
432 * Sets the result message.
433 *
434 *----------------------------------------------------------------------
435 */
436 int
NsfUnexpectedArgumentError(Tcl_Interp * interp,const char * argumentString,Nsf_Object * object,Nsf_Param const * paramPtr,Tcl_Obj * methodPathObj)437 NsfUnexpectedArgumentError(Tcl_Interp *interp, const char *argumentString,
438 Nsf_Object *object, Nsf_Param const *paramPtr,
439 Tcl_Obj *methodPathObj) {
440 Tcl_DString ds, *dsPtr = &ds;
441
442 nonnull_assert(interp != NULL);
443 nonnull_assert(argumentString != NULL);
444 nonnull_assert(paramPtr != NULL);
445 nonnull_assert(methodPathObj != NULL);
446
447 DSTRING_INIT(dsPtr);
448 Nsf_DStringPrintf(dsPtr, "invalid argument '%s', maybe too many arguments;", argumentString);
449 NsfArgumentError(interp, Tcl_DStringValue(dsPtr), paramPtr, (object != NULL) ? object->cmdName : NULL,
450 methodPathObj);
451 DSTRING_FREE(dsPtr);
452 return TCL_ERROR;
453 }
454
455 /*
456 *----------------------------------------------------------------------
457 *
458 * NsfUnexpectedNonposArgumentError --
459 *
460 * Produce an error message on an invalid non-positional argument.
461 *
462 * Results:
463 * TCL_ERROR
464 *
465 * Side effects:
466 * Sets the result message.
467 *
468 *----------------------------------------------------------------------
469 */
470 int
NsfUnexpectedNonposArgumentError(Tcl_Interp * interp,const char * argumentString,Nsf_Object * object,Nsf_Param const * currentParamPtr,Nsf_Param const * paramPtr,Tcl_Obj * methodPathObj)471 NsfUnexpectedNonposArgumentError(
472 Tcl_Interp *interp,
473 const char *argumentString,
474 Nsf_Object *object,
475 Nsf_Param const *currentParamPtr,
476 Nsf_Param const *paramPtr,
477 Tcl_Obj *methodPathObj
478 ) {
479 Tcl_DString ds, *dsPtr = &ds;
480 const Nsf_Param *pPtr;
481
482 nonnull_assert(interp != NULL);
483 nonnull_assert(argumentString != NULL);
484 nonnull_assert(currentParamPtr != NULL);
485 nonnull_assert(paramPtr != NULL);
486 nonnull_assert(methodPathObj != NULL);
487
488 DSTRING_INIT(dsPtr);
489 Nsf_DStringPrintf(dsPtr, "invalid non-positional argument '%s', valid are: ", argumentString);
490 for (pPtr = currentParamPtr; (pPtr->name != NULL) && (*pPtr->name == '-'); pPtr ++) {
491 if (pPtr->flags & NSF_ARG_NOCONFIG) {
492 continue;
493 }
494 Tcl_DStringAppend(dsPtr, pPtr->name, -1);
495 Tcl_DStringAppend(dsPtr, ", ", -1);
496 }
497 Tcl_DStringSetLength(dsPtr, Tcl_DStringLength(dsPtr) - 2);
498 Tcl_DStringAppend(dsPtr, ";\n", 2);
499
500 NsfArgumentError(interp, Tcl_DStringValue(dsPtr), paramPtr, (object != NULL) ? object->cmdName : NULL,
501 methodPathObj);
502 DSTRING_FREE(dsPtr);
503 return TCL_ERROR;
504 }
505
506 /*
507 *----------------------------------------------------------------------
508 *
509 * NsfDispatchClientDataError --
510 *
511 * Produce an error message when a method was not dispatched on an
512 * object.
513 *
514 * Results:
515 * TCL_ERROR
516 *
517 * Side effects:
518 * Sets the result message.
519 *
520 *----------------------------------------------------------------------
521 */
522 int
NsfDispatchClientDataError(Tcl_Interp * interp,ClientData clientData,const char * what,const char * methodName)523 NsfDispatchClientDataError(
524 Tcl_Interp *interp, ClientData clientData,
525 const char *what, const char *methodName
526 ) {
527
528 nonnull_assert(interp != NULL);
529 nonnull_assert(what != NULL);
530 nonnull_assert(methodName != NULL);
531
532 if (clientData != NULL) {
533 return NsfPrintError(interp, "method %s not dispatched on valid %s",
534 methodName, what);
535 } else {
536 return NsfNoCurrentObjectError(interp, methodName);
537 }
538 }
539
540 /*
541 *----------------------------------------------------------------------
542 *
543 * NsfNoCurrentObjectError --
544 *
545 * Produce an error message when a method/command was called
546 * outside the context of an object or a method. The passed in
547 * methodName is NULL when e.g. "self" is called outside of a NSF
548 * context.
549 *
550 * Results:
551 * TCL_ERROR
552 *
553 * Side effects:
554 * Sets the result message.
555 *
556 *----------------------------------------------------------------------
557 */
558 int
NsfNoCurrentObjectError(Tcl_Interp * interp,const char * methodName)559 NsfNoCurrentObjectError(Tcl_Interp *interp, const char *methodName) {
560
561 nonnull_assert(interp != NULL);
562
563 return NsfPrintError(interp, "no current object; %s called outside the context of a Next Scripting method",
564 (methodName != NULL) ? methodName : "command");
565 }
566
567 /*
568 *----------------------------------------------------------------------
569 *
570 * NsfObjErrType --
571 *
572 * Produce a general error message when an NSF method is called with
573 * an invalid value for some argument.
574 *
575 * Results:
576 * TCL_ERROR
577 *
578 * Side effects:
579 * Sets the result message.
580 *
581 *----------------------------------------------------------------------
582 */
583 int
NsfObjErrType(Tcl_Interp * interp,const char * context,Tcl_Obj * value,const char * type,Nsf_Param const * NsfObjErrType)584 NsfObjErrType(
585 Tcl_Interp *interp,
586 const char *context,
587 Tcl_Obj *value,
588 const char *type,
589 Nsf_Param const *NsfObjErrType
590 ) {
591 bool isNamed = (NsfObjErrType && (NsfObjErrType->flags & NSF_ARG_UNNAMED) == 0);
592 int returnValue = !isNamed && NsfObjErrType && (NsfObjErrType->flags & NSF_ARG_IS_RETURNVALUE);
593 int errMsgLen;
594 const char *prevErrMsg = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &errMsgLen);
595 Tcl_DString ds;
596
597 Tcl_DStringInit(&ds);
598 if (errMsgLen > 0) {
599 Tcl_DStringAppend(&ds, prevErrMsg, errMsgLen);
600 Tcl_DStringAppend(&ds, " 2nd error: ", -1);
601 }
602
603 if (context != NULL) {
604 Tcl_DStringAppend(&ds, context, -1);
605 Tcl_DStringAppend(&ds, ": ", 2);
606 }
607
608 Nsf_DStringPrintf(&ds, "expected %s but got \"%s\"", type, ObjStr(value));
609 if (isNamed) {
610 Nsf_DStringPrintf(&ds, " for parameter \"%s\"", NsfObjErrType->name);
611 } else if (returnValue != 0) {
612 Tcl_DStringAppend(&ds, " as return value", -1);
613 }
614
615 Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)));
616 Tcl_DStringFree(&ds);
617
618 return TCL_ERROR;
619 }
620
621 /*
622 * Local Variables:
623 * mode: c
624 * c-basic-offset: 2
625 * fill-column: 72
626 * indent-tabs-mode: nil
627 * eval: (c-guess)
628 * End:
629 */
630