1 /*
2  * Copyright 2010-2014, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "slang_rs_reflect_utils.h"
18 
19 #include <cstdio>
20 #include <cstring>
21 #include <string>
22 #include <iomanip>
23 
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/Path.h"
27 
28 #include "os_sep.h"
29 #include "slang_assert.h"
30 
31 namespace slang {
32 
33 using std::string;
34 
GetFileNameStem(const char * fileName)35 string RSSlangReflectUtils::GetFileNameStem(const char *fileName) {
36   const char *dot = fileName + strlen(fileName);
37   const char *slash = dot - 1;
38   while (slash >= fileName) {
39     if (*slash == OS_PATH_SEPARATOR) {
40       break;
41     }
42     if ((*slash == '.') && (*dot == 0)) {
43       dot = slash;
44     }
45     --slash;
46   }
47   ++slash;
48   return string(slash, dot - slash);
49 }
50 
ComputePackagedPath(const char * prefixPath,const char * packageName)51 string RSSlangReflectUtils::ComputePackagedPath(const char *prefixPath,
52                                                 const char *packageName) {
53   string packaged_path(prefixPath);
54   if (!packaged_path.empty() &&
55       (packaged_path[packaged_path.length() - 1] != OS_PATH_SEPARATOR)) {
56     packaged_path += OS_PATH_SEPARATOR_STR;
57   }
58   size_t s = packaged_path.length();
59   packaged_path += packageName;
60   while (s < packaged_path.length()) {
61     if (packaged_path[s] == '.') {
62       packaged_path[s] = OS_PATH_SEPARATOR;
63     }
64     ++s;
65   }
66   return packaged_path;
67 }
68 
InternalFileNameConvert(const char * rsFileName,bool toLower)69 static string InternalFileNameConvert(const char *rsFileName, bool toLower) {
70   const char *dot = rsFileName + strlen(rsFileName);
71   const char *slash = dot - 1;
72   while (slash >= rsFileName) {
73     if (*slash == OS_PATH_SEPARATOR) {
74       break;
75     }
76     if ((*slash == '.') && (*dot == 0)) {
77       dot = slash;
78     }
79     --slash;
80   }
81   ++slash;
82   char ret[256];
83   int i = 0;
84   for (; (i < 255) && (slash < dot); ++slash) {
85     if (isalnum(*slash) || *slash == '_') {
86       if (toLower) {
87         ret[i] = tolower(*slash);
88       } else {
89         ret[i] = *slash;
90       }
91       ++i;
92     }
93   }
94   ret[i] = 0;
95   return string(ret);
96 }
97 
98 std::string
JavaClassNameFromRSFileName(const char * rsFileName)99 RSSlangReflectUtils::JavaClassNameFromRSFileName(const char *rsFileName) {
100   return InternalFileNameConvert(rsFileName, false);
101 }
102 
RootNameFromRSFileName(const std::string & rsFileName)103 std::string RootNameFromRSFileName(const std::string &rsFileName) {
104   return InternalFileNameConvert(rsFileName.c_str(), false);
105 }
106 
107 std::string
BCFileNameFromRSFileName(const char * rsFileName)108 RSSlangReflectUtils::BCFileNameFromRSFileName(const char *rsFileName) {
109   return InternalFileNameConvert(rsFileName, true);
110 }
111 
JavaBitcodeClassNameFromRSFileName(const char * rsFileName)112 std::string RSSlangReflectUtils::JavaBitcodeClassNameFromRSFileName(
113     const char *rsFileName) {
114   std::string tmp(InternalFileNameConvert(rsFileName, false));
115   return tmp.append("BitCode");
116 }
117 
GenerateAccessorMethod(const RSSlangReflectUtils::BitCodeAccessorContext & context,int bitwidth,GeneratedFile & out)118 static bool GenerateAccessorMethod(
119     const RSSlangReflectUtils::BitCodeAccessorContext &context,
120     int bitwidth, GeneratedFile &out) {
121   // the prototype of the accessor method
122   out.indent() << "// return byte array representation of the " << bitwidth
123                << "-bit bitcode.\n";
124   out.indent() << "public static byte[] getBitCode" << bitwidth << "()";
125   out.startBlock();
126   out.indent() << "return getBitCode" << bitwidth << "Internal();\n";
127   out.endBlock(true);
128   return true;
129 }
130 
131 // Java method size must not exceed 64k,
132 // so we have to split the bitcode into multiple segments.
GenerateSegmentMethod(const char * buff,int blen,int bitwidth,int seg_num,GeneratedFile & out)133 static bool GenerateSegmentMethod(const char *buff, int blen, int bitwidth,
134                                   int seg_num, GeneratedFile &out) {
135   out.indent() << "private static byte[] getSegment" << bitwidth << "_"
136                << seg_num << "()";
137   out.startBlock();
138   out.indent() << "byte[] data = {";
139   out.increaseIndent();
140 
141   const int kEntriesPerLine = 16;
142   int position = kEntriesPerLine;  // We start with a new line and indent.
143   for (int written = 0; written < blen; written++) {
144     if (++position >= kEntriesPerLine) {
145       out << "\n";
146       out.indent();
147       position = 0;
148     } else {
149       out << " ";
150     }
151     out << std::setw(4) << static_cast<int>(buff[written]) << ",";
152   }
153   out << "\n";
154 
155   out.decreaseIndent();
156   out.indent() << "};\n";
157   out.indent() << "return data;\n";
158   out.endBlock();
159 
160   return true;
161 }
162 
GenerateJavaCodeAccessorMethodForBitwidth(const RSSlangReflectUtils::BitCodeAccessorContext & context,int bitwidth,GeneratedFile & out)163 static bool GenerateJavaCodeAccessorMethodForBitwidth(
164     const RSSlangReflectUtils::BitCodeAccessorContext &context,
165     int bitwidth, GeneratedFile &out) {
166 
167   std::string filename(context.bc32FileName);
168   if (bitwidth == 64) {
169     filename = context.bc64FileName;
170   }
171 
172   FILE *pfin = fopen(filename.c_str(), "rb");
173   if (pfin == nullptr) {
174     fprintf(stderr, "Error: could not read file %s\n", filename.c_str());
175     return false;
176   }
177 
178   // start the accessor method
179   GenerateAccessorMethod(context, bitwidth, out);
180 
181   // output the data
182   // make sure the generated function for a segment won't break the Javac
183   // size limitation (64K).
184   static const int SEG_SIZE = 0x2000;
185   char *buff = new char[SEG_SIZE];
186   int read_length;
187   int seg_num = 0;
188   int total_length = 0;
189   while ((read_length = fread(buff, 1, SEG_SIZE, pfin)) > 0) {
190     GenerateSegmentMethod(buff, read_length, bitwidth, seg_num, out);
191     ++seg_num;
192     total_length += read_length;
193   }
194   delete[] buff;
195   fclose(pfin);
196 
197   // output the internal accessor method
198   out.indent() << "private static int bitCode" << bitwidth << "Length = "
199                << total_length << ";\n\n";
200   out.indent() << "private static byte[] getBitCode" << bitwidth
201                << "Internal()";
202   out.startBlock();
203   out.indent() << "byte[] bc = new byte[bitCode" << bitwidth << "Length];\n";
204   out.indent() << "int offset = 0;\n";
205   out.indent() << "byte[] seg;\n";
206   for (int i = 0; i < seg_num; ++i) {
207     out.indent() << "seg = getSegment" << bitwidth << "_" << i << "();\n";
208     out.indent() << "System.arraycopy(seg, 0, bc, offset, seg.length);\n";
209     out.indent() << "offset += seg.length;\n";
210   }
211   out.indent() << "return bc;\n";
212   out.endBlock();
213 
214   return true;
215 }
216 
GenerateJavaCodeAccessorMethod(const RSSlangReflectUtils::BitCodeAccessorContext & context,GeneratedFile & out)217 static bool GenerateJavaCodeAccessorMethod(
218     const RSSlangReflectUtils::BitCodeAccessorContext &context,
219     GeneratedFile &out) {
220   if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 32, out)) {
221     slangAssert(false && "Couldn't generate 32-bit embedded bitcode!");
222     return false;
223   }
224   if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 64, out)) {
225     slangAssert(false && "Couldn't generate 64-bit embedded bitcode!");
226     return false;
227   }
228 
229   return true;
230 }
231 
GenerateAccessorClass(const RSSlangReflectUtils::BitCodeAccessorContext & context,const char * clazz_name,GeneratedFile & out)232 static bool GenerateAccessorClass(
233     const RSSlangReflectUtils::BitCodeAccessorContext &context,
234     const char *clazz_name, GeneratedFile &out) {
235   // begin the class.
236   out << "/**\n";
237   out << " * @hide\n";
238   out << " */\n";
239   out << "public class " << clazz_name;
240   out.startBlock();
241 
242   bool ret = true;
243   switch (context.bcStorage) {
244   case BCST_APK_RESOURCE:
245     slangAssert(false &&
246                 "Invalid generation of bitcode accessor with resource");
247     break;
248   case BCST_JAVA_CODE:
249     ret = GenerateJavaCodeAccessorMethod(context, out);
250     break;
251   default:
252     ret = false;
253   }
254 
255   // end the class.
256   out.endBlock();
257 
258   return ret;
259 }
260 
GenerateJavaBitCodeAccessor(const BitCodeAccessorContext & context)261 bool RSSlangReflectUtils::GenerateJavaBitCodeAccessor(
262     const BitCodeAccessorContext &context) {
263   string output_path =
264       ComputePackagedPath(context.reflectPath, context.packageName);
265   if (std::error_code EC = llvm::sys::fs::create_directories(
266           llvm::sys::path::parent_path(output_path))) {
267     fprintf(stderr, "Error: could not create dir %s: %s\n",
268             output_path.c_str(), EC.message().c_str());
269     return false;
270   }
271 
272   string clazz_name(JavaBitcodeClassNameFromRSFileName(context.rsFileName));
273   string filename(clazz_name);
274   filename += ".java";
275 
276   GeneratedFile out;
277   if (!out.startFile(output_path, filename, context.rsFileName,
278                      context.licenseNote, true, context.verbose)) {
279     return false;
280   }
281 
282   out << "package " << context.packageName << ";\n\n";
283 
284   bool ret = GenerateAccessorClass(context, clazz_name.c_str(), out);
285 
286   out.closeFile();
287   return ret;
288 }
289 
JoinPath(const std::string & path1,const std::string & path2)290 std::string JoinPath(const std::string &path1, const std::string &path2) {
291   if (path1.empty()) {
292     return path2;
293   }
294   if (path2.empty()) {
295     return path1;
296   }
297   std::string fullPath = path1;
298   if (fullPath[fullPath.length() - 1] != OS_PATH_SEPARATOR) {
299     fullPath += OS_PATH_SEPARATOR;
300   }
301   if (path2[0] == OS_PATH_SEPARATOR) {
302     fullPath += path2.substr(1, string::npos);
303   } else {
304     fullPath += path2;
305   }
306   return fullPath;
307 }
308 
309 // Replace all instances of "\" with "\\" in a single string to prevent
310 // formatting errors.  In Java, this can happen even within comments, as
311 // Java processes \u before the comments are stripped.  E.g. if the generated
312 // file in Windows contains the note:
313 //     /* Do not modify!  Generated from \Users\MyName\MyDir\foo.cs */
314 // Java will think that \U tells of a Unicode character.
SanitizeString(std::string * s)315 static void SanitizeString(std::string *s) {
316   size_t p = 0;
317   while ((p = s->find('\\', p)) != std::string::npos) {
318     s->replace(p, 1, "\\\\");
319     p += 2;
320   }
321 }
322 
323 static const char *const gApacheLicenseNote =
324     "/*\n"
325     " * Copyright (C) 2011-2014 The Android Open Source Project\n"
326     " *\n"
327     " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
328     " * you may not use this file except in compliance with the License.\n"
329     " * You may obtain a copy of the License at\n"
330     " *\n"
331     " *      http://www.apache.org/licenses/LICENSE-2.0\n"
332     " *\n"
333     " * Unless required by applicable law or agreed to in writing, software\n"
334     " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
335     " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or "
336     "implied.\n"
337     " * See the License for the specific language governing permissions and\n"
338     " * limitations under the License.\n"
339     " */\n"
340     "\n";
341 
startFile(const string & outDirectory,const string & outFileName,const string & sourceFileName,const string * optionalLicense,bool isJava,bool verbose)342 bool GeneratedFile::startFile(const string &outDirectory,
343                               const string &outFileName,
344                               const string &sourceFileName,
345                               const string *optionalLicense, bool isJava,
346                               bool verbose) {
347   if (verbose) {
348     printf("Generating %s\n", outFileName.c_str());
349   }
350 
351   // Create the parent directories.
352   if (!outDirectory.empty()) {
353     if (std::error_code EC = llvm::sys::fs::create_directories(
354             llvm::sys::path::parent_path(outDirectory))) {
355       fprintf(stderr, "Error: %s\n", EC.message().c_str());
356       return false;
357     }
358   }
359 
360   std::string FilePath = JoinPath(outDirectory, outFileName);
361 
362   // Open the file.
363   open(FilePath.c_str());
364   if (!good()) {
365     fprintf(stderr, "Error: could not write file %s\n", outFileName.c_str());
366     return false;
367   }
368 
369   // Write the license.
370   if (optionalLicense != nullptr) {
371     *this << *optionalLicense;
372   } else {
373     *this << gApacheLicenseNote;
374   }
375 
376   // Write a notice that this is a generated file.
377   std::string source(sourceFileName);
378   if (isJava) {
379     SanitizeString(&source);
380   }
381 
382   *this << "/*\n"
383         << " * This file is auto-generated. DO NOT MODIFY!\n"
384         << " * The source Renderscript file: " << source << "\n"
385         << " */\n\n";
386 
387   return true;
388 }
389 
closeFile()390 void GeneratedFile::closeFile() { close(); }
391 
increaseIndent()392 void GeneratedFile::increaseIndent() { mIndent.append("    "); }
393 
decreaseIndent()394 void GeneratedFile::decreaseIndent() {
395   slangAssert(!mIndent.empty() && "No indent");
396   mIndent.erase(0, 4);
397 }
398 
comment(const std::string & s)399 void GeneratedFile::comment(const std::string &s) {
400   indent() << "/* ";
401   // +3 for the " * " starting each line.
402   std::size_t indentLength = mIndent.length() + 3;
403   std::size_t lengthOfCommentOnLine = 0;
404   const std::size_t maxPerLine = 80;
405   for (std::size_t start = 0, length = s.length(), nextStart = 0;
406        start < length; start = nextStart) {
407     std::size_t p = s.find_first_of(" \n", start);
408     std::size_t toCopy = 1;
409     bool forceBreak = false;
410     if (p == std::string::npos) {
411       toCopy = length - start;
412       nextStart = length;
413     } else {
414       toCopy = p - start;
415       nextStart = p + 1;
416       forceBreak = s[p] == '\n';
417     }
418     if (lengthOfCommentOnLine > 0) {
419       if (indentLength + lengthOfCommentOnLine + toCopy >= maxPerLine) {
420         *this << "\n";
421         indent() << " * ";
422         lengthOfCommentOnLine = 0;
423       } else {
424         *this << " ";
425       }
426     }
427 
428     *this << s.substr(start, toCopy);
429     if (forceBreak) {
430       lengthOfCommentOnLine = maxPerLine;
431     } else {
432       lengthOfCommentOnLine += toCopy;
433     }
434   }
435   *this << "\n";
436   indent() << " */\n";
437 }
438 
439 } // namespace slang
440