Summary: TOPL properties are essentially automata, which specify a bad pattern. This commit is just a parser for them. Reviewed By: jvillard Differential Revision: D14477671 fbshipit-source-id: c38a8ef37master
parent
0a9c77e779
commit
8bf65086e3
@ -0,0 +1,30 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
module L = Logging
|
||||
|
||||
let initialized = ref false
|
||||
|
||||
let properties = ref []
|
||||
|
||||
let parse topl_file =
|
||||
let f ch =
|
||||
let lexbuf = Lexing.from_channel ch in
|
||||
try ToplParser.properties (ToplLexer.token ()) lexbuf with ToplParser.Error ->
|
||||
let Lexing.({pos_lnum; pos_bol; pos_cnum; _}) = Lexing.lexeme_start_p lexbuf in
|
||||
let col = pos_cnum - pos_bol + 1 in
|
||||
L.(die UserError) "@[%s:%d:%d: topl parse error@]@\n@?" topl_file pos_lnum col
|
||||
in
|
||||
try In_channel.with_file topl_file ~f with Sys_error msg ->
|
||||
L.(die UserError) "@[topl:%s: %s@]@\n@?" topl_file msg
|
||||
|
||||
|
||||
let init () =
|
||||
if not !initialized then (
|
||||
properties := List.concat_map ~f:parse Config.topl_properties ;
|
||||
initialized := true )
|
@ -0,0 +1,11 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
val init : unit -> unit
|
||||
(** Parse properties, mentioned by [Config.topl_properties]. Does this only once. *)
|
@ -0,0 +1,33 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
type register_name = string
|
||||
|
||||
(** TODO: use Const.t *)
|
||||
type constant = string
|
||||
|
||||
type value_pattern =
|
||||
| Ignore
|
||||
| SaveInRegister of register_name
|
||||
| EqualToRegister of register_name
|
||||
| EqualToConstant of constant
|
||||
|
||||
(** a regular expression *)
|
||||
type procedure_name_pattern = string
|
||||
|
||||
type label =
|
||||
{ return: value_pattern
|
||||
; procedure_name: procedure_name_pattern
|
||||
; arguments: value_pattern list option }
|
||||
|
||||
type vertex = string
|
||||
|
||||
type transition = {source: vertex; target: vertex; label: label}
|
||||
|
||||
type t = {name: string; message: string option; prefixes: string list; transitions: transition list}
|
@ -0,0 +1,76 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
{
|
||||
open !IStd
|
||||
open ToplParser
|
||||
|
||||
module L = Logging
|
||||
|
||||
let new_line x y lexbuf =
|
||||
let m = x |> String.filter ~f:(Char.equal '\n') |> String.length in
|
||||
let n = y |> String.length in
|
||||
let open Lexing in
|
||||
let lcp = lexbuf.lex_curr_p in
|
||||
lexbuf.lex_curr_p <-
|
||||
{ lcp with pos_lnum = lcp.pos_lnum + m ; pos_bol = lcp.pos_cnum - n } ;
|
||||
(INDENT n)
|
||||
|
||||
let quoted = Str.regexp "\\\\\\(.\\)"
|
||||
let unquote x = Str.global_replace quoted "\\1" x
|
||||
|
||||
(* We open Caml, because ocamllex generates code that uses Array.make,
|
||||
which is not available in Core. Ideally, this should go away. *)
|
||||
open! Caml
|
||||
}
|
||||
|
||||
let id_head = ['a'-'z' 'A'-'Z']
|
||||
let id_tail = ['a'-'z' 'A'-'Z' '0'-'9']*
|
||||
|
||||
rule raw_token = parse
|
||||
| '\t' { raise Error }
|
||||
| ((' '* ("//" [^ '\n']*)? '\n')+ as x) (' '* as y) { new_line x y lexbuf }
|
||||
| ' '+ { raw_token lexbuf }
|
||||
| "->" { ARROW }
|
||||
| '=' { ASGN }
|
||||
| ':' { COLON }
|
||||
| ',' { COMMA }
|
||||
| '(' { LP }
|
||||
| ')' { RP }
|
||||
| '*' { STAR }
|
||||
| '<' (([^ '<' '>' '\n' '\\'] | ('\\' _))* as x) '>' { CONSTANT (unquote x) }
|
||||
| '"' ([^ '"' '\n']* as x) '"' { STRING x }
|
||||
| "prefix" { PREFIX }
|
||||
| "property" { PROPERTY }
|
||||
| "message" { MESSAGE }
|
||||
| id_head id_tail as id { ID id }
|
||||
| eof { EOF }
|
||||
| _ { raise Error }
|
||||
|
||||
{
|
||||
let token () =
|
||||
let indents = ref [0] in
|
||||
let scheduled_rc = ref 0 in
|
||||
let last_indent () = match !indents with
|
||||
| x :: _ -> x
|
||||
| [] -> L.(die InternalError) "ToplLexer.indents should be nonempty"
|
||||
in
|
||||
let add_indent n = indents := n :: !indents in
|
||||
let rec drop_to_indent n = match !indents with
|
||||
| x :: xs when x > n -> (incr scheduled_rc; indents := xs; drop_to_indent n)
|
||||
| x :: _ when x < n -> raise Error (* bad indentation *)
|
||||
| _ -> ()
|
||||
in
|
||||
let rec step lexbuf =
|
||||
if !scheduled_rc > 0 then (decr scheduled_rc; RC)
|
||||
else match raw_token lexbuf with
|
||||
| INDENT n when n > last_indent () -> (add_indent n; LC)
|
||||
| INDENT n when n < last_indent () -> (drop_to_indent n; step lexbuf)
|
||||
| INDENT _ -> step lexbuf
|
||||
| t -> t
|
||||
in
|
||||
step
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
%{
|
||||
open !IStd
|
||||
|
||||
let is_guard i = Char.is_lowercase i.[0]
|
||||
|
||||
let normalize_id i = String.uncapitalize i
|
||||
|
||||
let value_pattern_of_id i =
|
||||
assert (String.length i > 0) ;
|
||||
let j = normalize_id i in
|
||||
if is_guard i then ToplAst.EqualToRegister j else ToplAst.SaveInRegister j
|
||||
%}
|
||||
|
||||
%token <int> INDENT (* The lexer uses this token only internally. *)
|
||||
%token <string> CONSTANT
|
||||
%token <string> ID
|
||||
%token <string> STRING
|
||||
%token ARROW
|
||||
%token ASGN
|
||||
%token COLON
|
||||
%token COMMA
|
||||
%token EOF
|
||||
%token LC
|
||||
%token LP
|
||||
%token MESSAGE
|
||||
%token PREFIX
|
||||
%token PROPERTY
|
||||
%token RC
|
||||
%token RP
|
||||
%token STAR
|
||||
|
||||
%start <ToplAst.t list> properties
|
||||
|
||||
%%
|
||||
|
||||
properties: ps=one_property* EOF { ps }
|
||||
|
||||
one_property:
|
||||
PROPERTY name=ID LC message=message? prefixes=prefix* transitions=transition* RC
|
||||
{ ToplAst.{name; message; prefixes; transitions} }
|
||||
|
||||
message: MESSAGE s=STRING { s }
|
||||
|
||||
prefix: PREFIX c=CONSTANT { c }
|
||||
|
||||
transition:
|
||||
source=ID ARROW target=ID COLON label=label
|
||||
{ ToplAst.{source; target; label} }
|
||||
|
||||
label:
|
||||
return=value_pattern ASGN c=call_pattern
|
||||
{ let procedure_name, arguments = c in ToplAst.{return; procedure_name; arguments} }
|
||||
| c=call_pattern
|
||||
{ let procedure_name, arguments = c in ToplAst.{return=Ignore; procedure_name; arguments} }
|
||||
|
||||
call_pattern: p=procedure_pattern a=arguments_pattern? { (p, a) }
|
||||
|
||||
procedure_pattern:
|
||||
i=ID { i }
|
||||
| c=CONSTANT { c }
|
||||
| STAR { ".*" }
|
||||
|
||||
arguments_pattern: LP a=separated_list(COMMA, value_pattern) RP { a }
|
||||
|
||||
value_pattern:
|
||||
i=ID { value_pattern_of_id i }
|
||||
| c=CONSTANT { ToplAst.EqualToConstant c }
|
||||
| STAR { ToplAst.Ignore }
|
||||
|
||||
%%
|
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2019-present, Facebook, Inc.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
TESTS_DIR = ../../..
|
||||
|
||||
INFER_OPTIONS = --topl-properties tomcat.topl --biabduction-only
|
||||
INFERPRINT_OPTIONS = --issues-tests
|
||||
|
||||
SOURCES = $(wildcard *.java)
|
||||
|
||||
include $(TESTS_DIR)/javac.make
|
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
class TomcatFail {
|
||||
void f() {}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
property ForwardUncommitted
|
||||
message "A ServletResponse was forwarded before being committed."
|
||||
// TODO assume InterleavedResponse_Weak
|
||||
prefix <javax.servlet>
|
||||
prefix <javax.servlet.{ServletOutputStream,ServletResponse}>
|
||||
prefix <java.io.PrintWriter>
|
||||
start -> start: *
|
||||
start -> tracking: R = <ServletResponse.\<init\>>
|
||||
tracking -> ok: flushBuffer(r)
|
||||
tracking -> gotWriter: W = getWriter(r)
|
||||
gotWriter -> ok: flush(w)
|
||||
gotWrite -> ok: flushBuffer(r)
|
||||
tracking -> gotStream: S = getOutputStream(r)
|
||||
gotStream -> ok: flush(s)
|
||||
gotStream -> ok: flushBuffer(r)
|
||||
tracking -> error: <RequestDispatcher.forward>(*, *, r)
|
||||
gotWriter -> error: <RequestDispatcher.forward>(*, *, r)
|
||||
gotStream -> error: <RequestDispatcher.forward>(*, *, r)
|
||||
|
||||
// The documentation of ServletResponse.getOutputStream says that "either this
|
||||
// method getWriter may be called to write to the body, not both." So,
|
||||
// technically, the property is InterleavedResponse1. However, this property is
|
||||
// broken, which is why we also have the weaker version InterleavedResponse2.
|
||||
property InterleavedResponse1
|
||||
message "A ServletResponse was asked for both a writer and a stream."
|
||||
prefix <javax.servlet.ServletResponse>
|
||||
start -> start: *
|
||||
start -> gotWriter: W = getWriter(R)
|
||||
start -> gotStream: S = getOutputStream(R)
|
||||
gotWriter -> error: getOutputStream(r)
|
||||
gotStream -> error: getWriter(r)
|
||||
|
||||
property InterleavedResponse2
|
||||
// vertex names: w = got writer; W = used writer; similarly for s, S
|
||||
message "Incompatible methods for putting data into a response were used."
|
||||
prefix <javax.servlet.ServletResponse>
|
||||
start -> start: *
|
||||
start -> w: W = getWriter(R)
|
||||
start -> s: S = getOutputStream(R)
|
||||
w -> sw: S = getOutputStream(r)
|
||||
s -> sw: W = getWriter(r)
|
||||
w -> W: *(w)
|
||||
sw -> sW: *(w)
|
||||
s -> S: *(s)
|
||||
sw -> Sw: *(s)
|
||||
W -> sW: S = getOutputStream(r)
|
||||
S -> Sw: W = getWriter(r)
|
||||
sW -> error: *(s)
|
||||
Sw -> error: *(w)
|
Loading…
Reference in new issue