Home | History | Annotate | Download | only in inspector
      1 #!/usr/bin/env python
      2 # Copyright (c) 2013 Google Inc. 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
     11 # copyright notice, this list of conditions and the following disclaimer
     12 # in the documentation and/or other materials provided with the
     13 # distribution.
     14 #     * Neither the name of Google Inc. nor the names of its
     15 # contributors may be used to endorse or promote products derived from
     16 # this software without specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 import optparse
     31 import re
     32 import string
     33 import sys
     34 
     35 template_h = string.Template("""// Code generated from InspectorInstrumentation.idl
     36 
     37 #ifndef ${file_name}_h
     38 #define ${file_name}_h
     39 
     40 ${includes}
     41 
     42 namespace blink {
     43 
     44 ${forward_declarations}
     45 
     46 namespace InspectorInstrumentation {
     47 
     48 $methods
     49 } // namespace InspectorInstrumentation
     50 
     51 } // namespace blink
     52 
     53 #endif // !defined(${file_name}_h)
     54 """)
     55 
     56 template_inline = string.Template("""
     57 inline void ${name}(${params_public})
     58 {   ${fast_return}
     59     if (${condition})
     60         ${name}Impl(${params_impl});
     61 }
     62 """)
     63 
     64 template_inline_forward = string.Template("""
     65 inline void ${name}(${params_public})
     66 {   ${fast_return}
     67     ${name}Impl(${params_impl});
     68 }
     69 """)
     70 
     71 template_inline_returns_value = string.Template("""
     72 inline ${return_type} ${name}(${params_public})
     73 {   ${fast_return}
     74     if (${condition})
     75         return ${name}Impl(${params_impl});
     76     return ${default_return_value};
     77 }
     78 """)
     79 
     80 
     81 template_cpp = string.Template("""// Code generated from InspectorInstrumentation.idl
     82 
     83 #include "config.h"
     84 
     85 ${includes}
     86 
     87 namespace blink {
     88 ${extra_definitions}
     89 
     90 namespace InspectorInstrumentation {
     91 $methods
     92 
     93 } // namespace InspectorInstrumentation
     94 
     95 } // namespace blink
     96 """)
     97 
     98 template_outofline = string.Template("""
     99 ${return_type} ${name}Impl(${params_impl})
    100 {${impl_lines}
    101 }""")
    102 
    103 template_agent_call = string.Template("""
    104     if (${agent_class}* agent = ${agent_fetch})
    105         ${maybe_return}agent->${name}(${params_agent});""")
    106 
    107 template_agent_call_timeline_returns_cookie = string.Template("""
    108     int timelineAgentId = 0;
    109     if (InspectorTimelineAgent* agent = agents->inspectorTimelineAgent()) {
    110         if (agent->${name}(${params_agent}))
    111             timelineAgentId = agent->id();
    112     }""")
    113 
    114 
    115 template_instrumenting_agents_h = string.Template("""// Code generated from InspectorInstrumentation.idl
    116 
    117 #ifndef InstrumentingAgentsInl_h
    118 #define InstrumentingAgentsInl_h
    119 
    120 #include "platform/heap/Handle.h"
    121 #include "wtf/FastAllocBase.h"
    122 #include "wtf/Noncopyable.h"
    123 #include "wtf/PassRefPtr.h"
    124 #include "wtf/RefCounted.h"
    125 
    126 namespace blink {
    127 
    128 ${forward_list}
    129 
    130 class InstrumentingAgents : public RefCountedWillBeGarbageCollectedFinalized<InstrumentingAgents> {
    131     WTF_MAKE_NONCOPYABLE(InstrumentingAgents);
    132     WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED;
    133 public:
    134     static PassRefPtrWillBeRawPtr<InstrumentingAgents> create()
    135     {
    136         return adoptRefWillBeNoop(new InstrumentingAgents());
    137     }
    138     ~InstrumentingAgents() { }
    139     void trace(Visitor*);
    140     void reset();
    141 
    142 ${accessor_list}
    143 
    144 private:
    145     InstrumentingAgents();
    146 
    147 ${member_list}
    148 };
    149 
    150 }
    151 
    152 #endif // !defined(InstrumentingAgentsInl_h)
    153 """)
    154 
    155 template_instrumenting_agent_accessor = string.Template("""
    156     ${class_name}* ${getter_name}() const { return ${member_name}; }
    157     void set${class_name}(${class_name}* agent) { ${member_name} = agent; }""")
    158 
    159 template_instrumenting_agents_cpp = string.Template("""
    160 InstrumentingAgents::InstrumentingAgents()
    161     : $init_list
    162 {
    163 }
    164 
    165 void InstrumentingAgents::trace(Visitor* visitor)
    166 {
    167     $trace_list
    168 }
    169 
    170 void InstrumentingAgents::reset()
    171 {
    172     $reset_list
    173 }""")
    174 
    175 
    176 
    177 def match_and_consume(pattern, source):
    178     match = re.match(pattern, source)
    179     if match:
    180         return match, source[len(match.group(0)):].strip()
    181     return None, source
    182 
    183 
    184 def load_model_from_idl(source):
    185     source = re.sub("//.*", "", source)  # Remove line comments
    186     source = re.sub("/\*(.|\n)*?\*/", "", source, re.MULTILINE)  # Remove block comments
    187     source = re.sub("\]\s*?\n\s*", "] ", source)  # Merge the method annotation with the next line
    188     source = source.strip()
    189 
    190     model = []
    191 
    192     while len(source):
    193         match, source = match_and_consume("interface\s(\w*)\s?\{([^\{]*)\}", source)
    194         if not match:
    195             sys.stderr.write("Cannot parse %s\n" % source[:100])
    196             sys.exit(1)
    197         model.append(File(match.group(1), match.group(2)))
    198 
    199     return model
    200 
    201 
    202 class File:
    203     def __init__(self, name, source):
    204         self.name = name
    205         self.header_name = self.name + "Inl"
    206         self.includes = [include_inspector_header("InspectorInstrumentation")]
    207         self.forward_declarations = []
    208         self.declarations = []
    209         for line in map(str.strip, source.split("\n")):
    210             line = re.sub("\s{2,}", " ", line).strip()  # Collapse whitespace
    211             if len(line) == 0:
    212                 continue
    213             if line[0] == "#":
    214                 self.includes.append(line)
    215             elif line.startswith("class "):
    216                 self.forward_declarations.append(line)
    217             else:
    218                 self.declarations.append(Method(line))
    219         self.includes.sort()
    220         self.forward_declarations.sort()
    221 
    222     def generate(self, cpp_lines, used_agents):
    223         header_lines = []
    224         for declaration in self.declarations:
    225             for agent in set(declaration.agents):
    226                 used_agents.add(agent)
    227             declaration.generate_header(header_lines)
    228             declaration.generate_cpp(cpp_lines)
    229 
    230         return template_h.substitute(None,
    231                                      file_name=self.header_name,
    232                                      includes="\n".join(self.includes),
    233                                      forward_declarations="\n".join(self.forward_declarations),
    234                                      methods="\n".join(header_lines))
    235 
    236 
    237 class Method:
    238     def __init__(self, source):
    239         match = re.match("(\[[\w|,|=|\s]*\])?\s?(\w*\*?) (\w*)\((.*)\)\s?;", source)
    240         if not match:
    241             sys.stderr.write("Cannot parse %s\n" % source)
    242             sys.exit(1)
    243 
    244         self.options = []
    245         if match.group(1):
    246             options_str = re.sub("\s", "", match.group(1)[1:-1])
    247             if len(options_str) != 0:
    248                 self.options = options_str.split(",")
    249 
    250         self.return_type = match.group(2)
    251 
    252         self.name = match.group(3)
    253 
    254         # Splitting parameters by a comma, assuming that attribute lists contain no more than one attribute.
    255         self.params = map(Parameter, map(str.strip, match.group(4).split(",")))
    256 
    257         self.accepts_cookie = len(self.params) and self.params[0].type == "const InspectorInstrumentationCookie&"
    258         self.returns_cookie = self.return_type == "InspectorInstrumentationCookie"
    259 
    260         self.returns_value = self.return_type != "void"
    261 
    262         if self.return_type == "bool":
    263             self.default_return_value = "false"
    264         elif self.return_type == "int":
    265             self.default_return_value = "0"
    266         elif self.return_type == "String":
    267             self.default_return_value = "\"\""
    268         else:
    269             self.default_return_value = self.return_type + "()"
    270 
    271         for param in self.params:
    272             if "DefaultReturn" in param.options:
    273                 self.default_return_value = param.name
    274 
    275         self.params_impl = self.params
    276         if not self.accepts_cookie and not "Inline=Forward" in self.options:
    277             if not "Keep" in self.params_impl[0].options:
    278                 self.params_impl = self.params_impl[1:]
    279             self.params_impl = [Parameter("InstrumentingAgents* agents")] + self.params_impl
    280 
    281         self.agents = filter(lambda option: not "=" in option, self.options)
    282 
    283     def generate_header(self, header_lines):
    284         if "Inline=Custom" in self.options:
    285             return
    286 
    287         header_lines.append("%s %sImpl(%s);" % (
    288             self.return_type, self.name, ", ".join(map(Parameter.to_str_class, self.params_impl))))
    289 
    290         if "Inline=FastReturn" in self.options or "Inline=Forward" in self.options:
    291             fast_return = "\n    FAST_RETURN_IF_NO_FRONTENDS(%s);" % self.default_return_value
    292         else:
    293             fast_return = ""
    294 
    295         for param in self.params:
    296             if "FastReturn" in param.options:
    297                 fast_return += "\n    if (!%s)\n        return %s;" % (param.name, self.default_return_value)
    298 
    299         if self.accepts_cookie:
    300             condition = "%s.isValid()" % self.params_impl[0].name
    301             template = template_inline
    302         elif "Inline=Forward" in self.options:
    303             condition = ""
    304             template = template_inline_forward
    305         else:
    306             condition = "InstrumentingAgents* agents = instrumentingAgentsFor(%s)" % self.params[0].name
    307 
    308             if self.returns_value:
    309                 template = template_inline_returns_value
    310             else:
    311                 template = template_inline
    312 
    313         header_lines.append(template.substitute(
    314             None,
    315             name=self.name,
    316             fast_return=fast_return,
    317             return_type=self.return_type,
    318             default_return_value=self.default_return_value,
    319             params_public=", ".join(map(Parameter.to_str_full, self.params)),
    320             params_impl=", ".join(map(Parameter.to_str_name, self.params_impl)),
    321             condition=condition))
    322 
    323     def generate_cpp(self, cpp_lines):
    324         if len(self.agents) == 0:
    325             return
    326 
    327         body_lines = map(self.generate_ref_ptr, self.params)
    328         body_lines += map(self.generate_agent_call, self.agents)
    329 
    330         if self.returns_cookie:
    331             if "Timeline" in self.agents:
    332                 timeline_agent_id = "timelineAgentId"
    333             else:
    334                 timeline_agent_id = "0"
    335             body_lines.append("\n    return InspectorInstrumentationCookie(agents, %s);" % timeline_agent_id)
    336         elif self.returns_value:
    337             body_lines.append("\n    return %s;" % self.default_return_value)
    338 
    339         cpp_lines.append(template_outofline.substitute(
    340             None,
    341             return_type=self.return_type,
    342             name=self.name,
    343             params_impl=", ".join(map(Parameter.to_str_class_and_name, self.params_impl)),
    344             impl_lines="".join(body_lines)))
    345 
    346     def generate_agent_call(self, agent):
    347         agent_class, agent_getter = agent_getter_signature(agent)
    348 
    349         leading_param_name = self.params_impl[0].name
    350         if not self.accepts_cookie:
    351             agent_fetch = "%s->%s()" % (leading_param_name, agent_getter)
    352         elif agent == "Timeline":
    353             agent_fetch = "retrieveTimelineAgent(%s)" % leading_param_name
    354         else:
    355             agent_fetch = "%s.instrumentingAgents()->%s()" % (leading_param_name, agent_getter)
    356 
    357         if agent == "Timeline" and self.returns_cookie:
    358             template = template_agent_call_timeline_returns_cookie
    359         else:
    360             template = template_agent_call
    361 
    362         if not self.returns_value or self.returns_cookie:
    363             maybe_return = ""
    364         else:
    365             maybe_return = "return "
    366 
    367         return template.substitute(
    368             None,
    369             name=self.name,
    370             agent_class=agent_class,
    371             agent_fetch=agent_fetch,
    372             maybe_return=maybe_return,
    373             params_agent=", ".join(map(Parameter.to_str_value, self.params_impl)[1:]))
    374 
    375     def generate_ref_ptr(self, param):
    376         if param.is_prp:
    377             return "\n    RefPtr<%s> %s = %s;" % (param.inner_type, param.value, param.name)
    378         else:
    379             return ""
    380 
    381 class Parameter:
    382     def __init__(self, source):
    383         self.options = []
    384         match, source = match_and_consume("\[(\w*)\]", source)
    385         if match:
    386             self.options.append(match.group(1))
    387 
    388         parts = map(str.strip, source.split("="))
    389         if len(parts) == 1:
    390             self.default_value = None
    391         else:
    392             self.default_value = parts[1]
    393 
    394         param_decl = parts[0]
    395 
    396         if re.match("(const|unsigned long) ", param_decl):
    397             min_type_tokens = 2
    398         else:
    399             min_type_tokens = 1
    400 
    401         if len(param_decl.split(" ")) > min_type_tokens:
    402             parts = param_decl.split(" ")
    403             self.type = " ".join(parts[:-1])
    404             self.name = parts[-1]
    405         else:
    406             self.type = param_decl
    407             self.name = generate_param_name(self.type)
    408 
    409         if re.match("PassRefPtr<", param_decl):
    410             self.is_prp = True
    411             self.value = self.name
    412             self.name = "prp" + self.name[0].upper() + self.name[1:]
    413             self.inner_type = re.match("PassRefPtr<(.+)>", param_decl).group(1)
    414         else:
    415             self.is_prp = False
    416             self.value = self.name
    417 
    418 
    419     def to_str_full(self):
    420         if self.default_value is None:
    421             return self.to_str_class_and_name()
    422         return "%s %s = %s" % (self.type, self.name, self.default_value)
    423 
    424     def to_str_class_and_name(self):
    425         return "%s %s" % (self.type, self.name)
    426 
    427     def to_str_class(self):
    428         return self.type
    429 
    430     def to_str_name(self):
    431         return self.name
    432 
    433     def to_str_value(self):
    434         return self.value
    435 
    436 
    437 def generate_param_name(param_type):
    438     base_name = re.match("(const |PassRefPtr<)?(\w*)", param_type).group(2)
    439     return "param" + base_name
    440 
    441 
    442 def agent_class_name(agent):
    443     custom_agent_names = ["PageDebugger", "PageRuntime", "WorkerRuntime"]
    444     if agent in custom_agent_names:
    445         return "%sAgent" % agent
    446     return "Inspector%sAgent" % agent
    447 
    448 
    449 def agent_getter_signature(agent):
    450     agent_class = agent_class_name(agent)
    451     return agent_class, agent_class[0].lower() + agent_class[1:]
    452 
    453 
    454 def include_header(name):
    455     return "#include \"%s.h\"" % name
    456 
    457 
    458 def include_inspector_header(name):
    459     return include_header("core/inspector/" + name)
    460 
    461 
    462 def generate_instrumenting_agents(used_agents):
    463     agents = list(used_agents)
    464 
    465     forward_list = []
    466     accessor_list = []
    467     member_list = []
    468     init_list = []
    469     trace_list = []
    470     reset_list = []
    471 
    472     for agent in agents:
    473         class_name, getter_name = agent_getter_signature(agent)
    474         member_name = "m_" + getter_name
    475 
    476         forward_list.append("class %s;" % class_name)
    477         accessor_list.append(template_instrumenting_agent_accessor.substitute(
    478             None,
    479             class_name=class_name,
    480             getter_name=getter_name,
    481             member_name=member_name))
    482         member_list.append("    RawPtrWillBeMember<%s> %s;" % (class_name, member_name))
    483         init_list.append("%s(nullptr)" % member_name)
    484         trace_list.append("visitor->trace(%s);" % member_name)
    485         reset_list.append("%s = nullptr;" % member_name)
    486 
    487     forward_list.sort()
    488     accessor_list.sort()
    489     member_list.sort()
    490     init_list.sort()
    491     trace_list.sort()
    492     reset_list.sort()
    493 
    494     header_lines = template_instrumenting_agents_h.substitute(
    495         None,
    496         forward_list="\n".join(forward_list),
    497         accessor_list="\n".join(accessor_list),
    498         member_list="\n".join(member_list))
    499 
    500     cpp_lines = template_instrumenting_agents_cpp.substitute(
    501         None,
    502         init_list="\n    , ".join(init_list),
    503         trace_list="\n    ".join(trace_list),
    504         reset_list="\n    ".join(reset_list))
    505 
    506     return header_lines, cpp_lines
    507 
    508 
    509 def generate(input_path, output_dir):
    510     fin = open(input_path, "r")
    511     files = load_model_from_idl(fin.read())
    512     fin.close()
    513 
    514     cpp_includes = []
    515     cpp_lines = []
    516     used_agents = set()
    517     for f in files:
    518         cpp_includes.append(include_header(f.header_name))
    519 
    520         fout = open(output_dir + "/" + f.header_name + ".h", "w")
    521         fout.write(f.generate(cpp_lines, used_agents))
    522         fout.close()
    523 
    524     for agent in used_agents:
    525         cpp_includes.append(include_inspector_header(agent_class_name(agent)))
    526     cpp_includes.append(include_header("InstrumentingAgentsInl"))
    527     cpp_includes.sort()
    528 
    529     instrumenting_agents_header, instrumenting_agents_cpp = generate_instrumenting_agents(used_agents)
    530 
    531     fout = open(output_dir + "/" + "InstrumentingAgentsInl.h", "w")
    532     fout.write(instrumenting_agents_header)
    533     fout.close()
    534 
    535     fout = open(output_dir + "/InspectorInstrumentationImpl.cpp", "w")
    536     fout.write(template_cpp.substitute(None,
    537                                        includes="\n".join(cpp_includes),
    538                                        extra_definitions=instrumenting_agents_cpp,
    539                                        methods="\n".join(cpp_lines)))
    540     fout.close()
    541 
    542 
    543 cmdline_parser = optparse.OptionParser()
    544 cmdline_parser.add_option("--output_dir")
    545 
    546 try:
    547     arg_options, arg_values = cmdline_parser.parse_args()
    548     if (len(arg_values) != 1):
    549         raise Exception("Exactly one plain argument expected (found %s)" % len(arg_values))
    550     input_path = arg_values[0]
    551     output_dirpath = arg_options.output_dir
    552     if not output_dirpath:
    553         raise Exception("Output directory must be specified")
    554 except Exception:
    555     # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
    556     exc = sys.exc_info()[1]
    557     sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
    558     sys.stderr.write("Usage: <script> --output_dir <output_dir> InspectorInstrumentation.idl\n")
    559     exit(1)
    560 
    561 generate(input_path, output_dirpath)
    562