1
0
mirror of https://github.com/OlafvdSpek/ctemplate.git synced 2025-09-28 19:05:49 +08:00

* BUGFIX: was reloading string tpls in some situations (panicker)

* BUGFIX: fix recounting to avoid accessing freed memory (panicker)
	* Performance improvements for small_map (wonchun)
	* PORTING: Avoid SIGBUS on sparc by better aligning memory (csilvers)
	* Allow lowercase words in pragma contexts (csilvers)
	* BUGFIX: Fix a C++ conformance bug involving const (chandlerc)
	* Enable full word matching for tpl filenames (aneeshnaman)
This commit is contained in:
csilvers 2010-09-16 18:43:33 +00:00
parent 8c338f322e
commit 2f5f4b7baa
10 changed files with 117 additions and 44 deletions

View File

@ -66,13 +66,13 @@ _START_GOOGLE_NAMESPACE_
// map type has a "key_equal" member (hash_map does), then that
// will be used by default. Otherwise you must specify this
// manually.
// MapInitFunctor: A functor that takes a ManualConstructor<NormalMap>*
// and uses it to initialize the map. This functor will be
// called at most once per small_map, when the map exceeds the
// threshold of kArraySize and we are about to copy values from
// the array to the map. The functor *must* call one of the Init()
// methods provided by ManualConstructor, since after it runs we
// assume that the NormalMap has been initialized.
// MapInit: A functor that takes a ManualConstructor<NormalMap>* and uses it to
// initialize the map. This functor will be called at most once per
// small_map, when the map exceeds the threshold of kArraySize and we
// are about to copy values from the array to the map. The functor
// *must* call one of the Init() methods provided by
// ManualConstructor, since after it runs we assume that the NormalMap
// has been initialized.
//
// example:
// small_map<hash_map<string, int> > days;
@ -83,6 +83,9 @@ _START_GOOGLE_NAMESPACE_
// days["thursday" ] = 4;
// days["friday" ] = 5;
// days["saturday" ] = 6;
//
// You should assume that small_map might invalidate all the iterators
// on any call to erase(), insert() and operator[].
template <typename NormalMap>
class small_map_default_init {
public:
@ -105,7 +108,7 @@ class small_map {
small_map() : size_(0), functor_(MapInit()) {}
small_map(const MapInit& functor) : size_(0), functor_(functor) {}
explicit small_map(const MapInit& functor) : size_(0), functor_(functor) {}
// Allow copy-constructor and assignment, since STL allows them too.
small_map(const small_map& src) {
@ -274,6 +277,7 @@ class small_map {
return iterator(map()->find(key));
}
}
const_iterator find(const key_type& key) const {
key_equal compare;
if (size_ >= 0) {
@ -288,11 +292,14 @@ class small_map {
}
}
// Invalidates iterators.
data_type& operator[](const key_type& key) {
key_equal compare;
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
// operator[] searches backwards, favoring recently-added
// elements.
for (int i = size_-1; i >= 0; --i) {
if (compare(array_[i]->first, key)) {
return array_[i]->second;
}
@ -309,6 +316,7 @@ class small_map {
}
}
// Invalidates iterators.
std::pair<iterator, bool> insert(const value_type& x) {
key_equal compare;
@ -319,7 +327,7 @@ class small_map {
}
}
if (size_ == kArraySize) {
ConvertToRealMap();
ConvertToRealMap(); // Invalidates all iterators!
std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x);
return make_pair(iterator(ret.first), ret.second);
} else {
@ -332,6 +340,7 @@ class small_map {
}
}
// Invalidates iterators.
template <class InputIterator>
void insert(InputIterator f, InputIterator l) {
while (f != l) {
@ -381,16 +390,16 @@ class small_map {
size_ = 0;
}
// Invalidates iterators.
void erase(const iterator& position) {
if (size_ >= 0) {
int i = position.array_iter_ - array_;
array_[i++].Destroy();
// Move the rest of the array back one.
for (; i < size_; i++) {
array_[i - 1].Init(*array_[i]);
array_[i].Destroy();
}
array_[i].Destroy();
--size_;
if (i != size_) {
array_[i].Init(*array_[size_]);
array_[size_].Destroy();
}
} else {
map_->erase(position.hash_iter_);
}
@ -440,6 +449,7 @@ class small_map {
private:
int size_; // negative = using hash_map
MapInit functor_;
// We want to call constructors and destructors manually, but we don't

View File

@ -52,6 +52,14 @@ bool @ac_windows_dllexport@ IsDirectory(const std::string& path); // ends in "
void @ac_windows_dllexport@ NormalizeDirectory(std::string* dir); // appends /
std::string @ac_windows_dllexport@ Basename(const std::string& path);
// Returns true iff text contains the word as a full word, i.e. delimited by one
// of [.,_-#*?:] on both the sides.
// This is used while loading a template, to check that the file's name matches
// the auto-escape mode specified by it.
// NOTE: This assumes that the word doesn't contain any of the delimiter
// characters.
bool @ac_windows_dllexport@ ContainsFullWord(const std::string& text, const std::string& word);
@ac_google_end_namespace@
#endif // TEMPLATE_TEMPLATE_PATHOPS_H_

View File

@ -445,18 +445,19 @@ static const char *memmatch(const char *haystack, size_t haystack_len,
// we ignore it and just rely on the LOG(WARNING) in the logs.
static bool FilenameValidForContext(const string& filename,
TemplateContext context) {
// TODO(jad): Improve by also checking for "word" boundaries.
if ( filename.find("css") != string::npos ||
filename.find("stylesheet") != string::npos ||
filename.find("style") != string::npos) {
string stripped_filename = Basename(filename);
if (ctemplate::ContainsFullWord(stripped_filename, "css") ||
ctemplate::ContainsFullWord(stripped_filename, "stylesheet") ||
ctemplate::ContainsFullWord(stripped_filename, "style")) {
if (context != TC_CSS) {
LOG(WARNING) << "Template filename " << filename
<< " indicates CSS but given TemplateContext"
<< " was not TC_CSS." << endl;
return false;
}
} else if (filename.find("js") != string::npos ||
filename.find("javascript") != string::npos) {
} else if (ctemplate::ContainsFullWord(stripped_filename, "js") ||
ctemplate::ContainsFullWord(stripped_filename, "javascript")) {
if (context != TC_JS) {
LOG(WARNING) << "Template filename " << filename
<< " indicates javascript but given TemplateContext"
@ -489,15 +490,15 @@ static TemplateContext GetTemplateContextFromPragma(
const string* context = pragma.GetAttributeValue("context");
if (context == NULL)
return TC_MANUAL;
if (*context == "HTML")
if (*context == "HTML" || *context == "html")
return TC_HTML;
else if (*context == "JAVASCRIPT")
else if (*context == "JAVASCRIPT" || *context == "javascript")
return TC_JS;
else if (*context == "CSS")
else if (*context == "CSS" || *context == "css")
return TC_CSS;
else if (*context == "JSON")
else if (*context == "JSON" || *context == "json")
return TC_JSON;
else if (*context == "XML")
else if (*context == "XML" || *context == "xml")
return TC_XML;
return TC_MANUAL;
}
@ -1940,7 +1941,8 @@ TemplateToken SectionTemplateNode::GetNextToken(Template *my_template) {
const string* parser_state = pragma.GetAttributeValue("state");
bool in_tag = false;
if (parser_state != NULL) {
if (context == TC_HTML && *parser_state == "IN_TAG")
if (context == TC_HTML && (*parser_state == "IN_TAG" ||
*parser_state == "in_tag"))
in_tag = true;
else if (*parser_state != "default")
FAIL("Unsupported state '" + *parser_state +

View File

@ -311,8 +311,10 @@ TemplateCache::RefcountedTemplate* TemplateCache::GetTemplateLocked(
// Create a new template, insert it into the cache under
// template_cache_key, and DecRef() the old one to indicate
// the cache no longer has a reference to it.
it->second.refcounted_tpl->DecRef();
const Template* tpl = new Template(filename, strip, this);
// DecRef after creating the new template since DecRef may free up
// the storage for filename,
it->second.refcounted_tpl->DecRef();
it->second = CachedTemplate(tpl, CachedTemplate::FILE_BASED);
}
it->second.should_reload = false;
@ -670,10 +672,7 @@ void TemplateCache::ReloadAllIfChanged(ReloadType reload_type) {
it->second.should_reload = true;
if (reload_type == IMMEDIATE_RELOAD) {
const Template* tpl = it->second.refcounted_tpl->tpl();
// TODO(csilvers): have template_file() return a TemplateString?
TemplateCacheKey template_cache_key = TemplateCacheKey(
TemplateString(tpl->template_file()).GetGlobalId(), tpl->strip());
GetTemplateLocked(tpl->template_file(), tpl->strip(), template_cache_key);
GetTemplateLocked(tpl->template_file(), tpl->strip(), it->first);
}
}
}

View File

@ -87,9 +87,8 @@ template<class T> void ArenaAllocator<T>::deallocate(pointer p, size_type n) {
arena_->Free(p, n * sizeof(T));
}
// TODO(csilvers): sizeof(void*) may be too big. But is probably ok.
/*static*/ template<class T> const int ArenaAllocator<T>::kAlignment =
(1 == sizeof(T) ? 1 : sizeof(void*));
(1 == sizeof(T) ? 1 : BaseArena::kDefaultAlignment);
// ----------------------------------------------------------------------
// TemplateDictionary::map_arena_init
@ -123,7 +122,8 @@ inline void TemplateDictionary::LazilyCreateDict(T** dict) {
if (*dict != NULL)
return;
// Placement new: construct the map in the memory used by *dict.
void* buffer = arena_->AllocAligned(sizeof(**dict), sizeof(void*));
void* buffer = arena_->AllocAligned(sizeof(**dict),
BaseArena::kDefaultAlignment);
new (buffer) T(arena_);
*dict = reinterpret_cast<T*>(buffer);
}
@ -138,7 +138,8 @@ inline void TemplateDictionary::LazyCreateTemplateGlobalDict() {
}
inline TemplateDictionary::DictVector* TemplateDictionary::CreateDictVector() {
void* buffer = arena_->AllocAligned(sizeof(DictVector), sizeof(void*));
void* buffer = arena_->AllocAligned(sizeof(DictVector),
BaseArena::kDefaultAlignment);
// Placement new: construct the vector in the memory used by buffer.
new (buffer) DictVector(arena_);
return reinterpret_cast<DictVector*>(buffer);
@ -149,7 +150,8 @@ inline TemplateDictionary* TemplateDictionary::CreateTemplateSubdict(
UnsafeArena* arena,
TemplateDictionary* parent_dict,
TemplateDictionary* template_global_dict_owner) {
void* buffer = arena->AllocAligned(sizeof(TemplateDictionary), sizeof(void*));
void* buffer = arena->AllocAligned(sizeof(TemplateDictionary),
BaseArena::kDefaultAlignment);
// Placement new: construct the sub-tpl in the memory used by tplbuf.
new (buffer) TemplateDictionary(name, arena, parent_dict,
template_global_dict_owner);

View File

@ -102,7 +102,9 @@ const TemplateNamelist::MissingListType& TemplateNamelist::GetMissingList(
const string path = Template::FindTemplateFilename(*iter);
if (path.empty() || access(path.c_str(), R_OK) != 0) {
missing_list_->push_back(*iter);
std::cerr << "ERROR: Template file missing: " << path << std::endl;
std::cerr << "ERROR: Template file missing: " << *iter
<< " at path: " << (path.empty() ? "(empty path)" : path)
<< std::endl;
}
}
}

View File

@ -37,6 +37,7 @@
#include "config.h"
#include <string>
#include <ctype.h> // for isalpha, used on windows
#include <string.h> // for strchr
#include <ctemplate/template_pathops.h>
using std::string;
@ -109,5 +110,37 @@ string Basename(const string& path) {
return path; // no path-separator found, so whole string is the basename
}
bool ContainsFullWord(const string& text, const string& word) {
// List of delimiter characters to be considered. Please update the comment in
// the header file if you change this list.
static const char* delim = ".,_-#*?:";
const int inputlength = text.length();
const int wordlength = word.length();
// corner cases
if (inputlength == 0 || wordlength == 0 || wordlength > inputlength) {
return false;
}
int nextmatchpos = 0; // position from where search in the input string
while (nextmatchpos < inputlength) {
const int pos = text.find(word, nextmatchpos);
if (pos == string::npos) {
return false; // no match at all
}
// if found, check that it is surrounded by delimiter characters.
bool pre_delimited = (pos == 0) ||
(strchr(delim, text.at(pos - 1)) != NULL);
bool post_delimited = (pos >= inputlength - wordlength) ||
(strchr(delim, text.at(pos + wordlength)) != NULL);
if (pre_delimited && post_delimited) return true;
nextmatchpos = (pos + wordlength + 1);
}
return false;
}
_END_GOOGLE_NAMESPACE_

View File

@ -216,15 +216,12 @@ struct TemplateStringHasher {
assert(IsTemplateIdInitialized(id_b));
return hasher(id_a, id_b);
}
// static makes this compile under MSVC (shrug)
static const TemplateIdHasher hasher;
TemplateIdHasher hasher;
// These two public members are required by msvc. 4 and 8 are defaults.
static const size_t bucket_size = 4;
static const size_t min_buckets = 8;
};
/*static*/ const TemplateIdHasher TemplateStringHasher::hasher = {};
namespace {
Mutex mutex(Mutex::LINKER_INITIALIZED);

View File

@ -1956,6 +1956,12 @@ class TemplateUnittest {
expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}" // lowercase ok
"href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
ASSERT(tpl = StringToTemplate(text, strip));
expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
// Repeat the test with trailing HTML that closes the tag. This is
// undefined behavior. We test it to ensure the parser does not choke.
text += ">Hello</a><span>Some text</span></body></html>";
@ -1987,6 +1993,12 @@ class TemplateUnittest {
expected_mods = "VAL:xml_escape\nDATA:h\n";
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
text = "{{%AUTOESCAPE context=\"xml\"}}" // lower-case XML
"<PARAM name=\"{{VAL}}\">{{DATA:h}}";
ASSERT(tpl = StringToTemplate(text, strip));
expected_mods = "VAL:xml_escape\nDATA:h\n";
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}"; // after comment
ASSERT(tpl = StringToTemplate(text, strip));
text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}";
@ -2034,7 +2046,7 @@ class TemplateUnittest {
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
text = "{{%AUTOESCAPE context=\"HTML\" }}"; // extra whitesp
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
text = "{{%AUTOESCAPE context=\"xml\"}}"; // lower-case xml
text = "{{%AUTOESCAPE context=\"Xml\"}}"; // mixed-case xml
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}"; // bad state
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);

View File

@ -57,6 +57,14 @@ bool CTEMPLATE_DLL_DECL IsDirectory(const std::string& path); // ends in "/"?
void CTEMPLATE_DLL_DECL NormalizeDirectory(std::string* dir); // appends /
std::string CTEMPLATE_DLL_DECL Basename(const std::string& path);
// Returns true iff text contains the word as a full word, i.e. delimited by one
// of [.,_-#*?:] on both the sides.
// This is used while loading a template, to check that the file's name matches
// the auto-escape mode specified by it.
// NOTE: This assumes that the word doesn't contain any of the delimiter
// characters.
bool CTEMPLATE_DLL_DECL ContainsFullWord(const std::string& text, const std::string& word);
}
#endif // TEMPLATE_TEMPLATE_PATHOPS_H_