Commit 4590b7c7 authored by Federico Mena Quintero's avatar Federico Mena Quintero

Merge branch 'pborelli/librsvg-acquired_nodes'

parents bcbe99a7 e7a16afb
Pipeline #149679 passed with stages
in 34 minutes
......@@ -10,9 +10,10 @@ use std::rc::Rc;
use crate::allowed_url::{AllowedUrl, AllowedUrlError, Fragment};
use crate::create_node::create_node;
use crate::css::{self, Origin, Stylesheet};
use crate::error::LoadingError;
use crate::error::{AcquireError, LoadingError};
use crate::handle::LoadOptions;
use crate::io::{self, BinaryData};
use crate::limits;
use crate::node::{NodeData, NodeType, RsvgNode};
use crate::property_bag::PropertyBag;
use crate::structure::{IntrinsicDimensions, Svg};
......@@ -229,6 +230,164 @@ fn load_image(
Ok(surface)
}
pub struct AcquiredNode {
stack: Option<Rc<RefCell<NodeStack>>>,
node: RsvgNode,
}
impl Drop for AcquiredNode {
fn drop(&mut self) {
if let Some(ref stack) = self.stack {
let mut stack = stack.borrow_mut();
let last = stack.pop().unwrap();
assert!(last == self.node);
}
}
}
impl AcquiredNode {
pub fn get(&self) -> &RsvgNode {
&self.node
}
}
/// This helper struct is used when looking up urls to other nodes.
/// Its methods do recursion checking and thereby avoid infinite loops.
///
/// Malformed SVGs, for example, may reference a marker by its IRI, but
/// the object referenced by the IRI is not a marker.
///
/// Note that if you acquire a node, you have to release it before trying to
/// acquire it again. If you acquire a node "#foo" and don't release it before
/// trying to acquire "foo" again, you will obtain a None the second time.
pub struct AcquiredNodes<'i> {
document: &'i Document,
num_elements_acquired: usize,
node_stack: Rc<RefCell<NodeStack>>,
}
impl<'i> AcquiredNodes<'i> {
pub fn new(document: &Document) -> AcquiredNodes {
AcquiredNodes {
document,
num_elements_acquired: 0,
node_stack: Rc::new(RefCell::new(NodeStack::new())),
}
}
pub fn lookup_node(
&self,
fragment: &Fragment,
node_types: &[NodeType],
) -> Result<RsvgNode, AcquireError> {
let node = self.document.lookup(fragment).map_err(|_| {
// FIXME: callers shouldn't have to know that get_node() can initiate a file load.
// Maybe we should have the following stages:
// - load main SVG XML
//
// - load secondary SVG XML and other files like images; all document::Resources and
// document::Images loaded
//
// - Now that all files are loaded, resolve URL references
AcquireError::LinkNotFound(fragment.clone())
})?;
if node_types.is_empty() {
Ok(node)
} else {
let node_type = node.borrow().get_type();
if node_types.iter().find(|&&t| t == node_type).is_some() {
Ok(node)
} else {
Err(AcquireError::InvalidLinkType(fragment.clone()))
}
}
}
pub fn lookup_image(&self, href: &str) -> Result<SharedImageSurface, LoadingError> {
self.document.lookup_image(href)
}
/// Acquires a node.
/// Specify `node_types` when expecting the node to be of a particular type,
/// or use an empty slice for `node_types` if you want a node of any type.
/// Nodes acquired by this function must be released in reverse acquiring order.
pub fn acquire(
&mut self,
fragment: &Fragment,
node_types: &[NodeType],
) -> Result<AcquiredNode, AcquireError> {
self.num_elements_acquired += 1;
// This is a mitigation for SVG files that try to instance a huge number of
// elements via <use>, recursive patterns, etc. See limits.rs for details.
if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
return Err(AcquireError::MaxReferencesExceeded);
}
let node = self.lookup_node(fragment, node_types)?;
if node_is_accessed_by_reference(&node) {
self.acquire_ref(&node)
} else {
Ok(AcquiredNode {
stack: None,
node: node.clone(),
})
}
}
pub fn acquire_ref(&self, node: &RsvgNode) -> Result<AcquiredNode, AcquireError> {
if self.node_stack.borrow().contains(&node) {
Err(AcquireError::CircularReference(node.clone()))
} else {
self.node_stack.borrow_mut().push(&node);
Ok(AcquiredNode {
stack: Some(self.node_stack.clone()),
node: node.clone(),
})
}
}
}
// Returns whether a node of a particular type is only accessed by reference
// from other nodes' atributes. The node could in turn cause other nodes
// to get referenced, potentially causing reference cycles.
fn node_is_accessed_by_reference(node: &RsvgNode) -> bool {
use NodeType::*;
match node.borrow().get_type() {
ClipPath | Filter | LinearGradient | Marker | Mask | Pattern | RadialGradient => true,
_ => false,
}
}
/// Keeps a stack of nodes and can check if a certain node is contained in the stack
///
/// Sometimes parts of the code cannot plainly use the implicit stack of acquired
/// nodes as maintained by DrawingCtx::acquire_node(), and they must keep their
/// own stack of nodes to test for reference cycles. NodeStack can be used to do that.
pub struct NodeStack(Vec<RsvgNode>);
impl NodeStack {
pub fn new() -> NodeStack {
NodeStack(Vec::new())
}
pub fn push(&mut self, node: &RsvgNode) {
self.0.push(node.clone());
}
pub fn pop(&mut self) -> Option<RsvgNode> {
self.0.pop()
}
pub fn contains(&self, node: &RsvgNode) -> bool {
self.0.iter().find(|n| **n == *node).is_some()
}
}
pub struct DocumentBuilder {
load_options: LoadOptions,
tree: Option<RsvgNode>,
......
This diff is collapsed.
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -76,10 +77,11 @@ impl FilterEffect for FeBlend {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input_2 = ctx.get_input(draw_ctx, self.in2.as_ref())?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let input_2 = ctx.get_input(acquired_nodes, draw_ctx, self.in2.as_ref())?;
let bounds = self
.base
.get_bounds(ctx)
......
......@@ -2,6 +2,7 @@ use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use nalgebra::{Matrix3, Matrix4x5, Matrix5, Vector5};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -153,9 +154,10 @@ impl FilterEffect for FeColorMatrix {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let bounds = self
.base
.get_bounds(ctx)
......
......@@ -3,6 +3,7 @@ use std::cmp::min;
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, NodeType, RsvgNode};
......@@ -269,9 +270,10 @@ impl FilterEffect for FeComponentTransfer {
&self,
node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let bounds = self
.base
.get_bounds(ctx)
......
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -76,10 +77,11 @@ impl FilterEffect for FeComposite {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input_2 = ctx.get_input(draw_ctx, self.in2.as_ref())?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let input_2 = ctx.get_input(acquired_nodes, draw_ctx, self.in2.as_ref())?;
let bounds = self
.base
.get_bounds(ctx)
......
......@@ -4,6 +4,7 @@ use std::f64;
use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::{DrawingCtx, ViewParams};
use crate::filter::Filter;
use crate::node::RsvgNode;
......@@ -270,6 +271,7 @@ impl FilterContext {
fn get_paint_server_surface(
&self,
draw_ctx: &mut DrawingCtx,
acquired_nodes: &mut AcquiredNodes,
paint_server: &PaintServer,
opacity: UnitInterval,
) -> Result<SharedImageSurface, cairo::Status> {
......@@ -286,6 +288,7 @@ impl FilterContext {
// FIXME: we are ignoring the following error; propagate it upstream
let _ = draw_ctx
.set_source_paint_server(
acquired_nodes,
paint_server,
opacity,
&self.node_bbox,
......@@ -311,6 +314,7 @@ impl FilterContext {
/// Does not take `processing_linear_rgb` into account.
fn get_input_raw(
&self,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
in_: Option<&Input>,
) -> Result<FilterInput, FilterError> {
......@@ -350,12 +354,22 @@ impl FilterContext {
.map(FilterInput::StandardInput),
Input::FillPaint => self
.get_paint_server_surface(draw_ctx, &values.fill.0, values.fill_opacity.0)
.get_paint_server_surface(
draw_ctx,
acquired_nodes,
&values.fill.0,
values.fill_opacity.0,
)
.map_err(FilterError::CairoError)
.map(FilterInput::StandardInput),
Input::StrokePaint => self
.get_paint_server_surface(draw_ctx, &values.stroke.0, values.stroke_opacity.0)
.get_paint_server_surface(
draw_ctx,
acquired_nodes,
&values.stroke.0,
values.stroke_opacity.0,
)
.map_err(FilterError::CairoError)
.map(FilterInput::StandardInput),
......@@ -371,10 +385,11 @@ impl FilterContext {
/// Retrieves the filter input surface according to the SVG rules.
pub fn get_input(
&self,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
in_: Option<&Input>,
) -> Result<FilterInput, FilterError> {
let raw = self.get_input_raw(draw_ctx, in_)?;
let raw = self.get_input_raw(acquired_nodes, draw_ctx, in_)?;
// Convert the input surface to the desired format.
let (surface, bounds) = match raw {
......
......@@ -2,6 +2,7 @@ use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns, QualName};
use nalgebra::{DMatrix, Dynamic, VecStorage};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -198,9 +199,10 @@ impl FilterEffect for FeConvolveMatrix {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let mut bounds = self
.base
.get_bounds(ctx)
......
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -74,10 +75,11 @@ impl FilterEffect for FeDisplacementMap {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let displacement_input = ctx.get_input(draw_ctx, self.in2.as_ref())?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let displacement_input = ctx.get_input(acquired_nodes, draw_ctx, self.in2.as_ref())?;
let bounds = self
.base
.get_bounds(ctx)
......
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::node::{CascadedValues, NodeResult, NodeTrait, RsvgNode};
use crate::property_bag::PropertyBag;
......@@ -34,6 +35,7 @@ impl FilterEffect for FeFlood {
&self,
node: &RsvgNode,
ctx: &FilterContext,
_acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let bounds = self.base.get_bounds(ctx).into_irect(draw_ctx);
......
......@@ -4,6 +4,7 @@ use std::f64;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use nalgebra::{DMatrix, Dynamic, VecStorage};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -202,9 +203,10 @@ impl FilterEffect for FeGaussianBlur {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let bounds = self
.base
.get_bounds(ctx)
......
......@@ -2,6 +2,7 @@ use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::allowed_url::{Fragment, Href};
use crate::aspect_ratio::AspectRatio;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{CascadedValues, NodeResult, NodeTrait, RsvgNode};
......@@ -37,12 +38,13 @@ impl FeImage {
fn render_node(
&self,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
bounds: Rect,
fragment: &Fragment,
) -> Result<FilterResult, FilterError> {
let acquired_drawable = draw_ctx
.acquire_node(fragment, &[])
let acquired_drawable = acquired_nodes
.acquire(fragment, &[])
.map_err(|_| FilterError::InvalidInput)?;
let drawable = acquired_drawable.get();
......@@ -51,6 +53,7 @@ impl FeImage {
let image = draw_ctx.draw_node_to_surface(
&drawable,
acquired_nodes,
&cascaded,
ctx.paffine(),
ctx.source_graphic().width(),
......@@ -72,13 +75,14 @@ impl FeImage {
fn render_external_image(
&self,
ctx: &FilterContext,
draw_ctx: &DrawingCtx,
acquired_nodes: &mut AcquiredNodes,
_draw_ctx: &DrawingCtx,
bounds: Rect,
unclipped_bounds: &Rect,
url: &str,
) -> Result<FilterResult, FilterError> {
// FIXME: translate the error better here
let image = draw_ctx
let image = acquired_nodes
.lookup_image(url)
.map_err(|_| FilterError::InvalidInput)?;
......@@ -137,6 +141,7 @@ impl FilterEffect for FeImage {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let bounds_builder = self.base.get_bounds(ctx);
......@@ -145,9 +150,18 @@ impl FilterEffect for FeImage {
match self.href.as_ref() {
Some(Href::PlainUrl(url)) => {
let unclipped_bounds = bounds_builder.into_rect_without_clipping(draw_ctx);
self.render_external_image(ctx, draw_ctx, bounds, &unclipped_bounds, url)
self.render_external_image(
ctx,
acquired_nodes,
draw_ctx,
bounds,
&unclipped_bounds,
url,
)
}
Some(Href::WithFragment(ref frag)) => {
self.render_node(ctx, acquired_nodes, draw_ctx, bounds, frag)
}
Some(Href::WithFragment(ref frag)) => self.render_node(ctx, draw_ctx, bounds, frag),
_ => Err(FilterError::InvalidInput),
}
}
......
......@@ -5,6 +5,7 @@ use nalgebra::Vector3;
use num_traits::identities::Zero;
use rayon::prelude::*;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::filters::{
......@@ -249,9 +250,13 @@ macro_rules! impl_lighting_filter {
&self,
node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.common().base.get_input(ctx, draw_ctx)?;
let input = self
.common()
.base
.get_input(ctx, acquired_nodes, draw_ctx)?;
let mut bounds = self
.common()
.base
......
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::node::{NodeResult, NodeTrait, NodeType, RsvgNode};
use crate::parsers::ParseValue;
......@@ -59,11 +60,12 @@ impl FeMergeNode {
fn render(
&self,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
bounds: IRect,
output_surface: Option<SharedImageSurface>,
) -> Result<SharedImageSurface, FilterError> {
let input = ctx.get_input(draw_ctx, self.in_.as_ref())?;
let input = ctx.get_input(acquired_nodes, draw_ctx, self.in_.as_ref())?;
if output_surface.is_none() {
return Ok(input.surface().clone());
......@@ -81,6 +83,7 @@ impl FilterEffect for FeMerge {
&self,
node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
// Compute the filter bounds, taking each child node's input into account.
......@@ -94,6 +97,7 @@ impl FilterEffect for FeMerge {
}
let input = ctx.get_input(
acquired_nodes,
draw_ctx,
child.borrow().get_impl::<FeMergeNode>().in_.as_ref(),
)?;
......@@ -109,6 +113,7 @@ impl FilterEffect for FeMerge {
{
output_surface = Some(child.borrow().get_impl::<FeMergeNode>().render(
ctx,
acquired_nodes,
draw_ctx,
bounds,
output_surface,
......
......@@ -7,6 +7,7 @@ use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::{RenderingError, ValueErrorKind};
use crate::filter::Filter;
......@@ -40,6 +41,7 @@ pub trait FilterEffect: NodeTrait {
&self,
node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError>;
......@@ -205,9 +207,10 @@ impl PrimitiveWithInput {
fn get_input(
&self,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterInput, FilterError> {
ctx.get_input(draw_ctx, self.in_.as_ref())
ctx.get_input(acquired_nodes, draw_ctx, self.in_.as_ref())
}
}
......@@ -240,6 +243,7 @@ pub fn render(
filter_node: &RsvgNode,
computed_from_node_being_filtered: &ComputedValues,
source_surface: SharedImageSurface,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
node_bbox: BoundingBox,
) -> Result<SharedImageSurface, RenderingError> {
......@@ -293,7 +297,7 @@ pub fn render(
let mut render = |filter_ctx: &mut FilterContext| {
if let Err(err) = filter
.render(&c, filter_ctx, draw_ctx)
.render(&c, filter_ctx, acquired_nodes, draw_ctx)
.and_then(|result| filter_ctx.store_result(result))
{
rsvg_log!("(filter primitive {} returned an error: {})", c, err);
......
......@@ -3,6 +3,7 @@ use std::cmp::{max, min};
use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
......@@ -77,9 +78,10 @@ impl FilterEffect for FeMorphology {
&self,
_node: &RsvgNode,
ctx: &FilterContext,
acquired_nodes: &mut AcquiredNodes,
draw_ctx: &mut DrawingCtx,
) -> Result<FilterResult, FilterError> {
let input = self.base.get_input(ctx, draw_ctx)?;
let input = self.base.get_input(ctx, acquired_nodes, draw_ctx)?;
let bounds = self
.base
.get_bounds(ctx)
......
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::node::{NodeResult, NodeTrait, RsvgNode};
use crate::parsers::ParseValue;
......@@ -50,9 +51,10 @@ impl FilterEffect for FeOffset {
&self,
_node: &RsvgNode,
ctx: &FilterContext,