1# Copyright 2019 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import web_idl 6 7_CODE_GEN_EXPR_PASS_KEY = object() 8 9 10class CodeGenExpr(object): 11 """ 12 Represents an expression which is composable to produce another expression. 13 14 This is designed primarily to represent conditional expressions and basic 15 logical operators (expr_not, expr_and, expr_or) come along with. 16 """ 17 18 def __init__(self, expr, is_compound=False, pass_key=None): 19 assert isinstance(expr, (bool, str)) 20 assert isinstance(is_compound, bool) 21 assert pass_key is _CODE_GEN_EXPR_PASS_KEY 22 23 if isinstance(expr, bool): 24 self._text = "true" if expr else "false" 25 else: 26 self._text = expr 27 self._is_compound = is_compound 28 self._is_always_false = expr is False 29 self._is_always_true = expr is True 30 31 def __eq__(self, other): 32 if not isinstance(self, other.__class__): 33 return NotImplemented 34 # Assume that, as long as the two texts are the same, the two 35 # expressions must be the same, i.e. |_is_compound|, etc. must be the 36 # same or do not matter. 37 return self.to_text() == other.to_text() 38 39 def __ne__(self, other): 40 return not (self == other) 41 42 def __hash__(self): 43 return hash(self.to_text()) 44 45 def __str__(self): 46 """ 47 __str__ is designed to be used when composing another expression. If 48 you'd only like to have a string representation, |to_text| works better. 49 """ 50 if self._is_compound: 51 return "({})".format(self.to_text()) 52 return self.to_text() 53 54 def to_text(self): 55 return self._text 56 57 @property 58 def is_always_false(self): 59 """ 60 The expression is always False, and code generators have chances of 61 optimizations. 62 """ 63 return self._is_always_false 64 65 @property 66 def is_always_true(self): 67 """ 68 The expression is always True, and code generators have chances of 69 optimizations. 70 """ 71 return self._is_always_true 72 73 74def _Expr(*args, **kwargs): 75 return CodeGenExpr(*args, pass_key=_CODE_GEN_EXPR_PASS_KEY, **kwargs) 76 77 78def _unary_op(op, term): 79 assert isinstance(op, str) 80 assert isinstance(term, CodeGenExpr) 81 82 return _Expr("{}{}".format(op, term), is_compound=True) 83 84 85def _binary_op(op, terms): 86 assert isinstance(op, str) 87 assert isinstance(terms, (list, tuple)) 88 assert all(isinstance(term, CodeGenExpr) for term in terms) 89 assert all( 90 not (term.is_always_false or term.is_always_true) for term in terms) 91 92 return _Expr(op.join(map(str, terms)), is_compound=True) 93 94 95def expr_not(term): 96 assert isinstance(term, CodeGenExpr) 97 98 if term.is_always_false: 99 return _Expr(True) 100 if term.is_always_true: 101 return _Expr(False) 102 return _unary_op("!", term) 103 104 105def expr_and(terms): 106 assert isinstance(terms, (list, tuple)) 107 assert all(isinstance(term, CodeGenExpr) for term in terms) 108 assert terms 109 110 if any(term.is_always_false for term in terms): 111 return _Expr(False) 112 terms = filter(lambda x: not x.is_always_true, terms) 113 if not terms: 114 return _Expr(True) 115 if len(terms) == 1: 116 return terms[0] 117 return _binary_op(" && ", expr_uniq(terms)) 118 119 120def expr_or(terms): 121 assert isinstance(terms, (list, tuple)) 122 assert all(isinstance(term, CodeGenExpr) for term in terms) 123 assert terms 124 125 if any(term.is_always_true for term in terms): 126 return _Expr(True) 127 terms = filter(lambda x: not x.is_always_false, terms) 128 if not terms: 129 return _Expr(False) 130 if len(terms) == 1: 131 return terms[0] 132 return _binary_op(" || ", expr_uniq(terms)) 133 134 135def expr_uniq(terms): 136 assert isinstance(terms, (list, tuple)) 137 assert all(isinstance(term, CodeGenExpr) for term in terms) 138 139 uniq_terms = [] 140 for term in terms: 141 if term not in uniq_terms: 142 uniq_terms.append(term) 143 return uniq_terms 144 145 146def expr_from_exposure(exposure, global_names=None): 147 """ 148 Args: 149 exposure: web_idl.Exposure of the target construct. 150 global_names: When specified, it's taken into account that the global 151 object implements |global_names|. 152 """ 153 assert isinstance(exposure, web_idl.Exposure) 154 assert (global_names is None 155 or (isinstance(global_names, (list, tuple)) 156 and all(isinstance(name, str) for name in global_names))) 157 158 def ref_enabled(feature): 159 arg = "${execution_context}" if feature.is_context_dependent else "" 160 return _Expr("RuntimeEnabledFeatures::{}Enabled({})".format( 161 feature, arg)) 162 163 top_terms = [_Expr(True)] 164 165 # [Exposed] 166 GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST = { 167 "AnimationWorklet": "IsAnimationWorkletGlobalScope", 168 "AudioWorklet": "IsAudioWorkletGlobalScope", 169 "DedicatedWorker": "IsDedicatedWorkerGlobalScope", 170 "LayoutWorklet": "IsLayoutWorkletGlobalScope", 171 "PaintWorklet": "IsPaintWorkletGlobalScope", 172 "ServiceWorker": "IsServiceWorkerGlobalScope", 173 "SharedWorker": "IsSharedWorkerGlobalScope", 174 "Window": "IsDocument", 175 "Worker": "IsWorkerGlobalScope", 176 "Worklet": "IsWorkletGlobalScope", 177 } 178 exposed_terms = [] 179 if global_names: 180 matched_global_count = 0 181 for entry in exposure.global_names_and_features: 182 if entry.global_name not in global_names: 183 continue 184 matched_global_count += 1 185 if entry.feature: 186 exposed_terms.append(ref_enabled(entry.feature)) 187 assert (not exposure.global_names_and_features 188 or matched_global_count > 0) 189 else: 190 for entry in exposure.global_names_and_features: 191 terms = [] 192 pred = GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[entry.global_name] 193 terms.append(_Expr("${{execution_context}}->{}()".format(pred))) 194 if entry.feature: 195 terms.append(ref_enabled(entry.feature)) 196 if terms: 197 exposed_terms.append(expr_and(terms)) 198 if exposed_terms: 199 top_terms.append(expr_or(exposed_terms)) 200 201 # [RuntimeEnabled] 202 if exposure.runtime_enabled_features: 203 terms = map(ref_enabled, exposure.runtime_enabled_features) 204 top_terms.append(expr_or(terms)) 205 206 # [SecureContext] 207 if exposure.only_in_secure_contexts is True: 208 top_terms.append(_Expr("${is_in_secure_context}")) 209 elif exposure.only_in_secure_contexts is False: 210 top_terms.append(_Expr(True)) 211 else: 212 terms = map(ref_enabled, exposure.only_in_secure_contexts) 213 top_terms.append( 214 expr_or( 215 [_Expr("${is_in_secure_context}"), 216 expr_not(expr_and(terms))])) 217 218 return expr_and(top_terms) 219