1 // Copyright 2012 Google Inc. 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/tree.ipp" 36 37 namespace config = utils::config; 38 39 40 namespace { 41 42 43 /// Gets the tree singleton stored in the Lua state. 44 /// 45 /// \param state The Lua state. The registry must contain a key named 46 /// "tree" with a pointer to the singleton. 47 /// 48 /// \return A reference to the tree associated with the Lua state. 49 /// 50 /// \throw syntax_error If the tree cannot be located. 51 config::tree& 52 get_global_tree(lutok::state& state) 53 { 54 lutok::stack_cleaner cleaner(state); 55 56 state.push_value(lutok::registry_index); 57 state.push_string("tree"); 58 state.get_table(-2); 59 if (state.is_nil()) 60 throw config::syntax_error("Cannot find tree singleton; global state " 61 "corrupted?"); 62 config::tree& tree = **state.to_userdata< config::tree* >(); 63 state.pop(1); 64 return tree; 65 } 66 67 68 /// Gets a fully-qualified tree key from the state. 69 /// 70 /// \param state The Lua state. 71 /// \param table_index An index to the Lua stack pointing to the table being 72 /// accessed. If this table contains a tree_key metadata property, this is 73 /// considered to be the prefix of the tree key. 74 /// \param field_index An index to the Lua stack pointing to the entry 75 /// containing the name of the field being indexed. 76 /// 77 /// \return A dotted key. 78 /// 79 /// \throw invalid_key_error If the name of the key is invalid. 80 static std::string 81 get_tree_key(lutok::state& state, const int table_index, const int field_index) 82 { 83 PRE(state.is_string(field_index)); 84 const std::string field = state.to_string(field_index); 85 if (!field.empty() && field[0] == '_') 86 throw config::invalid_key_error( 87 F("Configuration key cannot have an underscore as a prefix; " 88 "found %s") % field); 89 90 std::string tree_key; 91 if (state.get_metafield(table_index, "tree_key")) { 92 tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1); 93 state.pop(1); 94 } else 95 tree_key = state.to_string(field_index); 96 return tree_key; 97 } 98 99 100 static int redirect_newindex(lutok::state&); 101 static int redirect_index(lutok::state&); 102 103 104 /// Creates a table for a new configuration inner node. 105 /// 106 /// \post state(-1) Contains the new table. 107 /// 108 /// \param state The Lua state in which to push the table. 109 /// \param tree_key The key to which the new table corresponds. 110 static void 111 new_table_for_key(lutok::state& state, const std::string& tree_key) 112 { 113 state.new_table(); 114 { 115 state.new_table(); 116 { 117 state.push_string("__index"); 118 state.push_cxx_function(redirect_index); 119 state.set_table(-3); 120 121 state.push_string("__newindex"); 122 state.push_cxx_function(redirect_newindex); 123 state.set_table(-3); 124 125 state.push_string("tree_key"); 126 state.push_string(tree_key); 127 state.set_table(-3); 128 } 129 state.set_metatable(-2); 130 } 131 } 132 133 134 /// Sets the value of an configuration node. 135 /// 136 /// \pre state(-3) The table to index. If this is not _G, then the table 137 /// metadata must contain a tree_key property describing the path to 138 /// current level. 139 /// \pre state(-2) The field to index into the table. Must be a string. 140 /// \pre state(-1) The value to set the indexed table field to. 141 /// 142 /// \param state The Lua state in which to operate. 143 /// 144 /// \return The number of result values on the Lua stack; always 0. 145 /// 146 /// \throw invalid_key_error If the provided key is invalid. 147 /// \throw unknown_key_error If the key cannot be located. 148 /// \throw value_error If the value has an unsupported type or cannot be 149 /// set on the key, or if the input table or index are invalid. 150 static int 151 redirect_newindex(lutok::state& state) 152 { 153 if (!state.is_table(-3)) 154 throw config::value_error("Indexed object is not a table"); 155 if (!state.is_string(-2)) 156 throw config::value_error("Invalid field in configuration object " 157 "reference; must be a string"); 158 159 const std::string dotted_key = get_tree_key(state, -3, -2); 160 try { 161 config::tree& tree = get_global_tree(state); 162 tree.set_lua(dotted_key, state, -1); 163 } catch (const config::value_error& e) { 164 throw config::value_error(F("Invalid value for key '%s' (%s)") % 165 dotted_key % e.what()); 166 } 167 168 // Now really set the key in the Lua table, but prevent direct accesses from 169 // the user by prefixing it. We do this to ensure that re-setting the same 170 // key of the tree results in a call to __newindex instead of __index. 171 state.push_string("_" + state.to_string(-2)); 172 state.push_value(-2); 173 state.raw_set(-5); 174 175 return 0; 176 } 177 178 179 /// Indexes a configuration node. 180 /// 181 /// \pre state(-3) The table to index. If this is not _G, then the table 182 /// metadata must contain a tree_key property describing the path to 183 /// current level. If the field does not exist, a new table is created. 184 /// \pre state(-1) The field to index into the table. Must be a string. 185 /// 186 /// \param state The Lua state in which to operate. 187 /// 188 /// \return The number of result values on the Lua stack; always 1. 189 /// 190 /// \throw value_error If the input table or index are invalid. 191 static int 192 redirect_index(lutok::state& state) 193 { 194 if (!state.is_table(-2)) 195 throw config::value_error("Indexed object is not a table"); 196 if (!state.is_string(-1)) 197 throw config::value_error("Invalid field in configuration object " 198 "reference; must be a string"); 199 200 // Query if the key has already been set by a call to redirect_newindex. 201 state.push_string("_" + state.to_string(-1)); 202 state.raw_get(-3); 203 if (!state.is_nil(-1)) 204 return 1; 205 state.pop(1); 206 207 state.push_value(-1); // Duplicate the field name. 208 state.raw_get(-3); // Get table[field] to see if it's defined. 209 if (state.is_nil(-1)) { 210 state.pop(1); 211 212 // The stack is now the same as when we entered the function, but we 213 // know that the field is undefined and thus have to create a new 214 // configuration table. 215 INV(state.is_table(-2)); 216 INV(state.is_string(-1)); 217 218 const config::tree& tree = get_global_tree(state); 219 const std::string tree_key = get_tree_key(state, -2, -1); 220 if (tree.is_set(tree_key)) { 221 // Publish the pre-recorded value in the tree to the Lua state, 222 // instead of considering this table key a new inner node. 223 tree.push_lua(tree_key, state); 224 } else { 225 state.push_string("_" + state.to_string(-1)); 226 state.insert(-2); 227 state.pop(1); 228 229 new_table_for_key(state, tree_key); 230 231 // Duplicate the newly created table and place it deep in the stack 232 // so that the raw_set below leaves us with the return value of this 233 // function at the top of the stack. 234 state.push_value(-1); 235 state.insert(-4); 236 237 state.raw_set(-3); 238 state.pop(1); 239 } 240 } 241 return 1; 242 } 243 244 245 } // anonymous namespace 246 247 248 /// Install wrappers for globals to set values in the configuration tree. 249 /// 250 /// This function installs wrappers to capture all accesses to global variables. 251 /// Such wrappers redirect the reads and writes to the out_tree, which is the 252 /// entity that defines what configuration variables exist. 253 /// 254 /// \param state The Lua state into which to install the wrappers. 255 /// \param out_tree The tree with the layout definition and where the 256 /// configuration settings will be collected. 257 void 258 config::redirect(lutok::state& state, tree& out_tree) 259 { 260 lutok::stack_cleaner cleaner(state); 261 262 state.get_global_table(); 263 { 264 state.push_string("__index"); 265 state.push_cxx_function(redirect_index); 266 state.set_table(-3); 267 268 state.push_string("__newindex"); 269 state.push_cxx_function(redirect_newindex); 270 state.set_table(-3); 271 } 272 state.set_metatable(-1); 273 274 state.push_value(lutok::registry_index); 275 state.push_string("tree"); 276 config::tree** tree = state.new_userdata< config::tree* >(); 277 *tree = &out_tree; 278 state.set_table(-3); 279 state.pop(1); 280 } 281