Skip to content

Commit ad3d3f2

Browse files
committed
Improve SyntaxErrors for bad future statements. Set file and location
for errors raised in future.c. Move some helper functions from compile.c to errors.c and make them API functions: PyErr_SyntaxLocation() and PyErr_ProgramText().
1 parent 5687ffe commit ad3d3f2

File tree

4 files changed

+113
-94
lines changed

4 files changed

+113
-94
lines changed

Include/pyerrors.h

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ extern DL_IMPORT(int) PyErr_Warn(PyObject *, char *);
9898
/* In sigcheck.c or signalmodule.c */
9999
extern DL_IMPORT(int) PyErr_CheckSignals(void);
100100
extern DL_IMPORT(void) PyErr_SetInterrupt(void);
101+
102+
/* Support for adding program text to SyntaxErrors */
103+
extern DL_IMPORT(void) PyErr_SyntaxLocation(char *, int);
104+
extern DL_IMPORT(PyObject *) PyErr_ProgramText(char *, int);
101105

102106

103107
#ifdef __cplusplus

Python/compile.c

+6-84
Original file line numberDiff line numberDiff line change
@@ -381,49 +381,6 @@ int is_free(int v)
381381
return 0;
382382
}
383383

384-
/* com_fetch_program_text will attempt to load the line of text that
385-
the exception refers to. If it fails, it will return NULL but will
386-
not set an exception.
387-
388-
XXX The functionality of this function is quite similar to the
389-
functionality in tb_displayline() in traceback.c.
390-
*/
391-
392-
static PyObject *
393-
fetch_program_text(char *filename, int lineno)
394-
{
395-
FILE *fp;
396-
int i;
397-
char linebuf[1000];
398-
399-
if (filename == NULL || lineno <= 0)
400-
return NULL;
401-
fp = fopen(filename, "r");
402-
if (fp == NULL)
403-
return NULL;
404-
for (i = 0; i < lineno; i++) {
405-
char *pLastChar = &linebuf[sizeof(linebuf) - 2];
406-
do {
407-
*pLastChar = '\0';
408-
if (fgets(linebuf, sizeof linebuf, fp) == NULL)
409-
break;
410-
/* fgets read *something*; if it didn't get as
411-
far as pLastChar, it must have found a newline
412-
or hit the end of the file; if pLastChar is \n,
413-
it obviously found a newline; else we haven't
414-
yet seen a newline, so must continue */
415-
} while (*pLastChar != '\0' && *pLastChar != '\n');
416-
}
417-
fclose(fp);
418-
if (i == lineno) {
419-
char *p = linebuf;
420-
while (*p == ' ' || *p == '\t' || *p == '\014')
421-
p++;
422-
return PyString_FromString(p);
423-
}
424-
return NULL;
425-
}
426-
427384
static void
428385
com_error(struct compiling *c, PyObject *exc, char *msg)
429386
{
@@ -445,7 +402,7 @@ com_error(struct compiling *c, PyObject *exc, char *msg)
445402
if (v == NULL)
446403
return; /* MemoryError, too bad */
447404

448-
line = fetch_program_text(c->c_filename, c->c_lineno);
405+
line = PyErr_ProgramText(c->c_filename, c->c_lineno);
449406
if (line == NULL) {
450407
Py_INCREF(Py_None);
451408
line = Py_None;
@@ -4028,41 +3985,6 @@ get_ref_type(struct compiling *c, char *name)
40283985

40293986
/* Helper function for setting lineno and filename */
40303987

4031-
static void
4032-
set_error_location(char *filename, int lineno)
4033-
{
4034-
PyObject *exc, *v, *tb, *tmp;
4035-
4036-
/* add attributes for the line number and filename for the error */
4037-
PyErr_Fetch(&exc, &v, &tb);
4038-
PyErr_NormalizeException(&exc, &v, &tb);
4039-
tmp = PyInt_FromLong(lineno);
4040-
if (tmp == NULL)
4041-
PyErr_Clear();
4042-
else {
4043-
if (PyObject_SetAttrString(v, "lineno", tmp))
4044-
PyErr_Clear();
4045-
Py_DECREF(tmp);
4046-
}
4047-
if (filename != NULL) {
4048-
tmp = PyString_FromString(filename);
4049-
if (tmp == NULL)
4050-
PyErr_Clear();
4051-
else {
4052-
if (PyObject_SetAttrString(v, "filename", tmp))
4053-
PyErr_Clear();
4054-
Py_DECREF(tmp);
4055-
}
4056-
4057-
tmp = fetch_program_text(filename, lineno);
4058-
if (tmp) {
4059-
PyObject_SetAttrString(v, "text", tmp);
4060-
Py_DECREF(tmp);
4061-
}
4062-
}
4063-
PyErr_Restore(exc, v, tb);
4064-
}
4065-
40663988
static int
40673989
symtable_build(struct compiling *c, node *n)
40683990
{
@@ -4198,7 +4120,7 @@ symtable_update_flags(struct compiling *c, PySymtableEntryObject *ste,
41984120
PyErr_Format(PyExc_SyntaxError,
41994121
ILLEGAL_DYNAMIC_SCOPE,
42004122
PyString_AS_STRING(ste->ste_name));
4201-
set_error_location(c->c_symtable->st_filename,
4123+
PyErr_SyntaxLocation(c->c_symtable->st_filename,
42024124
ste->ste_lineno);
42034125
return -1;
42044126
} else {
@@ -4273,7 +4195,7 @@ symtable_load_symbols(struct compiling *c)
42734195
if (flags & DEF_PARAM) {
42744196
PyErr_Format(PyExc_SyntaxError, LOCAL_GLOBAL,
42754197
PyString_AS_STRING(name));
4276-
set_error_location(st->st_filename,
4198+
PyErr_SyntaxLocation(st->st_filename,
42774199
ste->ste_lineno);
42784200
st->st_errors++;
42794201
goto fail;
@@ -4581,7 +4503,7 @@ symtable_add_def_o(struct symtable *st, PyObject *dict,
45814503
if ((flag & DEF_PARAM) && (val & DEF_PARAM)) {
45824504
PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT,
45834505
PyString_AsString(name));
4584-
set_error_location(st->st_filename,
4506+
PyErr_SyntaxLocation(st->st_filename,
45854507
st->st_cur->ste_lineno);
45864508
return -1;
45874509
}
@@ -4904,7 +4826,7 @@ symtable_global(struct symtable *st, node *n)
49044826
PyErr_Format(PyExc_SyntaxError,
49054827
"name '%.400s' is local and global",
49064828
name);
4907-
set_error_location(st->st_filename,
4829+
PyErr_SyntaxLocation(st->st_filename,
49084830
st->st_cur->ste_lineno);
49094831
st->st_errors++;
49104832
return;
@@ -4958,7 +4880,7 @@ symtable_import(struct symtable *st, node *n)
49584880
if (n->n_lineno >= st->st_future->ff_last_lineno) {
49594881
PyErr_SetString(PyExc_SyntaxError,
49604882
LATE_FUTURE);
4961-
set_error_location(st->st_filename,
4883+
PyErr_SyntaxLocation(st->st_filename,
49624884
n->n_lineno);
49634885
st->st_errors++;
49644886
return;

Python/errors.c

+79
Original file line numberDiff line numberDiff line change
@@ -622,3 +622,82 @@ PyErr_Warn(PyObject *category, char *message)
622622
return 0;
623623
}
624624
}
625+
626+
void
627+
PyErr_SyntaxLocation(char *filename, int lineno)
628+
{
629+
PyObject *exc, *v, *tb, *tmp;
630+
631+
/* add attributes for the line number and filename for the error */
632+
PyErr_Fetch(&exc, &v, &tb);
633+
PyErr_NormalizeException(&exc, &v, &tb);
634+
/* XXX check that it is, indeed, a syntax error */
635+
tmp = PyInt_FromLong(lineno);
636+
if (tmp == NULL)
637+
PyErr_Clear();
638+
else {
639+
if (PyObject_SetAttrString(v, "lineno", tmp))
640+
PyErr_Clear();
641+
Py_DECREF(tmp);
642+
}
643+
if (filename != NULL) {
644+
tmp = PyString_FromString(filename);
645+
if (tmp == NULL)
646+
PyErr_Clear();
647+
else {
648+
if (PyObject_SetAttrString(v, "filename", tmp))
649+
PyErr_Clear();
650+
Py_DECREF(tmp);
651+
}
652+
653+
tmp = PyErr_ProgramText(filename, lineno);
654+
if (tmp) {
655+
PyObject_SetAttrString(v, "text", tmp);
656+
Py_DECREF(tmp);
657+
}
658+
}
659+
PyErr_Restore(exc, v, tb);
660+
}
661+
662+
/* com_fetch_program_text will attempt to load the line of text that
663+
the exception refers to. If it fails, it will return NULL but will
664+
not set an exception.
665+
666+
XXX The functionality of this function is quite similar to the
667+
functionality in tb_displayline() in traceback.c.
668+
*/
669+
670+
PyObject *
671+
PyErr_ProgramText(char *filename, int lineno)
672+
{
673+
FILE *fp;
674+
int i;
675+
char linebuf[1000];
676+
677+
if (filename == NULL || lineno <= 0)
678+
return NULL;
679+
fp = fopen(filename, "r");
680+
if (fp == NULL)
681+
return NULL;
682+
for (i = 0; i < lineno; i++) {
683+
char *pLastChar = &linebuf[sizeof(linebuf) - 2];
684+
do {
685+
*pLastChar = '\0';
686+
if (fgets(linebuf, sizeof linebuf, fp) == NULL)
687+
break;
688+
/* fgets read *something*; if it didn't get as
689+
far as pLastChar, it must have found a newline
690+
or hit the end of the file; if pLastChar is \n,
691+
it obviously found a newline; else we haven't
692+
yet seen a newline, so must continue */
693+
} while (*pLastChar != '\0' && *pLastChar != '\n');
694+
}
695+
fclose(fp);
696+
if (i == lineno) {
697+
char *p = linebuf;
698+
while (*p == ' ' || *p == '\t' || *p == '\014')
699+
p++;
700+
return PyString_FromString(p);
701+
}
702+
return NULL;
703+
}

Python/future.c

+24-10
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,40 @@
66
#include "symtable.h"
77

88
#define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined"
9+
#define FUTURE_IMPORT_STAR "future statement does not support import *"
910

1011
#define FUTURE_POSSIBLE(FF) ((FF)->ff_last_lineno == -1)
1112

1213
static int
13-
future_check_features(PyFutureFeatures *ff, node *n)
14+
future_check_features(PyFutureFeatures *ff, node *n, char *filename)
1415
{
1516
int i;
1617
char *feature;
18+
node *ch;
1719

1820
REQ(n, import_stmt); /* must by from __future__ import ... */
1921

2022
for (i = 3; i < NCH(n); ++i) {
21-
feature = STR(CHILD(CHILD(n, i), 0));
23+
ch = CHILD(n, i);
24+
if (TYPE(ch) == STAR) {
25+
PyErr_SetString(PyExc_SyntaxError,
26+
FUTURE_IMPORT_STAR);
27+
PyErr_SyntaxLocation(filename, ch->n_lineno);
28+
return -1;
29+
}
30+
REQ(ch, import_as_name);
31+
feature = STR(CHILD(ch, 0));
2232
if (strcmp(feature, FUTURE_NESTED_SCOPES) == 0) {
2333
ff->ff_nested_scopes = 1;
34+
} else if (strcmp(feature, "braces") == 0) {
35+
PyErr_SetString(PyExc_SyntaxError,
36+
"not a chance");
37+
PyErr_SyntaxLocation(filename, CHILD(ch, 0)->n_lineno);
38+
return -1;
2439
} else {
2540
PyErr_Format(PyExc_SyntaxError,
2641
UNDEFINED_FUTURE_FEATURE, feature);
42+
PyErr_SyntaxLocation(filename, CHILD(ch, 0)->n_lineno);
2743
return -1;
2844
}
2945
}
@@ -36,6 +52,7 @@ future_error(node *n, char *filename)
3652
PyErr_SetString(PyExc_SyntaxError,
3753
"from __future__ imports must occur at the "
3854
"beginning of the file");
55+
PyErr_SyntaxLocation(filename, n->n_lineno);
3956
/* XXX set filename and lineno */
4057
}
4158

@@ -45,8 +62,10 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
4562
file_input: (NEWLINE | stmt)* ENDMARKER
4663
stmt: simple_stmt | compound_stmt
4764
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
48-
small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | exec_stmt | assert_stmt
49-
import_stmt: 'import' dotted_as_name (',' dotted_as_name)* | 'from' dotted_name 'import' ('*' | import_as_name (',' import_as_name)*)
65+
small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt
66+
| import_stmt | global_stmt | exec_stmt | assert_stmt
67+
import_stmt: 'import' dotted_as_name (',' dotted_as_name)*
68+
| 'from' dotted_name 'import' ('*' | import_as_name (',' import_as_name)*)
5069
import_as_name: NAME [NAME NAME]
5170
dotted_as_name: dotted_name [NAME NAME]
5271
dotted_name: NAME ('.' NAME)*
@@ -64,11 +83,6 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
6483
int i, r;
6584
loop:
6685

67-
/* fprintf(stderr, "future_parse(%d, %d, %s, %d)\n",
68-
TYPE(n), NCH(n), (n == NULL) ? "NULL" : STR(n),
69-
n->n_lineno);
70-
*/
71-
7286
switch (TYPE(n)) {
7387

7488
case single_input:
@@ -162,7 +176,7 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
162176
name = CHILD(n, 1);
163177
if (strcmp(STR(CHILD(name, 0)), "__future__") != 0)
164178
return 0;
165-
if (future_check_features(ff, n) < 0)
179+
if (future_check_features(ff, n, filename) < 0)
166180
return -1;
167181
ff->ff_last_lineno = n->n_lineno + 1;
168182
return 1;

0 commit comments

Comments
 (0)