Index: pylib/gyp/input.py |
=================================================================== |
--- pylib/gyp/input.py (revision 640) |
+++ pylib/gyp/input.py (working copy) |
@@ -1,5 +1,13 @@ |
#!/usr/bin/python |
+from compiler.ast import Const |
+from compiler.ast import Dict |
+from compiler.ast import Discard |
+from compiler.ast import List |
+from compiler.ast import Module |
+from compiler.ast import Node |
+from compiler.ast import Stmt |
+import compiler |
import copy |
import gyp.common |
import optparse |
@@ -107,9 +115,51 @@ |
return included |
+def CheckedEval(file_contents): |
+ """Return the eval of a gyp file. |
+ The gyp file is restricted to dictionaries and lists only, and |
+ repeated keys are not allowed. |
+ |
+ Note that this is slower than eval() is. |
+ """ |
+ |
+ ast = compiler.parse(file_contents) |
+ assert isinstance(ast, Module) |
+ c1 = ast.getChildren() |
+ assert c1[0] is None |
+ assert isinstance(c1[1], Stmt) |
+ c2 = c1[1].getChildren() |
+ assert isinstance(c2[0], Discard) |
+ c3 = c2[0].getChildren() |
+ assert len(c3) == 1 |
+ return CheckNode(c3[0],0) |
+ |
+def CheckNode(node, level): |
+ if isinstance(node, Dict): |
+ c = node.getChildren() |
+ dict = {} |
+ for n in range(0, len(c), 2): |
+ assert isinstance(c[n], Const) |
+ key = c[n].getChildren()[0] |
+ if key in dict: |
+ raise KeyError, "Key '" + key + "' repeated at level " \ |
+ + repr(level) |
+ dict[key] = CheckNode(c[n + 1], level + 1) |
+ return dict |
+ elif isinstance(node, List): |
+ c = node.getChildren() |
+ list = [] |
+ for child in c: |
+ list.append(CheckNode(child, level + 1)) |
+ return list |
+ elif isinstance(node, Const): |
+ return node.getChildren()[0] |
+ else: |
+ raise TypeError, "Unknown AST node " + repr(node) |
+ |
def LoadOneBuildFile(build_file_path, data, aux_data, variables, includes, |
- is_target): |
+ is_target, check): |
if build_file_path in data: |
return data[build_file_path] |
@@ -120,7 +170,11 @@ |
build_file_data = None |
try: |
- build_file_data = eval(build_file_contents, {'__builtins__': None}, None) |
+ if check: |
+ build_file_data = CheckedEval(build_file_contents) |
+ else: |
+ build_file_data = eval(build_file_contents, {'__builtins__': None}, |
+ None) |
except SyntaxError, e: |
e.filename = build_file_path |
raise |
@@ -135,10 +189,10 @@ |
try: |
if is_target: |
LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, |
- aux_data, variables, includes) |
+ aux_data, variables, includes, check) |
else: |
LoadBuildFileIncludesIntoDict(build_file_data, build_file_path, data, |
- aux_data, variables, None) |
+ aux_data, variables, None, check) |
except Exception, e: |
gyp.common.ExceptionAppend(e, |
'while reading includes of ' + build_file_path) |
@@ -148,7 +202,7 @@ |
def LoadBuildFileIncludesIntoDict(subdict, subdict_path, data, aux_data, |
- variables, includes): |
+ variables, includes, check): |
includes_list = [] |
if includes != None: |
includes_list.extend(includes) |
@@ -170,35 +224,36 @@ |
aux_data[subdict_path]['included'].append(include) |
MergeDicts(subdict, |
LoadOneBuildFile(include, data, aux_data, variables, None, |
- False), |
+ False, check), |
subdict_path, include) |
# Recurse into subdictionaries. |
for k, v in subdict.iteritems(): |
if v.__class__ == dict: |
LoadBuildFileIncludesIntoDict(v, subdict_path, data, aux_data, variables, |
- None) |
+ None, check) |
elif v.__class__ == list: |
- LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, variables) |
+ LoadBuildFileIncludesIntoList(v, subdict_path, data, aux_data, variables, |
+ check) |
# This recurses into lists so that it can look for dicts. |
def LoadBuildFileIncludesIntoList(sublist, sublist_path, data, aux_data, |
- variables): |
+ variables, check): |
for item in sublist: |
if item.__class__ == dict: |
LoadBuildFileIncludesIntoDict(item, sublist_path, data, aux_data, |
- variables, None) |
+ variables, None, check) |
elif item.__class__ == list: |
LoadBuildFileIncludesIntoList(item, sublist_path, data, aux_data, |
- variables) |
+ variables, check) |
# TODO(mark): I don't love this name. It just means that it's going to load |
# a build file that contains targets and is expected to provide a targets dict |
# that contains the targets... |
def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, |
- depth): |
+ depth, check): |
global absolute_build_file_paths |
# If depth is set, predefine the DEPTH variable to be a relative path from |
@@ -219,7 +274,7 @@ |
return |
build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, variables, |
- includes, True) |
+ includes, True, check) |
# Store DEPTH for later use in generators. |
build_file_data['_DEPTH'] = depth |
@@ -283,7 +338,7 @@ |
gyp.common.BuildFileAndTarget(build_file_path, dependency)[0] |
try: |
LoadTargetBuildFile(other_build_file, data, aux_data, variables, |
- includes, depth) |
+ includes, depth, check) |
except Exception, e: |
gyp.common.ExceptionAppend( |
e, 'while loading dependencies of %s' % build_file_path) |
@@ -1684,7 +1739,7 @@ |
"in file %s should be a dictionary." % |
(target_name, build_file)) |
-def Load(build_files, variables, includes, depth, generator_input_info): |
+def Load(build_files, variables, includes, depth, generator_input_info, check): |
# Set up path_sections and non_configuration_keys with the default data plus |
# the generator-specifc data. |
global path_sections |
@@ -1718,7 +1773,8 @@ |
# used as keys to the data dict and for references between input files. |
build_file = os.path.normpath(build_file) |
try: |
- LoadTargetBuildFile(build_file, data, aux_data, variables, includes, depth) |
+ LoadTargetBuildFile(build_file, data, aux_data, variables, includes, |
+ depth, check) |
except Exception, e: |
gyp.common.ExceptionAppend(e, 'while trying to load %s' % build_file) |
raise |