ded5c48ad3c99829db7a8735a432a0a8cffd47f4
[pspp] / src / language / dictionary / delete-variables.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2007, 2010, 2011, 2013 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <stdlib.h>
20
21 #include "data/casereader.h"
22 #include "data/dataset.h"
23 #include "data/dictionary.h"
24 #include "language/command.h"
25 #include "language/lexer/variable-parser.h"
26 #include "libpspp/message.h"
27
28 #include "gettext.h"
29 #define _(msgid) gettext (msgid)
30
31 /* Performs DELETE VARIABLES command. */
32 int
33 cmd_delete_variables (struct lexer *lexer, struct dataset *ds)
34 {
35   struct variable **vars;
36   size_t var_cnt;
37   bool ok;
38
39   if (proc_make_temporary_transformations_permanent (ds))
40     msg (SE, _("%s may not be used after %s.  "
41                "Temporary transformations will be made permanent."),
42          "DELETE VARIABLES", "TEMPORARY");
43
44   if (!parse_variables (lexer, dataset_dict (ds), &vars, &var_cnt, PV_NONE))
45     goto error;
46   if (var_cnt == dict_get_var_cnt (dataset_dict (ds)))
47     {
48       msg (SE, _("%s may not be used to delete all variables "
49                  "from the active dataset dictionary.  "
50                  "Use %s instead."), "DELETE VARIABLES", "NEW FILE");
51       goto error;
52     }
53
54   ok = casereader_destroy (proc_open_filtering (ds, false));
55   ok = proc_commit (ds) && ok;
56   if (!ok)
57     goto error;
58
59   dict_delete_vars (dataset_dict (ds), vars, var_cnt);
60
61   /* XXX A bunch of bugs conspire to make executing transformations again here
62      necessary, even though it shouldn't be.
63
64      Consider the following (which is included in delete-variables.at):
65
66         DATA LIST NOTABLE /s1 TO s2 1-2(A).
67         BEGIN DATA
68         12
69         END DATA.
70         DELETE VARIABLES s1.
71         NUMERIC n1.
72         LIST.
73
74      The DATA LIST gives us a caseproto with widths 1,1.  DELETE VARIABLES
75      deletes the first variable so we now have -1,1.  This already is
76      technically a problem because proc_casereader_read() calls
77      case_unshare_and_resize() from the former to the latter caseproto, and
78      these caseprotos are not conformable (which is a requirement for
79      case_resize()).  It doesn't cause an assert by default because
80      case_resize() uses expensive_assert() to check for it though.  However, in
81      practice we don't see a problem yet because case_resize() only does work
82      if the number of widths in the source and dest caseproto are different.
83
84      Executing NUMERIC adds a third variable, though, so we have -1,1,0.  This
85      makes caseproto_resize() notice that there are fewer strings in the new
86      caseproto.  Therefore it destroys the second one (s2).  It should destroy
87      the first one (s1), but if the caseprotos were really conformable then it
88      would have destroyed the right one.  This mistake eventually causes a bad
89      memory reference.
90
91      Executing transformations a second time after DELETE VARIABLES, like we do
92      below, works around the problem because we can never run into a situation
93      where we've got both new variables (triggering a resize) and deleted
94      variables (triggering the bad free).
95
96      We should fix this in a better way.  Doing it cleanly seems hard.  This
97      seems to work for now. */
98   ok = casereader_destroy (proc_open_filtering (ds, false));
99   ok = proc_commit (ds) && ok;
100   if (!ok)
101     goto error;
102
103   free (vars);
104
105   return CMD_SUCCESS;
106
107  error:
108   free (vars);
109   return CMD_CASCADING_FAILURE;
110 }