Files
2026-05-11 17:27:55 +00:00

122 lines
3.9 KiB
JavaScript

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
let { FuzzedDataProvider } = require('@jazzer.js/core')
let postcss = require('../../lib/postcss')
module.exports.fuzz = function (data) {
let provider = new FuzzedDataProvider(data)
// The CSS input itself is randomized: every byte the fuzzer produces (or
// mutates from the seed corpus) flows directly into `cssString` via
// consumeRemainingAsString(). The option flags below are read from the
// *back* of the buffer (jazzer.js consumes integrals/booleans from the
// tail), so seed CSS files from postcss-parser-tests are fed into the
// parser nearly verbatim, with only their last few bytes nibbled off as
// option control.
let useMap = provider.consumeBoolean()
let useFrom = provider.consumeBoolean()
let useProcessor = provider.consumeBoolean()
let splitMode = provider.consumeIntegralInRange(0, 2)
let cssString = provider.consumeRemainingAsString()
let parseOptions = {}
if (useFrom) parseOptions.from = 'fuzz.css'
if (useMap) parseOptions.map = { annotation: false, inline: false }
let root
try {
root = postcss.parse(cssString, parseOptions)
} catch (e) {
if (e instanceof postcss.CssSyntaxError) return
throw e
}
// Walk the AST and exercise common node accessors. This also stresses
// raws/source bookkeeping for any node returned by the parser.
try {
root.walk(node => {
void node.type
void node.toString()
if (typeof node.error === 'function') {
// Generating an error message touches input/source-map machinery.
node.error('fuzz').message
}
})
} catch (e) {
if (!isExpected(e, postcss)) throw e
}
// Round-trip via stringify and re-parse. Output should itself be parseable.
let serialized
try {
serialized = root.toString()
} catch (e) {
if (!isExpected(e, postcss)) throw e
return
}
try {
postcss.parse(serialized)
} catch (e) {
if (!(e instanceof postcss.CssSyntaxError)) throw e
}
// Exercise the JSON serialization round-trip.
try {
let json = root.toJSON()
postcss.fromJSON(json)
} catch (e) {
if (!isExpected(e)) throw e
}
// Exercise the main public entry point: postcss().process(). This drives
// the LazyResult / NoWorkResult pipeline that real plugin chains use.
if (useProcessor) {
try {
let result = postcss().process(cssString, parseOptions)
void result.css
void result.warnings()
} catch (e) {
if (!isExpected(e)) throw e
}
}
// Exercise the list helpers, which have their own quoting/escape logic.
try {
if (splitMode === 0) {
postcss.list.comma(cssString)
} else if (splitMode === 1) {
postcss.list.space(cssString)
} else {
postcss.list.split(cssString, [',', ' '], false)
}
} catch (e) {
if (!isExpected(e)) throw e
}
}
function isExpected(error) {
if (error instanceof postcss.CssSyntaxError) return true
if (!error || typeof error.message !== 'string') return false
// Some legitimate inputs reach known-shaped TypeErrors during stringify or
// walk because the CSS allows constructs whose textual form is ambiguous.
// Suppress only those well-defined cases so real bugs still surface.
let benign = ['Unknown node type', 'Unknown word']
return benign.some(msg => error.message.indexOf(msg) !== -1)
}