context.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. // Copyright 2019-2022 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::path::{Path, PathBuf};
  5. use std::{ffi::OsStr, str::FromStr};
  6. use base64::Engine;
  7. use proc_macro2::TokenStream;
  8. use quote::quote;
  9. use sha2::{Digest, Sha256};
  10. use tauri_utils::assets::AssetKey;
  11. use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
  12. use tauri_utils::html::{
  13. inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
  14. };
  15. #[cfg(feature = "shell-scope")]
  16. use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope};
  17. use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError};
  18. /// Necessary data needed by [`context_codegen`] to generate code for a Tauri application context.
  19. pub struct ContextData {
  20. pub dev: bool,
  21. pub config: Config,
  22. pub config_parent: PathBuf,
  23. pub root: TokenStream,
  24. }
  25. fn map_core_assets(
  26. options: &AssetOptions,
  27. target: Target,
  28. ) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
  29. #[cfg(feature = "isolation")]
  30. let pattern = tauri_utils::html::PatternObject::from(&options.pattern);
  31. let csp = options.csp;
  32. let dangerous_disable_asset_csp_modification =
  33. options.dangerous_disable_asset_csp_modification.clone();
  34. move |key, path, input, csp_hashes| {
  35. if path.extension() == Some(OsStr::new("html")) {
  36. #[allow(clippy::collapsible_if)]
  37. if csp {
  38. let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
  39. if target == Target::Linux {
  40. ::tauri_utils::html::inject_csp_token(&mut document);
  41. }
  42. inject_nonce_token(&mut document, &dangerous_disable_asset_csp_modification);
  43. if dangerous_disable_asset_csp_modification.can_modify("script-src") {
  44. if let Ok(inline_script_elements) = document.select("script:not(empty)") {
  45. let mut scripts = Vec::new();
  46. for inline_script_el in inline_script_elements {
  47. let script = inline_script_el.as_node().text_contents();
  48. let mut hasher = Sha256::new();
  49. hasher.update(&script);
  50. let hash = hasher.finalize();
  51. scripts.push(format!(
  52. "'sha256-{}'",
  53. base64::engine::general_purpose::STANDARD.encode(hash)
  54. ));
  55. }
  56. csp_hashes
  57. .inline_scripts
  58. .entry(key.clone().into())
  59. .or_default()
  60. .append(&mut scripts);
  61. }
  62. }
  63. #[cfg(feature = "isolation")]
  64. if dangerous_disable_asset_csp_modification.can_modify("style-src") {
  65. if let tauri_utils::html::PatternObject::Isolation { .. } = &pattern {
  66. // create the csp for the isolation iframe styling now, to make the runtime less complex
  67. let mut hasher = Sha256::new();
  68. hasher.update(tauri_utils::pattern::isolation::IFRAME_STYLE);
  69. let hash = hasher.finalize();
  70. csp_hashes.styles.push(format!(
  71. "'sha256-{}'",
  72. base64::engine::general_purpose::STANDARD.encode(hash)
  73. ));
  74. }
  75. }
  76. *input = serialize_html_node(&document);
  77. }
  78. }
  79. Ok(())
  80. }
  81. }
  82. #[cfg(feature = "isolation")]
  83. fn map_isolation(
  84. _options: &AssetOptions,
  85. dir: PathBuf,
  86. ) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
  87. move |_key, path, input, _csp_hashes| {
  88. if path.extension() == Some(OsStr::new("html")) {
  89. let mut isolation_html =
  90. tauri_utils::html::parse(String::from_utf8_lossy(input).into_owned());
  91. // this is appended, so no need to reverse order it
  92. tauri_utils::html::inject_codegen_isolation_script(&mut isolation_html);
  93. // temporary workaround for windows not loading assets
  94. tauri_utils::html::inline_isolation(&mut isolation_html, &dir);
  95. *input = isolation_html.to_string().as_bytes().to_vec()
  96. }
  97. Ok(())
  98. }
  99. }
  100. #[derive(PartialEq, Eq, Clone, Copy)]
  101. enum Target {
  102. Linux,
  103. Windows,
  104. Darwin,
  105. Android,
  106. // iOS.
  107. Ios,
  108. }
  109. /// Build a `tauri::Context` for including in application code.
  110. pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsError> {
  111. let ContextData {
  112. dev,
  113. config,
  114. config_parent,
  115. root,
  116. } = data;
  117. let target = if let Ok(target) = std::env::var("TARGET") {
  118. if target.contains("unknown-linux") {
  119. Target::Linux
  120. } else if target.contains("pc-windows") {
  121. Target::Windows
  122. } else if target.contains("apple-darwin") {
  123. Target::Darwin
  124. } else if target.contains("android") {
  125. Target::Android
  126. } else if target.contains("apple-ios") {
  127. Target::Ios
  128. } else {
  129. panic!("unknown codegen target {target}");
  130. }
  131. } else if cfg!(target_os = "linux") {
  132. Target::Linux
  133. } else if cfg!(windows) {
  134. Target::Windows
  135. } else if cfg!(target_os = "macos") {
  136. Target::Darwin
  137. } else if cfg!(target_os = "android") {
  138. Target::Android
  139. } else if cfg!(target_os = "ios") {
  140. Target::Ios
  141. } else {
  142. panic!("unknown codegen target");
  143. };
  144. let mut options = AssetOptions::new(config.tauri.pattern.clone())
  145. .freeze_prototype(config.tauri.security.freeze_prototype)
  146. .dangerous_disable_asset_csp_modification(
  147. config
  148. .tauri
  149. .security
  150. .dangerous_disable_asset_csp_modification
  151. .clone(),
  152. );
  153. let csp = if dev {
  154. config
  155. .tauri
  156. .security
  157. .dev_csp
  158. .clone()
  159. .or_else(|| config.tauri.security.csp.clone())
  160. } else {
  161. config.tauri.security.csp.clone()
  162. };
  163. if csp.is_some() {
  164. options = options.with_csp();
  165. }
  166. let app_url = if dev {
  167. &config.build.dev_path
  168. } else {
  169. &config.build.dist_dir
  170. };
  171. let assets = match app_url {
  172. AppUrl::Url(url) => match url {
  173. WindowUrl::External(_) => Default::default(),
  174. WindowUrl::App(path) => {
  175. if path.components().count() == 0 {
  176. panic!(
  177. "The `{}` configuration cannot be empty",
  178. if dev { "devPath" } else { "distDir" }
  179. )
  180. }
  181. let assets_path = config_parent.join(path);
  182. if !assets_path.exists() {
  183. panic!(
  184. "The `{}` configuration is set to `{:?}` but this path doesn't exist",
  185. if dev { "devPath" } else { "distDir" },
  186. path
  187. )
  188. }
  189. EmbeddedAssets::new(assets_path, &options, map_core_assets(&options, target))?
  190. }
  191. _ => unimplemented!(),
  192. },
  193. AppUrl::Files(files) => EmbeddedAssets::new(
  194. files
  195. .iter()
  196. .map(|p| config_parent.join(p))
  197. .collect::<Vec<_>>(),
  198. &options,
  199. map_core_assets(&options, target),
  200. )?,
  201. _ => unimplemented!(),
  202. };
  203. let out_dir = {
  204. let out_dir = std::env::var("OUT_DIR")
  205. .map_err(|_| EmbeddedAssetsError::OutDir)
  206. .map(PathBuf::from)
  207. .and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
  208. // make sure that our output directory is created
  209. std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
  210. out_dir
  211. };
  212. let default_window_icon = {
  213. if target == Target::Windows {
  214. // handle default window icons for Windows targets
  215. let icon_path = find_icon(
  216. &config,
  217. &config_parent,
  218. |i| i.ends_with(".ico"),
  219. "icons/icon.ico",
  220. );
  221. if icon_path.exists() {
  222. ico_icon(&root, &out_dir, icon_path)?
  223. } else {
  224. let icon_path = find_icon(
  225. &config,
  226. &config_parent,
  227. |i| i.ends_with(".png"),
  228. "icons/icon.png",
  229. );
  230. png_icon(&root, &out_dir, icon_path)?
  231. }
  232. } else if target == Target::Linux {
  233. // handle default window icons for Linux targets
  234. let icon_path = find_icon(
  235. &config,
  236. &config_parent,
  237. |i| i.ends_with(".png"),
  238. "icons/icon.png",
  239. );
  240. png_icon(&root, &out_dir, icon_path)?
  241. } else {
  242. quote!(None)
  243. }
  244. };
  245. let app_icon = if target == Target::Darwin && dev {
  246. let mut icon_path = find_icon(
  247. &config,
  248. &config_parent,
  249. |i| i.ends_with(".icns"),
  250. "icons/icon.png",
  251. );
  252. if !icon_path.exists() {
  253. icon_path = find_icon(
  254. &config,
  255. &config_parent,
  256. |i| i.ends_with(".png"),
  257. "icons/icon.png",
  258. );
  259. }
  260. raw_icon(&out_dir, icon_path)?
  261. } else {
  262. quote!(None)
  263. };
  264. let package_name = if let Some(product_name) = &config.package.product_name {
  265. quote!(#product_name.to_string())
  266. } else {
  267. quote!(env!("CARGO_PKG_NAME").to_string())
  268. };
  269. let package_version = if let Some(version) = &config.package.version {
  270. semver::Version::from_str(version)?;
  271. quote!(#version.to_string())
  272. } else {
  273. quote!(env!("CARGO_PKG_VERSION").to_string())
  274. };
  275. let package_info = quote!(
  276. #root::PackageInfo {
  277. name: #package_name,
  278. version: #package_version.parse().unwrap(),
  279. authors: env!("CARGO_PKG_AUTHORS"),
  280. description: env!("CARGO_PKG_DESCRIPTION"),
  281. }
  282. );
  283. let system_tray_icon = if let Some(tray) = &config.tauri.system_tray {
  284. let system_tray_icon_path = config_parent.join(&tray.icon_path);
  285. let ext = system_tray_icon_path.extension();
  286. if ext.map_or(false, |e| e == "ico") {
  287. ico_icon(&root, &out_dir, system_tray_icon_path)?
  288. } else if ext.map_or(false, |e| e == "png") {
  289. png_icon(&root, &out_dir, system_tray_icon_path)?
  290. } else {
  291. quote!(compile_error!(
  292. "The tray icon extension must be either `.ico` or `.png`."
  293. ))
  294. }
  295. } else {
  296. quote!(None)
  297. };
  298. #[cfg(target_os = "macos")]
  299. let info_plist = if target == Target::Darwin && dev {
  300. let info_plist_path = config_parent.join("Info.plist");
  301. let mut info_plist = if info_plist_path.exists() {
  302. plist::Value::from_file(&info_plist_path)
  303. .unwrap_or_else(|e| panic!("failed to read plist {}: {}", info_plist_path.display(), e))
  304. } else {
  305. plist::Value::Dictionary(Default::default())
  306. };
  307. if let Some(plist) = info_plist.as_dictionary_mut() {
  308. if let Some(product_name) = &config.package.product_name {
  309. plist.insert("CFBundleName".into(), product_name.clone().into());
  310. }
  311. if let Some(version) = &config.package.version {
  312. plist.insert("CFBundleShortVersionString".into(), version.clone().into());
  313. }
  314. let format =
  315. time::format_description::parse("[year][month][day].[hour][minute][second]").unwrap();
  316. if let Ok(build_number) = time::OffsetDateTime::now_utc().format(&format) {
  317. plist.insert("CFBundleVersion".into(), build_number.into());
  318. }
  319. }
  320. let out_path = out_dir.join("Info.plist");
  321. info_plist
  322. .to_file_xml(&out_path)
  323. .expect("failed to write Info.plist");
  324. let info_plist_path = out_path.display().to_string();
  325. quote!({
  326. tauri::embed_plist::embed_info_plist!(#info_plist_path);
  327. })
  328. } else {
  329. quote!(())
  330. };
  331. #[cfg(not(target_os = "macos"))]
  332. let info_plist = quote!(());
  333. let pattern = match &options.pattern {
  334. PatternKind::Brownfield => quote!(#root::Pattern::Brownfield(std::marker::PhantomData)),
  335. #[cfg(not(feature = "isolation"))]
  336. PatternKind::Isolation { dir: _ } => {
  337. quote!(#root::Pattern::Brownfield(std::marker::PhantomData))
  338. }
  339. #[cfg(feature = "isolation")]
  340. PatternKind::Isolation { dir } => {
  341. let dir = config_parent.join(dir);
  342. if !dir.exists() {
  343. panic!("The isolation application path is set to `{dir:?}` but it does not exist")
  344. }
  345. let mut sets_isolation_hook = false;
  346. let key = uuid::Uuid::new_v4().to_string();
  347. let map_isolation = map_isolation(&options, dir.clone());
  348. let assets = EmbeddedAssets::new(dir, &options, |key, path, input, csp_hashes| {
  349. // we check if `__TAURI_ISOLATION_HOOK__` exists in the isolation code
  350. // before modifying the files since we inject our own `__TAURI_ISOLATION_HOOK__` reference in HTML files
  351. if String::from_utf8_lossy(input).contains("__TAURI_ISOLATION_HOOK__") {
  352. sets_isolation_hook = true;
  353. }
  354. map_isolation(key, path, input, csp_hashes)
  355. })?;
  356. if !sets_isolation_hook {
  357. panic!("The isolation application does not contain a file setting the `window.__TAURI_ISOLATION_HOOK__` value.");
  358. }
  359. let schema = options.isolation_schema;
  360. quote!(#root::Pattern::Isolation {
  361. assets: ::std::sync::Arc::new(#assets),
  362. schema: #schema.into(),
  363. key: #key.into(),
  364. crypto_keys: std::boxed::Box::new(::tauri::utils::pattern::isolation::Keys::new().expect("unable to generate cryptographically secure keys for Tauri \"Isolation\" Pattern")),
  365. })
  366. }
  367. };
  368. #[cfg(feature = "shell-scope")]
  369. let shell_scope_config = {
  370. use regex::Regex;
  371. use tauri_utils::config::ShellAllowlistOpen;
  372. let shell_scopes = get_allowed_clis(&root, &config.tauri.allowlist.shell.scope);
  373. let shell_scope_open = match &config.tauri.allowlist.shell.open {
  374. ShellAllowlistOpen::Flag(false) => quote!(::std::option::Option::None),
  375. ShellAllowlistOpen::Flag(true) => {
  376. quote!(::std::option::Option::Some(#root::regex::Regex::new(r#"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+"#).unwrap()))
  377. }
  378. ShellAllowlistOpen::Validate(regex) => match Regex::new(regex) {
  379. Ok(_) => quote!(::std::option::Option::Some(#root::regex::Regex::new(#regex).unwrap())),
  380. Err(error) => {
  381. let error = error.to_string();
  382. quote!({
  383. compile_error!(#error);
  384. ::std::option::Option::Some(#root::regex::Regex::new(#regex).unwrap())
  385. })
  386. }
  387. },
  388. _ => panic!("unknown shell open format, unable to prepare"),
  389. };
  390. quote!(#root::ShellScopeConfig {
  391. open: #shell_scope_open,
  392. scopes: #shell_scopes
  393. })
  394. };
  395. #[cfg(not(feature = "shell-scope"))]
  396. let shell_scope_config = quote!();
  397. Ok(quote!(#root::Context::new(
  398. #config,
  399. ::std::sync::Arc::new(#assets),
  400. #default_window_icon,
  401. #app_icon,
  402. #system_tray_icon,
  403. #package_info,
  404. #info_plist,
  405. #pattern,
  406. #shell_scope_config
  407. )))
  408. }
  409. fn ico_icon<P: AsRef<Path>>(
  410. root: &TokenStream,
  411. out_dir: &Path,
  412. path: P,
  413. ) -> Result<TokenStream, EmbeddedAssetsError> {
  414. let path = path.as_ref();
  415. let bytes = std::fs::read(path)
  416. .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
  417. .to_vec();
  418. let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))
  419. .unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e));
  420. let entry = &icon_dir.entries()[0];
  421. let rgba = entry
  422. .decode()
  423. .unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e))
  424. .rgba_data()
  425. .to_vec();
  426. let width = entry.width();
  427. let height = entry.height();
  428. let out_path = out_dir.join(path.file_name().unwrap());
  429. write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
  430. path: path.to_owned(),
  431. error,
  432. })?;
  433. let out_path = out_path.display().to_string();
  434. let icon = quote!(Some(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }));
  435. Ok(icon)
  436. }
  437. fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, EmbeddedAssetsError> {
  438. let path = path.as_ref();
  439. let bytes = std::fs::read(path)
  440. .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
  441. .to_vec();
  442. let out_path = out_dir.join(path.file_name().unwrap());
  443. write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite {
  444. path: path.to_owned(),
  445. error,
  446. })?;
  447. let out_path = out_path.display().to_string();
  448. let icon = quote!(Some(include_bytes!(#out_path).to_vec()));
  449. Ok(icon)
  450. }
  451. fn png_icon<P: AsRef<Path>>(
  452. root: &TokenStream,
  453. out_dir: &Path,
  454. path: P,
  455. ) -> Result<TokenStream, EmbeddedAssetsError> {
  456. let path = path.as_ref();
  457. let bytes = std::fs::read(path)
  458. .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
  459. .to_vec();
  460. let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
  461. let mut reader = decoder
  462. .read_info()
  463. .unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
  464. let mut buffer: Vec<u8> = Vec::new();
  465. while let Ok(Some(row)) = reader.next_row() {
  466. buffer.extend(row.data());
  467. }
  468. let width = reader.info().width;
  469. let height = reader.info().height;
  470. let out_path = out_dir.join(path.file_name().unwrap());
  471. write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
  472. path: path.to_owned(),
  473. error,
  474. })?;
  475. let out_path = out_path.display().to_string();
  476. let icon = quote!(Some(#root::Icon::Rgba { rgba: include_bytes!(#out_path).to_vec(), width: #width, height: #height }));
  477. Ok(icon)
  478. }
  479. fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
  480. use std::fs::File;
  481. use std::io::Write;
  482. if let Ok(curr) = std::fs::read(out_path) {
  483. if curr == data {
  484. return Ok(());
  485. }
  486. }
  487. let mut out_file = File::create(out_path)?;
  488. out_file.write_all(data)
  489. }
  490. fn find_icon<F: Fn(&&String) -> bool>(
  491. config: &Config,
  492. config_parent: &Path,
  493. predicate: F,
  494. default: &str,
  495. ) -> PathBuf {
  496. let icon_path = config
  497. .tauri
  498. .bundle
  499. .icon
  500. .iter()
  501. .find(|i| predicate(i))
  502. .cloned()
  503. .unwrap_or_else(|| default.to_string());
  504. config_parent.join(icon_path)
  505. }
  506. #[cfg(feature = "shell-scope")]
  507. fn get_allowed_clis(root: &TokenStream, scope: &ShellAllowlistScope) -> TokenStream {
  508. let commands = scope
  509. .0
  510. .iter()
  511. .map(|scope| {
  512. let sidecar = &scope.sidecar;
  513. let name = &scope.name;
  514. let name = quote!(#name.into());
  515. let command = scope.command.to_string_lossy();
  516. let command = quote!(::std::path::PathBuf::from(#command));
  517. let args = match &scope.args {
  518. ShellAllowedArgs::Flag(true) => quote!(::std::option::Option::None),
  519. ShellAllowedArgs::Flag(false) => quote!(::std::option::Option::Some(::std::vec![])),
  520. ShellAllowedArgs::List(list) => {
  521. let list = list.iter().map(|arg| match arg {
  522. ShellAllowedArg::Fixed(fixed) => {
  523. quote!(#root::scope::ShellScopeAllowedArg::Fixed(#fixed.into()))
  524. }
  525. ShellAllowedArg::Var { validator } => {
  526. let validator = match regex::Regex::new(validator) {
  527. Ok(regex) => {
  528. let regex = regex.as_str();
  529. quote!(#root::regex::Regex::new(#regex).unwrap())
  530. }
  531. Err(error) => {
  532. let error = error.to_string();
  533. quote!({
  534. compile_error!(#error);
  535. #root::regex::Regex::new(#validator).unwrap()
  536. })
  537. }
  538. };
  539. quote!(#root::scope::ShellScopeAllowedArg::Var { validator: #validator })
  540. }
  541. _ => panic!("unknown shell scope arg, unable to prepare"),
  542. });
  543. quote!(::std::option::Option::Some(::std::vec![#(#list),*]))
  544. }
  545. _ => panic!("unknown shell scope command, unable to prepare"),
  546. };
  547. (
  548. quote!(#name),
  549. quote!(
  550. #root::scope::ShellScopeAllowedCommand {
  551. command: #command,
  552. args: #args,
  553. sidecar: #sidecar,
  554. }
  555. ),
  556. )
  557. })
  558. .collect::<Vec<_>>();
  559. if commands.is_empty() {
  560. quote!(::std::collections::HashMap::new())
  561. } else {
  562. let insertions = commands
  563. .iter()
  564. .map(|(name, value)| quote!(hashmap.insert(#name, #value);));
  565. quote!({
  566. let mut hashmap = ::std::collections::HashMap::new();
  567. #(#insertions)*
  568. hashmap
  569. })
  570. }
  571. }