1 // Copyright 2012 The Kyua Authors. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // * Neither the name of Google Inc. nor the names of its contributors 14 // may be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 #include "utils/config/lua_module.hpp" 30 31 #include <lutok/stack_cleaner.hpp> 32 #include <lutok/state.ipp> 33 34 #include "utils/config/exceptions.hpp" 35 #include "utils/config/keys.hpp" 36 #include "utils/config/tree.ipp" 37 38 namespace config = utils::config; 39 namespace detail = utils::config::detail; 40 41 42 namespace { 43 44 45 /// Gets the tree singleton stored in the Lua state. 46 /// 47 /// \param state The Lua state. The registry must contain a key named 48 /// "tree" with a pointer to the singleton. 49 /// 50 /// \return A reference to the tree associated with the Lua state. 51 /// 52 /// \throw syntax_error If the tree cannot be located. 53 config::tree& 54 get_global_tree(lutok::state& state) 55 { 56 lutok::stack_cleaner cleaner(state); 57 58 state.push_value(lutok::registry_index); 59 state.push_string("tree"); 60 state.get_table(-2); 61 if (state.is_nil(-1)) 62 throw config::syntax_error("Cannot find tree singleton; global state " 63 "corrupted?"); 64 config::tree& tree = **state.to_userdata< config::tree* >(-1); 65 state.pop(1); 66 return tree; 67 } 68 69 70 /// Gets a fully-qualified tree key from the state. 71 /// 72 /// \param state The Lua state. 73 /// \param table_index An index to the Lua stack pointing to the table being 74 /// accessed. If this table contains a tree_key metadata property, this is 75 /// considered to be the prefix of the tree key. 76 /// \param field_index An index to the Lua stack pointing to the entry 77 /// containing the name of the field being indexed. 78 /// 79 /// \return A dotted key. 80 /// 81 /// \throw invalid_key_error If the name of the key is invalid. 82 static std::string 83 get_tree_key(lutok::state& state, const int table_index, const int field_index) 84 { 85 PRE(state.is_string(field_index)); 86 const std::string field = state.to_string(field_index); 87 if (!field.empty() && field[0] == '_') 88 throw config::invalid_key_error( 89 F("Configuration key cannot have an underscore as a prefix; " 90 "found %s") % field); 91 92 std::string tree_key; 93 if (state.get_metafield(table_index, "tree_key")) { 94 tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1); 95 state.pop(1); 96 } else 97 tree_key = state.to_string(field_index); 98 return tree_key; 99 } 100 101 102 static int redirect_newindex(lutok::state&); 103 static int redirect_index(lutok::state&); 104 105 106 /// Creates a table for a new configuration inner node. 107 /// 108 /// \post state(-1) Contains the new table. 109 /// 110 /// \param state The Lua state in which to push the table. 111 /// \param tree_key The key to which the new table corresponds. 112 static void 113 new_table_for_key(lutok::state& state, const std::string& tree_key) 114 { 115 state.new_table(); 116 { 117 state.new_table(); 118 { 119 state.push_string("__index"); 120 state.push_cxx_function(redirect_index); 121 state.set_table(-3); 122 123 state.push_string("__newindex"); 124 state.push_cxx_function(redirect_newindex); 125 state.set_table(-3); 126 127 state.push_string("tree_key"); 128 state.push_string(tree_key); 129 state.set_table(-3); 130 } 131 state.set_metatable(-2); 132 } 133 } 134 135 136 /// Sets the value of an configuration node. 137 /// 138 /// \pre state(-3) The table to index. If this is not _G, then the table 139 /// metadata must contain a tree_key property describing the path to 140 /// current level. 141 /// \pre state(-2) The field to index into the table. Must be a string. 142 /// \pre state(-1) The value to set the indexed table field to. 143 /// 144 /// \param state The Lua state in which to operate. 145 /// 146 /// \return The number of result values on the Lua stack; always 0. 147 /// 148 /// \throw invalid_key_error If the provided key is invalid. 149 /// \throw unknown_key_error If the key cannot be located. 150 /// \throw value_error If the value has an unsupported type or cannot be 151 /// set on the key, or if the input table or index are invalid. 152 static int 153 redirect_newindex(lutok::state& state) 154 { 155 if (!state.is_table(-3)) 156 throw config::value_error("Indexed object is not a table"); 157 if (!state.is_string(-2)) 158 throw config::value_error("Invalid field in configuration object " 159 "reference; must be a string"); 160 161 const std::string dotted_key = get_tree_key(state, -3, -2); 162 try { 163 config::tree& tree = get_global_tree(state); 164 tree.set_lua(dotted_key, state, -1); 165 } catch (const config::value_error& e) { 166 throw config::invalid_key_value(detail::parse_key(dotted_key), 167 e.what()); 168 } 169 170 // Now really set the key in the Lua table, but prevent direct accesses from 171 // the user by prefixing it. We do this to ensure that re-setting the same 172 // key of the tree results in a call to __newindex instead of __index. 173 state.push_string("_" + state.to_string(-2)); 174 state.push_value(-2); 175 state.raw_set(-5); 176 177 return 0; 178 } 179 180 181 /// Indexes a configuration node. 182 /// 183 /// \pre state(-3) The table to index. If this is not _G, then the table 184 /// metadata must contain a tree_key property describing the path to 185 /// current level. If the field does not exist, a new table is created. 186 /// \pre state(-1) The field to index into the table. Must be a string. 187 /// 188 /// \param state The Lua state in which to operate. 189 /// 190 /// \return The number of result values on the Lua stack; always 1. 191 /// 192 /// \throw value_error If the input table or index are invalid. 193 static int 194 redirect_index(lutok::state& state) 195 { 196 if (!state.is_table(-2)) 197 throw config::value_error("Indexed object is not a table"); 198 if (!state.is_string(-1)) 199 throw config::value_error("Invalid field in configuration object " 200 "reference; must be a string"); 201 202 // Query if the key has already been set by a call to redirect_newindex. 203 state.push_string("_" + state.to_string(-1)); 204 state.raw_get(-3); 205 if (!state.is_nil(-1)) 206 return 1; 207 state.pop(1); 208 209 state.push_value(-1); // Duplicate the field name. 210 state.raw_get(-3); // Get table[field] to see if it's defined. 211 if (state.is_nil(-1)) { 212 state.pop(1); 213 214 // The stack is now the same as when we entered the function, but we 215 // know that the field is undefined and thus have to create a new 216 // configuration table. 217 INV(state.is_table(-2)); 218 INV(state.is_string(-1)); 219 220 const config::tree& tree = get_global_tree(state); 221 const std::string tree_key = get_tree_key(state, -2, -1); 222 if (tree.is_set(tree_key)) { 223 // Publish the pre-recorded value in the tree to the Lua state, 224 // instead of considering this table key a new inner node. 225 tree.push_lua(tree_key, state); 226 } else { 227 state.push_string("_" + state.to_string(-1)); 228 state.insert(-2); 229 state.pop(1); 230 231 new_table_for_key(state, tree_key); 232 233 // Duplicate the newly created table and place it deep in the stack 234 // so that the raw_set below leaves us with the return value of this 235 // function at the top of the stack. 236 state.push_value(-1); 237 state.insert(-4); 238 239 state.raw_set(-3); 240 state.pop(1); 241 } 242 } 243 return 1; 244 } 245 246 247 } // anonymous namespace 248 249 250 /// Install wrappers for globals to set values in the configuration tree. 251 /// 252 /// This function installs wrappers to capture all accesses to global variables. 253 /// Such wrappers redirect the reads and writes to the out_tree, which is the 254 /// entity that defines what configuration variables exist. 255 /// 256 /// \param state The Lua state into which to install the wrappers. 257 /// \param out_tree The tree with the layout definition and where the 258 /// configuration settings will be collected. 259 void 260 config::redirect(lutok::state& state, tree& out_tree) 261 { 262 lutok::stack_cleaner cleaner(state); 263 264 state.get_global_table(); 265 { 266 state.push_string("__index"); 267 state.push_cxx_function(redirect_index); 268 state.set_table(-3); 269 270 state.push_string("__newindex"); 271 state.push_cxx_function(redirect_newindex); 272 state.set_table(-3); 273 } 274 state.set_metatable(-1); 275 276 state.push_value(lutok::registry_index); 277 state.push_string("tree"); 278 config::tree** tree = state.new_userdata< config::tree* >(); 279 *tree = &out_tree; 280 state.set_table(-3); 281 state.pop(1); 282 } 283