#![allow(clippy::pattern_type_mismatch)]
use alloc::format;
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
use crate::languages::azure_policy::ast::{Expr, ExprLiteral, JsonValue, ValueOrExpr};
use crate::rvm::Instruction;
use crate::Value;
use super::core::Compiler;
use super::utils::{extract_string_literal, json_value_to_runtime};
const MAX_JSON_DEPTH: usize = 32;
impl Compiler {
pub(super) fn compile_value_or_expr(
&mut self,
voe: &ValueOrExpr,
span: &crate::lexer::Span,
) -> Result<u8> {
match voe {
ValueOrExpr::Value(value) => self.compile_json_value_inner(value, span, 0, true),
ValueOrExpr::Expr { expr, .. } => self.compile_expr(expr),
}
}
pub(super) fn compile_json_value(
&mut self,
value: &crate::languages::azure_policy::ast::JsonValue,
span: &crate::lexer::Span,
) -> Result<u8> {
self.compile_json_value_inner(value, span, 0, false)
}
fn compile_json_value_inner(
&mut self,
value: &crate::languages::azure_policy::ast::JsonValue,
span: &crate::lexer::Span,
depth: usize,
resolved_top: bool,
) -> Result<u8> {
if depth > MAX_JSON_DEPTH {
bail!(span.error(&format!(
"JSON value nesting exceeds maximum depth of {MAX_JSON_DEPTH}"
)));
}
use crate::languages::azure_policy::expr::ExprParser;
use crate::languages::azure_policy::parser::is_template_expr;
if !resolved_top {
if let JsonValue::Str(str_span, s) = value {
if is_template_expr(s) {
let inner = s
.strip_prefix('[')
.and_then(|inner| inner.strip_suffix(']'))
.ok_or_else(|| {
str_span.error("invalid template expression: missing brackets")
})?;
let expr = ExprParser::parse_from_brackets(inner, str_span)
.map_err(|e| anyhow!("{}", e))?;
return self.compile_expr(&expr);
}
}
}
if let JsonValue::Array(_, items) = value {
if contains_template_expr(value) {
return self.compile_dynamic_array(items, span, depth.saturating_add(1));
}
}
if let JsonValue::Object(_, entries) = value {
if contains_template_expr(value) {
return self.compile_dynamic_object(entries, span, depth.saturating_add(1));
}
}
check_json_depth(value, 0).map_err(|_| {
anyhow!(span.error(&alloc::format!(
"JSON value nesting exceeds maximum depth of {MAX_JSON_DEPTH}"
)))
})?;
let runtime_value = json_value_to_runtime(value)?;
self.load_literal(runtime_value, span)
}
fn compile_dynamic_array(
&mut self,
items: &[JsonValue],
span: &crate::lexer::Span,
depth: usize,
) -> Result<u8> {
let mut element_regs = Vec::with_capacity(items.len());
for item in items {
let reg = self.compile_json_value_inner(item, item.span(), depth, false)?;
element_regs.push(reg);
}
let arr_dest = self.alloc_register()?;
let params = self.program.instruction_data.add_array_create_params(
crate::rvm::instructions::ArrayCreateParams {
dest: arr_dest,
elements: element_regs,
},
);
self.emit(
Instruction::ArrayCreate {
params_index: params,
},
span,
);
Ok(arr_dest)
}
fn compile_dynamic_object(
&mut self,
entries: &[crate::languages::azure_policy::ast::ObjectEntry],
span: &crate::lexer::Span,
depth: usize,
) -> Result<u8> {
let mut keys: Vec<(u16, u8)> = Vec::with_capacity(entries.len());
for entry in entries {
let val_reg =
self.compile_json_value_inner(&entry.value, entry.value.span(), depth, false)?;
let key_idx = self.add_literal_u16(Value::from(entry.key.clone()))?;
keys.push((key_idx, val_reg));
}
super::effects::build_object_from_keys(self, keys, span)
}
pub(super) fn compile_expr(&mut self, expr: &Expr) -> Result<u8> {
match expr {
Expr::Literal { span, value } => {
let v = match value {
ExprLiteral::Number(n) => Value::from_numeric_string(n)?,
ExprLiteral::String(s) => Value::from(s.clone()),
ExprLiteral::Bool(b) => Value::Bool(*b),
};
self.load_literal(v, span)
}
Expr::Ident { name, span } => match name.to_ascii_lowercase().as_str() {
"true" => self.load_literal(Value::Bool(true), span),
"false" => self.load_literal(Value::Bool(false), span),
"null" => self.load_literal(Value::Null, span),
_ => bail!(span.error(&alloc::format!(
"unsupported bare identifier in template expression: {}",
name
))),
},
Expr::Call { span, func, args } => self.compile_call_expr(span, func, args),
Expr::Dot {
span,
object,
field,
..
} => {
let object_reg = self.compile_expr(object)?;
let dest = self.alloc_register()?;
let literal_idx = self.add_literal_u16(Value::from(field.clone()))?;
self.emit(
Instruction::IndexLiteral {
dest,
container: object_reg,
literal_idx,
},
span,
);
Ok(dest)
}
Expr::Index {
span,
object,
index,
} => {
let object_reg = self.compile_expr(object)?;
let index_reg = self.compile_expr(index)?;
let dest = self.alloc_register()?;
self.emit(
Instruction::Index {
dest,
container: object_reg,
key: index_reg,
},
span,
);
Ok(dest)
}
}
}
fn compile_call_expr(
&mut self,
span: &crate::lexer::Span,
func: &Expr,
args: &[Expr],
) -> Result<u8> {
let Expr::Ident { name, .. } = func else {
bail!(span.error("unsupported dynamic function expression"));
};
let function_name = name.to_ascii_lowercase();
match function_name.as_str() {
"parameters" => {
let [first_arg] = args else {
bail!(span.error("parameters() requires exactly one argument"));
};
let param_name = extract_string_literal(first_arg)?;
let input_reg = self.load_input(span)?;
let params_reg =
self.emit_chained_index_literal_path(input_reg, &["parameters"], span)?;
let defaults_literal_idx = match self.cached_defaults_literal_idx {
Some(idx) => idx,
None => {
let val = self
.parameter_defaults
.clone()
.unwrap_or_else(Value::new_object);
let idx = self.add_literal_u16(val)?;
self.cached_defaults_literal_idx = Some(idx);
idx
}
};
let defaults_reg = self.alloc_register()?;
self.emit(
Instruction::Load {
dest: defaults_reg,
literal_idx: defaults_literal_idx,
},
span,
);
let name_reg = self.load_literal(Value::from(param_name), span)?;
self.emit_builtin_call(
"azure.policy.get_parameter",
&[params_reg, defaults_reg, name_reg],
span,
)
}
"field" => {
let [first_arg] = args else {
bail!(span.error("field() requires exactly one argument"));
};
let field_path = extract_string_literal(first_arg)?;
let resolved = match field_path.to_ascii_lowercase().as_str() {
"type" | "id" | "kind" | "name" | "location" | "fullname" | "tags"
| "identity.type" | "apiversion" => field_path.clone(),
s if s.starts_with("identity.") => field_path.clone(),
s if s.starts_with("tags.") || s.starts_with("tags[") => field_path.clone(),
_ => self.resolve_alias_path(&field_path, span)?,
};
let saved_override = self.resource_override_reg.take();
let reg = self.compile_field_path_expression(&resolved, span)?;
self.resource_override_reg = saved_override;
let reg = if resolved.contains("[*]") {
if self.resolve_count_binding(&resolved)?.is_some() {
let arr = self.alloc_register()?;
self.emit(Instruction::ArrayNew { dest: arr }, span);
self.emit(Instruction::ArrayPush { arr, value: reg }, span);
arr
} else {
reg
}
} else {
reg
};
self.emit_coalesce_undefined_to_null(reg, span);
Ok(reg)
}
"current" => match args.first() {
Some(first_arg) => {
let key = extract_string_literal(first_arg)?;
self.compile_current_reference(&key, span)
}
None => {
let binding = self.count_bindings.last().ok_or_else(|| {
anyhow::anyhow!("{}", span.error("current() used outside a count scope"))
})?;
let current_reg = binding.current_reg;
let dest = self.alloc_register()?;
self.emit(
crate::rvm::Instruction::Move {
dest,
src: current_reg,
},
span,
);
Ok(dest)
}
},
"resourcegroup" => {
if !args.is_empty() {
bail!(span.error("resourceGroup() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["resourceGroup"], span)
}
"subscription" => {
if !args.is_empty() {
bail!(span.error("subscription() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["subscription"], span)
}
"requestcontext" => {
if !args.is_empty() {
bail!(span.error("requestContext() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["requestContext"], span)
}
"claims" => {
if !args.is_empty() {
bail!(span.error("claims() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["claims"], span)
}
"policy" => {
if !args.is_empty() {
bail!(span.error("policy() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["policy"], span)
}
"utcnow" => {
if !args.is_empty() {
bail!(span.error("utcNow() takes no arguments"))
}
let ctx_reg = self.load_context(span)?;
self.emit_chained_index_literal_path(ctx_reg, &["utcNow"], span)
}
"concat" | "if" | "and" | "not" | "tolower" | "toupper" | "replace" | "substring"
| "length" | "add" | "equals" | "greaterorequals" | "lessorequals" | "contains" => self
.compile_arm_template_function(&function_name, span, args)?
.ok_or_else(|| anyhow!("{}", span.error("unreachable"))),
other => {
if let Some(dest) = self.compile_arm_template_function(other, span, args)? {
Ok(dest)
} else {
bail!(span.error(&alloc::format!("unsupported template function '{}'", other)))
}
}
}
}
pub(super) fn compile_call_args(&mut self, args: &[Expr]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(args.len());
for arg in args {
out.push(self.compile_expr(arg)?);
}
Ok(out)
}
}
fn contains_template_expr(value: &JsonValue) -> bool {
contains_template_expr_inner(value, 0)
}
fn contains_template_expr_inner(value: &JsonValue, depth: usize) -> bool {
if depth > MAX_JSON_DEPTH {
return false;
}
use crate::languages::azure_policy::parser::is_template_expr;
match value {
JsonValue::Str(_, s) => is_template_expr(s),
JsonValue::Array(_, items) => items
.iter()
.any(|item| contains_template_expr_inner(item, depth.saturating_add(1))),
JsonValue::Object(_, entries) => entries
.iter()
.any(|e| contains_template_expr_inner(&e.value, depth.saturating_add(1))),
_ => false,
}
}
pub(super) fn check_json_depth(value: &JsonValue, current_depth: usize) -> Result<()> {
if current_depth > MAX_JSON_DEPTH {
bail!("JSON value nesting exceeds maximum depth of {MAX_JSON_DEPTH}");
}
match value {
JsonValue::Array(_, items) => {
for item in items {
check_json_depth(item, current_depth.saturating_add(1))?;
}
}
JsonValue::Object(_, entries) => {
for entry in entries {
check_json_depth(&entry.value, current_depth.saturating_add(1))?;
}
}
_ => {}
}
Ok(())
}