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