| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import web_idl |
| |
| _CODE_GEN_EXPR_PASS_KEY = object() |
| |
| |
| class CodeGenExpr(object): |
| """ |
| Represents an expression which is composable to produce another expression. |
| |
| This is designed primarily to represent conditional expressions and basic |
| logical operators (expr_not, expr_and, expr_or) come along with. |
| """ |
| |
| def __init__(self, expr, is_compound=False, pass_key=None): |
| assert isinstance(expr, (bool, str)) |
| assert isinstance(is_compound, bool) |
| assert pass_key is _CODE_GEN_EXPR_PASS_KEY |
| |
| if isinstance(expr, bool): |
| self._text = "true" if expr else "false" |
| else: |
| self._text = expr |
| self._is_compound = is_compound |
| self._is_always_false = expr is False |
| self._is_always_true = expr is True |
| |
| def __eq__(self, other): |
| if not isinstance(self, other.__class__): |
| return NotImplemented |
| # Assume that, as long as the two texts are the same, the two |
| # expressions must be the same, i.e. |_is_compound|, etc. must be the |
| # same or do not matter. |
| return self.to_text() == other.to_text() |
| |
| def __ne__(self, other): |
| return not (self == other) |
| |
| def __hash__(self): |
| return hash(self.to_text()) |
| |
| def __str__(self): |
| """ |
| __str__ is designed to be used when composing another expression. If |
| you'd only like to have a string representation, |to_text| works better. |
| """ |
| if self._is_compound: |
| return "({})".format(self.to_text()) |
| return self.to_text() |
| |
| def to_text(self): |
| return self._text |
| |
| @property |
| def is_always_false(self): |
| """ |
| The expression is always False, and code generators have chances of |
| optimizations. |
| """ |
| return self._is_always_false |
| |
| @property |
| def is_always_true(self): |
| """ |
| The expression is always True, and code generators have chances of |
| optimizations. |
| """ |
| return self._is_always_true |
| |
| |
| def _Expr(*args, **kwargs): |
| return CodeGenExpr(*args, pass_key=_CODE_GEN_EXPR_PASS_KEY, **kwargs) |
| |
| |
| def _unary_op(op, term): |
| assert isinstance(op, str) |
| assert isinstance(term, CodeGenExpr) |
| |
| return _Expr("{}{}".format(op, term), is_compound=True) |
| |
| |
| def _binary_op(op, terms): |
| assert isinstance(op, str) |
| assert isinstance(terms, (list, tuple)) |
| assert all(isinstance(term, CodeGenExpr) for term in terms) |
| assert all( |
| not (term.is_always_false or term.is_always_true) for term in terms) |
| |
| return _Expr(op.join(map(str, terms)), is_compound=True) |
| |
| |
| def expr_not(term): |
| assert isinstance(term, CodeGenExpr) |
| |
| if term.is_always_false: |
| return _Expr(True) |
| if term.is_always_true: |
| return _Expr(False) |
| return _unary_op("!", term) |
| |
| |
| def expr_and(terms): |
| assert isinstance(terms, (list, tuple)) |
| assert all(isinstance(term, CodeGenExpr) for term in terms) |
| assert terms |
| |
| if any(term.is_always_false for term in terms): |
| return _Expr(False) |
| terms = list(filter(lambda x: not x.is_always_true, terms)) |
| if not terms: |
| return _Expr(True) |
| if len(terms) == 1: |
| return terms[0] |
| return _binary_op(" && ", expr_uniq(terms)) |
| |
| |
| def expr_or(terms): |
| assert isinstance(terms, (list, tuple)) |
| assert all(isinstance(term, CodeGenExpr) for term in terms) |
| assert terms |
| |
| if any(term.is_always_true for term in terms): |
| return _Expr(True) |
| terms = list(filter(lambda x: not x.is_always_false, terms)) |
| if not terms: |
| return _Expr(False) |
| if len(terms) == 1: |
| return terms[0] |
| return _binary_op(" || ", expr_uniq(terms)) |
| |
| |
| def expr_uniq(terms): |
| assert isinstance(terms, (list, tuple)) |
| assert all(isinstance(term, CodeGenExpr) for term in terms) |
| |
| uniq_terms = [] |
| for term in terms: |
| if term not in uniq_terms: |
| uniq_terms.append(term) |
| return uniq_terms |
| |
| |
| def expr_from_exposure(exposure, |
| global_names=None, |
| may_use_feature_selector=False): |
| """ |
| Returns an expression to determine whether this property should be exposed |
| or not. |
| |
| Args: |
| exposure: web_idl.Exposure of the target construct. |
| global_names: When specified, it's taken into account that the global |
| object implements |global_names|. |
| may_use_feature_selector: True enables use of ${feature_selector} iff |
| the exposure is context dependent. |
| """ |
| assert isinstance(exposure, web_idl.Exposure) |
| assert (global_names is None |
| or (isinstance(global_names, (list, tuple)) |
| and all(isinstance(name, str) for name in global_names))) |
| |
| # The property exposures are categorized into three. |
| # - Unconditional: Always exposed. |
| # - Context-independent: Enabled per v8::Isolate. |
| # - Context-dependent: Enabled per v8::Context, e.g. origin trials. |
| # |
| # Context-dependent properties can be installed in two phases. |
| # - The first phase installs all the properties that are associated with the |
| # features enabled at the moment. This phase is represented by |
| # FeatureSelector as FeatureSelector.IsAll(). |
| # - The second phase installs the properties associated with the specified |
| # feature. This phase is represented as FeatureSelector.IsAny(feature). |
| # |
| # The exposure condition is represented as; |
| # (and feature_selector-independent-term |
| # (or |
| # feature_selector-1st-phase-term |
| # feature_selector-2nd-phase-term)) |
| # which can be represented in more details as: |
| # (and secure_context_term |
| # uncond_exposed_term |
| # (or |
| # (and feature_selector.IsAll() # 1st phase; all enabled |
| # cond_exposed_term |
| # (or feature_enabled_term |
| # context_enabled_term)) |
| # (or exposed_selector_term # 2nd phase; any selected |
| # feature_selector_term))) |
| # where |
| # secure_context_term represents [SecureContext=F1] |
| # uncond_exposed_term represents [Exposed=(G1, G2)] |
| # cond_exposed_term represents [Exposed(G1 F1, G2 F2)] |
| # feature_enabled_term represents [RuntimeEnabled=(F1, F2)] |
| # context_enabled_term represents [ContextEnabled=F1] |
| # exposed_selector_term represents [Exposed(G1 F1, G2 F2)] |
| # feature_selector_term represents [RuntimeEnabled=(F1, F2)] |
| uncond_exposed_terms = [] |
| cond_exposed_terms = [] |
| feature_enabled_terms = [] |
| context_enabled_terms = [] |
| exposed_selector_terms = [] |
| feature_selector_names = [] # Will turn into feature_selector.IsAnyOf(...) |
| |
| def ref_enabled(feature): |
| arg = "${execution_context}" if feature.is_context_dependent else "" |
| return _Expr("RuntimeEnabledFeatures::{}Enabled({})".format( |
| feature, arg)) |
| |
| def ref_selected(features): |
| feature_tokens = map( |
| lambda feature: "OriginTrialFeature::k{}".format(feature), |
| features) |
| return _Expr("${{feature_selector}}.IsAnyOf({})".format( |
| ", ".join(feature_tokens))) |
| |
| # [SecureContext] |
| if exposure.only_in_secure_contexts is True: |
| secure_context_term = _Expr("${is_in_secure_context}") |
| elif exposure.only_in_secure_contexts is False: |
| secure_context_term = _Expr(True) |
| else: |
| terms = list(map(ref_enabled, exposure.only_in_secure_contexts)) |
| secure_context_term = expr_or( |
| [_Expr("${is_in_secure_context}"), |
| expr_not(expr_and(terms))]) |
| |
| # [Exposed] |
| GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST = { |
| "AnimationWorklet": "IsAnimationWorkletGlobalScope", |
| "AudioWorklet": "IsAudioWorkletGlobalScope", |
| "DedicatedWorker": "IsDedicatedWorkerGlobalScope", |
| "LayoutWorklet": "IsLayoutWorkletGlobalScope", |
| "PaintWorklet": "IsPaintWorkletGlobalScope", |
| "ServiceWorker": "IsServiceWorkerGlobalScope", |
| "SharedWorker": "IsSharedWorkerGlobalScope", |
| "Window": "IsWindow", |
| "Worker": "IsWorkerGlobalScope", |
| "Worklet": "IsWorkletGlobalScope", |
| } |
| if global_names: |
| matched_global_count = 0 |
| for entry in exposure.global_names_and_features: |
| if entry.global_name not in global_names: |
| continue |
| matched_global_count += 1 |
| if entry.feature: |
| cond_exposed_terms.append(ref_enabled(entry.feature)) |
| if entry.feature.is_context_dependent: |
| feature_selector_names.append(entry.feature) |
| assert (not exposure.global_names_and_features |
| or matched_global_count > 0) |
| else: |
| for entry in exposure.global_names_and_features: |
| pred_term = _Expr("${{execution_context}}->{}()".format( |
| GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[entry.global_name])) |
| if not entry.feature: |
| uncond_exposed_terms.append(pred_term) |
| else: |
| cond_exposed_terms.append( |
| expr_and([pred_term, ref_enabled(entry.feature)])) |
| if entry.feature.is_context_dependent: |
| exposed_selector_terms.append( |
| expr_and([pred_term, |
| ref_selected([entry.feature])])) |
| |
| # [RuntimeEnabled] |
| if exposure.runtime_enabled_features: |
| feature_enabled_terms.extend( |
| map(ref_enabled, exposure.runtime_enabled_features)) |
| feature_selector_names.extend( |
| exposure.context_dependent_runtime_enabled_features) |
| |
| # [ContextEnabled] |
| if exposure.context_enabled_features: |
| terms = list( |
| map( |
| lambda feature: _Expr( |
| "${{context_feature_settings}}->is{}Enabled()".format( |
| feature)), exposure.context_enabled_features)) |
| context_enabled_terms.append( |
| expr_and([_Expr("${context_feature_settings}"), |
| expr_or(terms)])) |
| |
| # Build an expression. |
| top_level_terms = [] |
| top_level_terms.append(secure_context_term) |
| if uncond_exposed_terms: |
| top_level_terms.append(expr_or(uncond_exposed_terms)) |
| |
| if not (may_use_feature_selector |
| and exposure.is_context_dependent(global_names)): |
| if cond_exposed_terms: |
| top_level_terms.append(expr_or(cond_exposed_terms)) |
| if feature_enabled_terms: |
| top_level_terms.append(expr_and(feature_enabled_terms)) |
| return expr_and(top_level_terms) |
| |
| all_enabled_terms = [_Expr("${feature_selector}.IsAll()")] |
| if cond_exposed_terms: |
| all_enabled_terms.append(expr_or(cond_exposed_terms)) |
| if feature_enabled_terms or context_enabled_terms: |
| terms = [] |
| if feature_enabled_terms: |
| terms.append(expr_and(feature_enabled_terms)) |
| if context_enabled_terms: |
| terms.append(expr_or(context_enabled_terms)) |
| all_enabled_terms.append(expr_or(terms)) |
| |
| selector_terms = [] |
| if exposed_selector_terms: |
| selector_terms.append(expr_or(exposed_selector_terms)) |
| if feature_selector_names: |
| selector_terms.append(ref_selected(feature_selector_names)) |
| |
| terms = [] |
| terms.append(expr_and(all_enabled_terms)) |
| if selector_terms: |
| terms.append(expr_or(selector_terms)) |
| top_level_terms.append(expr_or(terms)) |
| |
| return expr_and(top_level_terms) |