Skip to content

Commit

Permalink
Check that constants are never subject to local/global assignments
Browse files Browse the repository at this point in the history
Of course using low-level \cs_ functions one can still assign to constants.
  • Loading branch information
Bruno Le Floch committed Nov 29, 2017
1 parent bf69f14 commit f59b697
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 36 deletions.
85 changes: 51 additions & 34 deletions l3kernel/l3basics.dtx
Expand Up @@ -1259,21 +1259,31 @@
% \cs{cs_if_exist_p:N}, and if not raises a kernel-level error.
% \end{function}
%
% \begin{function}{\__debug_chk_var_scope:NN}
% \begin{syntax}
% \cs{__debug_chk_var_scope:NN} \meta{scope} \meta{var}
% \end{syntax}
% Checks the \meta{var} has the correct \meta{scope}, and if not
% raises a kernel-level error. This function is only created if
% debugging is enabled. The \meta{scope} is a single letter |l|, |g|,
% |c| denoting local variables, global variables, or constants. More
% precisely, if the variable name starts with a letter and an
% underscore (normal \pkg{expl3} convention) the function checks that
% this single letter matches the \meta{scope}. Otherwise the function
% cannot know the scope \meta{var} the first time: instead, it defines
% |\__debug_chk_/|\meta{var name} to store that information for the
% next call. Thus, if a given \meta{var} is subject to assignments of
% different scopes a kernel error will result.
% \end{function}
%
% \begin{function}{\__debug_chk_var_local:N, \__debug_chk_var_global:N}
% \begin{syntax}
% \cs{__debug_chk_var_local:N} \meta{var}
% \cs{__debug_chk_var_global:N} \meta{var}
% \end{syntax}
% These functions are only created if debugging is enabled. They
% check that \meta{var} is a local/global variable, and if not raises
% a kernel-level error. More precisely, if the variable name starts
% with a letter and an underscore (normal \pkg{expl3} convention) the
% functions check that this single letter is |l| or |g| as
% appropriate. Otherwise the functions cannot know the first time
% whether \meta{var} is local or global: instead, they define
% |\__debug_chk_/|\meta{var name} to store the information, and if
% both \cs{__debug_chk_var_local:N} and \cs{__debug_chk_var_global:N}
% are called on the same variable a kernel-level error is raised.
% Applies \cs{__debug_chk_var_exist:N} \meta{var}, then
% \cs{__debug_chk_var_scope:NN} \meta{scope} \meta{var}, where
% \meta{scope} is |l| or~|g|.
% \end{function}
%
% \begin{function}{\__debug_log:x}
Expand Down Expand Up @@ -1858,14 +1868,16 @@
% \begin{macro}[int]{\@@_chk_var_exist:N}
% \begin{macro}[int]{\@@_chk_cs_exist:N, \@@_chk_cs_exist:c}
% \begin{macro}[int]{\@@_chk_var_local:N, \@@_chk_var_global:N}
% \begin{macro}[int]{\@@_chk_var_scope:NN}
% When debugging is enabled these two functions set up functions that
% test their argument (when \texttt{check-declarations} is active)
% \begin{itemize}
% \item \cs{@@_chk_var_exist:N} and \cs{@@_chk_cs_exist:N}, two
% functions that test that their argument is defined;
% \item \cs{@@_chk_var_local:N} and \cs{@@_chk_var_global:N}, two
% functions that check that their argument is a local (resp.\@
% global) variable.
% \item \cs{@@_chk_var_scope:NN} that checks that its argument |#2|
% has scope |#1|.
% \item \cs{@@_chk_var_local:N} and \cs{@@_chk_var_global:N} that
% perform both checks.
% \end{itemize}
% \begin{macrocode}
\@@:TF
Expand All @@ -1890,27 +1902,31 @@
{ \token_to_str:N ##1 }
}
}
\cs_set_protected:Npn \@@_chk_var_scope:NN
{
\@@_suspended:T \use_none:nnn
\@@_chk_var_scope_aux:NN
}
\cs_set_protected:Npn \@@_chk_var_local:N ##1
{
\@@_suspended:T \use_none:nnnnn
\@@_chk_var_exist:N ##1
\@@_chk_var_local_aux:NN l ##1
\@@_chk_var_scope_aux:NN l ##1
}
\cs_set_protected:Npn \@@_chk_var_global:N ##1
{
\@@_suspended:T \use_none:nnnnn
\@@_chk_var_exist:N ##1
\@@_chk_var_local_aux:NN g ##1
\@@_chk_var_scope_aux:NN g ##1
}
}
\exp_args:Nc \cs_set_protected:Npn { @@_check-declarations_off: }
{
\cs_set_protected:Npn \@@_chk_var_exist:N ##1 { }
\cs_set_protected:Npn \@@_chk_cs_exist:N ##1 { }
\cs_set_protected:Npn \@@_chk_var_local:N ##1
{ \@@_chk_var_exist:N ##1 }
\cs_set_protected:Npn \@@_chk_var_global:N ##1
{ \@@_chk_var_exist:N ##1 }
\cs_set_protected:Npn \@@_chk_var_local:N ##1 { }
\cs_set_protected:Npn \@@_chk_var_global:N ##1 { }
\cs_set_protected:Npn \@@_chk_var_scope:NN ##1##2 { }
}
\cs_set_protected:Npn \@@_chk_cs_exist:c
{ \exp_args:Nc \@@_chk_cs_exist:N }
Expand All @@ -1926,40 +1942,41 @@
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[aux]{\@@_chk_var_local_aux:NN}
% \begin{macro}[aux]{\@@_chk_var_local_aux:Nn}
% \begin{macro}[aux]{\@@_chk_var_local_aux:NNn}
% \begin{macro}[aux]{\@@_chk_var_scope_aux:NN}
% \begin{macro}[aux]{\@@_chk_var_scope_aux:Nn}
% \begin{macro}[aux]{\@@_chk_var_scope_aux:NNn}
% First check whether the name of the variable |#2| starts with
% \meta{letter}|_|. If it does then pass that letter, the target
% letter |l| or |g|, and the variable name to
% \cs{@@_chk_var_local_aux:NNn}. That function compares the two
% \meta{letter}|_|. If it does then pass that letter, the
% \meta{scope}, and the variable name to
% \cs{@@_chk_var_scope_aux:NNn}. That function compares the two
% letters and triggers an error if they differ (the \cs{scan_stop:}
% case is not reachable here). If the second character was not |_|
% then pass the same data to the same auxiliary, except for its first
% argument which is now a control sequence. That control sequence is
% actually a token list (but to avoid triggering the checking code we
% manipulate it using \cs{cs_set_nopar:Npn}) containing a single
% letter |l| or |g| according to what the first assignment to the
% letter \meta{scope} according to what the first assignment to the
% given variable was.
% \begin{macrocode}
\@@:TF
{
\cs_set_protected:Npn \@@_chk_var_local_aux:NN #1#2
{ \exp_args:NNf \@@_chk_var_local_aux:Nn #1 { \cs_to_str:N #2 } }
\cs_set_protected:Npn \@@_chk_var_local_aux:Nn #1#2
\cs_set_protected:Npn \@@_chk_var_scope_aux:NN #1#2
{ \exp_args:NNf \@@_chk_var_scope_aux:Nn #1 { \cs_to_str:N #2 } }
\cs_set_protected:Npn \@@_chk_var_scope_aux:Nn #1#2
{
\if:w _ \use_i:nn \tl_head:w #2 ? ? \q_stop
\exp_after:wN \@@_chk_var_local_aux:NNn
\tl_head:w #2 ? \q_stop
\if:w _ \use_i:nn \use_i_delimit_by_q_stop:nw #2 ? ? \q_stop
\exp_after:wN \@@_chk_var_scope_aux:NNn
\use_i_delimit_by_q_stop:nw #2 ? \q_stop
#1 {#2}
\else:
\exp_args:Nc \@@_chk_var_local_aux:NNn
\exp_args:Nc \@@_chk_var_scope_aux:NNn
{ @@_chk_/ #2 }
#1 {#2}
\fi:
}
\cs_set_protected:Npn \@@_chk_var_local_aux:NNn #1#2#3
\cs_set_protected:Npn \@@_chk_var_scope_aux:NNn #1#2#3
{
\if:w #1 #2
\else:
Expand Down
1 change: 1 addition & 0 deletions l3kernel/l3candidates.dtx
Expand Up @@ -1977,6 +1977,7 @@
% \begin{macro}[added = 2017-11-28]{\bool_const:Nn, \bool_const:cn}
% A merger between \cs{tl_const:Nn} and \cs{bool_set:Nn}.
% \begin{macrocode}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \bool_const:Nn #1#2
{
\__chk_if_free_cs:N #1
Expand Down
4 changes: 3 additions & 1 deletion l3kernel/l3int.dtx
Expand Up @@ -1203,7 +1203,9 @@
% We cannot use \cs{int_gset:Nn} because (when |check-declarations| is
% enabled) this runs some checks that constants would fail.
% \begin{macrocode}
\__debug_patch_args:nNNpn
\__debug_patch_args:nnnNNpn
{ \__debug_chk_var_scope:NN c #1 }
{ }
{ {#1} { \__debug_chk_expr:nNnN {#2} \@@_eval:w { } \int_const:Nn } }
\cs_new_protected:Npn \int_const:Nn #1#2
{
Expand Down
7 changes: 6 additions & 1 deletion l3kernel/l3quark.dtx
Expand Up @@ -375,7 +375,12 @@
% \UnitTested
% Allocate a new quark.
% \begin{macrocode}
\cs_new_protected:Npn \quark_new:N #1 { \tl_const:Nn #1 {#1} }
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN q #1 } { }
\cs_new_protected:Npn \quark_new:N #1
{
\__chk_if_free_cs:N #1
\cs_gset_nopar:Npn #1 {#1}
}
% \end{macrocode}
% \end{macro}
%
Expand Down
3 changes: 3 additions & 0 deletions l3kernel/l3skip.dtx
Expand Up @@ -1064,6 +1064,7 @@
% \cs{dim_eval:n} to avoid needing a debugging patch that wraps the
% expression in checking code.
% \begin{macrocode}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \dim_const:Nn #1#2
{
\dim_new:N #1
Expand Down Expand Up @@ -1658,6 +1659,7 @@
% even for constants. See \cs{dim_const:Nn} for why we cannot use
% \cs{skip_gset:Nn}.
% \begin{macrocode}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \skip_const:Nn #1#2
{
\skip_new:N #1
Expand Down Expand Up @@ -1949,6 +1951,7 @@
% \begin{macro}{\muskip_const:Nn, \muskip_const:cn}
% See \cs{skip_const:Nn}.
% \begin{macrocode}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \muskip_const:Nn #1#2
{
\muskip_new:N #1
Expand Down
2 changes: 2 additions & 0 deletions l3kernel/l3tl.dtx
Expand Up @@ -1131,11 +1131,13 @@
% \begin{macro}{\tl_const:Nn, \tl_const:Nx, \tl_const:cn, \tl_const:cx}
% Constants are also easy to generate.
% \begin{macrocode}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \tl_const:Nn #1#2
{
\__chk_if_free_cs:N #1
\cs_gset_nopar:Npx #1 { \exp_not:n {#2} }
}
\__debug_patch:nnNNpn { \__debug_chk_var_scope:NN c #1 } { }
\cs_new_protected:Npn \tl_const:Nx #1#2
{
\__chk_if_free_cs:N #1
Expand Down

0 comments on commit f59b697

Please sign in to comment.