# Copyright 2019 The Chromium Authors
# 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)
terms = expr_uniq(terms)
if len(terms) == 1:
return terms[0]
return _binary_op(" && ", 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)
terms = expr_uniq(terms)
if len(terms) == 1:
return terms[0]
return _binary_op(" || ", 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)))
assert isinstance(may_use_feature_selector, bool)
# 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, browser
# controlled features.
#
# 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 cross_origin_isolated_term
# injection_mitigated_term
# isolated_context_term
# 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
# cross_origin_isolated_term represents [CrossOriginIsolated]
# injection_mitigated_term represents [InjectionMitigated]
# isolated_context_term represents [IsolatedContext]
# 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: "mojom::blink::OriginTrialFeature::k{}".format(
feature), features)
return _Expr("${{feature_selector}}.IsAnyOf({})".format(
", ".join(feature_tokens)))
# [CrossOriginIsolated], [CrossOriginIsolatedOrRuntimeEnabled]
if exposure.only_in_coi_contexts:
cross_origin_isolated_term = _Expr("${is_cross_origin_isolated}")
elif exposure.only_in_coi_contexts_or_runtime_enabled_features:
cross_origin_isolated_term = expr_or([
_Expr("${is_cross_origin_isolated}"),
expr_or(
list(
map(
ref_enabled, exposure.
only_in_coi_contexts_or_runtime_enabled_features)))
])
else:
cross_origin_isolated_term = _Expr(True)
# [InjectionMitigated]
if exposure.only_in_injection_mitigated_contexts:
injection_mitigated_context_term = _Expr(
"${is_in_injection_mitigated_context}")
else:
injection_mitigated_context_term = _Expr(True)
# [IsolatedContext]
if exposure.only_in_isolated_contexts:
isolated_context_term = _Expr("${is_in_isolated_context}")
else:
isolated_context_term = _Expr(True)
# [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",
"ShadowRealm": "IsShadowRealmGlobalScope",
"SharedWorker": "IsSharedWorkerGlobalScope",
"SharedStorageWorklet": "IsSharedStorageWorkletGlobalScope",
"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 == "*":
# [Exposed(GLOBAL_NAME FEATURE_NAME)] is not supported.
assert entry.feature is None
# Constructs with the wildcard exposure ([Exposed=*]) are
# unconditionally exposed.
pass
elif 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_origin_trial:
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:
if entry.global_name == "*":
# [Exposed(GLOBAL_NAME FEATURE_NAME)] is not supported.
assert entry.feature is None
# Constructs with the wildcard exposure ([Exposed=*]) are
# unconditionally exposed.
continue
try:
execution_context_check = GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[
entry.global_name]
except KeyError:
# We don't currently have a general way of checking the exposure
# of [TargetOfExposed] exposure. If this is actually a global,
# add it to GLOBAL_NAME_TO_EXECUTION_CONTEXT_CHECK.
return _Expr(
"(::logging::NotReachedError::NotReached() << "
"\"{} exposure test is not supported at runtime\", false)".
format(entry.global_name))
pred_term = _Expr(
"${{execution_context}}->{}()".format(execution_context_check))
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_origin_trial:
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))
if exposure.origin_trial_features:
feature_selector_names.extend(exposure.origin_trial_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(cross_origin_isolated_term)
top_level_terms.append(injection_mitigated_context_term)
top_level_terms.append(isolated_context_term)
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))
if context_enabled_terms:
top_level_terms.append(expr_or(context_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:
# Remove duplicates
selector_terms.append(ref_selected(sorted(
set(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)