mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 15:25:19 +00:00
Fuzz code generator, too
This commit is contained in:
parent
e80b710f67
commit
b716014612
1
.github/workflows/rust.yml
vendored
1
.github/workflows/rust.yml
vendored
@ -216,6 +216,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
fuzz_target:
|
fuzz_target:
|
||||||
- all
|
- all
|
||||||
|
- derive
|
||||||
- filters
|
- filters
|
||||||
- html
|
- html
|
||||||
- parser
|
- parser
|
||||||
|
@ -15,11 +15,17 @@ rustdoc-args = ["--generate-link-to-definition", "--cfg=docsrs"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { path = "../../askama", features = ["serde_json"] }
|
askama = { path = "../../askama", features = ["serde_json"] }
|
||||||
askama_parser = { path = "../../askama_parser" }
|
askama_parser = { path = "../../askama_parser" }
|
||||||
|
askama_derive = { path = "../../askama_derive", default-features = false, features = ["serde_json", "std", "urlencode"] }
|
||||||
|
|
||||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||||
html-escape = "0.2.13"
|
html-escape = "0.2.13"
|
||||||
libfuzzer-sys = "0.4.7"
|
libfuzzer-sys = "0.4.7"
|
||||||
|
prettyplease = "0.2.32"
|
||||||
|
proc-macro2 = { version = "1.0.95", default-features = false }
|
||||||
|
quote = { version = "1.0.40", default-features = false }
|
||||||
|
syn = { version = "2.0.101", default-features = false, features = ["full"] }
|
||||||
thiserror = "2.0.3"
|
thiserror = "2.0.3"
|
||||||
|
unicode-ident = "=1.0.18"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "all"
|
name = "all"
|
||||||
@ -27,6 +33,12 @@ path = "fuzz_targets/all.rs"
|
|||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "derive"
|
||||||
|
path = "fuzz_targets/derive.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "filters"
|
name = "filters"
|
||||||
path = "fuzz_targets/filters.rs"
|
path = "fuzz_targets/filters.rs"
|
||||||
|
5
fuzzing/fuzz/fuzz_targets/derive.rs
Normal file
5
fuzzing/fuzz/fuzz_targets/derive.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#![no_main]
|
||||||
|
|
||||||
|
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
|
||||||
|
let _ = <fuzz::derive::Scenario as fuzz::Scenario>::fuzz(data);
|
||||||
|
});
|
279
fuzzing/fuzz/src/derive.rs
Normal file
279
fuzzing/fuzz/src/derive.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
use askama_derive::derive_template;
|
||||||
|
use prettyplease::unparse;
|
||||||
|
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
use syn::token::Comma;
|
||||||
|
use syn::{File, parse2};
|
||||||
|
use unicode_ident::{is_xid_continue, is_xid_start};
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
assert!(
|
||||||
|
!askama_derive::CAN_USE_EXTERNAL_SOURCES,
|
||||||
|
"`askama_derive` can use external sources. Denying to fuzz for safety reasons.",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'a> super::Scenario<'a> for Scenario<'a> {
|
||||||
|
type RunError = syn::Error;
|
||||||
|
|
||||||
|
fn new(data: &'a [u8]) -> Result<Self, arbitrary::Error> {
|
||||||
|
Self::arbitrary_take_rest(Unstructured::new(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self) -> Result<(), Self::RunError> {
|
||||||
|
let input = self.item.to_token_stream();
|
||||||
|
// Any input AST should be parsable by the generator, maybe returning a `compile_error!`.
|
||||||
|
let output = derive_template(input, import_askama);
|
||||||
|
// The generated code should be parsable as rust source.
|
||||||
|
let _: File = parse2(output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_askama() -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
extern crate askama;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Arbitrary)]
|
||||||
|
pub struct Scenario<'a> {
|
||||||
|
item: DeriveItem<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Scenario<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self { item } = self;
|
||||||
|
let ts = quote! {
|
||||||
|
use askama_derive::Ast;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() -> Result<(), syn::Error> {
|
||||||
|
let input = quote! {
|
||||||
|
#item
|
||||||
|
};
|
||||||
|
let output = derive_template(input, import_askama);
|
||||||
|
let _: syn::File = syn::parse2(output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_askama() -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
extern crate askama;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
f.write_str(&unparse(&parse2(ts).map_err(|_| fmt::Error)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Arbitrary)]
|
||||||
|
pub struct DeriveItem<'a> {
|
||||||
|
params: Option<MetaTemplate<'a>>,
|
||||||
|
item: Item<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for DeriveItem<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { params, item } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#params
|
||||||
|
#item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
enum Item<'a> {
|
||||||
|
StructItem(StructItem<'a>),
|
||||||
|
Enum(Enum<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Item<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
Item::StructItem(s) => tokens.extend(quote! {
|
||||||
|
struct #s
|
||||||
|
}),
|
||||||
|
Item::Enum(s) => s.to_tokens(tokens),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
enum StructItem<'a> {
|
||||||
|
Empty(Empty<'a>),
|
||||||
|
Struct(Struct<'a>),
|
||||||
|
Tuple(Tuple<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for StructItem<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
StructItem::Empty(s) => s.to_tokens(tokens),
|
||||||
|
StructItem::Struct(s) => s.to_tokens(tokens),
|
||||||
|
StructItem::Tuple(s) => s.to_tokens(tokens),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Empty<'a> {
|
||||||
|
name: Name<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Empty<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
self.name.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Struct<'a> {
|
||||||
|
name: Name<'a>,
|
||||||
|
fields: Vec<Field<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Struct<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { name, fields } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#name {
|
||||||
|
#(#fields,)*
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Field<'a> {
|
||||||
|
name: Name<'a>,
|
||||||
|
type_: Name<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Field<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { name, type_ } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#name: #type_
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Tuple<'a> {
|
||||||
|
name: Name<'a>,
|
||||||
|
fields: Vec<Name<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Tuple<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { name, fields } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#name(#(#fields),*)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Enum<'a> {
|
||||||
|
name: Name<'a>,
|
||||||
|
variants: Vec<Variant<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Enum<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { name, variants } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
enum #name {
|
||||||
|
#(#variants),*
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Variant<'a> {
|
||||||
|
params: Option<MetaTemplate<'a>>,
|
||||||
|
item: StructItem<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Variant<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { params, item } = self;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#params
|
||||||
|
#item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct MetaTemplate<'a> {
|
||||||
|
ext: Option<Ext<'a>>,
|
||||||
|
source: Option<Source<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MetaTemplate<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { ext, source } = self;
|
||||||
|
let comma = (ext.is_some() && source.is_some()).then(Comma::default);
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#[template(#ext #comma #source)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Ext<'a>(LiteralString<'a>);
|
||||||
|
|
||||||
|
impl ToTokens for Ext<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self(ext) = self;
|
||||||
|
tokens.extend(quote!(ext = #ext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct Source<'a>(LiteralString<'a>);
|
||||||
|
|
||||||
|
impl ToTokens for Source<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self(source) = self;
|
||||||
|
tokens.extend(quote!(source = #source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Name<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<'a> Arbitrary<'a> for Name<'a> {
|
||||||
|
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||||
|
let ident = <&str>::arbitrary(u)?;
|
||||||
|
let mut chars = ident.chars();
|
||||||
|
if chars.next().is_some_and(is_xid_start) && chars.all(is_xid_continue) {
|
||||||
|
Ok(Self(ident))
|
||||||
|
} else {
|
||||||
|
Err(arbitrary::Error::IncorrectFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Name<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
tokens.append(Ident::new(self.0, Span::call_site()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Arbitrary)]
|
||||||
|
struct LiteralString<'a>(&'a str);
|
||||||
|
|
||||||
|
impl ToTokens for LiteralString<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
tokens.append(Literal::string(self.0));
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
pub mod all;
|
pub mod all;
|
||||||
mod ascii_str;
|
mod ascii_str;
|
||||||
|
pub mod derive;
|
||||||
pub mod filters;
|
pub mod filters;
|
||||||
pub mod html;
|
pub mod html;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
@ -13,6 +14,9 @@ use std::fmt;
|
|||||||
|
|
||||||
pub const TARGETS: &[(&str, TargetBuilder)] = &[
|
pub const TARGETS: &[(&str, TargetBuilder)] = &[
|
||||||
("all", |data| NamedTarget::new::<all::Scenario<'_>>(data)),
|
("all", |data| NamedTarget::new::<all::Scenario<'_>>(data)),
|
||||||
|
("derive", |data| {
|
||||||
|
NamedTarget::new::<derive::Scenario<'_>>(data)
|
||||||
|
}),
|
||||||
("filters", |data| {
|
("filters", |data| {
|
||||||
NamedTarget::new::<filters::Scenario<'_>>(data)
|
NamedTarget::new::<filters::Scenario<'_>>(data)
|
||||||
}),
|
}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user