mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-28 13:30:59 +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:
|
||||
fuzz_target:
|
||||
- all
|
||||
- derive
|
||||
- filters
|
||||
- html
|
||||
- parser
|
||||
|
@ -15,11 +15,17 @@ rustdoc-args = ["--generate-link-to-definition", "--cfg=docsrs"]
|
||||
[dependencies]
|
||||
askama = { path = "../../askama", features = ["serde_json"] }
|
||||
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"] }
|
||||
html-escape = "0.2.13"
|
||||
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"
|
||||
unicode-ident = "=1.0.18"
|
||||
|
||||
[[bin]]
|
||||
name = "all"
|
||||
@ -27,6 +33,12 @@ path = "fuzz_targets/all.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "derive"
|
||||
path = "fuzz_targets/derive.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "filters"
|
||||
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;
|
||||
mod ascii_str;
|
||||
pub mod derive;
|
||||
pub mod filters;
|
||||
pub mod html;
|
||||
pub mod parser;
|
||||
@ -13,6 +14,9 @@ use std::fmt;
|
||||
|
||||
pub const TARGETS: &[(&str, TargetBuilder)] = &[
|
||||
("all", |data| NamedTarget::new::<all::Scenario<'_>>(data)),
|
||||
("derive", |data| {
|
||||
NamedTarget::new::<derive::Scenario<'_>>(data)
|
||||
}),
|
||||
("filters", |data| {
|
||||
NamedTarget::new::<filters::Scenario<'_>>(data)
|
||||
}),
|
||||
|
Loading…
x
Reference in New Issue
Block a user