OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 # pylint: disable=W0104,W0106,F0401,R0201 |
| 7 |
| 8 import optparse |
| 9 import os.path |
| 10 import sys |
| 11 |
| 12 import interface |
| 13 |
| 14 |
| 15 def _ScriptDir(): |
| 16 return os.path.dirname(os.path.abspath(__file__)) |
| 17 |
| 18 |
| 19 def _GetDirAbove(dirname): |
| 20 """Returns the directory "above" this file containing |dirname| (which must |
| 21 also be "above" this file).""" |
| 22 path = _ScriptDir() |
| 23 while True: |
| 24 path, tail = os.path.split(path) |
| 25 assert tail |
| 26 if tail == dirname: |
| 27 return path |
| 28 |
| 29 |
| 30 def _AddThirdPartyImportPath(): |
| 31 sys.path.append(os.path.join(_GetDirAbove('mojo'), 'third_party')) |
| 32 |
| 33 |
| 34 _AddThirdPartyImportPath() |
| 35 import jinja2 |
| 36 |
| 37 loader = jinja2.FileSystemLoader(_ScriptDir()) |
| 38 jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True) |
| 39 |
| 40 |
| 41 # Accumulate lines of code with varying levels of indentation. |
| 42 class CodeWriter(object): |
| 43 def __init__(self): |
| 44 self._lines = [] |
| 45 self._margin = '' |
| 46 self._margin_stack = [] |
| 47 |
| 48 def __lshift__(self, line): |
| 49 self._lines.append((self._margin + line).rstrip()) |
| 50 |
| 51 def PushMargin(self): |
| 52 self._margin_stack.append(self._margin) |
| 53 self._margin += ' ' |
| 54 |
| 55 def PopMargin(self): |
| 56 self._margin = self._margin_stack.pop() |
| 57 |
| 58 def GetValue(self): |
| 59 return '\n'.join(self._lines).rstrip() + '\n' |
| 60 |
| 61 def Indent(self): |
| 62 return Indent(self) |
| 63 |
| 64 |
| 65 # Context handler that automatically indents and dedents a CodeWriter |
| 66 class Indent(object): |
| 67 def __init__(self, writer): |
| 68 self._writer = writer |
| 69 |
| 70 def __enter__(self): |
| 71 self._writer.PushMargin() |
| 72 |
| 73 def __exit__(self, type_, value, traceback): |
| 74 self._writer.PopMargin() |
| 75 |
| 76 |
| 77 def TemplateFile(name): |
| 78 return os.path.join(os.path.dirname(__file__), name) |
| 79 |
| 80 |
| 81 # Wraps comma separated lists as needed. |
| 82 def Wrap(pre, items, post): |
| 83 complete = pre + ', '.join(items) + post |
| 84 if len(complete) <= 80: |
| 85 return [complete] |
| 86 lines = [pre] |
| 87 indent = ' ' |
| 88 for i, item in enumerate(items): |
| 89 if i < len(items) - 1: |
| 90 lines.append(indent + item + ',') |
| 91 else: |
| 92 lines.append(indent + item + post) |
| 93 return lines |
| 94 |
| 95 |
| 96 def GeneratorWarning(): |
| 97 return ('// WARNING this file was generated by %s\n// Do not edit by hand.' % |
| 98 os.path.basename(__file__)) |
| 99 |
| 100 |
| 101 # Untrusted library implementing the public Mojo API. |
| 102 def GenerateLibMojo(functions, out): |
| 103 template = jinja_env.get_template('libmojo.cc.tmpl') |
| 104 |
| 105 code = CodeWriter() |
| 106 |
| 107 for f in functions: |
| 108 for line in Wrap('%s %s(' % (f.return_type, f.name), f.ParamList(), ') {'): |
| 109 code << line |
| 110 |
| 111 # 2 extra parameters: message ID and return value. |
| 112 num_params = len(f.params) + 2 |
| 113 |
| 114 with code.Indent(): |
| 115 code << 'uint32_t params[%d];' % num_params |
| 116 return_type = f.result_param.base_type |
| 117 if return_type == 'MojoResult': |
| 118 default = 'MOJO_RESULT_INVALID_ARGUMENT' |
| 119 elif return_type == 'MojoTimeTicks': |
| 120 default = '0' |
| 121 else: |
| 122 raise Exception('Unhandled return type: ' + return_type) |
| 123 code << '%s %s = %s;' % (return_type, f.result_param.name, default) |
| 124 |
| 125 # Message ID |
| 126 code << 'params[0] = %d;' % f.uid |
| 127 # Parameter pointers |
| 128 cast_template = 'params[%d] = reinterpret_cast<uint32_t>(%s);' |
| 129 for p in f.params: |
| 130 ptr = p.name |
| 131 if p.IsPassedByValue(): |
| 132 ptr = '&' + ptr |
| 133 code << cast_template % (p.uid + 1, ptr) |
| 134 # Return value pointer |
| 135 code << cast_template % (num_params - 1, '&' + f.result_param.name) |
| 136 |
| 137 code << 'DoMojoCall(params, sizeof(params));' |
| 138 code << 'return %s;' % f.result_param.name |
| 139 |
| 140 code << '}' |
| 141 code << '' |
| 142 |
| 143 body = code.GetValue() |
| 144 text = template.render( |
| 145 generator_warning=GeneratorWarning(), |
| 146 body=body) |
| 147 out.write(text) |
| 148 |
| 149 |
| 150 # Parameters passed into trusted code are handled differently depending on |
| 151 # details of the parameter. ParamImpl instances encapsulate these differences |
| 152 # and are used to generate the code that transfers parameters across the |
| 153 # untrusted/trusted boundary. |
| 154 class ParamImpl(object): |
| 155 def __init__(self, param): |
| 156 self.param = param |
| 157 |
| 158 # Declare whatever variables are needed to handle this particular parameter. |
| 159 def DeclareVars(self, code): |
| 160 raise NotImplementedError() |
| 161 |
| 162 # Convert the untrusted representation of the parameter into a trusted |
| 163 # representation, such as a scalar value or a trusted pointer into the |
| 164 # untrusted address space. |
| 165 def ConvertParam(self): |
| 166 raise NotImplementedError() |
| 167 |
| 168 # For this particular parameter, what expression should be passed when |
| 169 # invoking the trusted Mojo API function? |
| 170 def CallParam(self): |
| 171 raise NotImplementedError() |
| 172 |
| 173 # After invoking the trusted Mojo API function, transfer data back into |
| 174 # untrusted memory. Overriden for Out and InOut parameters. |
| 175 def CopyOut(self, code): |
| 176 pass |
| 177 |
| 178 # Converting array parameters needs to be defered until after the scalar |
| 179 # parameter containing the size of the array has itself been converted. |
| 180 def IsArray(self): |
| 181 return False |
| 182 |
| 183 |
| 184 class ScalarInputImpl(ParamImpl): |
| 185 def DeclareVars(self, code): |
| 186 code << '%s %s_value;' % (self.param.base_type, self.param.name) |
| 187 |
| 188 def ConvertParam(self): |
| 189 p = self.param |
| 190 return ('ConvertScalarInput(nap, params[%d], &%s_value)' % |
| 191 (p.uid + 1, p.name)) |
| 192 |
| 193 def CallParam(self): |
| 194 return '%s_value' % self.param.name |
| 195 |
| 196 |
| 197 class ScalarOutputImpl(ParamImpl): |
| 198 def DeclareVars(self, code): |
| 199 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) |
| 200 code << '%s %s_value;' % (self.param.base_type, self.param.name) |
| 201 |
| 202 def ConvertParam(self): |
| 203 p = self.param |
| 204 return 'ConvertScalarOutput(nap, params[%d], &%s_ptr)' % (p.uid + 1, p.name) |
| 205 |
| 206 def CallParam(self): |
| 207 return '&%s_value' % self.param.name |
| 208 |
| 209 def CopyOut(self, code): |
| 210 name = self.param.name |
| 211 code << '*%s_ptr = %s_value;' % (name, name) |
| 212 |
| 213 |
| 214 class ScalarInOutImpl(ParamImpl): |
| 215 def DeclareVars(self, code): |
| 216 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) |
| 217 code << '%s %s_value;' % (self.param.base_type, self.param.name) |
| 218 |
| 219 def ConvertParam(self): |
| 220 p = self.param |
| 221 return ('ConvertScalarInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' % |
| 222 (p.uid + 1, CBool(p.is_optional), p.name, p.name)) |
| 223 |
| 224 def CallParam(self): |
| 225 name = self.param.name |
| 226 expr = '&%s_value' % name |
| 227 if self.param.is_optional: |
| 228 expr = '%s_ptr ? %s : NULL' % (name, expr) |
| 229 return expr |
| 230 |
| 231 def CopyOut(self, code): |
| 232 name = self.param.name |
| 233 if self.param.is_optional: |
| 234 code << 'if (%s_ptr != NULL) {' % (name) |
| 235 with code.Indent(): |
| 236 code << '*%s_ptr = %s_value;' % (name, name) |
| 237 code << '}' |
| 238 else: |
| 239 code << '*%s_ptr = %s_value;' % (name, name) |
| 240 |
| 241 |
| 242 class ArrayImpl(ParamImpl): |
| 243 def DeclareVars(self, code): |
| 244 code << '%s %s;' % (self.param.param_type, self.param.name) |
| 245 |
| 246 def ConvertParam(self): |
| 247 p = self.param |
| 248 if p.base_type == 'void': |
| 249 element_size = '1' |
| 250 else: |
| 251 element_size = 'sizeof(*%s)' % p.name |
| 252 |
| 253 return ('ConvertArray(nap, params[%d], %s, %s, %s, &%s)' % |
| 254 (p.uid + 1, p.size + '_value', element_size, CBool(p.is_optional), |
| 255 p.name)) |
| 256 |
| 257 def CallParam(self): |
| 258 return self.param.name |
| 259 |
| 260 def IsArray(self): |
| 261 return True |
| 262 |
| 263 |
| 264 class StructInputImpl(ParamImpl): |
| 265 def DeclareVars(self, code): |
| 266 code << '%s %s;' % (self.param.param_type, self.param.name) |
| 267 |
| 268 def ConvertParam(self): |
| 269 p = self.param |
| 270 return ('ConvertStruct(nap, params[%d], %s, &%s)' % |
| 271 (p.uid + 1, CBool(p.is_optional), p.name)) |
| 272 |
| 273 def CallParam(self): |
| 274 return self.param.name |
| 275 |
| 276 |
| 277 def ImplForParam(p): |
| 278 if p.IsScalar(): |
| 279 if p.is_output: |
| 280 if p.is_input: |
| 281 return ScalarInOutImpl(p) |
| 282 else: |
| 283 return ScalarOutputImpl(p) |
| 284 else: |
| 285 return ScalarInputImpl(p) |
| 286 elif p.is_array: |
| 287 return ArrayImpl(p) |
| 288 elif p.is_struct: |
| 289 return StructInputImpl(p) |
| 290 else: |
| 291 assert False, p |
| 292 |
| 293 |
| 294 def CBool(value): |
| 295 return 'true' if value else 'false' |
| 296 |
| 297 |
| 298 # A trusted wrapper that validates the arguments passed from untrusted code |
| 299 # before passing them to the underlying public Mojo API. |
| 300 def GenerateMojoSyscall(functions, out): |
| 301 template = jinja_env.get_template('mojo_syscall.cc.tmpl') |
| 302 |
| 303 code = CodeWriter() |
| 304 code.PushMargin() |
| 305 |
| 306 for f in functions: |
| 307 impls = [ImplForParam(p) for p in f.params] |
| 308 impls.append(ImplForParam(f.result_param)) |
| 309 |
| 310 code << 'case %d:' % f.uid |
| 311 |
| 312 code.PushMargin() |
| 313 |
| 314 code << '{' |
| 315 |
| 316 with code.Indent(): |
| 317 num_params = len(f.params) + 2 |
| 318 code << 'if (num_params != %d) {' % num_params |
| 319 with code.Indent(): |
| 320 code << 'return -1;' |
| 321 code << '}' |
| 322 |
| 323 # Declare temporaries. |
| 324 for impl in impls: |
| 325 impl.DeclareVars(code) |
| 326 |
| 327 def ConvertParam(code, impl): |
| 328 code << 'if (!%s) {' % impl.ConvertParam() |
| 329 with code.Indent(): |
| 330 code << 'return -1;' |
| 331 code << '}' |
| 332 |
| 333 code << '{' |
| 334 with code.Indent(): |
| 335 code << 'ScopedCopyLock copy_lock(nap);' |
| 336 # Convert and validate pointers in two passes. |
| 337 # Arrays cannot be validated until the size parameter has been |
| 338 # converted. |
| 339 for impl in impls: |
| 340 if not impl.IsArray(): |
| 341 ConvertParam(code, impl) |
| 342 for impl in impls: |
| 343 if impl.IsArray(): |
| 344 ConvertParam(code, impl) |
| 345 code << '}' |
| 346 code << '' |
| 347 |
| 348 # Call |
| 349 getParams = [impl.CallParam() for impl in impls[:-1]] |
| 350 code << 'result_value = %s(%s);' % (f.name, ', '.join(getParams)) |
| 351 code << '' |
| 352 |
| 353 # Write outputs |
| 354 code << '{' |
| 355 with code.Indent(): |
| 356 code << 'ScopedCopyLock copy_lock(nap);' |
| 357 for impl in impls: |
| 358 impl.CopyOut(code) |
| 359 code << '}' |
| 360 code << '' |
| 361 |
| 362 code << 'return 0;' |
| 363 code << '}' |
| 364 |
| 365 code.PopMargin() |
| 366 |
| 367 body = code.GetValue() |
| 368 text = template.render( |
| 369 generator_warning=GeneratorWarning(), |
| 370 body=body) |
| 371 out.write(text) |
| 372 |
| 373 |
| 374 def OutFile(dir_path, name): |
| 375 if not os.path.exists(dir_path): |
| 376 os.makedirs(dir_path) |
| 377 return open(os.path.join(dir_path, name), 'w') |
| 378 |
| 379 |
| 380 def main(args): |
| 381 usage = 'usage: %prog [options]' |
| 382 parser = optparse.OptionParser(usage=usage) |
| 383 parser.add_option( |
| 384 '-d', |
| 385 dest='out_dir', |
| 386 metavar='DIR', |
| 387 help='output generated code into directory DIR') |
| 388 options, args = parser.parse_args(args=args) |
| 389 if not options.out_dir: |
| 390 parser.error('-d is required') |
| 391 if args: |
| 392 parser.error('unexpected positional arguments: %s' % ' '.join(args)) |
| 393 |
| 394 mojo = interface.MakeInterface() |
| 395 |
| 396 out = OutFile(options.out_dir, 'libmojo.cc') |
| 397 GenerateLibMojo(mojo.functions, out) |
| 398 |
| 399 out = OutFile(options.out_dir, 'mojo_syscall.cc') |
| 400 GenerateMojoSyscall(mojo.functions, out) |
| 401 |
| 402 |
| 403 if __name__ == '__main__': |
| 404 main(sys.argv[1:]) |
OLD | NEW |