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,
147                       global_names=None,
148                       may_use_feature_selector=False):
149    """
150    Returns an expression to determine whether this property should be exposed
151    or not.
152
153    Args:
154        exposure: web_idl.Exposure of the target construct.
155        global_names: When specified, it's taken into account that the global
156            object implements |global_names|.
157        may_use_feature_selector: True enables use of ${feature_selector} iff
158            the exposure is context dependent.
159    """
160    assert isinstance(exposure, web_idl.Exposure)
161    assert (global_names is None
162            or (isinstance(global_names, (list, tuple))
163                and all(isinstance(name, str) for name in global_names)))
164
165    # The property exposures are categorized into three.
166    # - Unconditional: Always exposed.
167    # - Context-independent: Enabled per v8::Isolate.
168    # - Context-dependent: Enabled per v8::Context, e.g. origin trials.
169    #
170    # Context-dependent properties can be installed in two phases.
171    # - The first phase installs all the properties that are associated with the
172    #   features enabled at the moment.  This phase is represented by
173    #   FeatureSelector as FeatureSelector.IsAll().
174    # - The second phase installs the properties associated with the specified
175    #   feature.  This phase is represented as FeatureSelector.IsAny(feature).
176    #
177    # The exposure condition is represented as;
178    #   (and feature_selector-independent-term
179    #        (or
180    #         feature_selector-1st-phase-term
181    #         feature_selector-2nd-phase-term))
182    # which can be represented in more details as:
183    #   (and secure_context_term
184    #        uncond_exposed_term
185    #        (or
186    #         (and feature_selector.IsAll()  # 1st phase; all enabled
187    #              cond_exposed_term
188    #              (or feature_enabled_term
189    #                  context_enabled_term))
190    #         (or exposed_selector_term      # 2nd phase; any selected
191    #             feature_selector_term)))
192    # where
193    #   secure_context_term represents [SecureContext=F1]
194    #   uncond_exposed_term represents [Exposed=(G1, G2)]
195    #   cond_exposed_term represents [Exposed(G1 F1, G2 F2)]
196    #   feature_enabled_term represents [RuntimeEnabled=(F1, F2)]
197    #   context_enabled_term represents [ContextEnabled=F1]
198    #   exposed_selector_term represents [Exposed(G1 F1, G2 F2)]
199    #   feature_selector_term represents [RuntimeEnabled=(F1, F2)]
200    uncond_exposed_terms = []
201    cond_exposed_terms = []
202    feature_enabled_terms = []
203    context_enabled_terms = []
204    exposed_selector_terms = []
205    feature_selector_names = []  # Will turn into feature_selector.IsAnyOf(...)
206
207    def ref_enabled(feature):
208        arg = "${execution_context}" if feature.is_context_dependent else ""
209        return _Expr("RuntimeEnabledFeatures::{}Enabled({})".format(
210            feature, arg))
211
212    def ref_selected(features):
213        feature_tokens = map(
214            lambda feature: "OriginTrialFeature::k{}".format(feature),
215            features)
216        return _Expr("${{feature_selector}}.IsAnyOf({})".format(
217            ", ".join(feature_tokens)))
218
219    # [SecureContext]
220    if exposure.only_in_secure_contexts is True:
221        secure_context_term = _Expr("${is_in_secure_context}")
222    elif exposure.only_in_secure_contexts is False:
223        secure_context_term = _Expr(True)
224    else:
225        terms = map(ref_enabled, exposure.only_in_secure_contexts)
226        secure_context_term = expr_or(
227            [_Expr("${is_in_secure_context}"),
228             expr_not(expr_and(terms))])
229
230    # [Exposed]
231    GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST = {
232        "AnimationWorklet": "IsAnimationWorkletGlobalScope",
233        "AudioWorklet": "IsAudioWorkletGlobalScope",
234        "DedicatedWorker": "IsDedicatedWorkerGlobalScope",
235        "LayoutWorklet": "IsLayoutWorkletGlobalScope",
236        "PaintWorklet": "IsPaintWorkletGlobalScope",
237        "ServiceWorker": "IsServiceWorkerGlobalScope",
238        "SharedWorker": "IsSharedWorkerGlobalScope",
239        "Window": "IsWindow",
240        "Worker": "IsWorkerGlobalScope",
241        "Worklet": "IsWorkletGlobalScope",
242    }
243    if global_names:
244        matched_global_count = 0
245        for entry in exposure.global_names_and_features:
246            if entry.global_name not in global_names:
247                continue
248            matched_global_count += 1
249            if entry.feature:
250                cond_exposed_terms.append(ref_enabled(entry.feature))
251                if entry.feature.is_context_dependent:
252                    feature_selector_names.append(entry.feature)
253        assert (not exposure.global_names_and_features
254                or matched_global_count > 0)
255    else:
256        for entry in exposure.global_names_and_features:
257            pred_term = _Expr("${{execution_context}}->{}()".format(
258                GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[entry.global_name]))
259            if not entry.feature:
260                uncond_exposed_terms.append(pred_term)
261            else:
262                cond_exposed_terms.append(
263                    expr_and([pred_term, ref_enabled(entry.feature)]))
264                if entry.feature.is_context_dependent:
265                    exposed_selector_terms.append(
266                        expr_and([pred_term,
267                                  ref_selected([entry.feature])]))
268
269    # [RuntimeEnabled]
270    if exposure.runtime_enabled_features:
271        feature_enabled_terms.extend(
272            map(ref_enabled, exposure.runtime_enabled_features))
273        feature_selector_names.extend(
274            exposure.context_dependent_runtime_enabled_features)
275
276    # [ContextEnabled]
277    if exposure.context_enabled_features:
278        terms = map(
279            lambda feature: _Expr(
280                "${{context_feature_settings}}->is{}Enabled()".format(
281                    feature)), exposure.context_enabled_features)
282        context_enabled_terms.append(
283            expr_and([_Expr("${context_feature_settings}"),
284                      expr_or(terms)]))
285
286    # Build an expression.
287    top_level_terms = []
288    top_level_terms.append(secure_context_term)
289    if uncond_exposed_terms:
290        top_level_terms.append(expr_or(uncond_exposed_terms))
291
292    if not (may_use_feature_selector
293            and exposure.is_context_dependent(global_names)):
294        if cond_exposed_terms:
295            top_level_terms.append(expr_or(cond_exposed_terms))
296        if feature_enabled_terms:
297            top_level_terms.append(expr_and(feature_enabled_terms))
298        return expr_and(top_level_terms)
299
300    all_enabled_terms = [_Expr("${feature_selector}.IsAll()")]
301    if cond_exposed_terms:
302        all_enabled_terms.append(expr_or(cond_exposed_terms))
303    if feature_enabled_terms or context_enabled_terms:
304        terms = []
305        if feature_enabled_terms:
306            terms.append(expr_and(feature_enabled_terms))
307        if context_enabled_terms:
308            terms.append(expr_or(context_enabled_terms))
309        all_enabled_terms.append(expr_or(terms))
310
311    selector_terms = []
312    if exposed_selector_terms:
313        selector_terms.append(expr_or(exposed_selector_terms))
314    if feature_selector_names:
315        selector_terms.append(ref_selected(feature_selector_names))
316
317    terms = []
318    terms.append(expr_and(all_enabled_terms))
319    if selector_terms:
320        terms.append(expr_or(selector_terms))
321    top_level_terms.append(expr_or(terms))
322
323    return expr_and(top_level_terms)
324