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 38namespace config = utils::config; 39namespace detail = utils::config::detail; 40 41 42namespace { 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. 53config::tree& 54get_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. 82static std::string 83get_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 102static int redirect_newindex(lutok::state&); 103static 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. 112static void 113new_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. 152static int 153redirect_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. 193static int 194redirect_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. 259void 260config::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